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"
"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 {

View File

@ -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())
}

View File

@ -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 {

View File

@ -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)