mirror of https://github.com/docker/docs.git
Merge pull request #2168 from dgageot/2167-b2d-upgrade
FIX #2167 b2d download timeout during upgrade
This commit is contained in:
commit
b0f687b1b8
|
@ -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 {
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
return err
|
||||||
)
|
|
||||||
|
|
||||||
if _, err = provisioner.SSHCommand(fmt.Sprintf("sudo /etc/init.d/%s %s", name, action.String())); err != nil {
|
|
||||||
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 {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue