-
-
Notifications
You must be signed in to change notification settings - Fork 3k
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
Feat/migration ipfs download #8064
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just did a first pass as it's quite late. I can do a second one tomorrow.
) | ||
|
||
func TestIpfsFetcher(t *testing.T) { | ||
t.Skip("manually-run dev test only") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe hide it behind a flag, then :) otherwise the only way to run it is by editing the source.
is there any way to eventually make this test work in a local way without extra setup? e.g. by setting up some IPFS/HTTP servers/nodes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done - hidden behind flag
For the HttpFetcher I created a dummy test server. I am not really sure there is a great way to do this for IPFS. Mocking interfaces seems a bit excessive.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm just a bit worried that we have a test that will practically never be run. Mocking isn't great, but if running a real IPFS node for the test is too difficult, at least testing with a mock by default would be better than testing nothing by default. The flag could still be used to test against a real IPFS node instead of a mock.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another pattern is to use an environment variable and put the name of it in the Skip message. Inspired by this blog post https://peter.bourgon.org/blog/2021/04/02/dont-use-build-tags-for-integration-tests.html
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was handled by adding a skipUnlessEpic
function like as is done here:
https://github.com/ipfs/go-ipfs/blob/ef866a1400b3b2861e5e8b6cc9edc8633b890a0a/test/integration/addcat_test.go#L173?
@mvdan I addressed all review comments and made suggested changes. One other big change I needed to make was to move IpfsFetcher into its own package. This is so that things outside of ipfs, that import |
Remaining items:
|
600fd42
to
bd7e480
Compare
cmd/ipfs/daemon.go
Outdated
@@ -288,9 +291,30 @@ func daemonFunc(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment | |||
return fmt.Errorf("fs-repo requires migration") | |||
} | |||
|
|||
// Read from existing config | |||
cfg, err := cctx.GetConfig() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, we can't get the config until have the migration has been performed since the format of the config may have changed between versions.
An alternative way to do this is to have a --migration-temp-node-config
(or better name 😊) option that takes a config file to use for the temporary node. This could protect us from a variety of bugs/corner cases as it allows users to configure the temporary node as if it was a regular one.
Some potential corner cases include:
- If we change/move/rename any of the config options we use
- Peering.Peers might not be what you're looking for since they might not have the migrations and might not be DHT servers
- DHT bootstrap nodes might not be sufficient since your node might need to turn off DHT usage for some reason
- We could add future routing/friend-ing options that wouldn't get picked up from a config file used for an older node version
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like allowing an entire config to be specified for a temp node also invites many problems, as users will not necessarily know what the new configuration looks like until after the migration and may typically just use their existing config (leading to issues mentioned above). Also, the configuration for a temp, used only to download a migration or two, does not need more than a default config. Users not knowing this may think it best to use a config similar to what they are using for their real node.
Instead of a config file, WDYT about letting the user specify peer(s) on the command line, if they happen to know one or more that may be useful for migration?
I have implemented the above, and peers can be specified using the migration-peers
flag. I am not sure this is an ideal solution, but it does provide a way to specify peers until a decision is made regarding config files. Maybe do that in a follow-up PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like allowing an entire config to be specified for a temp node also invites many problems...
Ya, it's using a pretty big hammer to deal with a smaller set of problems but needing to modify the config for the purposes of migrations at all seems like a potentially advanced requirement for an end user, perhaps sufficiently so that they might as well get access to the full range of tools.
However, there are also potentially a large number of options that could be specified during a migration so idk if command line flags for each of them is really a reasonable alternative. For example, if someone is on a private network and therefore needs the network symmetric key they're a bit out of luck with just some peer flags. If we make our routing systems more configurable we're similarly out of luck.
cc @Stebalien as this and #8064 (comment) seem to be related to the comments in the proposal PR (https://github.com/protocol/web3-dev-team/pull/86/files#diff-991744cab1b93840a21929cbf5aa7bc00a37ae4244b5c569df086214e36e47cfR66 and https://github.com/protocol/web3-dev-team/pull/86/files#r610934803)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An alternative way to do this is to have a --migration-temp-node-config (or better name blush) option that takes a config file to use for the temporary node. This could protect us from a variety of bugs/corner cases as it allows users to configure the temporary node as if it was a regular one.
We could, maybe, later, support that for advanced users/distros. That's not going to really help in this case because users will be very confused about why their migration options aren't being applied. Users are going to be less confused if we start a temporary node that ignores their config.
I've left a comment on the proposal. IMO, we should just try to read the relevant part of the config and ignore the rest. Downsides:
- Migrations can't touch the migration part of the config. Honestly, I'm fine with that.
- If we decide to move the config, things could get... tricky. But trying a set of configs in order of precedence, just to read a small portion probably isn't a huge issue.
We could also do this on the command-line, but it could get icky (as @aschmahmann said, lots of options).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I'm fine with this approach. If we define the migrations config options as described in the proposal and then we try to deserialize the config file (or object) into a struct that only has the migration values that should be fine.
This will mean that unlike the rest of the config which is nominatively typed in that backwards incompatible changes are tied to a repo version, that we are likely to use structural typing for the migrations specific section (although if this proves too hard we can add a field into the migrations struct to track versioning).
This reliance on structural typing means we basically don't have to worry too much about the potential migration config changes for the time being and it's possible we'll end up with a repo/package that contains all the migration configs (like what we used to do with fs-repo-migrations, but really small since it's only for a portion of the config file).
For the time being we can probably punt on dealing with private swarms or people with custom bootstrap nodes since they can:
- Just use HTTP, like they've been doing so far
- Download the migrations binary manually, or run the full fs-repo-migrations binary or ipfs-update
If this becomes a problem and we need the sledge hammer of passing in --migration-temp-node-config
we can do that, but let's delay until we have a user + use case who needs this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am going to leave the configuration for a later time. Currently, go-ipfs accepts a couple of optional flags that allow the user to specify how to download the migrations (--download-migration
) and if/how to keep downloaded migrations (--keep-migration
). Even if this information is available from a config file in the future, I think these flags will will still be useful to override that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, I thought this solution would be fine... but I've changed my mind. We have way too many special-purpose daemon flags that should be settings. We're already trying to get rid of the existing daemon flags, I don't want to add more that we'll need to maintain into the future for backwards compatibility.
If we want to be able to override config options, we need a coordinated system to do that but this isn't that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Stebalien Done. Now reading migration setting from config file, and using defaults for any values not present. Removed CLI flags for specifying migration configuration.
repo/fsrepo/migrations/fetcher.go
Outdated
err := fmt.Errorf("fetch errors:") | ||
for i := range errs { | ||
err = fmt.Errorf("%s\n%d) %s", err.Error(), i, errs[i]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the idea here, to return the error from the last fetcher? We should probably just use something like https://github.com/hashicorp/go-multierror (it's already used in go-ipfs).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Returns list of errors if all fetchers fail. Switched to using multierror
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI, https://github.com/uber-go/multierr/ nicer in my experience (doesn't matter too much).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
daemon was already using https://github.com/hashicorp/go-multierror. 🤷
// configure the temporary node | ||
cfg.Routing.Type = "dhtclient" | ||
|
||
// Disable listening for inbound connections |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could do that, but then if you try and import the data into the new node from the old node for seeding purposes you're going to need to either:
- Use the import/export APIs (e.g. use CAR files to move the data around)
- Have the temporary node connect to the new node so it can pull the data using Bitswap
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the files are already available on the local disk (since they need to be run), it is just as easy for me to add the files to the real node after migration. Unless there is a compelling reason to transfer via bitswap instead of reading the files, then I think this is OK.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it is just as easy for me to add the files to the real node after migration
Right, but if we do this it needs to be as a CAR file not a UnixFS import of some binary since we want the CIDs to match and ipfs add <file>
is not guaranteed to be the same across versions and boxing ourselves into some parameters now wouldn't be a good idea either. i.e. go-ipfs needs to understand "I am importing this DAG" not "I am importing this binary", whether it's a CAR file via DAG import, or a UnixFS DAG via Bitswap isn't really a big deal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- When migrations are downloaded via IPFS, add the migration archives to IPFS by connecting to the temporary migration node and getting the migrations over IPFS.
- When migrations are downloaded via HTTP, add the migration archive files directly to IPFS. After talking to @Stebalien he thought it was still worth to add the files directly, even though it might result in a different dag. If we decide later to not do that, it is a trivial change.
|
||
func TestIpfsFetcher(t *testing.T) { | ||
if !*runIpfsTest { | ||
t.Skip("manually-run dev test, use '-ipfstest' flage to run") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the reason this test is manual because it makes network requests?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, and I did not know a good way to simulate or mock an IPFS node, but I wanted to have some test that a developer could run to check if things are working. WDYT? Leave it, toss it, create a better test?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are some tests in the integration test package (e.g. https://github.com/ipfs/go-ipfs/blob/master/test/integration/three_legged_cat_test.go) that could be of inspiration here.
If it's a pain to write the test lmk and we can decide on an alternative. Using a flag/env var seems fine (I'm not sure if we have an appropriate one, although perhaps this one https://github.com/ipfs/go-ipfs/blob/ef866a1400b3b2861e5e8b6cc9edc8633b890a0a/test/integration/addcat_test.go#L173 would do), although I don't really trust tests that don't run in CI since they aren't reliably run before merging PRs and are subject to breaking.
@Stebalien lmk if you have any additional thoughts here given you've been thinking about our CI situation a bit, or if it's just 👍.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to test this in CI but we can use sharness tests. If a test is disabled in CI, it might as well not exist.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@aschmahmann I will see what I can do with sharness, but likely this is going to have to become a job for your team if it needs to get done before the upcoming release.
cmd/ipfs/migration.go
Outdated
return err | ||
} | ||
|
||
ipfsPath, err := ufs.Add(ctx, files.NewReaderFile(f), options.Unixfs.Pin(pin)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the thing that doesn't work #8064 (comment).
For example, if we add the migration binaries using the current defaults for ipfs add
and then change the defaults in a subsequent release then the swarms won't converge.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: @gammazero if this is a pain to implement for you given time constraints I'm happy to either drop this requirement for the time being or I'll just code up the CAR import/export stuff on my own (likely requires cut-pasting code from the commands package for DAG Import/Export into a function on the IpfsNode and then calling it).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I went with the approach of connecting to the temporary migration node and then getting the data over IPFS. Exposing a CAR interface on the node may be a better solution in the long run, and we can revisit that.
e3eba81
to
8a5dd4c
Compare
bump |
8a5dd4c
to
4989b69
Compare
Rebased on master to pick up fixed sharness tests. |
@aschmahmann If you merge PR #8076, then I can rebase and use CAR files to import downloaded migrations. I can also do that in a separate PR. In any case, once #8076 is in, import via CAR will be simple. Making that change to use CAR files will also enable PR #7658 to do the same. |
@gammazero yep, that's the idea. That PR seems close to being good, but has a bug causing it to fail sharness at the moment. In any event I wouldn't block on that PR here. |
case "": | ||
// Ignore empty string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because we state that if they leave the DownloadSources empty, that results in the default behavior. I thought it might be considered reasonable by some to think [""]
means empty.
cmd/ipfs/migration_test.go
Outdated
@@ -56,27 +71,52 @@ func TestGetMigrationFetcher(t *testing.T) { | |||
if mf.Len() != 3 { | |||
t.Fatal("expected3 fetchers in MultiFetcher") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
t.Fatal("expected3 fetchers in MultiFetcher") | |
t.Fatal("expected 3 fetchers in MultiFetcher") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
} | ||
|
||
a, err := ma.NewMultiaddr(tempNodeTcpAddr) | ||
addrs, err := ipfs.Swarm().LocalAddrs(ctx) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the idea behind where the background context is getting used and where the passed in context is getting used?
If this function doesn't complete then the temporary node is useless right (aside from the fact that if MDNS is working then everything is probably fine)?
If we don't get the addresses do we need to error + close the node?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The background context is used as a mechanism to shutdown the temporary node. Quote from go-ipfs/core/builder.go
:
// Shut down the application if the lifetime context is canceled. // NOTE: we _should_ stop the application by calling `Close()` // on the process. But we currently manage everything with contexts.
So, the background context is for now shutdown, in the absence of a node.Close()
method, and the passed-in context is used for cancellation of the current function call.
True, if the function does not complete, then the node is useless. However, failure to get the local swarm address only means that the downloaded migrations cannot be fetched from the temp node.... So, right, it does not need to fail out here.
Done.
4939e74
to
38d3c36
Compare
- Adding migrations is specified using the --keep-migrations flag. This may have one of the values: "keep", "pin", "discard". Default: "keep" - Choose how to fetch migrations based on download specification given by the --download-migration flag. This can be "ipfs", "http", or one or more custom gateways. Default: "http,ipfs" - Attempt to read existing config to get peers - Fetcher interface has Close() to do any cleanup when done with a fetcher. - Changes requested in PR review.
Reading from an existing config file, particularly before migration, may be problematic. Instead, let the user specify peers on the command line if they want to specify any to assist with downloading migrations.
- When migrations are downloaded via IPFS, add the migration archives to IPFS by connecting to the temporary migration node and getching the migrations. - When migrations are downloaded via HTTP, add the migration archive files directly to IPFS.
- Read "Migration" section of IPFS config file before migrations are - Use default values for any items not specified in config - Download migrations and save downloads according to config
1. This will only decode the "migration" section of the config. Other sections may not be compatible. 2. This will avoid _caching_ the pre-migration config in the command context.
And close the file.
- Add unit test for readMigrationConfig
- Peer info is not provided by Migration condif - Do not fail, but use defaults, if Bootstrap or Peering config is not readable
ddaffdd
to
e37896d
Compare
go-ipfs can now fetch migrations via IPFS
Project proposal: protocol/web3-dev-team#86