Merge pull request #2168 from dgageot/2167-b2d-upgrade

FIX #2167 b2d download timeout during upgrade
This commit is contained in:
Nathan LeClaire 2015-11-06 18:16:52 -08:00
commit b0f687b1b8
4 changed files with 129 additions and 71 deletions

View File

@ -11,7 +11,6 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
"time"
"github.com/docker/machine/libmachine/log" "github.com/docker/machine/libmachine/log"
) )
@ -20,12 +19,8 @@ var (
GithubAPIToken string GithubAPIToken string
) )
const (
timeout = time.Second * 5
)
func defaultTimeout(network, addr string) (net.Conn, error) { func defaultTimeout(network, addr string) (net.Conn, error) {
return net.DialTimeout(network, addr, timeout) return net.Dial(network, addr)
} }
func getClient() *http.Client { 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. // DownloadISO downloads boot2docker ISO image for the given tag and save it at dest.
func (b *B2dUtils) DownloadISO(dir, file, isoURL string) error { func (b *B2dUtils) DownloadISO(dir, file, isoURL string) error {
u, err := url.Parse(isoURL) u, err := url.Parse(isoURL)
var src io.ReadCloser var src io.ReadCloser
if u.Scheme == "file" || u.Scheme == "" { if u.Scheme == "file" || u.Scheme == "" {
s, err := os.Open(u.Path) s, err := os.Open(u.Path)
if err != nil { if err != nil {
return err return err
} }
src = s src = s
} else { } else {
client := getClient() client := getClient()
@ -152,7 +149,12 @@ func (b *B2dUtils) DownloadISO(dir, file, isoURL string) error {
if err != nil { if err != nil {
return err return err
} }
src = s.Body
src = &ReaderWithProgress{
ReadCloser: s.Body,
out: os.Stdout,
expectedLength: s.ContentLength,
}
} }
defer src.Close() defer src.Close()
@ -170,7 +172,6 @@ func (b *B2dUtils) DownloadISO(dir, file, isoURL string) error {
}() }()
if _, err := io.Copy(f, src); err != nil { if _, err := io.Copy(f, src); err != nil {
// TODO: display download progress?
return err return err
} }
@ -194,6 +195,39 @@ func (b *B2dUtils) DownloadISO(dir, file, isoURL string) error {
return nil 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 { func (b *B2dUtils) DownloadLatestBoot2Docker(apiURL string) error {
latestReleaseURL, err := b.GetLatestBoot2DockerReleaseURL(apiURL) latestReleaseURL, err := b.GetLatestBoot2DockerReleaseURL(apiURL)
if err != nil { if err != nil {

View File

@ -7,6 +7,10 @@ import (
"net/http/httptest" "net/http/httptest"
"path/filepath" "path/filepath"
"testing" "testing"
"bytes"
"github.com/stretchr/testify/assert"
) )
func TestGetLatestBoot2DockerReleaseUrl(t *testing.T) { func TestGetLatestBoot2DockerReleaseUrl(t *testing.T) {
@ -18,14 +22,9 @@ func TestGetLatestBoot2DockerReleaseUrl(t *testing.T) {
b := NewB2dUtils("/tmp/isos") b := NewB2dUtils("/tmp/isos")
isoURL, err := b.GetLatestBoot2DockerReleaseURL(ts.URL + "/repos/org/repo/releases") 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) assert.NoError(t, err)
if isoURL != expectedURL { assert.Equal(t, fmt.Sprintf("%s/org/repo/releases/download/0.1/boot2docker.iso", ts.URL), isoURL)
t.Fatalf("expected url %s; received %s", expectedURL, isoURL)
}
} }
func TestDownloadIso(t *testing.T) { func TestDownloadIso(t *testing.T) {
@ -38,49 +37,76 @@ func TestDownloadIso(t *testing.T) {
filename := "test" filename := "test"
tmpDir, err := ioutil.TempDir("", "machine-test-") tmpDir, err := ioutil.TempDir("", "machine-test-")
if err != nil {
t.Fatal(err) assert.NoError(t, err)
}
b := NewB2dUtils("/tmp/artifacts") b := NewB2dUtils("/tmp/artifacts")
if err := b.DownloadISO(tmpDir, filename, ts.URL); err != nil { err = b.DownloadISO(tmpDir, filename, ts.URL)
t.Fatal(err)
} assert.NoError(t, err)
data, err := ioutil.ReadFile(filepath.Join(tmpDir, filename)) data, err := ioutil.ReadFile(filepath.Join(tmpDir, filename))
if err != nil {
t.Fatal(err)
}
if string(data) != testData { assert.NoError(t, err)
t.Fatalf("expected data \"%s\"; received \"%s\"", testData, string(data)) assert.Equal(t, testData, string(data))
}
} }
func TestGetReleasesRequestNoToken(t *testing.T) { func TestGetReleasesRequestNoToken(t *testing.T) {
GithubAPIToken = "" GithubAPIToken = ""
b2d := NewB2dUtils("/tmp/store") b2d := NewB2dUtils("/tmp/store")
req, err := b2d.getReleasesRequest("http://some.github.api") 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") != "" { assert.NoError(t, err)
t.Fatal("Expected not to get an 'Authorization' header, but got one: ", req.Header.Get("Authorization")) assert.Empty(t, req.Header.Get("Authorization"))
}
} }
func TestGetReleasesRequest(t *testing.T) { func TestGetReleasesRequest(t *testing.T) {
expectedToken := "CATBUG" expectedToken := "CATBUG"
GithubAPIToken = expectedToken GithubAPIToken = expectedToken
b2d := NewB2dUtils("/tmp/store") b2d := NewB2dUtils("/tmp/store")
req, err := b2d.getReleasesRequest("http://some.github.api") 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) { readerWithProgress.Read(buffer)
t.Fatal("Header was not set as expected: ", req.Header.Get("Authorization")) 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())
} }

View File

@ -42,18 +42,36 @@ type Boot2DockerProvisioner struct {
} }
func (provisioner *Boot2DockerProvisioner) Service(name string, action serviceaction.ServiceAction) error { func (provisioner *Boot2DockerProvisioner) Service(name string, action serviceaction.ServiceAction) error {
var ( _, err := provisioner.SSHCommand(fmt.Sprintf("sudo /etc/init.d/%s %s", name, action.String()))
err error
)
if _, err = provisioner.SSHCommand(fmt.Sprintf("sudo /etc/init.d/%s %s", name, action.String())); err != nil {
return err return err
}
return nil
} }
func (provisioner *Boot2DockerProvisioner) upgradeIso() error { 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...") log.Info("Stopping machine to do the upgrade...")
if err := provisioner.Driver.Stop(); err != nil { if err := provisioner.Driver.Stop(); err != nil {
@ -66,30 +84,8 @@ func (provisioner *Boot2DockerProvisioner) upgradeIso() error {
machineName := provisioner.GetDriver().GetMachineName() 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 // Either download the latest version of the b2d url that was explicitly
// specified when creating the VM or copy the (updated) default ISO // specified when creating the VM or copy the (updated) default ISO
if err := b2dutils.CopyIsoToMachineDir(d.Boot2DockerURL, machineName); err != nil { if err := b2dutils.CopyIsoToMachineDir(d.Boot2DockerURL, machineName); err != nil {

View File

@ -72,6 +72,8 @@ func Register(name string, p *RegisteredProvisioner) {
} }
func DetectProvisioner(d drivers.Driver) (Provisioner, error) { func DetectProvisioner(d drivers.Driver) (Provisioner, error) {
log.Info("Detecting the provisioner...")
osReleaseOut, err := drivers.RunSSHCommandFromDriver(d, "cat /etc/os-release") osReleaseOut, err := drivers.RunSSHCommandFromDriver(d, "cat /etc/os-release")
if err != nil { if err != nil {
return nil, fmt.Errorf("Error getting SSH command: %s", err) return nil, fmt.Errorf("Error getting SSH command: %s", err)