Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(137)

Unified Diff: go/src/infra/tools/cipd/fetcher.go

Issue 1129043003: cipd: Refactor client to make it more readable. (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: be more careful with network error handling Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: go/src/infra/tools/cipd/fetcher.go
diff --git a/go/src/infra/tools/cipd/fetcher.go b/go/src/infra/tools/cipd/fetcher.go
deleted file mode 100644
index 43a0d13982f4a8b85554bc3c3d6645476f1d8cba..0000000000000000000000000000000000000000
--- a/go/src/infra/tools/cipd/fetcher.go
+++ /dev/null
@@ -1,217 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package cipd
-
-import (
- "fmt"
- "io"
- "io/ioutil"
- "net/http"
- "os"
- "path/filepath"
- "time"
-
- "infra/libs/logging"
-)
-
-// FetchInstanceOptions contains parameters for FetchInstance function.
-type FetchInstanceOptions struct {
- // ServiceURL is root URL of the backend service, or "" to use default service.
- ServiceURL string
- // Client is http.Client to use to make requests, default is http.DefaultClient.
- Client *http.Client
- // Log is a logger to use for logs, default is logging.DefaultLogger.
- Log logging.Logger
-
- // PackageName is a name of the package to fetch.
- PackageName string
- // InstanceID identifies an instance of the package to fetch.
- InstanceID string
- // Output is where to write the fetched data to. Must be nil when used with FetchAndDeployInstance.
- Output io.WriteSeeker
-}
-
-// FetchInstance downloads package instance file from the repository.
-func FetchInstance(options FetchInstanceOptions) (err error) {
- // Fill in default options.
- if options.ServiceURL == "" {
- options.ServiceURL = DefaultServiceURL()
- }
- if options.Client == nil {
- options.Client = http.DefaultClient
- }
- if options.Log == nil {
- options.Log = logging.DefaultLogger
- }
- log := options.Log
- remote := newRemoteService(options.Client, options.ServiceURL, log)
-
- // Logs the error before returning it.
- defer func() {
- if err != nil {
- log.Errorf("cipd: failed to fetch %s (%s)", options.PackageName, err)
- }
- }()
-
- // Grab fetch URL.
- log.Infof("cipd: resolving %s:%s", options.PackageName, options.InstanceID)
- fetchInfo, err := remote.fetchInstance(options.PackageName, options.InstanceID)
- if err != nil {
- return
- }
-
- // reportProgress print fetch progress, throttling the reports rate.
- var prevProgress int64 = 1000
- var prevReportTs time.Time
- reportProgress := func(read int64, total int64) {
- now := time.Now()
- progress := read * 100 / total
- if progress < prevProgress || read == total || now.Sub(prevReportTs) > 5*time.Second {
- log.Infof("cipd: fetching %s: %d%%", options.InstanceID, progress)
- prevReportTs = now
- prevProgress = progress
- }
- }
-
- // download is a separate function to be able to use deferred close.
- download := func(out io.WriteSeeker, src io.ReadCloser, totalLen int64) error {
- defer src.Close()
- log.Infof("cipd: fetching %s (%.1f Mb)", options.InstanceID, float32(totalLen)/1024.0/1024.0)
- reportProgress(0, totalLen)
- _, err := io.Copy(out, &readerWithProgress{
- reader: src,
- callback: func(read int64) { reportProgress(read, totalLen) },
- })
- if err == nil {
- log.Infof("cipd: successfully fetched %s", options.InstanceID)
- }
- return err
- }
-
- // Download the actual data (several attempts).
- maxAttempts := 10
- for attempt := 0; attempt < maxAttempts; attempt++ {
- // Rewind output to zero offset.
- _, err = options.Output.Seek(0, os.SEEK_SET)
- if err != nil {
- return
- }
-
- // Send the request.
- log.Infof("cipd: initiating the fetch")
- var req *http.Request
- var resp *http.Response
- req, err = http.NewRequest("GET", fetchInfo.FetchURL, nil)
- if err != nil {
- return
- }
- req.Header.Set("User-Agent", userAgent())
- resp, err = options.Client.Do(req)
- if err != nil {
- return
- }
-
- // Transient error, retry.
- if resp.StatusCode == 408 || resp.StatusCode >= 500 {
- resp.Body.Close()
- log.Warningf("cipd: transient HTTP error %d while fetching the file", resp.StatusCode)
- continue
- }
-
- // Fatal error, abort.
- if resp.StatusCode >= 400 {
- resp.Body.Close()
- return fmt.Errorf("Server replied with HTTP code %d", resp.StatusCode)
- }
-
- // Try to fetch.
- err = download(options.Output, resp.Body, resp.ContentLength)
- if err != nil {
- log.Warningf("cipd: transient error fetching the file: %s", err)
- continue
- }
-
- // Success.
- err = nil
- return
- }
-
- err = fmt.Errorf("All %d fetch attempts failed", maxAttempts)
- return
-}
-
-// FetchAndDeployInstance fetches the package instance and deploys it into
-// a site root. It doesn't check whether the instance is already deployed.
-// options.Output field is not used and must be set to nil.
-func FetchAndDeployInstance(root string, options FetchInstanceOptions) error {
- // Be paranoid.
- err := ValidatePackageName(options.PackageName)
- if err != nil {
- return err
- }
- err = ValidateInstanceID(options.InstanceID)
- if err != nil {
- return err
- }
-
- // This field is not supported.
- if options.Output != nil {
- return fmt.Errorf("Passed non-nil Output to FetchAndDeployInstance")
- }
-
- // Use temp file for storing package file. Delete it when done.
- var instance PackageInstance
- tempPath := filepath.Join(root, siteServiceDir, "tmp")
- err = os.MkdirAll(tempPath, 0777)
- if err != nil {
- return err
- }
- f, err := ioutil.TempFile(tempPath, options.InstanceID)
- if err != nil {
- return err
- }
- defer func() {
- // Instance takes ownership of the file, no need to close it separately.
- if instance == nil {
- f.Close()
- }
- os.Remove(f.Name())
- }()
-
- // Fetch the package data to the provided storage.
- options.Output = f
- err = FetchInstance(options)
- if err != nil {
- return err
- }
-
- // Open the instance, verify the instance ID.
- instance, err = OpenInstance(f, options.InstanceID)
- if err != nil {
- return err
- }
- defer instance.Close()
-
- // Deploy it. 'defer' will take care of removing the temp file if needed.
- _, err = DeployInstance(root, instance)
- return err
-}
-
-////////////////////////////////////////////////////////////////////////////////
-
-// readerWithProgress is io.Reader that calls callback whenever something is
-// read from it.
-type readerWithProgress struct {
- reader io.Reader
- total int64
- callback func(total int64)
-}
-
-func (r *readerWithProgress) Read(p []byte) (int, error) {
- n, err := r.reader.Read(p)
- r.total += int64(n)
- r.callback(r.total)
- return n, err
-}

Powered by Google App Engine
This is Rietveld 408576698