From 76b87d48d5c2ff86be50ce04d943335576daddfc Mon Sep 17 00:00:00 2001 From: Jussi Nummelin Date: Thu, 19 Sep 2024 22:29:28 +0300 Subject: [PATCH] Disable grab download resume This is to mitigate cases like #4296 By default grab tries to resume the download if the file name determined from either the url or from content-type headers already exists. This makes things go side ways, if the existing file is smaller than the new one, the old content would still be there and only the "extra" new bytes would get written. I.e. the download would be "resumed". :facepalm: So this change disables resuming altogether fro grab. To be able to download and replace k0s without resuming, i.e. "truncate" the file in-between. We need to force the download to download it as k0s.tmp first. Only after that we can safely move the file as truncating a running binary is not allowed and will error with `text file busy`. This is a minimal possible fix that we can easily backport. @twz123 is already working on bigger refactoring of autopilot download functionality that gets rid of grab. Grab seems to bring more (bad) surprises than real benefits. In the end, we just download files and we should pretty much always just replace them. No need for full library dependecy for that. Signed-off-by: Jussi Nummelin --- pkg/autopilot/constant/static.go | 1 + .../controller/signal/common/download.go | 1 - pkg/autopilot/controller/signal/k0s/apply.go | 17 +++++------------ pkg/autopilot/controller/signal/k0s/download.go | 3 +++ pkg/autopilot/download/downloader.go | 10 ++++++++++ 5 files changed, 19 insertions(+), 13 deletions(-) diff --git a/pkg/autopilot/constant/static.go b/pkg/autopilot/constant/static.go index 36557137de0c..31f68e59e547 100644 --- a/pkg/autopilot/constant/static.go +++ b/pkg/autopilot/constant/static.go @@ -19,6 +19,7 @@ const ( AutopilotNamespace = "k0s-autopilot" AutopilotConfigName = AutopilotName K0sBinaryDir = "/usr/local/bin" + K0sTempFilename = "k0s.tmp" K0sDefaultDataDir = "/var/lib/k0s" K0sManifestSubDir = "manifests" K0sImagesDir = "images" diff --git a/pkg/autopilot/controller/signal/common/download.go b/pkg/autopilot/controller/signal/common/download.go index 3d4e6952ef50..7bdee7694fdd 100644 --- a/pkg/autopilot/controller/signal/common/download.go +++ b/pkg/autopilot/controller/signal/common/download.go @@ -90,7 +90,6 @@ func (r *downloadController) Reconcile(ctx context.Context, req cr.Request) (cr. } else { logger.Infof("Download of '%s' successful", manifest.URL) - // When the download is complete move the status to the success state signalData.Status = apsigv2.NewStatus(manifest.SuccessState) } diff --git a/pkg/autopilot/controller/signal/k0s/apply.go b/pkg/autopilot/controller/signal/k0s/apply.go index c1a9972a726f..003792f7fc77 100644 --- a/pkg/autopilot/controller/signal/k0s/apply.go +++ b/pkg/autopilot/controller/signal/k0s/apply.go @@ -18,12 +18,12 @@ import ( "context" "errors" "fmt" - "net/url" "os" "path" "path/filepath" apcomm "github.com/k0sproject/k0s/pkg/autopilot/common" + apconst "github.com/k0sproject/k0s/pkg/autopilot/constant" apdel "github.com/k0sproject/k0s/pkg/autopilot/controller/delegate" apsigpred "github.com/k0sproject/k0s/pkg/autopilot/controller/signal/common/predicate" apsigv2 "github.com/k0sproject/k0s/pkg/autopilot/signaling/v2" @@ -102,30 +102,23 @@ func (r *applyingUpdate) Reconcile(ctx context.Context, req cr.Request) (cr.Resu } logger := r.log.WithField("signalnode", signalNode.GetName()) + logger.Info("Applying update") var signalData apsigv2.SignalData if err := signalData.Unmarshal(signalNode.GetAnnotations()); err != nil { return cr.Result{}, fmt.Errorf("unable to unmarshal signal data for node='%s': %w", req.NamespacedName.Name, err) } - // Get the filename fragment from the URL - updateURL, err := url.Parse(signalData.Command.K0sUpdate.URL) - if err != nil { - return cr.Result{}, fmt.Errorf("unable to get update request URL: %w", err) - } - - // TODO: make the filename part random - updateFilename := path.Base(updateURL.Path) - updateFilenamePath := path.Join(r.k0sBinaryDir, updateFilename) + updateFilenamePath := path.Join(r.k0sBinaryDir, apconst.K0sTempFilename) // Ensure that the expected file exists if _, err := os.Stat(updateFilenamePath); errors.Is(err, os.ErrNotExist) { - return cr.Result{}, fmt.Errorf("unable to find update file '%s': %w", updateFilename, err) + return cr.Result{}, fmt.Errorf("unable to find update file '%s': %w", apconst.K0sTempFilename, err) } // Ensure that the new file is executable if err := os.Chmod(updateFilenamePath, 0755); err != nil { - return cr.Result{}, fmt.Errorf("unable to chmod update file '%s': %w", updateFilename, err) + return cr.Result{}, fmt.Errorf("unable to chmod update file '%s': %w", apconst.K0sTempFilename, err) } // Perform the update atomically diff --git a/pkg/autopilot/controller/signal/k0s/download.go b/pkg/autopilot/controller/signal/k0s/download.go index 044b2d75ae1a..4b6bf73fcdd5 100644 --- a/pkg/autopilot/controller/signal/k0s/download.go +++ b/pkg/autopilot/controller/signal/k0s/download.go @@ -16,8 +16,10 @@ package k0s import ( "crypto/sha256" + "path/filepath" apcomm "github.com/k0sproject/k0s/pkg/autopilot/common" + apconst "github.com/k0sproject/k0s/pkg/autopilot/constant" apdel "github.com/k0sproject/k0s/pkg/autopilot/controller/delegate" apsigcomm "github.com/k0sproject/k0s/pkg/autopilot/controller/signal/common" apsigpred "github.com/k0sproject/k0s/pkg/autopilot/controller/signal/common/predicate" @@ -88,6 +90,7 @@ func (b downloadManifestBuilderK0s) Build(signalNode crcli.Object, signalData ap ExpectedHash: signalData.Command.K0sUpdate.Sha256, Hasher: sha256.New(), DownloadDir: b.k0sBinaryDir, + Filename: filepath.Join(b.k0sBinaryDir, apconst.K0sTempFilename), }, SuccessState: Cordoning, } diff --git a/pkg/autopilot/download/downloader.go b/pkg/autopilot/download/downloader.go index 11cdecdcec95..d80ace5bcc77 100644 --- a/pkg/autopilot/download/downloader.go +++ b/pkg/autopilot/download/downloader.go @@ -34,6 +34,7 @@ type Config struct { ExpectedHash string Hasher hash.Hash DownloadDir string + Filename string } type downloader struct { @@ -72,6 +73,15 @@ func (d *downloader) Download(ctx context.Context) error { dlreq.SetChecksum(d.config.Hasher, expectedHash, true) } + // We're never really resuming downloads, so disable this feature. + // This also allows to re-download the file if it's already present. + dlreq.NoResume = true + + if d.config.Filename != "" { + d.logger.Infof("Setting filename to %s", d.config.Filename) + dlreq.Filename = d.config.Filename + } + client := grab.NewClient() // Set user agent to mitigate 403 errors from GitHub // See https://github.com/cavaliergopher/grab/issues/104