diff --git a/libmachine/mcnutils/b2d.go b/libmachine/mcnutils/b2d.go index 3df4fcae29..031ebe8a83 100644 --- a/libmachine/mcnutils/b2d.go +++ b/libmachine/mcnutils/b2d.go @@ -11,7 +11,6 @@ import ( "os" "path/filepath" "regexp" - "time" "github.com/docker/machine/libmachine/log" ) @@ -20,12 +19,8 @@ var ( GithubAPIToken string ) -const ( - timeout = time.Second * 5 -) - func defaultTimeout(network, addr string) (net.Conn, error) { - return net.DialTimeout(network, addr, timeout) + return net.Dial(network, addr) } func getClient() *http.Client { @@ -139,12 +134,14 @@ func removeFileIfExists(name string) error { // DownloadISO downloads boot2docker ISO image for the given tag and save it at dest. func (b *B2dUtils) DownloadISO(dir, file, isoURL string) error { u, err := url.Parse(isoURL) + var src io.ReadCloser if u.Scheme == "file" || u.Scheme == "" { s, err := os.Open(u.Path) if err != nil { return err } + src = s } else { client := getClient() @@ -152,7 +149,12 @@ func (b *B2dUtils) DownloadISO(dir, file, isoURL string) error { if err != nil { return err } - src = s.Body + + src = &ReaderWithProgress{ + ReadCloser: s.Body, + out: os.Stdout, + expectedLength: s.ContentLength, + } } defer src.Close() @@ -170,7 +172,6 @@ func (b *B2dUtils) DownloadISO(dir, file, isoURL string) error { }() if _, err := io.Copy(f, src); err != nil { - // TODO: display download progress? return err } @@ -194,6 +195,39 @@ func (b *B2dUtils) DownloadISO(dir, file, isoURL string) error { return nil } +type ReaderWithProgress struct { + io.ReadCloser + out io.Writer + bytesTransferred int64 + expectedLength int64 + nextPercentToPrint int64 +} + +func (r *ReaderWithProgress) Read(p []byte) (int, error) { + n, err := r.ReadCloser.Read(p) + + if n > 0 { + r.bytesTransferred += int64(n) + percentage := r.bytesTransferred * 100 / r.expectedLength + + for percentage >= r.nextPercentToPrint { + if r.nextPercentToPrint%10 == 0 { + fmt.Fprintf(r.out, "%d%%", r.nextPercentToPrint) + } else if r.nextPercentToPrint%2 == 0 { + fmt.Fprint(r.out, ".") + } + r.nextPercentToPrint += 2 + } + } + + return n, err +} + +func (r *ReaderWithProgress) Close() error { + fmt.Fprintln(r.out) + return r.ReadCloser.Close() +} + func (b *B2dUtils) DownloadLatestBoot2Docker(apiURL string) error { latestReleaseURL, err := b.GetLatestBoot2DockerReleaseURL(apiURL) if err != nil { diff --git a/libmachine/mcnutils/b2d_test.go b/libmachine/mcnutils/b2d_test.go index a7fdd2addf..a1c1ea1185 100644 --- a/libmachine/mcnutils/b2d_test.go +++ b/libmachine/mcnutils/b2d_test.go @@ -7,6 +7,10 @@ import ( "net/http/httptest" "path/filepath" "testing" + + "bytes" + + "github.com/stretchr/testify/assert" ) func TestGetLatestBoot2DockerReleaseUrl(t *testing.T) { @@ -18,14 +22,9 @@ func TestGetLatestBoot2DockerReleaseUrl(t *testing.T) { b := NewB2dUtils("/tmp/isos") isoURL, err := b.GetLatestBoot2DockerReleaseURL(ts.URL + "/repos/org/repo/releases") - if err != nil { - t.Fatal(err) - } - expectedURL := fmt.Sprintf("%s/org/repo/releases/download/0.1/boot2docker.iso", ts.URL) - if isoURL != expectedURL { - t.Fatalf("expected url %s; received %s", expectedURL, isoURL) - } + assert.NoError(t, err) + assert.Equal(t, fmt.Sprintf("%s/org/repo/releases/download/0.1/boot2docker.iso", ts.URL), isoURL) } func TestDownloadIso(t *testing.T) { @@ -38,49 +37,76 @@ func TestDownloadIso(t *testing.T) { filename := "test" tmpDir, err := ioutil.TempDir("", "machine-test-") - if err != nil { - t.Fatal(err) - } + + assert.NoError(t, err) b := NewB2dUtils("/tmp/artifacts") - if err := b.DownloadISO(tmpDir, filename, ts.URL); err != nil { - t.Fatal(err) - } + err = b.DownloadISO(tmpDir, filename, ts.URL) + + assert.NoError(t, err) data, err := ioutil.ReadFile(filepath.Join(tmpDir, filename)) - if err != nil { - t.Fatal(err) - } - if string(data) != testData { - t.Fatalf("expected data \"%s\"; received \"%s\"", testData, string(data)) - } + assert.NoError(t, err) + assert.Equal(t, testData, string(data)) } func TestGetReleasesRequestNoToken(t *testing.T) { GithubAPIToken = "" + b2d := NewB2dUtils("/tmp/store") req, err := b2d.getReleasesRequest("http://some.github.api") - if err != nil { - t.Fatal("Expected err to be nil, got ", err) - } - if req.Header.Get("Authorization") != "" { - t.Fatal("Expected not to get an 'Authorization' header, but got one: ", req.Header.Get("Authorization")) - } + assert.NoError(t, err) + assert.Empty(t, req.Header.Get("Authorization")) } func TestGetReleasesRequest(t *testing.T) { expectedToken := "CATBUG" GithubAPIToken = expectedToken + b2d := NewB2dUtils("/tmp/store") - req, err := b2d.getReleasesRequest("http://some.github.api") - if err != nil { - t.Fatal("Expected err to be nil, got ", err) + + assert.NoError(t, err) + assert.Equal(t, fmt.Sprintf("token %s", expectedToken), req.Header.Get("Authorization")) +} + +type MockReadCloser struct { + blockLengths []int + currentBlock int +} + +func (r *MockReadCloser) Read(p []byte) (n int, err error) { + n = r.blockLengths[r.currentBlock] + r.currentBlock++ + return +} + +func (r *MockReadCloser) Close() error { + return nil +} + +func TestReaderWithProgress(t *testing.T) { + readCloser := MockReadCloser{blockLengths: []int{5, 45, 50}} + output := new(bytes.Buffer) + buffer := make([]byte, 100) + + readerWithProgress := ReaderWithProgress{ + ReadCloser: &readCloser, + out: output, + expectedLength: 100, } - if req.Header.Get("Authorization") != fmt.Sprintf("token %s", expectedToken) { - t.Fatal("Header was not set as expected: ", req.Header.Get("Authorization")) - } + readerWithProgress.Read(buffer) + assert.Equal(t, "0%..", output.String()) + + readerWithProgress.Read(buffer) + assert.Equal(t, "0%....10%....20%....30%....40%....50%", output.String()) + + readerWithProgress.Read(buffer) + assert.Equal(t, "0%....10%....20%....30%....40%....50%....60%....70%....80%....90%....100%", output.String()) + + readerWithProgress.Close() + assert.Equal(t, "0%....10%....20%....30%....40%....50%....60%....70%....80%....90%....100%\n", output.String()) } diff --git a/libmachine/provision/boot2docker.go b/libmachine/provision/boot2docker.go index 5c41857ff1..1bc24ed28d 100644 --- a/libmachine/provision/boot2docker.go +++ b/libmachine/provision/boot2docker.go @@ -42,18 +42,36 @@ type Boot2DockerProvisioner struct { } func (provisioner *Boot2DockerProvisioner) Service(name string, action serviceaction.ServiceAction) error { - var ( - err error - ) - - if _, err = provisioner.SSHCommand(fmt.Sprintf("sudo /etc/init.d/%s %s", name, action.String())); err != nil { - return err - } - - return nil + _, err := provisioner.SSHCommand(fmt.Sprintf("sudo /etc/init.d/%s %s", name, action.String())) + return err } func (provisioner *Boot2DockerProvisioner) upgradeIso() error { + // TODO: Ideally, we should not read from mcndirs directory at all. + // The driver should be able to communicate how and where to place the + // relevant files. + b2dutils := mcnutils.NewB2dUtils(mcndirs.GetBaseDir()) + + // Check if the driver has specified a custom b2d url + jsonDriver, err := json.Marshal(provisioner.GetDriver()) + if err != nil { + return err + } + var d struct { + Boot2DockerURL string + } + json.Unmarshal(jsonDriver, &d) + + log.Info("Downloading latest boot2docker iso...") + + // Usually we call this implicitly, but call it here explicitly to get + // the latest default boot2docker ISO. + if d.Boot2DockerURL == "" { + if err := b2dutils.DownloadLatestBoot2Docker(d.Boot2DockerURL); err != nil { + return err + } + } + log.Info("Stopping machine to do the upgrade...") if err := provisioner.Driver.Stop(); err != nil { @@ -66,30 +84,8 @@ func (provisioner *Boot2DockerProvisioner) upgradeIso() error { machineName := provisioner.GetDriver().GetMachineName() - log.Infof("Upgrading machine %s...", machineName) + log.Infof("Upgrading machine %q...", machineName) - // TODO: Ideally, we should not read from mcndirs directory at all. - // The driver should be able to communicate how and where to place the - // relevant files. - b2dutils := mcnutils.NewB2dUtils(mcndirs.GetBaseDir()) - - //Check if the driver has specifed a custom b2d url - jsonDriver, err := json.Marshal(provisioner.GetDriver()) - if err != nil { - return err - } - var d struct { - Boot2DockerURL string - } - json.Unmarshal(jsonDriver, &d) - - // Usually we call this implicitly, but call it here explicitly to get - // the latest default boot2docker ISO. - if d.Boot2DockerURL == "" { - if err := b2dutils.DownloadLatestBoot2Docker(d.Boot2DockerURL); err != nil { - return err - } - } // Either download the latest version of the b2d url that was explicitly // specified when creating the VM or copy the (updated) default ISO if err := b2dutils.CopyIsoToMachineDir(d.Boot2DockerURL, machineName); err != nil { diff --git a/libmachine/provision/provisioner.go b/libmachine/provision/provisioner.go index 542ea2aad9..c5d2c5a1d5 100644 --- a/libmachine/provision/provisioner.go +++ b/libmachine/provision/provisioner.go @@ -72,6 +72,8 @@ func Register(name string, p *RegisteredProvisioner) { } func DetectProvisioner(d drivers.Driver) (Provisioner, error) { + log.Info("Detecting the provisioner...") + osReleaseOut, err := drivers.RunSSHCommandFromDriver(d, "cat /etc/os-release") if err != nil { return nil, fmt.Errorf("Error getting SSH command: %s", err)