diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index 1c5355a71..a03ac72bd 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -41,6 +41,7 @@ jobs: cloudfuse_CFG: "./cloudfuse.yaml" cloudfuse_STREAM_CFG: "./cloudfuse_stream.yaml" cloudfuse_BLOCK_CFG: "./cloudfuse_block.yaml" + MOUNT_READY_TIMEOUT: 60 zig: 0.15.2 GH_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -210,7 +211,7 @@ jobs: rm -rf ${TEMP_DIR}/* ./cloudfuse.test -test.v -test.coverprofile=${WORK_DIR}/cloudfuse_block.cov mount ${MOUNT_DIR} --config-file=${cloudfuse_CFG} --foreground=true & - READY_TIMEOUT=20 # Seconds to wait for mount to be ready + READY_TIMEOUT=${MOUNT_READY_TIMEOUT} # Seconds to wait for mount to be ready POLL_INTERVAL=1 # Seconds between checks SECONDS_WAITED=0 while [ $SECONDS_WAITED -lt $READY_TIMEOUT ]; do @@ -221,7 +222,7 @@ jobs: SECONDS_WAITED=$((SECONDS_WAITED + POLL_INTERVAL)) done ps -aux | grep cloudfuse - rm -rf ${MOUNT_DIR}/* + rm -rf ${MOUNT_DIR}/* || true cd test/e2e_tests go test -v -timeout=7200s ./... -args -mnt-path=${MOUNT_DIR} -tmp-path=${TEMP_DIR} cd - @@ -257,7 +258,7 @@ jobs: cat /tmp/configBlockProfilerTemp.yaml ./cloudfuse.test -test.v -test.coverprofile=${WORK_DIR}/cloudfuse_block_profiler.cov mount ${MOUNT_DIR} --config-file=/tmp/configBlockProfilerTemp.yaml --foreground=true & - READY_TIMEOUT=20 # Seconds to wait for mount to be ready + READY_TIMEOUT=${MOUNT_READY_TIMEOUT} # Seconds to wait for mount to be ready POLL_INTERVAL=1 # Seconds between checks SECONDS_WAITED=0 while [ $SECONDS_WAITED -lt $READY_TIMEOUT ]; do @@ -268,7 +269,7 @@ jobs: SECONDS_WAITED=$((SECONDS_WAITED + POLL_INTERVAL)) done ps -aux | grep cloudfuse - rm -rf ${MOUNT_DIR}/* + rm -rf ${MOUNT_DIR}/* || true cd test/e2e_tests go test -v -timeout=7200s ./... -args -mnt-path=${MOUNT_DIR} -tmp-path=${TEMP_DIR} cd - @@ -309,7 +310,7 @@ jobs: rm -rf ${BLOCK_TEMP_DIR}/* ./cloudfuse.test -test.v -test.coverprofile=${WORK_DIR}/cloudfuse_block_block_cache.cov mount ${MOUNT_DIR} --config-file=${cloudfuse_CFG} --foreground=true & - READY_TIMEOUT=20 # Seconds to wait for mount to be ready + READY_TIMEOUT=${MOUNT_READY_TIMEOUT} # Seconds to wait for mount to be ready POLL_INTERVAL=1 # Seconds between checks SECONDS_WAITED=0 while [ $SECONDS_WAITED -lt $READY_TIMEOUT ]; do @@ -321,7 +322,7 @@ jobs: done ps -aux | grep cloudfuse - rm -rf ${MOUNT_DIR}/* + rm -rf ${MOUNT_DIR}/* || true cd test/e2e_tests go test -v -timeout=7200s ./... -args -mnt-path=${MOUNT_DIR} -tmp-path=${BLOCK_TEMP_DIR} cd - @@ -363,7 +364,7 @@ jobs: rm -rf ${TEMP_DIR}/* ./cloudfuse.test -test.v -test.coverprofile=${WORK_DIR}/cloudfuse_adls.cov mount ${MOUNT_DIR} --config-file=${cloudfuse_ADLS_CFG} --foreground=true & - READY_TIMEOUT=20 # Seconds to wait for mount to be ready + READY_TIMEOUT=${MOUNT_READY_TIMEOUT} # Seconds to wait for mount to be ready POLL_INTERVAL=1 # Seconds between checks SECONDS_WAITED=0 while [ $SECONDS_WAITED -lt $READY_TIMEOUT ]; do @@ -374,7 +375,7 @@ jobs: SECONDS_WAITED=$((SECONDS_WAITED + POLL_INTERVAL)) done ps -aux | grep cloudfuse - rm -rf ${MOUNT_DIR}/* + rm -rf ${MOUNT_DIR}/* || true cd test/e2e_tests go test -v -timeout=7200s ./... -args -mnt-path=${MOUNT_DIR} -adls=true -tmp-path=${TEMP_DIR} cd - @@ -410,7 +411,7 @@ jobs: cat /tmp/configAdlsProfilerTemp.yaml ./cloudfuse.test -test.v -test.coverprofile=${WORK_DIR}/cloudfuse_adls_profiler.cov mount ${MOUNT_DIR} --config-file=/tmp/configAdlsProfilerTemp.yaml --foreground=true & - READY_TIMEOUT=20 # Seconds to wait for mount to be ready + READY_TIMEOUT=${MOUNT_READY_TIMEOUT} # Seconds to wait for mount to be ready POLL_INTERVAL=1 # Seconds between checks SECONDS_WAITED=0 while [ $SECONDS_WAITED -lt $READY_TIMEOUT ]; do @@ -421,7 +422,7 @@ jobs: SECONDS_WAITED=$((SECONDS_WAITED + POLL_INTERVAL)) done ps -aux | grep cloudfuse - rm -rf ${MOUNT_DIR}/* + rm -rf ${MOUNT_DIR}/* || true cd test/e2e_tests go test -v -timeout=7200s ./... -args -mnt-path=${MOUNT_DIR} -tmp-path=${TEMP_DIR} cd - @@ -465,7 +466,7 @@ jobs: rm -rf ${TEMP_DIR}/* ./cloudfuse.test -test.v -test.coverprofile=${WORK_DIR}/cloudfuse_s3.cov mount ${MOUNT_DIR} --config-file=${cloudfuse_CFG} --foreground=true & - READY_TIMEOUT=20 # Seconds to wait for mount to be ready + READY_TIMEOUT=${MOUNT_READY_TIMEOUT} # Seconds to wait for mount to be ready POLL_INTERVAL=1 # Seconds between checks SECONDS_WAITED=0 while [ $SECONDS_WAITED -lt $READY_TIMEOUT ]; do @@ -510,7 +511,7 @@ jobs: cat /tmp/configBlockProfilerTemp.yaml ./cloudfuse.test -test.v -test.coverprofile=${WORK_DIR}/cloudfuse_s3_profiler.cov mount ${MOUNT_DIR} --config-file=/tmp/configBlockProfilerTemp.yaml --foreground=true & - READY_TIMEOUT=20 # Seconds to wait for mount to be ready + READY_TIMEOUT=${MOUNT_READY_TIMEOUT} # Seconds to wait for mount to be ready POLL_INTERVAL=1 # Seconds between checks SECONDS_WAITED=0 while [ $SECONDS_WAITED -lt $READY_TIMEOUT ]; do @@ -558,7 +559,7 @@ jobs: rm -rf ${BLOCK_TEMP_DIR}/* ./cloudfuse.test -test.v -test.coverprofile=${WORK_DIR}/cloudfuse_s3_block_cache.cov mount ${MOUNT_DIR} --config-file=${cloudfuse_CFG} --foreground=true & - READY_TIMEOUT=20 # Seconds to wait for mount to be ready + READY_TIMEOUT=${MOUNT_READY_TIMEOUT} # Seconds to wait for mount to be ready POLL_INTERVAL=1 # Seconds between checks SECONDS_WAITED=0 while [ $SECONDS_WAITED -lt $READY_TIMEOUT ]; do @@ -613,7 +614,7 @@ jobs: rm -rf ${TEMP_DIR}/* ./cloudfuse.test -test.v -test.coverprofile=${WORK_DIR}/cloudfuse_s3.cov mount ${MOUNT_DIR} --config-file=${cloudfuse_CFG} --foreground=true & - READY_TIMEOUT=20 # Seconds to wait for mount to be ready + READY_TIMEOUT=${MOUNT_READY_TIMEOUT} # Seconds to wait for mount to be ready POLL_INTERVAL=1 # Seconds between checks SECONDS_WAITED=0 while [ $SECONDS_WAITED -lt $READY_TIMEOUT ]; do @@ -666,7 +667,7 @@ jobs: TEMP_DIR: ${{ env.TEMP_DIR }} WORK_DIR: ${{ env.WORK_DIR }} cloudfuse_STREAM_CFG: ${{ env.cloudfuse_STREAM_CFG }} - run: "rm -rf ${MOUNT_DIR}/*\nrm -rf ${TEMP_DIR}/*\n./cloudfuse.test -test.v -test.coverprofile=${WORK_DIR}/cloudfuse_stream.cov mount ${MOUNT_DIR} --config-file=${cloudfuse_STREAM_CFG} --foreground=true &\nsleep 10\nps -aux | grep cloudfuse\nrm -rf ${MOUNT_DIR}/*\ncd test/e2e_tests\ngo test -v -timeout=7200s ./... -args -mnt-path=${MOUNT_DIR} -tmp-path=${TEMP_DIR}\ncd -\nsudo fusermount -u ${MOUNT_DIR} \nsleep 5" + run: "rm -rf ${MOUNT_DIR}/*\nrm -rf ${TEMP_DIR}/*\n./cloudfuse.test -test.v -test.coverprofile=${WORK_DIR}/cloudfuse_stream.cov mount ${MOUNT_DIR} --config-file=${cloudfuse_STREAM_CFG} --foreground=true &\nsleep 10\nps -aux | grep cloudfuse\nrm -rf ${MOUNT_DIR}/* || true\ncd test/e2e_tests\ngo test -v -timeout=7200s ./... -args -mnt-path=${MOUNT_DIR} -tmp-path=${TEMP_DIR}\ncd -\nsudo fusermount -u ${MOUNT_DIR} \nsleep 5" - name: Create Config File - Block Blob env: @@ -855,7 +856,7 @@ jobs: WORK_DIR: ${{ env.WORK_DIR }} cloudfuse_CFG: ${{ env.cloudfuse_CFG }} CONTAINER_NAME: ${{ matrix.containerName }} - run: "set +x\nrm -rf ${MOUNT_DIR}/*\nrm -rf ${TEMP_DIR}/*\n./cloudfuse.test unmount all\n./cloudfuse.test gen-test-config --config-file=azure_key.yaml --container-name=${CONTAINER_NAME} --temp-path=${TEMP_DIR} --output-file=${cloudfuse_CFG}\n\n./cloudfuse.test -test.v -test.coverprofile=${WORK_DIR}/secure_encrypt.cov secure encrypt --config-file=${cloudfuse_CFG} --output-file=${WORK_DIR}/cloudfuse.azsec --passphrase=12312312312312312312312312312312 \nif [ $? -ne 0 ]; then\n exit 1\nfi\n./cloudfuse.test -test.v -test.coverprofile=${WORK_DIR}/mount_secure.cov mount ${MOUNT_DIR} --config-file=${WORK_DIR}/cloudfuse.azsec --passphrase=12312312312312312312312312312312 &\nsleep 10\nps -aux | grep cloudfuse\nrm -rf ${MOUNT_DIR}/*\ncd test/e2e_tests\ngo test -v -timeout=7200s ./... -args -mnt-path=${MOUNT_DIR} -adls=false -tmp-path=${TEMP_DIR}\ncd -\n\n./cloudfuse.test -test.v -test.coverprofile=${WORK_DIR}/secure_set.cov secure set --config-file=${WORK_DIR}/cloudfuse.azsec --passphrase=12312312312312312312312312312312 --key=logging.level --value=log_debug\n./cloudfuse.test unmount all\nsleep 5" + run: "set +x\nrm -rf ${MOUNT_DIR}/*\nrm -rf ${TEMP_DIR}/*\n./cloudfuse.test unmount all\n./cloudfuse.test gen-test-config --config-file=azure_key.yaml --container-name=${CONTAINER_NAME} --temp-path=${TEMP_DIR} --output-file=${cloudfuse_CFG}\n\n./cloudfuse.test -test.v -test.coverprofile=${WORK_DIR}/secure_encrypt.cov secure encrypt --config-file=${cloudfuse_CFG} --output-file=${WORK_DIR}/cloudfuse.azsec --passphrase=12312312312312312312312312312312 \nif [ $? -ne 0 ]; then\n exit 1\nfi\n./cloudfuse.test -test.v -test.coverprofile=${WORK_DIR}/mount_secure.cov mount ${MOUNT_DIR} --config-file=${WORK_DIR}/cloudfuse.azsec --passphrase=12312312312312312312312312312312 &\nsleep 10\nps -aux | grep cloudfuse\nrm -rf ${MOUNT_DIR}/* || true\ncd test/e2e_tests\ngo test -v -timeout=7200s ./... -args -mnt-path=${MOUNT_DIR} -adls=false -tmp-path=${TEMP_DIR}\ncd -\n\n./cloudfuse.test -test.v -test.coverprofile=${WORK_DIR}/secure_set.cov secure set --config-file=${WORK_DIR}/cloudfuse.azsec --passphrase=12312312312312312312312312312312 --key=logging.level --value=log_debug\n./cloudfuse.test unmount all\nsleep 5" - name: "CLI : Health monitor stop pid" shell: bash {0} @@ -875,7 +876,7 @@ jobs: cat /tmp/configAdlsProfilerTemp.yaml ./cloudfuse.test mount ${MOUNT_DIR}/hmon_test --config-file=/tmp/configAdlsProfilerTemp.yaml - READY_TIMEOUT=20 # Seconds to wait for mount to be ready + READY_TIMEOUT=${MOUNT_READY_TIMEOUT} # Seconds to wait for mount to be ready POLL_INTERVAL=1 # Seconds between checks SECONDS_WAITED=0 while [ $SECONDS_WAITED -lt $READY_TIMEOUT ]; do @@ -906,7 +907,7 @@ jobs: cat /tmp/configAdlsProfilerTemp.yaml ./cloudfuse.test mount ${MOUNT_DIR}/hmon_test --config-file=/tmp/configAdlsProfilerTemp.yaml - READY_TIMEOUT=20 # Seconds to wait for mount to be ready + READY_TIMEOUT=${MOUNT_READY_TIMEOUT} # Seconds to wait for mount to be ready POLL_INTERVAL=1 # Seconds between checks SECONDS_WAITED=0 while [ $SECONDS_WAITED -lt $READY_TIMEOUT ]; do @@ -957,7 +958,7 @@ jobs: TEMP_DIR: ${{ env.TEMP_DIR }} WORK_DIR: ${{ env.WORK_DIR }} cloudfuse_CFG: ${{ env.cloudfuse_CFG }} - run: "rm -rf ${MOUNT_DIR}/*\nrm -rf ${TEMP_DIR}/*\n./cloudfuse.test -test.v -test.coverprofile=${WORK_DIR}/cloudfuse_block_proxy.cov mount ${MOUNT_DIR} --config-file=${cloudfuse_CFG} --foreground=true &\nsleep 10\nps -aux | grep cloudfuse\nrm -rf ${MOUNT_DIR}/*\ncd test/e2e_tests\ngo test -v -timeout=7200s ./... -args -mnt-path=${MOUNT_DIR} -tmp-path=${TEMP_DIR}\ncd -\nsudo fusermount -u ${MOUNT_DIR} \nsleep 5" + run: "rm -rf ${MOUNT_DIR}/*\nrm -rf ${TEMP_DIR}/*\n./cloudfuse.test -test.v -test.coverprofile=${WORK_DIR}/cloudfuse_block_proxy.cov mount ${MOUNT_DIR} --config-file=${cloudfuse_CFG} --foreground=true &\nsleep 10\nps -aux | grep cloudfuse\nrm -rf ${MOUNT_DIR}/* || true\ncd test/e2e_tests\ngo test -v -timeout=7200s ./... -args -mnt-path=${MOUNT_DIR} -tmp-path=${TEMP_DIR}\ncd -\nsudo fusermount -u ${MOUNT_DIR} \nsleep 5" - name: Create Config File - ADLS Proxy env: @@ -983,7 +984,7 @@ jobs: rm -rf ${TEMP_DIR}/* ./cloudfuse.test -test.v -test.coverprofile=${WORK_DIR}/cloudfuse_adls_proxy.cov mount ${MOUNT_DIR} --config-file=${cloudfuse_ADLS_CFG} --foreground=true & - READY_TIMEOUT=20 # Seconds to wait for mount to be ready + READY_TIMEOUT=${MOUNT_READY_TIMEOUT} # Seconds to wait for mount to be ready POLL_INTERVAL=1 # Seconds between checks SECONDS_WAITED=0 while [ $SECONDS_WAITED -lt $READY_TIMEOUT ]; do @@ -994,7 +995,7 @@ jobs: SECONDS_WAITED=$((SECONDS_WAITED + POLL_INTERVAL)) done ps -aux | grep cloudfuse - rm -rf ${MOUNT_DIR}/* + rm -rf ${MOUNT_DIR}/* || true cd test/e2e_tests go test -v -timeout=7200s ./... -args -mnt-path=${MOUNT_DIR} -adls=true -tmp-path=${TEMP_DIR} cd - @@ -1193,7 +1194,7 @@ jobs: rm -rf ${TEMP_DIR}/* ./cloudfuse.test -test.v -test.coverprofile=${WORK_DIR}/cloudfuse_block.cov mount ${MOUNT_DIR} --config-file=${cloudfuse_CFG} --foreground=true & - READY_TIMEOUT=20 # Seconds to wait for mount to be ready + READY_TIMEOUT=${MOUNT_READY_TIMEOUT} # Seconds to wait for mount to be ready POLL_INTERVAL=1 # Seconds between checks SECONDS_WAITED=0 while [ $SECONDS_WAITED -lt $READY_TIMEOUT ]; do @@ -1241,7 +1242,7 @@ jobs: cat /tmp/configBlockProfilerTemp.yaml ./cloudfuse.test -test.v -test.coverprofile=${WORK_DIR}/cloudfuse_block_profiler.cov mount ${MOUNT_DIR} --config-file=/tmp/configBlockProfilerTemp.yaml --foreground=true & - READY_TIMEOUT=20 # Seconds to wait for mount to be ready + READY_TIMEOUT=${MOUNT_READY_TIMEOUT} # Seconds to wait for mount to be ready POLL_INTERVAL=1 # Seconds between checks SECONDS_WAITED=0 while [ $SECONDS_WAITED -lt $READY_TIMEOUT ]; do @@ -1295,7 +1296,7 @@ jobs: ./cloudfuse.test -test.v -test.coverprofile=${WORK_DIR}/cloudfuse_block_block_cache.cov mount ${MOUNT_DIR} --config-file=${cloudfuse_CFG} --foreground=true & CFL_PID=$! - READY_TIMEOUT=20 # Seconds to wait for mount to be ready + READY_TIMEOUT=${MOUNT_READY_TIMEOUT} # Seconds to wait for mount to be ready POLL_INTERVAL=1 # Seconds between checks SECONDS_WAITED=0 while [ $SECONDS_WAITED -lt $READY_TIMEOUT ]; do @@ -1349,7 +1350,7 @@ jobs: rm -rf ${TEMP_DIR}/* ./cloudfuse.test -test.v -test.coverprofile=${WORK_DIR}/cloudfuse_adls.cov mount ${MOUNT_DIR} --config-file=${cloudfuse_ADLS_CFG} --foreground=true & - READY_TIMEOUT=20 # Seconds to wait for mount to be ready + READY_TIMEOUT=${MOUNT_READY_TIMEOUT} # Seconds to wait for mount to be ready POLL_INTERVAL=1 # Seconds between checks SECONDS_WAITED=0 while [ $SECONDS_WAITED -lt $READY_TIMEOUT ]; do @@ -1398,7 +1399,7 @@ jobs: cat ${cloudfuse_ADLS_CFG_HMON} ./cloudfuse.test -test.v -test.coverprofile=${WORK_DIR}/cloudfuse_adls_profiler.cov mount ${MOUNT_DIR} --config-file=${cloudfuse_ADLS_CFG_HMON} --foreground=true & - READY_TIMEOUT=20 # Seconds to wait for mount to be ready + READY_TIMEOUT=${MOUNT_READY_TIMEOUT} # Seconds to wait for mount to be ready POLL_INTERVAL=1 # Seconds between checks SECONDS_WAITED=0 while [ $SECONDS_WAITED -lt $READY_TIMEOUT ]; do @@ -1458,7 +1459,7 @@ jobs: rm -rf ${TEMP_DIR}/* ./cloudfuse.test -test.v -test.coverprofile=${WORK_DIR}/cloudfuse_s3.cov mount ${MOUNT_DIR} --config-file=${cloudfuse_CFG} --foreground=true & - READY_TIMEOUT=20 # Seconds to wait for mount to be ready + READY_TIMEOUT=${MOUNT_READY_TIMEOUT} # Seconds to wait for mount to be ready POLL_INTERVAL=1 # Seconds between checks SECONDS_WAITED=0 while [ $SECONDS_WAITED -lt $READY_TIMEOUT ]; do @@ -1505,7 +1506,7 @@ jobs: cat /tmp/configBlockProfilerTemp.yaml ./cloudfuse.test -test.v -test.coverprofile=${WORK_DIR}/cloudfuse_s3_profiler.cov mount ${MOUNT_DIR} --config-file=/tmp/configBlockProfilerTemp.yaml --foreground=true & - READY_TIMEOUT=20 # Seconds to wait for mount to be ready + READY_TIMEOUT=${MOUNT_READY_TIMEOUT} # Seconds to wait for mount to be ready POLL_INTERVAL=1 # Seconds between checks SECONDS_WAITED=0 while [ $SECONDS_WAITED -lt $READY_TIMEOUT ]; do @@ -1558,7 +1559,7 @@ jobs: ./cloudfuse.test -test.v -test.coverprofile=${WORK_DIR}/cloudfuse_s3_block_cache.cov mount ${MOUNT_DIR} --config-file=${cloudfuse_CFG} --foreground=true & CFL_PID=$! - READY_TIMEOUT=20 # Seconds to wait for mount to be ready + READY_TIMEOUT=${MOUNT_READY_TIMEOUT} # Seconds to wait for mount to be ready POLL_INTERVAL=1 # Seconds between checks SECONDS_WAITED=0 while [ $SECONDS_WAITED -lt $READY_TIMEOUT ]; do @@ -1618,7 +1619,7 @@ jobs: rm -rf ${TEMP_DIR}/* ./cloudfuse.test -test.v -test.coverprofile=${WORK_DIR}/cloudfuse_s3.cov mount ${MOUNT_DIR} --config-file=${cloudfuse_CFG} --foreground=true & - READY_TIMEOUT=20 # Seconds to wait for mount to be ready + READY_TIMEOUT=${MOUNT_READY_TIMEOUT} # Seconds to wait for mount to be ready POLL_INTERVAL=1 # Seconds between checks SECONDS_WAITED=0 while [ $SECONDS_WAITED -lt $READY_TIMEOUT ]; do @@ -1859,7 +1860,7 @@ jobs: cat ${cloudfuse_ADLS_CFG_HMON} ./cloudfuse.test mount ${MOUNT_DIR} --config-file=${cloudfuse_ADLS_CFG_HMON} --foreground=true & - READY_TIMEOUT=20 # Seconds to wait for mount to be ready + READY_TIMEOUT=${MOUNT_READY_TIMEOUT} # Seconds to wait for mount to be ready POLL_INTERVAL=1 # Seconds between checks SECONDS_WAITED=0 while [ $SECONDS_WAITED -lt $READY_TIMEOUT ]; do @@ -1899,7 +1900,7 @@ jobs: cat ${cloudfuse_ADLS_CFG_HMON} ./cloudfuse.test mount ${MOUNT_DIR} --config-file=${cloudfuse_ADLS_CFG_HMON} --foreground=true & - READY_TIMEOUT=20 # Seconds to wait for mount to be ready + READY_TIMEOUT=${MOUNT_READY_TIMEOUT} # Seconds to wait for mount to be ready POLL_INTERVAL=1 # Seconds between checks SECONDS_WAITED=0 while [ $SECONDS_WAITED -lt $READY_TIMEOUT ]; do diff --git a/component/attr_cache/attr_cache.go b/component/attr_cache/attr_cache.go index afa5e1a06..e782382b2 100644 --- a/component/attr_cache/attr_cache.go +++ b/component/attr_cache/attr_cache.go @@ -239,15 +239,6 @@ func (ac *AttrCache) deleteDirectory(path string, deletedAt time.Time) error { return nil } -// does the cache show this path as existing? -func (ac *AttrCache) pathExistsInCache(path string) bool { - item, found := ac.cache.get(path) - if !found { - return false - } - return item.exists() -} - // returns the parent directory (without a trailing slash) func getParentDir(childPath string) string { parentDir := path.Dir(internal.TruncateDirName(childPath)) @@ -437,14 +428,13 @@ func (ac *AttrCache) cleanupExpiredEntries() { if len(keysToDelete) > 0 { ac.cacheLock.Lock() for _, path := range keysToDelete { - // Re-check if entry still exists and is still expired - if item, exists := ac.cache.cacheMap[path]; exists { - if len(item.children) == 0 && - time.Since(item.cachedAt).Seconds() >= float64(ac.cacheTimeout) { + // Re-check if entry still exists, has no children, and is still expired + if item, found := ac.cache.cacheMap[path]; found && len(item.children) == 0 { + if time.Since(item.cachedAt).Seconds() >= float64(ac.cacheTimeout) { + if item.exists() { + item.invalidate() + } if item.parent != nil { - if item.exists() { - item.parent.listCache = nil - } delete(item.parent.children, item.attr.Name) } delete(ac.cache.cacheMap, path) @@ -466,33 +456,43 @@ func (ac *AttrCache) CreateDir(options internal.CreateDirOptions) error { ac.cacheLock.Lock() defer ac.cacheLock.Unlock() // does the directory already exist? - oldDirAttrCacheItem, found := ac.cache.get(options.Name) - directoryAlreadyExists := found && oldDirAttrCacheItem.exists() + dirAttrCacheItem, found := ac.cache.get(options.Name) + directoryAlreadyExists := found && dirAttrCacheItem.exists() // if the attribute cache tracks directory existence // then prevent redundant directory creation - if ac.cacheDirs && directoryAlreadyExists { - return os.ErrExist - } - // invalidate existing directory entry (this is redundant but readable) - if found { - oldDirAttrCacheItem.invalidate() - } - // add (or replace) the directory entry - newDirAttr := internal.CreateObjAttrDir(options.Name) - newDirAttrCacheItem := ac.cache.insert(insertOptions{ - attr: newDirAttr, - exists: true, - cachedAt: currentTime, - }) - if newDirAttrCacheItem != nil { - newDirAttrCacheItem.setMode(options.Mode) - } - // update flags for tracking directory existence - if ac.cacheDirs && newDirAttrCacheItem != nil { - newDirAttrCacheItem.markInCloud(false) + if directoryAlreadyExists { + if ac.cacheDirs { + return os.ErrExist + } + } else { + // invalidate existing directory entry (this is redundant but readable) + if found { + dirAttrCacheItem.markDeleted(currentTime) + } + // add (or replace) the directory entry + newDirAttr := internal.CreateObjAttrDir(options.Name) + dirAttrCacheItem = ac.cache.insert(insertOptions{ + attr: newDirAttr, + exists: true, + cachedAt: currentTime, + }) + // insert returns nil when entries are maxed out + if dirAttrCacheItem != nil { + // update flag for tracking directory existence + if ac.cacheDirs { + dirAttrCacheItem.markInCloud(false) + } + // this is a new directory, so we have a complete (empty) listing for it + dirAttrCacheItem.listingComplete = true + } + // if this is a new entry, update the parent directory timestamps + if err == nil { + ac.touchParentDirTimes(options.Name, currentTime, ac.cacheDirs) + } } - if err == nil && !directoryAlreadyExists { - ac.touchParentDirTimes(options.Name, currentTime, ac.cacheDirs) + // if returning success, update the mode + if err == nil && dirAttrCacheItem != nil { + dirAttrCacheItem.setMode(options.Mode) } } return err @@ -579,15 +579,6 @@ func (ac *AttrCache) StreamDir( } } } - // add cached items in - if len(cachedPathList) > 0 { - log.Info( - "AttrCache::StreamDir : %s merging in %d list cache entries...", - options.Name, - len(cachedPathList), - ) - pathList = append(pathList, cachedPathList...) - } // values should be returned in ascending order by key, without duplicates // sort slices.SortFunc[[]*internal.ObjAttr, *internal.ObjAttr]( @@ -603,7 +594,18 @@ func (ac *AttrCache) StreamDir( return a.Path == b.Path }, ) - ac.cacheListSegment(pathList, options.Name, options.Token, nextToken) + // cache the listing (if there was no error) + if err == nil { + // record when the directory was listed, an up to what token + // this will allow us to serve directory listings from this cache + ac.cacheListSegment(pathList, options.Name, options.Token, nextToken) + // if the listing is complete, record the fact that we have a complete listing + if nextToken == "" { + ac.markListingComplete(options.Name) + } + } else { + log.Err("AttrCache::StreamDir : %s encountered error [%v]", options.Name, err) + } log.Trace("AttrCache::StreamDir : %s returning %d entries", options.Name, len(pathList)) return pathList, nextToken, err } @@ -616,9 +618,8 @@ func (ac *AttrCache) fetchCachedDirList( path string, token string, ) ([]*internal.ObjAttr, string, error) { - var pathList []*internal.ObjAttr if !ac.cacheOnList { - return pathList, "", fmt.Errorf("cache on list is disabled") + return nil, "", fmt.Errorf("cache on list is disabled") } // start accessing the cache ac.cacheLock.RLock() @@ -627,25 +628,22 @@ func (ac *AttrCache) fetchCachedDirList( listDirCache, found := ac.cache.get(path) if !found { log.Warn("AttrCache::fetchCachedDirList : %s directory not found in cache", path) - return pathList, "", fmt.Errorf("%s directory not found in cache", path) + return nil, "", fmt.Errorf("%s directory not found in cache", path) } // is the requested data cached? - if listDirCache.listCache == nil { - listDirCache.listCache = make(map[string]listCacheSegment) - } cachedListSegment, found := listDirCache.listCache[token] if !found { // the data for this token is not in the cache // don't provide cached data when new (uncached) data is being requested log.Info("AttrCache::fetchCachedDirList : %s listing segment %s not cached", path, token) - return pathList, "", fmt.Errorf("%s directory listing segment %s not cached", path, token) + return nil, "", fmt.Errorf("%s directory listing segment %s not cached", path, token) } // check timeout if time.Since(cachedListSegment.cachedAt).Seconds() >= float64(ac.cacheTimeout) { log.Info("AttrCache::fetchCachedDirList : %s listing segment %s cache expired", path, token) // drop the invalid segment from the list cache delete(listDirCache.listCache, token) - return pathList, "", fmt.Errorf( + return nil, "", fmt.Errorf( "%s directory listing segment %s cache expired", path, token, @@ -727,6 +725,16 @@ func (ac *AttrCache) cacheListSegment( listDirPath, token, nextToken, len(pathList)) } +func (ac *AttrCache) markListingComplete(listDirPath string) { + ac.cacheLock.Lock() + defer ac.cacheLock.Unlock() + listDirItem, found := ac.cache.get(listDirPath) + if found { + listDirItem.listingComplete = true + listDirItem.cachedAt = time.Now() + } +} + // IsDirEmpty: Whether or not the directory is empty func (ac *AttrCache) IsDirEmpty(options internal.IsDirEmptyOptions) bool { log.Trace("AttrCache::IsDirEmpty : %s", options.Name) @@ -737,14 +745,15 @@ func (ac *AttrCache) IsDirEmpty(options internal.IsDirEmptyOptions) bool { "AttrCache::IsDirEmpty : %s Dir cache is disabled. Checking with container", options.Name, ) + // when offline, this will return false return ac.NextComponent().IsDirEmpty(options) } // Is the directory in our cache? ac.cacheLock.RLock() - pathInCache := ac.pathExistsInCache(options.Name) - ac.cacheLock.RUnlock() + defer ac.cacheLock.RUnlock() + item, found := ac.cache.get(options.Name) // If the directory does not exist in the attribute cache then let the next component answer - if !pathInCache { + if !found || !item.exists() { log.Debug( "AttrCache::IsDirEmpty : %s not found in attr_cache. Checking with container", options.Name, @@ -753,10 +762,15 @@ func (ac *AttrCache) IsDirEmpty(options internal.IsDirEmptyOptions) bool { } log.Debug("AttrCache::IsDirEmpty : %s found in attr_cache", options.Name) // Check if the cached directory is empty or not - if ac.anyContentsInCache(options.Name) { + if item.hasExistingChildren() { log.Debug("AttrCache::IsDirEmpty : %s has a subpath in attr_cache", options.Name) return false } + // do we have a complete listing? + if item.listingComplete && time.Since(item.cachedAt).Seconds() < float64(ac.cacheTimeout) { + // we know the directory is empty + return true + } // Dir is in cache but no contents are, so check with container log.Debug( "AttrCache::IsDirEmpty : %s children not found in cache. Checking with container", @@ -765,16 +779,10 @@ func (ac *AttrCache) IsDirEmpty(options internal.IsDirEmptyOptions) bool { return ac.NextComponent().IsDirEmpty(options) } -func (ac *AttrCache) anyContentsInCache(prefix string) bool { - ac.cacheLock.RLock() - defer ac.cacheLock.RUnlock() - - directory, found := ac.cache.get(prefix) - if found && directory.exists() { - for _, chldItem := range directory.children { - if chldItem.exists() { - return true - } +func (value *attrCacheItem) hasExistingChildren() bool { + for _, childItem := range value.children { + if childItem.exists() { + return true } } return false @@ -796,7 +804,7 @@ func (ac *AttrCache) RenameDir(options internal.RenameDirOptions) error { if ac.cacheDirs { // if attr_cache is tracking directories, validate this rename // First, check if the destination directory already exists - if ac.pathExistsInCache(options.Dst) { + if item, found := ac.cache.get(options.Dst); found && item.exists() { return os.ErrExist } } else { @@ -1056,18 +1064,10 @@ func (ac *AttrCache) CopyToFile(options internal.CopyToFileOptions) error { log.Trace("AttrCache::CopyToFile : %s", options.Name) err := ac.NextComponent().CopyToFile(options) - if err != nil { + if os.IsNotExist(err) { entry, found := ac.cache.get(options.Name) - if found { - entry.markDeleted(time.Now()) - } - // todo: invalidating path here rather than updating with etag - // due to some changes that are required in az storage comp which - // were not necessarily required. Once they were done invalidation - // of the attribute can be removed. - value, found := ac.cache.get(internal.TruncateDirName(options.Name)) - if found { - value.invalidate() + if found && entry.exists() { + entry.invalidate() } } return err @@ -1085,8 +1085,17 @@ func (ac *AttrCache) CopyFromFile(options internal.CopyFromFileOptions) error { return err } } + // preserve existing metadata if attr != nil { - options.Metadata = attr.Metadata + if options.Metadata == nil { + options.Metadata = attr.Metadata + } else { + for key, value := range attr.Metadata { + if _, exists := options.Metadata[key]; !exists { + options.Metadata[key] = value + } + } + } } err = ac.NextComponent().CopyFromFile(options) @@ -1109,6 +1118,9 @@ func (ac *AttrCache) CopyFromFile(options internal.CopyFromFileOptions) error { entry, found := ac.cache.get(options.Name) if found { entry.invalidate() + } else if parent, found := ac.cache.get(getParentDir(options.Name)); found && parent.exists() { + parent.listCache = nil + parent.listingComplete = false } } else { // replace entry @@ -1157,28 +1169,53 @@ func (ac *AttrCache) GetAttr(options internal.GetAttrOptions) (*internal.ObjAttr // Don't log these by default, as it noticeably affects performance // log.Trace("AttrCache::GetAttr : %s", options.Name) + // is the answer in the cache? + respondFromCache := false + var attrFromCache *internal.ObjAttr + var errFromCache error ac.cacheLock.RLock() value, found := ac.cache.get(options.Name) - ac.cacheLock.RUnlock() if found && value.valid() && time.Since(value.cachedAt).Seconds() < float64(ac.cacheTimeout) { - // Try to serve the request from the attribute cache - // Is the entry marked deleted? + // Serve the request from the attribute cache + respondFromCache = true if !value.exists() { log.Debug("AttrCache::GetAttr : %s (ENOENT) served from cache", options.Name) - return nil, syscall.ENOENT + errFromCache = syscall.ENOENT } else { - return value.attr, nil + attrFromCache = value.attr } } + if ac.cacheDirs { + // drill up for the nearest valid parent directory attribute cache + parent, found := ac.cache.getCachedParent(options.Name) + if found && time.Since(parent.cachedAt).Seconds() < float64(ac.cacheTimeout) { + // Remember, we have no entry for options.Name + // parent is its nearest valid ancestor + // So, if parent doesn't exist, options.Name must not exist + // Or, if parent does exist, and the full list of its contents are cached, + // then since options.Name is *not* in the cache, it must not exist + if !parent.exists() || parent.listingComplete { + respondFromCache = true + errFromCache = syscall.ENOENT + } + } + } + ac.cacheLock.RUnlock() + if respondFromCache { + return attrFromCache, errFromCache + } - // Get the attributes from next component and cache them + // The answer is not cached, or it's expired + // Get the attributes from next component pathAttr, err := ac.NextComponent().GetAttr(options) - + // return unexpected errors immediately (no valid response to cache) + if err != nil && !os.IsNotExist(err) { + return pathAttr, err + } + // response is valid - cache it ac.cacheLock.Lock() defer ac.cacheLock.Unlock() - - switch err { - case nil: + if err == nil { // Retrieved attributes so cache them ac.cache.insert(insertOptions{ attr: pathAttr, @@ -1188,7 +1225,7 @@ func (ac *AttrCache) GetAttr(options internal.GetAttrOptions) (*internal.ObjAttr if ac.cacheDirs { ac.markAncestorsInCloud(getParentDir(options.Name), time.Now()) } - case syscall.ENOENT: + } else { // cache this entity not existing ac.cache.insert(insertOptions{ attr: internal.CreateObjAttr(options.Name, 0, time.Now()), @@ -1239,6 +1276,12 @@ func (ac *AttrCache) FlushFile(options internal.FlushFileOptions) error { toBeInvalid, found := ac.cache.get(options.Handle.Path) if found { toBeInvalid.invalidate() + } else if parent, found := ac.cache.get(getParentDir(options.Handle.Path)); found && parent.exists() { + parent.listCache = nil + parent.listingComplete = false + } + if ac.cacheDirs { + ac.markAncestorsInCloud(getParentDir(options.Handle.Path), time.Now()) } } return err @@ -1286,6 +1329,12 @@ func (ac *AttrCache) CommitData(options internal.CommitDataOptions) error { entry, found := ac.cache.get(options.Name) if found { entry.invalidate() + } else if parent, found := ac.cache.get(getParentDir(options.Name)); found && parent.exists() { + parent.listCache = nil + parent.listingComplete = false + } + if ac.cacheDirs { + ac.markAncestorsInCloud(getParentDir(options.Name), time.Now()) } } return err diff --git a/component/attr_cache/attr_cache_test.go b/component/attr_cache/attr_cache_test.go index 7e876b60f..d43e073ac 100644 --- a/component/attr_cache/attr_cache_test.go +++ b/component/attr_cache/attr_cache_test.go @@ -957,6 +957,42 @@ func (suite *attrCacheTestSuite) TestIsDirEmptyFalseInCache() { suite.assert.False(empty) } +func (suite *attrCacheTestSuite) TestIsDirEmptyCompleteListingFresh() { + defer suite.cleanupTest() + + path := "dir/" + options := internal.IsDirEmptyOptions{Name: path} + suite.addPathToCache(path, false) + + item, found := suite.attrCache.cache.get(path) + suite.assert.True(found) + item.listingComplete = true + + suite.mock.EXPECT().IsDirEmpty(options).MaxTimes(0) + + empty := suite.attrCache.IsDirEmpty(options) + suite.assert.True(empty) +} + +func (suite *attrCacheTestSuite) TestIsDirEmptyCompleteListingExpired() { + defer suite.cleanupTest() + + path := "dir/" + options := internal.IsDirEmptyOptions{Name: path} + suite.addPathToCache(path, false) + + item, found := suite.attrCache.cache.get(path) + suite.assert.True(found) + item.listingComplete = true + item.cachedAt = time.Now(). + Add(-(time.Duration(suite.attrCache.cacheTimeout) * time.Second) - time.Minute) + + suite.mock.EXPECT().IsDirEmpty(options).Return(false) + + empty := suite.attrCache.IsDirEmpty(options) + suite.assert.False(empty) +} + // Tests Rename Directory func (suite *attrCacheTestSuite) TestRenameDir() { defer suite.cleanupTest() @@ -1790,6 +1826,7 @@ func (suite *attrCacheTestSuite) TestGetAttrDoesNotExist() { func (suite *attrCacheTestSuite) TestGetAttrOtherError() { defer suite.cleanupTest() var paths = []string{"a", "a/"} + errOther := errors.New("some other error") for _, path := range paths { // This is a little janky but required since testify suite does not support running setup or clean up for subtests. @@ -1799,10 +1836,10 @@ func (suite *attrCacheTestSuite) TestGetAttrOtherError() { truncatedPath := internal.TruncateDirName(path) options := internal.GetAttrOptions{Name: path} - suite.mock.EXPECT().GetAttr(options).Return(nil, os.ErrNotExist) + suite.mock.EXPECT().GetAttr(options).Return(nil, errOther) result, err := suite.attrCache.GetAttr(options) - suite.assert.Equal(err, os.ErrNotExist) + suite.assert.Equal(errOther, err) suite.assert.Nil(result) suite.assertNotInCache(truncatedPath) }) @@ -1835,6 +1872,25 @@ func (suite *attrCacheTestSuite) TestGetAttrEnoentError() { } } +func (suite *attrCacheTestSuite) TestGetAttrWithCompleteParentListing() { + defer suite.cleanupTest() + + parentPath := "dir/" + childPath := "dir/missing" + + suite.addPathToCache(parentPath, false) + parentItem, found := suite.attrCache.cache.get(parentPath) + suite.assert.True(found) + parentItem.listingComplete = true + + options := internal.GetAttrOptions{Name: childPath} + suite.mock.EXPECT().GetAttr(options).MaxTimes(0) + + result, err := suite.attrCache.GetAttr(options) + suite.assert.Equal(syscall.ENOENT, err) + suite.assert.Nil(result) +} + // Tests Cache Timeout func (suite *attrCacheTestSuite) TestCacheTimeout() { defer suite.cleanupTest() diff --git a/component/attr_cache/cacheMap.go b/component/attr_cache/cacheMap.go index a39f6cfe1..cd0d43819 100644 --- a/component/attr_cache/cacheMap.go +++ b/component/attr_cache/cacheMap.go @@ -58,6 +58,8 @@ type attrCacheItem struct { attrFlag common.BitMap64 children map[string]*attrCacheItem parent *attrCacheItem + + listingComplete bool } // all cache entries are organized into this structure @@ -162,6 +164,19 @@ func (ctm *cacheTreeMap) insertItem(newItem *attrCacheItem, fromDirList bool) { ctm.cacheMap[path] = newItem } +func (ctm *cacheTreeMap) getCachedParent(name string) (*attrCacheItem, bool) { + if name == "" { + return nil, false + } + parent := getParentDir(name) + item, found := ctm.get(parent) + if !found || !item.valid() { + // drill up recursively + return ctm.getCachedParent(parent) + } + return item, found +} + func (value *attrCacheItem) valid() bool { return value.attrFlag.IsSet(AttrFlagValid) } @@ -228,6 +243,7 @@ func (value *attrCacheItem) invalidate() { log.Warn("AttrCache::invalidate : %s has no pointer to its parent", value.attr.Path) } else if value.exists() { value.parent.listCache = nil + value.parent.listingComplete = false } } diff --git a/test/e2e_tests/data_validation_test.go b/test/e2e_tests/data_validation_test.go index 4244244d2..a0bc1e7e9 100644 --- a/test/e2e_tests/data_validation_test.go +++ b/test/e2e_tests/data_validation_test.go @@ -944,9 +944,18 @@ func TestDataValidationTestSuite(t *testing.T) { fmt.Printf("Could not cleanup cache dir before testing [%s]\n", err.Error()) } + // Validate mount path exists before trying to create subdirectories + if _, err := os.Stat(dataValidationMntPathPtr); err != nil { + t.Fatalf( + "Mount path does not exist or is not accessible: %s [%v]", + dataValidationMntPathPtr, + err, + ) + } + err = os.Mkdir(tObj.testMntPath, 0777) if err != nil { - t.Errorf("Failed to create test directory [%s]\n", err.Error()) + t.Fatalf("Failed to create test directory [%s]", err.Error()) } _, _ = rand.Read(minBuff) _, _ = rand.Read(medBuff) diff --git a/test/e2e_tests/dir_test.go b/test/e2e_tests/dir_test.go index a4e2dbfc2..4d8ba4770 100644 --- a/test/e2e_tests/dir_test.go +++ b/test/e2e_tests/dir_test.go @@ -910,9 +910,14 @@ func TestDirTestSuite(t *testing.T) { fmt.Printf("Could not cleanup feature dir before testing [%s]\n", err.Error()) } + // Validate mount path exists before trying to create subdirectories + if _, err := os.Stat(pathPtr); err != nil { + t.Fatalf("Mount path does not exist or is not accessible: %s [%v]", pathPtr, err) + } + err = os.Mkdir(dirTest.testPath, 0777) if err != nil { - t.Errorf("Failed to create test directory [%s]\n", err.Error()) + t.Fatalf("Failed to create test directory [%s]", err.Error()) } _, _ = rand.Read(dirTest.minBuff) _, _ = rand.Read(dirTest.medBuff) diff --git a/test/e2e_tests/file_test.go b/test/e2e_tests/file_test.go index b7719cd06..acd6ae550 100644 --- a/test/e2e_tests/file_test.go +++ b/test/e2e_tests/file_test.go @@ -872,9 +872,14 @@ func TestFileTestSuite(t *testing.T) { fmt.Printf("Could not cleanup feature dir before testing [%s]\n", err.Error()) } + // Validate mount path exists before trying to create subdirectories + if _, err := os.Stat(fileTestPathPtr); err != nil { + t.Fatalf("Mount path does not exist or is not accessible: %s [%v]", fileTestPathPtr, err) + } + err = os.Mkdir(fileTest.testPath, 0777) if err != nil { - t.Errorf("Failed to create test directory [%s]\n", err.Error()) + t.Fatalf("Failed to create test directory [%s]", err.Error()) } _, _ = rand.Read(fileTest.minBuff) _, _ = rand.Read(fileTest.medBuff)