Implement upgrade functionality for boot2docker

Signed-off-by: Nathan LeClaire <nathan.leclaire@gmail.com>
This commit is contained in:
Nathan LeClaire 2015-03-30 15:22:32 -07:00
parent a04b908fd3
commit 9b5f395c60
8 changed files with 193 additions and 62 deletions

View File

@ -579,12 +579,26 @@ dev * virtualbox Stopped
#### upgrade
Upgrade a machine to the latest version of Docker.
Upgrade a machine to the latest version of Docker. If the machine uses Ubuntu
as the underlying operating system, it will upgrade the package `lxc-docker`
(our recommended install method). If the machine uses boot2docker, this command
will download the latest boot2docker ISO and replace the machine's existing ISO
with the latest.
```
$ docker-machine upgrade dev
INFO[0000] Stopping machine to do the upgrade...
INFO[0005] Upgrading machine dev...
INFO[0006] Downloading latest boot2docker release to /tmp/store/cache/boot2docker.iso...
INFO[0008] Starting machine back up...
INFO[0008] Waiting for VM to start...
```
> **Note**: If you are using a custom boot2docker ISO specified using
> `--virtualbox-boot2docker-url` or an equivalent flag, running an upgrade on
> that machine will completely replace the specified ISO with the latest
> "vanilla" boot2docker ISO available.
#### url
Get the URL of a host
@ -826,7 +840,22 @@ Options:
- `--virtualbox-memory`: Size of memory for the host in MB. Default: `1024`
- `--virtualbox-cpu-count`: Number of CPUs to use to create the VM. Defaults to number of available CPUs.
The VirtualBox driver uses the latest boot2docker image.
The `--virtualbox-boot2docker-url` flag takes a few different forms. By
default, if no value is specified for this flag, Machine will check locally for
a boot2docker ISO. If one is found, that will be used as the ISO for the
created machine. If one is not found, the latest ISO release available on
[boot2docker/boot2docker](https://github.com/boot2docker/boot2docker) will be
downloaded and stored locally for future use. Note that this means you must run
`docker-machine upgrade` deliberately on a machine if you wish to update the "cached"
boot2docker ISO.
This is the default behavior (when `--virtualbox-boot2docker-url=""`), but the
option also supports specifying ISOs by the `http://` and `file://` protocols.
`file://` will look at the path specified locally to locate the ISO: for
instance, you could specify `--virtualbox-boot2docker-url
file://$HOME/Downloads/rc.iso` to test out a release candidate ISO that you have
downloaded already. You could also just get an ISO straight from the Internet
using the `http://` form.
Environment variables:
@ -840,6 +869,7 @@ variable and CLI option are provided the CLI option takes the precedence.
| `VIRTUALBOX_DISK_SIZE` | `--virtualbox-disk-size` |
| `VIRTUALBOX_BOOT2DOCKER_URL` | `--virtualbox-boot2docker-url` |
#### VMware Fusion
Creates machines locally on [VMware Fusion](http://www.vmware.com/products/fusion). Requires VMware Fusion to be installed.

View File

@ -6,6 +6,7 @@ import (
"os/exec"
"sort"
log "github.com/Sirupsen/logrus"
"github.com/codegangsta/cli"
"github.com/docker/machine/provider"
"github.com/docker/machine/ssh"
@ -175,3 +176,16 @@ func GetSSHCommandFromDriver(d Driver, args ...string) (*exec.Cmd, error) {
return ssh.GetSSHCommand(host, port, user, keyPath, args...), nil
}
func MachineInState(d Driver, desiredState state.State) func() bool {
return func() bool {
currentState, err := d.GetState()
if err != nil {
log.Debugf("Error getting machine state: %s", err)
}
if currentState == desiredState {
return true
}
return false
}
}

View File

@ -158,8 +158,7 @@ func (d *Driver) PreCreateCheck() error {
func (d *Driver) Create() error {
var (
err error
isoURL string
err error
)
// Check that VBoxManage exists and works
@ -172,46 +171,13 @@ func (d *Driver) Create() error {
return err
}
b2dutils := utils.NewB2dUtils("", "")
imgPath := utils.GetMachineCacheDir()
isoFilename := "boot2docker.iso"
commonIsoPath := filepath.Join(imgPath, "boot2docker.iso")
// just in case boot2docker.iso has been manually deleted
if _, err := os.Stat(imgPath); os.IsNotExist(err) {
if err := os.Mkdir(imgPath, 0700); err != nil {
return err
}
}
if d.Boot2DockerURL != "" {
isoURL = d.Boot2DockerURL
log.Infof("Downloading %s from %s...", isoFilename, isoURL)
if err := b2dutils.DownloadISO(d.storePath, isoFilename, isoURL); err != nil {
return err
}
} else {
// todo: check latest release URL, download if it's new
// until then always use "latest"
isoURL, err = b2dutils.GetLatestBoot2DockerReleaseURL()
if err != nil {
log.Warnf("Unable to check for the latest release: %s", err)
}
if _, err := os.Stat(commonIsoPath); os.IsNotExist(err) {
log.Infof("Downloading %s to %s...", isoFilename, commonIsoPath)
if err := b2dutils.DownloadISO(imgPath, isoFilename, isoURL); err != nil {
return err
}
}
isoDest := filepath.Join(d.storePath, isoFilename)
if err := utils.CopyFile(commonIsoPath, isoDest); err != nil {
return err
}
}
log.Infof("Creating SSH key...")
b2dutils := utils.NewB2dUtils("", "")
if err := b2dutils.CopyIsoToMachineDir(d.Boot2DockerURL, d.MachineName); err != nil {
return err
}
if err := ssh.GenerateSSHKey(d.GetSSHKeyPath()); err != nil {
return err
}

View File

@ -146,19 +146,6 @@ func (h *Host) GetSSHCommand(args ...string) (*exec.Cmd, error) {
return cmd, nil
}
func (h *Host) MachineInState(desiredState state.State) func() bool {
return func() bool {
currentState, err := h.Driver.GetState()
if err != nil {
log.Debugf("Error getting machine state: %s", err)
}
if currentState == desiredState {
return true
}
return false
}
}
func (h *Host) Start() error {
if err := h.Driver.Start(); err != nil {
return err
@ -168,7 +155,7 @@ func (h *Host) Start() error {
return err
}
return utils.WaitFor(h.MachineInState(state.Running))
return utils.WaitFor(drivers.MachineInState(h.Driver, state.Running))
}
func (h *Host) Stop() error {
@ -180,7 +167,7 @@ func (h *Host) Stop() error {
return err
}
return utils.WaitFor(h.MachineInState(state.Stopped))
return utils.WaitFor(drivers.MachineInState(h.Driver, state.Stopped))
}
func (h *Host) Kill() error {
@ -192,16 +179,16 @@ func (h *Host) Kill() error {
return err
}
return utils.WaitFor(h.MachineInState(state.Stopped))
return utils.WaitFor(drivers.MachineInState(h.Driver, state.Stopped))
}
func (h *Host) Restart() error {
if h.MachineInState(state.Running)() {
if drivers.MachineInState(h.Driver, state.Running)() {
if err := h.Stop(); err != nil {
return err
}
if err := utils.WaitFor(h.MachineInState(state.Stopped)); err != nil {
if err := utils.WaitFor(drivers.MachineInState(h.Driver, state.Stopped)); err != nil {
return err
}
}
@ -210,7 +197,7 @@ func (h *Host) Restart() error {
return err
}
if err := utils.WaitFor(h.MachineInState(state.Running)); err != nil {
if err := utils.WaitFor(drivers.MachineInState(h.Driver, state.Running)); err != nil {
return err
}

View File

@ -2,14 +2,17 @@ package provision
import (
"bytes"
"errors"
"fmt"
"os/exec"
"path"
log "github.com/Sirupsen/logrus"
"github.com/docker/machine/drivers"
"github.com/docker/machine/libmachine/auth"
"github.com/docker/machine/libmachine/provision/pkgaction"
"github.com/docker/machine/libmachine/swarm"
"github.com/docker/machine/state"
"github.com/docker/machine/utils"
)
@ -46,7 +49,54 @@ func (provisioner *Boot2DockerProvisioner) Service(name string, action pkgaction
return nil
}
func (provisioner *Boot2DockerProvisioner) upgradeIso() error {
log.Infof("Stopping machine to do the upgrade...")
switch provisioner.Driver.DriverName() {
case "vmwarefusion", "vmwarevsphere":
return errors.New("Upgrade functionality is currently not supported for these providers, as they use a custom ISO.")
}
if err := provisioner.Driver.Stop(); err != nil {
return err
}
if err := utils.WaitFor(drivers.MachineInState(provisioner.Driver, state.Stopped)); err != nil {
return err
}
machineName := provisioner.GetDriver().GetMachineName()
log.Infof("Upgrading machine %s...", machineName)
b2dutils := utils.NewB2dUtils("", "")
// Usually we call this implicitly, but call it here explicitly to get
// the latest boot2docker ISO.
if err := b2dutils.DownloadLatestBoot2Docker(); err != nil {
return err
}
// Copy the latest version of boot2docker ISO to the machine's directory
if err := b2dutils.CopyIsoToMachineDir("", machineName); err != nil {
return err
}
log.Infof("Starting machine back up...")
if err := provisioner.Driver.Start(); err != nil {
return err
}
return utils.WaitFor(drivers.MachineInState(provisioner.Driver, state.Running))
}
func (provisioner *Boot2DockerProvisioner) Package(name string, action pkgaction.PackageAction) error {
if name == "docker" && action == pkgaction.Upgrade {
if err := provisioner.upgradeIso(); err != nil {
return err
}
}
return nil
}

View File

@ -36,6 +36,12 @@ findCPUCount() {
run bash -c "VBoxManage showvminfo --machinereadable $NAME | grep cpus= | cut -d'=' -f2"
}
buildMachineWithOldIsoCheckUpgrade() {
run wget https://github.com/boot2docker/boot2docker/releases/download/v1.4.1/boot2docker.iso -O $MACHINE_STORAGE_PATH/cache/boot2docker.iso
run machine create -d virtualbox $NAME
run machine upgrade $NAME
}
@test "$DRIVER: machine should not exist" {
run machine active $NAME
[ "$status" -eq 1 ]
@ -316,6 +322,15 @@ findCPUCount() {
[ "$status" -eq 0 ]
}
@test "$DRIVER: upgrade should work" {
buildMachineWithOldIsoCheckUpgrade
[ "$status" -eq 0 ]
}
@test "$DRIVER: remove machine after upgrade test" {
run machine rm -f $NAME
}
# Cleanup of machine store should always be the last 'test'
@test "$DRIVER: cleanup" {
run rm -rf $MACHINE_STORAGE_PATH

View File

@ -11,6 +11,8 @@ import (
"os"
"path/filepath"
"time"
log "github.com/Sirupsen/logrus"
)
const (
@ -36,6 +38,9 @@ func getClient() *http.Client {
}
type B2dUtils struct {
isoFilename string
commonIsoPath string
imgCachePath string
githubApiBaseUrl string
githubBaseUrl string
}
@ -43,6 +48,8 @@ type B2dUtils struct {
func NewB2dUtils(githubApiBaseUrl, githubBaseUrl string) *B2dUtils {
defaultBaseApiUrl := "https://api.github.com"
defaultBaseUrl := "https://github.com"
imgCachePath := GetMachineCacheDir()
isoFilename := "boot2docker.iso"
if githubApiBaseUrl == "" {
githubApiBaseUrl = defaultBaseApiUrl
@ -53,6 +60,9 @@ func NewB2dUtils(githubApiBaseUrl, githubBaseUrl string) *B2dUtils {
}
return &B2dUtils{
isoFilename: isoFilename,
imgCachePath: GetMachineCacheDir(),
commonIsoPath: filepath.Join(imgCachePath, isoFilename),
githubApiBaseUrl: githubApiBaseUrl,
githubBaseUrl: githubBaseUrl,
}
@ -128,3 +138,62 @@ func (b *B2dUtils) DownloadISO(dir, file, isoUrl string) error {
return nil
}
func (b *B2dUtils) DownloadLatestBoot2Docker() error {
latestReleaseUrl, err := b.GetLatestBoot2DockerReleaseURL()
if err != nil {
return err
}
log.Infof("Downloading latest boot2docker release to %s...", b.commonIsoPath)
if err := b.DownloadISO(b.imgCachePath, b.isoFilename, latestReleaseUrl); err != nil {
return err
}
return nil
}
func (b *B2dUtils) CopyIsoToMachineDir(isoURL, machineName string) error {
machinesDir := GetMachineDir()
machineIsoPath := filepath.Join(machinesDir, machineName, b.isoFilename)
// just in case the cache dir has been manually deleted,
// check for it and recreate it if it's gone
if _, err := os.Stat(b.imgCachePath); os.IsNotExist(err) {
log.Infof("Image cache does not exist, creating it at %s...", b.imgCachePath)
if err := os.Mkdir(b.imgCachePath, 0700); err != nil {
return err
}
}
// By default just copy the existing "cached" iso to
// the machine's directory...
if isoURL == "" {
if err := b.copyDefaultIsoToMachine(machineIsoPath); err != nil {
return err
}
} else {
// But if ISO is specified go get it directly
log.Infof("Downloading %s from %s...", b.isoFilename, isoURL)
if err := b.DownloadISO(filepath.Join(machinesDir, machineName), b.isoFilename, isoURL); err != nil {
return err
}
}
return nil
}
func (b *B2dUtils) copyDefaultIsoToMachine(machineIsoPath string) error {
if _, err := os.Stat(b.commonIsoPath); os.IsNotExist(err) {
log.Info("No default boot2docker iso found locally, downloading the latest release...")
if err := b.DownloadLatestBoot2Docker(); err != nil {
return err
}
}
if err := CopyFile(b.commonIsoPath, machineIsoPath); err != nil {
return err
}
return nil
}

View File

@ -100,7 +100,7 @@ func WaitForDocker(ip string, daemonPort int) error {
return WaitFor(func() bool {
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", ip, daemonPort))
if err != nil {
log.Debug("Got an error it was", err)
log.Debugf("Got an error it was %s", err)
return false
}
conn.Close()