Skip to content

Commit

Permalink
PXP-4931 Handle WTS's IDP parameter (#28)
Browse files Browse the repository at this point in the history
  • Loading branch information
paulineribeyre authored Mar 20, 2020
1 parent 6146fe5 commit a61edd4
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 26 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ You can choose where errors are logged in the yaml config file. By default they
-mount-point=<directory_to_mount> \
-hostname=<commons_domain> \
-wtsURL=<workspace_token_service_url> \
-wtsIDP=<workspace_token_service_IDP> \
-api-key=<api_key>

Note the usage of the program above. All arguments are required except `wtsURL` and `api-key`.
Expand All @@ -59,7 +60,8 @@ If both arguments are provided, then the `api-key` takes precedence and Gen3Fuse
directly from fence and does not consult the workspace-token-service.
To be clear, Gen3Fuse does not in general depend on the workspace-token-service to run.
When Gen3Fuse is running in the workspace, it will use the workspace-token-service.
In any other environment, it is sufficient to provide an `api-key`, and Gen3Fuse will work.
In any other environment, it is sufficient to provide an `api-key`, and Gen3Fuse will work.
If a `wtsURL` is provided, the optional `wtsIDP` argument can be used to specify which IDP to get tokens for. A list of available IDPs is served at the WTS's `/external_oidc` endpoint.

You provide the yaml configuration file on the command line. In the repo, there are example yaml configs already completed.
For a Kubernetes deployment into a Jupyter pod, config.yaml may be appropriate. To run Gen3Fuse on your own computer, local-config.yaml might be useful.
Expand Down Expand Up @@ -87,6 +89,10 @@ If you've already performed the Gen3Fuse setup instructions listed above, you ca

go test tests/gen3fuse_test.go

## Gen3Fuse sidecar

The Gen3 workspace flow uses the Gen3Fuse sidecar to handle mounting files to the workspace. The sidecar code iterates through the IDPs made available by the workspace-token-service, obtains an access token from the WTS for the current user, and checks whether the user has uploaded a new manifest via the IDP's [manifest-service](https://github.com/uc-cdis/manifestservice). If it's the case, the Gen3Fuse CLI is then used to mount the files to the workspace.

## Performance tests
Below are the results of a set of performance tests. Each chosen x axis value was tested 5 times, the results are shown in the scatter.

Expand Down
2 changes: 1 addition & 1 deletion internal/gen3fuse.go
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,7 @@ func (fs *Gen3Fuse) ReadFile(
// op.Dst: The destination buffer, whose length gives the size of the read.

op.BytesRead, err = reader.ReadAt(op.Dst, 0)
FuseLog(strconv.Itoa(op.BytesRead))
FuseLog("Read " + strconv.Itoa(op.BytesRead) + " bytes")
if op.BytesRead > 0 {
return nil
}
Expand Down
4 changes: 4 additions & 0 deletions internal/wts.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type Gen3FuseConfig struct {

// Workspace Token Service configuration
WTSBaseURL string
WTSIdp string
WTSAccessTokenPath string `yaml:"WTSAccessTokenPath"`

// Fence configuration
Expand Down Expand Up @@ -121,6 +122,9 @@ func GetAccessTokenWithApiKey(gen3FuseConfig *Gen3FuseConfig) (accessToken strin

func GetAccessTokenFromWTS(gen3FuseConfig *Gen3FuseConfig) (accessToken string, err error) {
requestUrl := fmt.Sprint(gen3FuseConfig.WTSBaseURL + gen3FuseConfig.WTSAccessTokenPath)
if gen3FuseConfig.WTSIdp != "" {
requestUrl += "?idp=" + gen3FuseConfig.WTSIdp
}
tokenResponse := new(tokenResponse)
err = getJson(requestUrl, tokenResponse)

Expand Down
1 change: 1 addition & 0 deletions internal/wts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (

var testConfig = &Gen3FuseConfig{
WTSBaseURL: "http://localhost/wts",
WTSIdp: "my_idp",
WTSAccessTokenPath: "/token",
FencePresignedURLPath: "/user/data/download/%s",
FenceAccessTokenPath: "/user/credentials/api/access_token",
Expand Down
9 changes: 9 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ func main() {
mountPoint := flag.String("mount-point", "", "directory to mount")
hostname := flag.String("hostname", "", "commons domain")
wtsURL := flag.String("wtsURL", "", "workspace-token-service url")
wtsIDP := flag.String("wtsIDP", "", "workspace-token-service IDP to use (optional)")
apiKey := flag.String("api-key", "", "api key")

flag.Parse()
Expand All @@ -27,6 +28,7 @@ func main() {
-mount-point=<directory_to_mount> \
-hostname=<commons_domain> \
-wtsURL=<workspace_token_service_url> \
-wtsIDP=<workspace_token_service_idp> \
-api-key=<api_key>`)
os.Exit(1)
}
Expand All @@ -39,6 +41,12 @@ func main() {
os.Exit(1)
}

// wtsIDP is an optional config when using wtsURL instead of apiKey
if *wtsURL == "" && *wtsIDP != "" {
fmt.Fprint(os.Stderr, "workspace-token-service IDP can only be used when workspace-token-service URL is provided. Exiting gen3-fuse.\n")
os.Exit(1)
}

if _, err := os.Stat(*configFileName); os.IsNotExist(err) {
if err != nil {
fmt.Fprintf(os.Stderr, "The config yaml file argument provided at %s does not exist. Exiting gen3-fuse.\n", *configFileName)
Expand Down Expand Up @@ -66,6 +74,7 @@ func main() {
}
gen3FuseConfig.Hostname = *hostname
gen3FuseConfig.WTSBaseURL = *wtsURL
gen3FuseConfig.WTSIdp = *wtsIDP
gen3FuseConfig.ApiKey = *apiKey

gen3fuse.InitializeApp(gen3FuseConfig, *manifestFilePath, *mountPoint)
Expand Down
2 changes: 1 addition & 1 deletion sidecarDockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ WORKDIR $GOPATH/src/github.com/uc-cdis/gen3-fuse
COPY . .
RUN go build -ldflags "-linkmode external -extldflags -static"

RUN mv /go/src/github.com/uc-cdis/gen3-fuse/gen3-fuse /usr/local/bin/gen3-fuse
RUN mv $GOPATH/src/github.com/uc-cdis/gen3-fuse/gen3-fuse /usr/local/bin/gen3-fuse

COPY config.yaml /fuse-config.yaml
COPY sidecarDockerrun.sh /sidecarDockerrun.sh
Expand Down
92 changes: 69 additions & 23 deletions sidecarDockerrun.sh
Original file line number Diff line number Diff line change
@@ -1,44 +1,90 @@
#!/bin/bash

# Only the most recent manifest is mounted.
# If new manifests are added while the workspace is running,
# only keep the N latest manifests for each IDP:
MAX_MANIFESTS=5

cleanup() {
killall gen3-fuse
cd /data
for f in `ls -d`
for f in $(ls -d)
do
echo a $f b `pwd`
echo a $f b $(pwd)
fusermount -uz $f
rm -rf $f
done

exit 0
}

sed -i "s/LogFilePath: \"fuse_log.txt\"/LogFilePath: \"\/data\/manifest-sync-status.log\"/g" ~/fuse-config.yaml
# _jq ${ENCODED_JSON} ${KEY} returns JSON.KEY
_jq() {
(base64 -d | jq -r ${2}) <<< ${1}
}

sed -i "s/LogFilePath: \"fuse_log.txt\"/LogFilePath: \"\/data\/_manifest-sync-status.log\"/g" ~/fuse-config.yaml
trap cleanup SIGTERM

declare -A TOKEN_JSON # requires Bash 4
TOKEN_JSON['default']=$(curl http://workspace-token-service.$NAMESPACE/token/?idp=default 2>/dev/null | jq -r '.token')

while true; do
if [ $(df /data/man* | sed '1d' | wc -l) -lt 7 ]; then # remove header line, and also consider the existence of manifest snyc log
resp=`curl http://manifestservice-service.$NAMESPACE/ -H "Authorization: bearer $TOKEN_JSON" 2>/dev/null`
if [[ $(echo $resp | jq -r '.error') =~ 'log' ]]; then
echo get new token
TOKEN_JSON=`curl http://workspace-token-service.$NAMESPACE/token/ 2>/dev/null | jq -r '.token'`
resp=`curl http://manifestservice-service.$NAMESPACE/ -H "Authorization: bearer $TOKEN_JSON" 2>/dev/null`

# get the list of IDPs the current user is logged into
EXTERNAL_OIDC=$(curl http://workspace-token-service.$NAMESPACE/external_oidc/?unexpired=true -H "Authorization: bearer ${TOKEN_JSON['default']}" 2>/dev/null | jq -r '.providers')
IDPS=( "default" )
BASE_URLS=( "https://$HOSTNAME" )
for ROW in $(jq -r '.[] | @base64' <<< ${EXTERNAL_OIDC}); do
IDPS+=( $(_jq ${ROW} .idp) )
BASE_URLS+=( $(_jq ${ROW} .base_url) )
done

for i in "${!IDPS[@]}"; do
IDP=${IDPS[$i]}
BASE_URL=${BASE_URLS[$i]}
echo "getting manifests for IDP '$IDP' at $BASE_URL"

resp=$(curl $BASE_URL/manifests/ -H "Authorization: bearer ${TOKEN_JSON[$IDP]}" 2>/dev/null)

# if access token is expired, get a new one and try again
if [[ $(jq -r '.error' <<< $resp) =~ 'log' ]]; then
echo "get new token for IDP '$IDP'"
TOKEN_JSON[$IDP]=$(curl http://workspace-token-service.$NAMESPACE/token/?idp=$IDP 2>/dev/null | jq -r '.token')
resp=$(curl $BASE_URL/manifests/ -H "Authorization: bearer ${TOKEN_JSON[$IDP]}" 2>/dev/null)
fi

# get the name of the most recent manifest
MANIFEST_NAME=$(jq --raw-output .manifests[-1].filename <<< $resp)
if [[ $? != 0 ]]; then
echo "Manifests endpoints at $BASE_URL/manifests/ did not return JSON. Maybe it's not configured?"
continue
fi
MANIFESTEXT=`echo $resp | jq --raw-output .manifests[-1].filename`
if [ "$MANIFESTEXT" == "null" ]; then
# user doens't have any manifest
sleep 10
if [[ "$MANIFEST_NAME" == "null" ]]; then
# user doesn't have any manifest
continue
fi
FILENAME=`echo $MANIFESTEXT | sed 's/\.[^.]*$//'`
if [ ! -d /data/$FILENAME ]; then
echo mount manifest $MANIFESTEXT
curl http://manifestservice-service.$NAMESPACE/file/$MANIFESTEXT -H "Authorization: Bearer $TOKEN_JSON" > /manifest.json
gen3-fuse -config=/fuse-config.yaml -manifest=/manifest.json -mount-point=/data/$FILENAME -hostname=https://$HOSTNAME -wtsURL=http://workspace-token-service.$NAMESPACE >/proc/1/fd/1 2>/proc/1/fd/2
FILENAME=$(sed 's/\.[^.]*$//' <<< $MANIFEST_NAME)

# one folder per IDP
DOMAIN=$(awk -F/ '{print $3}' <<< $BASE_URL)
IDP_DATA_PATH="/data/$DOMAIN"

# gen3-fuse mounts the files in /data/<hostname> dir
if [ ! -d $IDP_DATA_PATH/$FILENAME ]; then
echo mount manifest at $IDP_DATA_PATH/$MANIFEST_NAME
mkdir -p $IDP_DATA_PATH
curl $BASE_URL/manifests/file/$MANIFEST_NAME -H "Authorization: Bearer ${TOKEN_JSON[$IDP]}" > /manifest.json
gen3-fuse -config=/fuse-config.yaml -manifest=/manifest.json -mount-point=$IDP_DATA_PATH/$FILENAME -hostname=$BASE_URL -wtsURL=http://workspace-token-service.$NAMESPACE -wtsIDP=$IDP >/proc/1/fd/1 2>/proc/1/fd/2
fi

# get the number of existing manifests. If there are more than
# MAX_MANIFESTS, delete the oldest one.
if [ $(df $IDP_DATA_PATH/manifest* | sed '1d' | wc -l) -gt $MAX_MANIFESTS ]; then # remove header line
OLDDIR=$(df $IDP_DATA_PATH/manifest* | grep manifest | cut -d'/' -f 4 | head -n 1)
echo unmount old manifest $OLDDIR
fusermount -u $IDP_DATA_PATH/$OLDDIR; rm -rf $IDP_DATA_PATH/$OLDDIR
fi
else
OLDDIR=`df /data/manifest* | grep manifest | cut -d'/' -f 3 | head -n 1`
echo unmount old manifest $OLDDIR
fusermount -u /data/$OLDDIR; rm -rf /data/$OLDDIR
fi
done
sleep 10
done

0 comments on commit a61edd4

Please sign in to comment.