Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add experimental daemon plugin for 9P2000.L support #6612

Closed
wants to merge 102 commits into from

Conversation

djdv
Copy link
Contributor

@djdv djdv commented Aug 29, 2019

Part of: ipfs-inactive/package-managers#74

Draft TODO:

  • Needs tests
    • test the interfaces directly (go test)
    • maybe blackbox tests (sharness, fsx, etc.)
  • go-ipfs/plugin should support plugin scoped configs (we can punt on this)
    • We want to remove the go-ipfs-config dependency in our plugin, and instead, receive some context from go-ipfs/plugin
    • e.g. var cfg Config.FileSystem = findCfgPathAndLoad(myNonStandardPath, &myWhateverFormat, ... could be avoided by changing Plugin.Init() to something like Plugin.Init(context, opts...)
    • Resolved via plugins: add support for plugin configs #6613
  • Trying to enable the experiment through ipfs config --json Experimental.FileSystemEnabled true should work
    • We can simply remove the plugin from the preload list, and document how to add it in experimental-features.md 🠗
  • Documentation
    • package / code
    • experimental-features.md

Other known issues:

  • go.mod has replace directives for this patchset, these need to point to the final dependency commits before merging
  • Directory reads in particular need to be fixed before merging. (currently reads entire directory every request and returns requested slice instead of using a forward-only stream)

@@ -0,0 +1,168 @@
package p9client
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is for testing, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is intended to become a wrapper library for clients to use. So that they can dial our plugin's server easily. Longer term it should shape the VFS interface. Currently we just use 9P semantics, but we can define operations such as Readdir here to be more brief, much like how MFS does with operations like Mkdir.

e.g.
doing ents, err := client.Readdir(someFilesystem, "/some/path") rather than using the interface directly yourself.

However this is more related to ipfs-inactive/package-managers#71 and should be broken up. This PR should only relate to the server portion at the moment so I'll remove this from the patchset.


cfg := &Config{}
if env.Config != nil {
byteRep, err := json.Marshal(env.Config)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm. Maybe we should pass in a json.RawMessage? Or does that leak too much?

Copy link
Contributor Author

@djdv djdv Sep 12, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you saying env.Config would change to be of type json.RawMessage rather than []byte interface{}?

That probably makes the most sense. I'm trying to think of a situation where you'd need raw bytes in the plugin config, that can't be fit inside formatted json, but nothing comes to immediate mind that wouldn't be better stored elsewhere.
(e.g. storing a CID to some binary in the config rather than encoding the (complete) binary itself)

So long as we discard the object once we've loaded and parsed, I'd imagine things wouldn't use any more resources than the current implementation. But I'm saying that without having doing any actual comparisons.

Copy link
Contributor Author

@djdv djdv Sep 12, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, I'm assuming you could have meant casting asserting env.Config to as a RawMessage as well.

Copy link
Contributor Author

@djdv djdv Sep 12, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went forward with the second one for now
b6d1506

but this isn't tested yet.

This #6612 (comment) likely needs to be resolved, and I'd like to see some method for Plugins to initiate a Save()/Store()/Whatever() for their section (only) of the config, from within go, if we possibly can.
e.g., I'd like to be able to load plugin config, modify some structure, and push it back to the config, but not have full config access.

plugin/plugins/filesystem/filesystem.go Show resolved Hide resolved
plugin/plugins/filesystem/nodes/base.go Outdated Show resolved Hide resolved
plugin/plugins/filesystem/nodes/base.go Show resolved Hide resolved
plugin/plugins/filesystem/nodes/ipfs.go Outdated Show resolved Hide resolved
var err error
for _, name := range names {
//TODO: timerctx; don't want to hang forever
if walkedNode.Path, err = id.core.ResolvePath(id.Ctx, corepath.Join(walkedNode.Path, name)); err != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm. This looks like something we might want to tweak in the CoreAPI. We probably want a version that returns a channel of Resolved{Component: string, To: Path} results.

TODO later: ipfs/interface-go-ipfs-core#42

plugin/plugins/filesystem/nodes/utils.go Show resolved Hide resolved
//defer asyncContext.Cancel()
defer close(relayChan)

for i := 0; i != oStat.NumLinks; i++ {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This won't work with sharded directories.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we just wait for Ls to close coreChan?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed how IPFS Readdir logic works, so that the system checks and stores the coreChan on Open(), and progresses on it during Readdir(), in a forward-only fashion.
https://github.com/ipfs/go-ipfs/blob/059c218a76149c270f6cd11d21a4a9dbd9172bae/plugin/plugins/filesystem/nodes/ipfs.go#L103

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^ needs test: create sharded directory and read from it

plugin/plugins/filesystem/nodes/utils.go Outdated Show resolved Hide resolved
@Stebalien
Copy link
Member

Main things to address now:

  1. Docs. This will make it easier to understand the architecture.
  2. QIDs. We're going to have to find some way to make this work and I would really like to avoid putting anything on disk. Unfortunately, 1e6 files is going to cost us probably 100MiB of memory if we keep tables in memory.

Copy link
Contributor

@lanzafame lanzafame left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A friendly reminder from someone trying to extract perf information out of ipfs to understand how gateways are performing, please pass contexts around everywhere before this gets merged. 😸

@djdv djdv force-pushed the feat/filesystem-plugin branch 4 times, most recently from 24c4aca to b9a988f Compare September 5, 2019 15:20
@djdv
Copy link
Contributor Author

djdv commented Sep 5, 2019

(Fixed building for linux,darwin 386 and linux arm,arm64. Now the tests will run, but they're broken at the moment)

@djdv djdv force-pushed the feat/filesystem-plugin branch 3 times, most recently from 2b9a5c2 to d922b96 Compare September 7, 2019 15:47
@djdv
Copy link
Contributor Author

djdv commented Oct 23, 2019

I apologize to anyone monitoring this thread for using all of their RAM. There's a lot of comments in here!
I did an entire pass on them to make sure nothing gets lost.

Judgment needed on unresolved issues (ping @Stebalien)
#6612 (comment)
#6612 (comment)
#6612 (comment)
#6612 (comment) -> moved to below
#6612 (comment)
#6612 (comment)
#6612 (comment)
└ updated ━ #6612 (comment)
#6612 (comment)


@ myself
Don't forget about these even if Github collapses them:
Document our root path thing
#6612 (comment)

basic: 0167805

Make sure to handle and test for sharded directories, HAMTs, etc.
#6612 (comment)
#6612 (comment)
-
#6612 (comment)

basic: 76d7d79

Do another pass on permissions
#6612 (comment)
#6612 (comment)
and attributes
#6612 (comment)

Looked at during restructure: 0b1fe20

Discuss how to handle overridding the config file's Multiaddr string / Environment variables
#6612 (comment)
#6612 (comment)

We've neglected to properly handle configuration in general, put thought into it and write a variety of tests
mimimum:

Don't forget to filter metadata requests:
#6612 (comment)

We currently fetch data on demand instead of storing and filtering the structure with the reference. Returning only what was asked for.

Consistency check in docs and loggers:
The protocol is "9P", the library is "p9", Linux's mount uses 9p as an argument. (<- add this itself somewhere up front so documentation is somewhat more clear)
#6612 (comment)

We partially amended this:
but we currently swallow bytesRead instead returning 0. Don't do that.
#6612 (comment)

92ed82c
+
c294ac8

Reassess how/why we use loggers the way we do, and document it regardless:
#6612 (comment)

Make a pass on QID's in general:
Make sure we have some semblance of consistency.
#6612 (comment)

better: 0b1fe20

Read doesn't loop until full:
https://github.com/ipfs/go-ipfs/pull/6612/files#r338454668

@djdv
Copy link
Contributor Author

djdv commented Oct 23, 2019

We'll also want to make sure this change gets bubbled up for our Windows users: multiformats/go-multiaddr-net#60

Copy link
Member

@Stebalien Stebalien left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

General advice:

  • Be careful about premature optimizations (or generalizations). The walking code is getting pretty complicated.
  • There are quite a few commented code chunks. When commenting out (then committing) code like this, explaining why it's commented helps.

Other than that, this looks like it's getting close.

// store error on the fs object then close our syncing channel (see use in `Close` below)
fs.serverErr = server.Serve(manet.NetListener(fs.listener))

if fs.closing { //[async] we expect `Accept` to fail while `Close` is in progress
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 (nice comment)

// Storage for file's metadata
Qid *p9.QID
meta *p9.Attr
metaMask *p9.AttrMask
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We discussed this offline. Just storing the data probably isn't an issue.

This will cause issues when moving back and forth though (walking back to ..), so we should probably store metadata in an index on the FS instead.

Define "cause issues"? If this breaks .., we should definitely just pass these by value and optimize later.

Qid *p9.QID
meta *p9.Attr
metaMask *p9.AttrMask
Logger logging.EventLogger
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bump

plugin/plugins/filesystem/nodes/base.go Outdated Show resolved Hide resolved

It is valid to return a newly constructed reference or modify and return the existing reference
as long as `QID` is ready to be called on the resulting node
and resources are reaped where sensible within the fs implementation
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still needs to clarify who owns what. The interface contract needs to specify constraints on the inputs/outputs, describing what may/may not happen internally isn't all that important.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something like "Step steps into the current directory, consuming the WalkRef. After calling Step, the original WalkRef must not be used."

select {
case entry, open := <-id.directory.entryChan:
if !open {
//id.operationsCancel()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uncomment or document?

plugin/plugins/filesystem/nodes/ipfs.go Show resolved Hide resolved
plugin/plugins/filesystem/nodes/keyfs.go Show resolved Hide resolved
}

// OverlayFileMeta holds data relevant to file system nodes themselves
type OverlayFileMeta struct {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems reasonable but I'd definitely include that reasoning in the code docs.


/* general helpers */

func (b *Base) ninePath() p9Path { return b.qid.Path }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This really doesn't need to exist.

@djdv djdv closed this Nov 23, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants