Skip to content

Conversation

@jsfakian
Copy link
Contributor

This PR introduces resumable, range-based downloads for HTTP datastores and fixes the process for Azure datastores:

  • Azure Blob datastore: Skips already-completed chunks instead of always starting from the first chunk.
  • HTTP datastore:
    • Resume from existing local file: if local file exists, and the HTTP server has Range capability.
    • Identify copiedSize from the doneParts.
    • Update doneParts periodically for each downloaded chunk.

Copy link
Contributor

@deitch deitch left a comment

Choose a reason for hiding this comment

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

lint is failing because your regex is not used, which means it probably doesn't work as expected.

The azure part makes sense. As long as you can find a sane way to test it, that is good.

The http stuff also looks good in principle. I have a few questions:

  1. does it handle http servers that don't support range requests?
  2. Can we create a test for this? It shouldn't be too hard, we should be able to spin up a test http server right inside a go test.
  3. Perhaps most importantly, is there any off-the-shelf library that supports all of this (the http part) and we can just get rid of our own code?


const (
// SingleMB contains chunk size
SingleMB int64 = 4 * 1024 * 1024
Copy link
Contributor

Choose a reason for hiding this comment

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

We actually had SingleMB defined for 4MB? 🤦‍♂️

@jsfakian jsfakian force-pushed the Support-for-http-datastores branch from 07c3322 to c0d4945 Compare October 21, 2025 13:08
@jsfakian
Copy link
Contributor Author

lint is failing because your regex is not used, which means it probably doesn't work as expected.

The azure part makes sense. As long as you can find a sane way to test it, that is good.

The http stuff also looks good in principle. I have a few questions:

  1. does it handle http servers that don't support range requests?
  2. Can we create a test for this? It shouldn't be too hard, we should be able to spin up a test http server right inside a go test.
  3. Perhaps most importantly, is there any off-the-shelf library that supports all of this (the http part) and we can just get rid of our own code?

For point 1, I updated the code to also work for servers that do not support range requests.
I am working on creating a test for it.
About point 3, we could use https://github.com/cavaliergopher/grab. It does not expose the inactivity timeout that we currently have in our code, but I will figure out how to use it with the library. I am not sure about switching to this lib.

@jsfakian jsfakian force-pushed the Support-for-http-datastores branch 2 times, most recently from af8ddeb to 325d4a9 Compare October 21, 2025 15:08
@jsfakian
Copy link
Contributor Author

I have added unit tests for resuming downloads in HTTP datastores.

@jsfakian
Copy link
Contributor Author

The tests fail due to the race addressed in the other PR.

@jsfakian jsfakian force-pushed the Support-for-http-datastores branch from 325d4a9 to 624ec0f Compare October 24, 2025 11:03
@jsfakian jsfakian force-pushed the Support-for-http-datastores branch from 624ec0f to c8666bd Compare October 24, 2025 11:05
}
if err := os.MkdirAll(nettraceFolder, 0755); err != nil {
t.Fatalf("unable to make nettrace log directory: %v", err)
}
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
}
}
defer os.RemoveAll(nettraceFolder)

just to be sure that if you run the test a second time, it does not interfere with leftovers from the first run

Perhaps the same for httpDownloadDir, but it seems you already remove all created files in there.

@jsfakian jsfakian force-pushed the Support-for-http-datastores branch from c8666bd to 94b2ddc Compare October 24, 2025 11:13
for _, p := range doneParts.Parts {
idx := int(p.Ind)
if 0 <= idx && idx < totalChunks && !doneChunks[idx] {
doneChunks[idx] = true
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't understand why you need an array. It makes sense to have an array if it is possible to have holes, but I don't see that (perhaps it is outside of this PR?).

Instead you can just store idx in a doneIdx and later do:

		if chunkIndex <= doneIdx { // already downloaded in a previous run
				continue
			}

Copy link
Contributor Author

@jsfakian jsfakian Oct 24, 2025

Choose a reason for hiding this comment

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

It is safer that way. We download 16 chunks concurrently so it might be the case that we have holes.

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay, that makes sense to me.

})
ts = httptest.NewServer(r)

cleanup = func() {
Copy link
Contributor

Choose a reason for hiding this comment

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

I see you like lambdas, so your lambda returns a lambda :D

Comment on lines 226 to 231
f, ferr := os.OpenFile(localFile, os.O_RDWR, 0644)
if ferr != nil {
stats.Error = ferr
return stats, Resp{}
}
local = f
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
f, ferr := os.OpenFile(localFile, os.O_RDWR, 0644)
if ferr != nil {
stats.Error = ferr
return stats, Resp{}
}
local = f
local, stats.Error = os.OpenFile(localFile, os.O_RDWR, 0644)
if stats.Error != nil {
return stats, Resp{}
}

@jsfakian jsfakian force-pushed the Support-for-http-datastores branch from 94b2ddc to 6b297fc Compare October 24, 2025 11:40
Comment on lines 225 to 245
if _, err := os.Stat(localFile); err == nil {
local, stats.Error = os.OpenFile(localFile, os.O_RDWR, 0644)
if stats.Error != nil {
return stats, Resp{}
}
if _, err = local.Seek(copiedSize, io.SeekStart); err != nil {
local.Close()
stats.Error = err
return stats, Resp{}
}
} else if os.IsNotExist(err) {
f, ferr := os.Create(localFile)
if ferr != nil {
stats.Error = ferr
return stats, Resp{}
}
local = f
} else {
stats.Error = err
return stats, Resp{}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of first checking if the file exists and depending on that creating the file or open it, can't you just open it with os.O_CREATE (but without os.O_TRUNC)?

Suggested change
if _, err := os.Stat(localFile); err == nil {
local, stats.Error = os.OpenFile(localFile, os.O_RDWR, 0644)
if stats.Error != nil {
return stats, Resp{}
}
if _, err = local.Seek(copiedSize, io.SeekStart); err != nil {
local.Close()
stats.Error = err
return stats, Resp{}
}
} else if os.IsNotExist(err) {
f, ferr := os.Create(localFile)
if ferr != nil {
stats.Error = ferr
return stats, Resp{}
}
local = f
} else {
stats.Error = err
return stats, Resp{}
}
local, stats.Error = os.OpenFile(localFile, os.O_RDWR | os.O_CREATE, 0644)
if stats.Error != nil {
return stats, Resp{}
}
if _, err = local.Seek(copiedSize, io.SeekStart); err != nil {
local.Close()
stats.Error = err
return stats, Resp{}
}

}
}

func TestHTTPDatastoreResume(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Thank you for adding a test :-)

Signed-off-by: Ioannis Sfakianakis <[email protected]>
@jsfakian jsfakian force-pushed the Support-for-http-datastores branch from 6b297fc to 921ca13 Compare October 24, 2025 12:11
@jsfakian jsfakian requested a review from deitch October 31, 2025 19:32
@jsfakian
Copy link
Contributor Author

jsfakian commented Nov 4, 2025

Should we merge it?

@deitch
Copy link
Contributor

deitch commented Nov 5, 2025

Should we merge it?

Definitely. I thought you would merge it after some approvals. I will do it now.

@deitch deitch merged commit 1c066d5 into lf-edge:main Nov 5, 2025
5 checks passed
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.

3 participants