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

Feat/migration ipfs download #8064

Merged
merged 33 commits into from
May 12, 2021
Merged

Feat/migration ipfs download #8064

merged 33 commits into from
May 12, 2021

Conversation

gammazero
Copy link
Contributor

go-ipfs can now fetch migrations via IPFS

Project proposal: protocol/web3-dev-team#86

@BigLep BigLep added this to the go-ipfs 0.9 milestone Apr 8, 2021
Copy link
Contributor

@mvdan mvdan left a 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.

repo/fsrepo/migrations/ipfsfetcher.go Outdated Show resolved Hide resolved
repo/fsrepo/migrations/ipfsfetcher.go Outdated Show resolved Hide resolved
repo/fsrepo/migrations/ipfsfetcher.go Outdated Show resolved Hide resolved
repo/fsrepo/migrations/ipfsfetcher.go Outdated Show resolved Hide resolved
repo/fsrepo/migrations/ipfsfetcher.go Outdated Show resolved Hide resolved
repo/fsrepo/migrations/ipfsfetcher.go Outdated Show resolved Hide resolved
repo/fsrepo/migrations/ipfsfetcher.go Outdated Show resolved Hide resolved
)

func TestIpfsFetcher(t *testing.T) {
t.Skip("manually-run dev test only")
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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.

Copy link
Contributor

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

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 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?

repo/fsrepo/migrations/ipfsfetcher_test.go Outdated Show resolved Hide resolved
@gammazero
Copy link
Contributor Author

@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 migrations, are not forced to link with all the IpfsFetcher baggage.

@gammazero gammazero requested a review from mvdan April 8, 2021 23:40
@gammazero
Copy link
Contributor Author

gammazero commented Apr 9, 2021

Remaining items:

  • Support migration download via IPFS and HTTP
  • Temp node should not listen for inbound external connections
  • Temp node should not expose an API/gateway.
  • Migration policy from config
    • Read IPFS peers from CLI flag migration config
    • Read keep setting from migration config
    • Read migration sources (ipfs/http/gateway) from CLI flag migration config
  • Discard/Keep/Pin migration binaries
  • Import migration downloads into migrated node
  • Document migration config

@gammazero gammazero force-pushed the feat/migration-ipfs-download branch from 600fd42 to bd7e480 Compare April 9, 2021 21:22
cmd/ipfs/daemon.go Outdated Show resolved Hide resolved
@@ -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()
Copy link
Contributor

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

Copy link
Contributor Author

@gammazero gammazero Apr 12, 2021

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?

Copy link
Contributor

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)

Copy link
Member

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:

  1. Migrations can't touch the migration part of the config. Honestly, I'm fine with that.
  2. 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).

Copy link
Contributor

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:

  1. Just use HTTP, like they've been doing so far
  2. 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.

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 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.

Copy link
Member

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.

Copy link
Contributor Author

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.

Comment on lines 59 to 61
err := fmt.Errorf("fetch errors:")
for i := range errs {
err = fmt.Errorf("%s\n%d) %s", err.Error(), i, errs[i])
Copy link
Contributor

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).

Copy link
Contributor Author

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

Copy link
Member

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).

Copy link
Contributor Author

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
Copy link
Contributor

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

Copy link
Contributor Author

@gammazero gammazero Apr 12, 2021

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.

Copy link
Contributor

@aschmahmann aschmahmann Apr 12, 2021

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.

Copy link
Contributor Author

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.

repo/fsrepo/migrations/ipfsfetcher/ipfsfetcher.go Outdated Show resolved Hide resolved
repo/fsrepo/migrations/ipfsfetcher/ipfsfetcher_test.go Outdated Show resolved Hide resolved

func TestIpfsFetcher(t *testing.T) {
if !*runIpfsTest {
t.Skip("manually-run dev test, use '-ipfstest' flage to run")
Copy link
Contributor

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?

Copy link
Contributor Author

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?

Copy link
Contributor

@aschmahmann aschmahmann Apr 12, 2021

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 👍.

Copy link
Member

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.

Copy link
Contributor Author

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.

@aschmahmann aschmahmann mentioned this pull request Apr 13, 2021
71 tasks
return err
}

ipfsPath, err := ufs.Add(ctx, files.NewReaderFile(f), options.Unixfs.Pin(pin))
Copy link
Contributor

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.

Copy link
Contributor

@aschmahmann aschmahmann Apr 13, 2021

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).

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 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.

@gammazero gammazero force-pushed the feat/migration-ipfs-download branch 2 times, most recently from e3eba81 to 8a5dd4c Compare April 14, 2021 16:46
@gammazero
Copy link
Contributor Author

bump

@gammazero gammazero force-pushed the feat/migration-ipfs-download branch from 8a5dd4c to 4989b69 Compare April 15, 2021 19:12
@gammazero
Copy link
Contributor Author

Rebased on master to pick up fixed sharness tests.

@gammazero
Copy link
Contributor Author

@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.

@aschmahmann
Copy link
Contributor

@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.

Comment on lines +60 to +146
case "":
// Ignore empty string
Copy link
Contributor

Choose a reason for hiding this comment

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

Why?

Copy link
Contributor Author

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.

@@ -56,27 +71,52 @@ func TestGetMigrationFetcher(t *testing.T) {
if mf.Len() != 3 {
t.Fatal("expected3 fetchers in MultiFetcher")
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
t.Fatal("expected3 fetchers in MultiFetcher")
t.Fatal("expected 3 fetchers in MultiFetcher")

Copy link
Contributor Author

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)
Copy link
Contributor

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?

Copy link
Contributor Author

@gammazero gammazero Apr 19, 2021

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.

gammazero and others added 23 commits May 11, 2021 10:06
- 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.
- 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
@gammazero gammazero force-pushed the feat/migration-ipfs-download branch from ddaffdd to e37896d Compare May 11, 2021 17:22
@aschmahmann aschmahmann merged commit c54cdaa into master May 12, 2021
@gammazero gammazero deleted the feat/migration-ipfs-download branch May 12, 2021 17:10
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.

6 participants