[pkgfs] add /pkgfs/needs/packages/*
This adds a directory tree that can be used to identify packages that are
part way through being cached, and provides blob installation endpoints for
their blobs.
Change-Id: I54d0425401e0dcf4e96e2990da4a22b7b19173a8
diff --git a/garnet/go/src/pmd/index/dynamic_index.go b/garnet/go/src/pmd/index/dynamic_index.go
index defdc4b..4b977be 100644
--- a/garnet/go/src/pmd/index/dynamic_index.go
+++ b/garnet/go/src/pmd/index/dynamic_index.go
@@ -254,6 +254,22 @@
return found
}
+func (idx *DynamicIndex) PkgHasNeed(pkg, root string) bool {
+ idx.mu.Lock()
+ defer idx.mu.Unlock()
+
+ needs, found := idx.needs[pkg]
+ if !found {
+ return found
+ }
+ for need := range needs {
+ if need == root {
+ return true
+ }
+ }
+ return false
+}
+
func (idx *DynamicIndex) NeedsList() []string {
idx.mu.Lock()
defer idx.mu.Unlock()
@@ -266,6 +282,40 @@
return names
}
+func (idx *DynamicIndex) PkgNeedsList(pkgRoot string) []string {
+ idx.mu.Lock()
+ defer idx.mu.Unlock()
+
+ pkgNeeds, found := idx.waiting[pkgRoot]
+ if !found {
+ return []string{}
+ }
+ blobs := make([]string, 0, len(pkgNeeds))
+ for blob := range pkgNeeds {
+ blobs = append(blobs, blob)
+ }
+ return blobs
+}
+
+func (idx *DynamicIndex) InstallingList() []string {
+ idx.mu.Lock()
+ defer idx.mu.Unlock()
+
+ names := make([]string, 0, len(idx.installing))
+ for name := range idx.installing {
+ names = append(names, name)
+ }
+ return names
+}
+
+func (idx *DynamicIndex) IsInstalling(merkle string) bool {
+ idx.mu.Lock()
+ defer idx.mu.Unlock()
+
+ _, found := idx.installing[merkle]
+ return found
+}
+
func (idx *DynamicIndex) Notify(roots ...string) {
if len(roots) == 0 {
return
diff --git a/garnet/go/src/pmd/pkgfs/needs_directory.go b/garnet/go/src/pmd/pkgfs/needs_directory.go
index 57a8127..6ca8fc98 100644
--- a/garnet/go/src/pmd/pkgfs/needs_directory.go
+++ b/garnet/go/src/pmd/pkgfs/needs_directory.go
@@ -10,6 +10,10 @@
"time"
)
+// needsRoot presents the following tree:
+// /pkgfs/needs/blobs/$BLOB_HASH
+// /pkgfs/needs/packages/$PACKAGE_HASH/$BLOB_HASH
+// the files are "needsFile" vnodes, so they're writable to blobfs.
type needsRoot struct {
unsupportedDirectory
@@ -39,6 +43,13 @@
return nbd.Open(parts[1], flags)
}
return nil, nbd, nil, nil
+ case "packages":
+ npr := &needsPkgRoot{unsupportedDirectory: unsupportedDirectory("/needs/packages"), fs: d.fs}
+ if len(parts) > 1 {
+ return npr.Open(parts[1], flags)
+ }
+ return nil, npr, nil, nil
+
default:
if len(parts) != 1 || flags.Create() {
return nil, nil, nil, fs.ErrNotSupported
@@ -57,18 +68,108 @@
return 0, d.fs.mountTime, d.fs.mountTime, nil
}
-type needsFile struct {
- unsupportedFile
+// needsPkgRoot serves a directory that indexes the blobs needed to fulfill a
+// package that is presently part way through caching.
+type needsPkgRoot struct {
+ unsupportedDirectory
fs *Filesystem
}
-func (f *needsFile) Close() error {
+func (d *needsPkgRoot) Dup() (fs.Directory, error) {
+ return d, nil
+}
+
+func (d *needsPkgRoot) Close() error {
return nil
}
-func (f *needsFile) Stat() (int64, time.Time, time.Time, error) {
- return 0, time.Time{}, time.Time{}, nil
+func (d *needsPkgRoot) Open(name string, flags fs.OpenFlags) (fs.File, fs.Directory, *fs.Remote, error) {
+ name = clean(name)
+ if name == "" {
+ return nil, d, nil, nil
+ }
+
+ parts := strings.SplitN(name, "/", 2)
+
+ root := parts[0]
+
+ if !d.fs.index.IsInstalling(root) {
+ return nil, nil, nil, fs.ErrNotFound
+ }
+
+ debugLog("pkgfs:needspkgroot:%q open", root)
+ pkgDir := &needsPkgDir{fs: d.fs, pkgRoot: root}
+
+ if len(parts) > 1 {
+ return pkgDir.Open(parts[1], flags)
+ }
+
+ return nil, pkgDir, nil, nil
+}
+
+func (d *needsPkgRoot) Read() ([]fs.Dirent, error) {
+ blobs := d.fs.index.InstallingList()
+ dirents := make([]fs.Dirent, len(blobs))
+ for i := range blobs {
+ dirents[i] = fileDirEnt(blobs[i])
+ }
+ return dirents, nil
+}
+
+func (d *needsPkgRoot) Stat() (int64, time.Time, time.Time, error) {
+ // TODO(raggi): provide more useful values
+ return 0, d.fs.mountTime, d.fs.mountTime, nil
+}
+
+// needsPkgDir serves a directory that indexes the blobs needed to fulfill a
+// package that is presently part way through caching.
+type needsPkgDir struct {
+ unsupportedDirectory
+
+ fs *Filesystem
+
+ pkgRoot string
+}
+
+func (d *needsPkgDir) Dup() (fs.Directory, error) {
+ return d, nil
+}
+
+func (d *needsPkgDir) Close() error {
+ return nil
+}
+
+func (d *needsPkgDir) Open(name string, flags fs.OpenFlags) (fs.File, fs.Directory, *fs.Remote, error) {
+ name = clean(name)
+ if name == "" {
+ return nil, d, nil, nil
+ }
+
+ if strings.Contains(name, "/") {
+ return nil, nil, nil, fs.ErrNotFound
+ }
+
+ if !d.fs.index.PkgHasNeed(d.pkgRoot, name) {
+ return nil, nil, nil, fs.ErrNotFound
+ }
+
+ debugLog("pkgfs:needspkgdir:%q open", name)
+ return &installFile{fs: d.fs, name: name, isPkg: false}, nil, nil, nil
+}
+
+func (d *needsPkgDir) Read() ([]fs.Dirent, error) {
+ names := d.fs.index.PkgNeedsList(d.pkgRoot)
+ dirents := make([]fs.Dirent, len(names))
+ for i := range names {
+ dirents[i] = fileDirEnt(names[i])
+ }
+ return dirents, nil
+}
+
+func (d *needsPkgDir) Stat() (int64, time.Time, time.Time, error) {
+ // TODO(raggi): provide more useful values
+ return 0, d.fs.mountTime, d.fs.mountTime, nil
}
type needsBlobsDir struct {
diff --git a/garnet/go/src/pmd/pkgfs/pkgfs_test.go b/garnet/go/src/pmd/pkgfs/pkgfs_test.go
index 07b9f25..3637e13 100644
--- a/garnet/go/src/pmd/pkgfs/pkgfs_test.go
+++ b/garnet/go/src/pmd/pkgfs/pkgfs_test.go
@@ -217,7 +217,6 @@
t.Fatalf("package blob missing after package write: %s", err)
}
- // TODO(raggi): check that the pacakge content blobs appear in the needs tree
manifest, err := cfg.Manifest()
if err != nil {
t.Fatal(err)
@@ -249,6 +248,29 @@
}
sort.Strings(needs)
+ f, err = pkgfsOpen(filepath.Join("needs", "packages", merkleroot), zxio.OpenRightReadable, zxio.ModeTypeDirectory)
+
+ needs2, err := f.Readdirnames(256)
+ f.Close()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ for i := range needs2 {
+ needs2[i] = filepath.Base(needs2[i])
+ }
+ sort.Strings(needs2)
+
+ if len(needs) != len(needs2) {
+ t.Errorf("expected needs dirs to be the same: %d != %d", len(needs), len(needs2))
+ }
+
+ for i, need := range needs {
+ if needs2[i] != need {
+ t.Errorf("needs from needs/blobs didn't match package needs at %d", i)
+ }
+ }
+
contents, err := ioutil.ReadFile(manifest.Paths["meta/contents"])
if err != nil {
t.Fatal(err)