From 98a3e94c48bacacb2d931d90f9eda60a87ce80ce Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Thu, 24 Jul 2014 15:37:49 +1000 Subject: [PATCH] Revert "Create driver model and refactor virtualbox dependencies into driver" --- cmds.go | 259 ++++++++++++++++++++++++++---- config.go | 45 +++++- driver/config.go | 39 ----- driver/dhcp.go | 12 -- driver/driver.go | 76 --------- dummy/machine.go | 157 ------------------ vbm.go | 107 ++++++++++++ virtualbox/dhcp.go | 25 +-- virtualbox/machine.go | 216 ++++--------------------- {driver => virtualbox}/nic.go | 2 +- {driver => virtualbox}/pfrule.go | 2 +- {driver => virtualbox}/storage.go | 2 +- virtualbox/vbm.go | 89 ---------- 13 files changed, 430 insertions(+), 601 deletions(-) delete mode 100644 driver/config.go delete mode 100644 driver/dhcp.go delete mode 100644 driver/driver.go delete mode 100644 dummy/machine.go create mode 100644 vbm.go rename {driver => virtualbox}/nic.go (97%) rename {driver => virtualbox}/pfrule.go (98%) rename {driver => virtualbox}/storage.go (98%) diff --git a/cmds.go b/cmds.go index ecf7595..62db7fc 100644 --- a/cmds.go +++ b/cmds.go @@ -1,33 +1,230 @@ package main import ( + "archive/tar" + "bytes" "encoding/json" "fmt" + "io/ioutil" + "net" "os" + "path/filepath" "runtime" "strings" "time" - _ "github.com/boot2docker/boot2docker-cli/dummy" - _ "github.com/boot2docker/boot2docker-cli/virtualbox" - - "github.com/boot2docker/boot2docker-cli/driver" + vbx "github.com/boot2docker/boot2docker-cli/virtualbox" ) +const dockerPort = 2375 + // Initialize the boot2docker VM from scratch. func cmdInit() int { - B2D.Init = true - _, err := driver.GetMachine(&B2D) + // TODO(@riobard) break up this command into multiple stages + m, err := vbx.GetMachine(B2D.VM) + if err == nil { + logf("Virtual machine %s already exists", B2D.VM) + return 1 + } + + if ping(fmt.Sprintf("localhost:%d", B2D.DockerPort)) { + logf("--dockerport=%d on localhost is occupied. Please choose another one.", B2D.DockerPort) + return 1 + } + + if ping(fmt.Sprintf("localhost:%d", B2D.SSHPort)) { + logf("--sshport=%d on localhost is occupied. Please choose another one.", B2D.SSHPort) + return 1 + } + + if _, err := os.Stat(B2D.ISO); err != nil { + if !os.IsNotExist(err) { + logf("Failed to open ISO image %q: %s", B2D.ISO, err) + return 1 + } + + if exitcode := cmdDownload(); exitcode != 0 { + return exitcode + } + } + if _, err := os.Stat(B2D.SSHKey); err != nil { + if !os.IsNotExist(err) { + logf("Something wrong with SSH Key file %q: %s", B2D.SSHKey, err) + return 1 + } + if err := cmdInteractive(B2D.SSHGen, "-t", "rsa", "-N", "", "-f", B2D.SSHKey); err != nil { + logf("Error generating new SSH Key into %s: %s", B2D.SSHKey, err) + return 1 + } + } + //TODO: print a ~/.ssh/config entry for our b2d connection that the user can c&p + + logf("Creating VM %s...", B2D.VM) + m, err = vbx.CreateMachine(B2D.VM, "") if err != nil { - logf("Failed to initialize machine %q: %s", B2D.VM, err) + logf("FIRST: Failed to create VM %q: %s", B2D.VM, err) + // double tap. VBox will sometimes (like on initial install, or reboot) + // fail to start the service the first time. + m, err = vbx.CreateMachine(B2D.VM, "") + if err != nil { + logf("Failed to create VM %q: %s", B2D.VM, err) + return 1 + } + } + + logf("Apply interim patch to VM %s (https://www.virtualbox.org/ticket/12748)", B2D.VM) + if err := vbx.SetExtra(B2D.VM, "VBoxInternal/CPUM/EnableHVP", "1"); err != nil { + logf("Failed to patch vm: %s", err) + return 1 + } + + m.OSType = "Linux26_64" + m.CPUs = uint(runtime.NumCPU()) + m.Memory = B2D.Memory + m.SerialFile = B2D.SerialFile + + m.Flag |= vbx.F_pae + m.Flag |= vbx.F_longmode // important: use x86-64 processor + m.Flag |= vbx.F_rtcuseutc + m.Flag |= vbx.F_acpi + m.Flag |= vbx.F_ioapic + m.Flag |= vbx.F_hpet + m.Flag |= vbx.F_hwvirtex + m.Flag |= vbx.F_vtxvpid + m.Flag |= vbx.F_largepages + m.Flag |= vbx.F_nestedpaging + + m.BootOrder = []string{"dvd"} + if err := m.Modify(); err != nil { + logf("Failed to modify VM %q: %s", B2D.VM, err) + return 1 + } + + logf("Setting NIC #1 to use NAT network...") + if err := m.SetNIC(1, vbx.NIC{Network: vbx.NICNetNAT, Hardware: vbx.VirtIO}); err != nil { + logf("Failed to add network interface to VM %q: %s", B2D.VM, err) + return 1 + } + + pfRules := map[string]vbx.PFRule{ + "ssh": {Proto: vbx.PFTCP, HostIP: net.ParseIP("127.0.0.1"), HostPort: B2D.SSHPort, GuestPort: 22}, + "docker": {Proto: vbx.PFTCP, HostIP: net.ParseIP("127.0.0.1"), HostPort: B2D.DockerPort, GuestPort: dockerPort}, + } + + for name, rule := range pfRules { + if err := m.AddNATPF(1, name, rule); err != nil { + logf("Failed to add port forwarding to VM %q: %s", B2D.VM, err) + return 1 + } + logf("Port forwarding [%s] %s", name, rule) + } + + hostIFName, err := getHostOnlyNetworkInterface() + if err != nil { + logf("Failed to create host-only network interface: %s", err) + return 1 + } + + logf("Setting NIC #2 to use host-only network %q...", hostIFName) + if err := m.SetNIC(2, vbx.NIC{Network: vbx.NICNetHostonly, Hardware: vbx.VirtIO, HostonlyAdapter: hostIFName}); err != nil { + logf("Failed to add network interface to VM %q: %s", B2D.VM, err) + return 1 + } + + logf("Setting VM storage...") + if err := m.AddStorageCtl("SATA", vbx.StorageController{SysBus: vbx.SysBusSATA, HostIOCache: true, Bootable: true}); err != nil { + logf("Failed to add storage controller to VM %q: %s", B2D.VM, err) + return 1 + } + + if err := m.AttachStorage("SATA", vbx.StorageMedium{Port: 0, Device: 0, DriveType: vbx.DriveDVD, Medium: B2D.ISO}); err != nil { + logf("Failed to attach ISO image %q: %s", B2D.ISO, err) return 1 } + + diskImg := filepath.Join(m.BaseFolder, fmt.Sprintf("%s.vmdk", B2D.VM)) + + if _, err := os.Stat(diskImg); err != nil { + if !os.IsNotExist(err) { + logf("Failed to open disk image %q: %s", diskImg, err) + return 1 + } + + if B2D.VMDK != "" { + logf("Using %v as base VMDK", B2D.VMDK) + if err := copyDiskImage(diskImg, B2D.VMDK); err != nil { + logf("Failed to copy disk image %v from %v: %s", diskImg, B2D.VMDK, err) + return 1 + } + } else { + magicString := "boot2docker, please format-me" + + buf := new(bytes.Buffer) + tw := tar.NewWriter(buf) + + // magicString first so the automount script knows to format the disk + file := &tar.Header{Name: magicString, Size: int64(len(magicString))} + if err := tw.WriteHeader(file); err != nil { + logf("Error making tarfile: %s", err) + return 1 + } + if _, err := tw.Write([]byte(magicString)); err != nil { + logf("Error making tarfile: %s", err) + return 1 + } + // .ssh/key.pub => authorized_keys + file = &tar.Header{Name: ".ssh", Typeflag: tar.TypeDir, Mode: 0700} + if err := tw.WriteHeader(file); err != nil { + logf("Error making tarfile: %s", err) + return 1 + } + pubKey, err := ioutil.ReadFile(B2D.SSHKey + ".pub") + if err != nil { + logf("Error making tarfile: %s", err) + return 1 + } + file = &tar.Header{Name: ".ssh/authorized_keys", Size: int64(len(pubKey)), Mode: 0644} + if err := tw.WriteHeader(file); err != nil { + logf("Error making tarfile: %s", err) + return 1 + } + if _, err := tw.Write([]byte(pubKey)); err != nil { + logf("Error making tarfile: %s", err) + return 1 + } + file = &tar.Header{Name: ".ssh/authorized_keys2", Size: int64(len(pubKey)), Mode: 0644} + if err := tw.WriteHeader(file); err != nil { + logf("Error making tarfile: %s", err) + return 1 + } + if _, err := tw.Write([]byte(pubKey)); err != nil { + logf("Error making tarfile: %s", err) + return 1 + } + if err := tw.Close(); err != nil { + logf("Error making tarfile: %s", err) + return 1 + } + + if err := makeDiskImage(diskImg, B2D.DiskSize, buf.Bytes()); err != nil { + logf("Failed to create disk image %q: %s", diskImg, err) + return 1 + } + } + } + + if err := m.AttachStorage("SATA", vbx.StorageMedium{Port: 1, Device: 0, DriveType: vbx.DriveHDD, Medium: diskImg}); err != nil { + logf("Failed to attach disk image %q: %s", diskImg, err) + return 1 + } + + logf("Done. Type `%s up` to start the VM.", os.Args[0]) return 0 } // Bring up the VM from all possible states. func cmdUp() int { - m, err := driver.GetMachine(&B2D) + m, err := vbx.GetMachine(B2D.VM) if err != nil { logf("Failed to get machine %q: %s", B2D.VM, err) return 2 @@ -41,7 +238,7 @@ func cmdUp() int { logf("Failed to start machine %q: %s", B2D.VM, err) return 1 } - if m.GetState() != driver.Running { + if m.State != vbx.Running { logf("Failed to start machine %q (run again with -v for details)", B2D.VM) return 1 } @@ -49,11 +246,11 @@ func cmdUp() int { logf("Waiting for VM to be started...") //give the VM a little time to start, so we don't kill the Serial Pipe/Socket time.Sleep(600 * time.Millisecond) - natSSH := fmt.Sprintf("localhost:%d", m.GetSSHPort()) + natSSH := fmt.Sprintf("localhost:%d", m.SSHPort) IP := "" for i := 1; i < 30; i++ { if B2D.Serial && runtime.GOOS != "windows" { - if IP = RequestIPFromSerialPort(m.GetSerialFile()); IP != "" { + if IP = RequestIPFromSerialPort(m.SerialFile); IP != "" { break } } @@ -86,9 +283,9 @@ func cmdUp() int { logf("Please run `boot2docker -v up` to diagnose.") } else { // Check if $DOCKER_HOST ENV var is properly configured. - if os.Getenv("DOCKER_HOST") != fmt.Sprintf("tcp://%s:%d", IP, driver.DockerPort) { + if os.Getenv("DOCKER_HOST") != fmt.Sprintf("tcp://%s:%d", IP, dockerPort) { logf("To connect the Docker client to the Docker daemon, please set:") - logf(" export DOCKER_HOST=tcp://%s:%d", IP, driver.DockerPort) + logf(" export DOCKER_HOST=tcp://%s:%d", IP, dockerPort) } else { logf("Your DOCKER_HOST env variable is already set correctly.") } @@ -112,7 +309,7 @@ func cmdConfig() int { // Suspend and save the current state of VM on disk. func cmdSave() int { - m, err := driver.GetMachine(&B2D) + m, err := vbx.GetMachine(B2D.VM) if err != nil { logf("Failed to get machine %q: %s", B2D.VM, err) return 2 @@ -126,7 +323,7 @@ func cmdSave() int { // Gracefully stop the VM by sending ACPI shutdown signal. func cmdStop() int { - m, err := driver.GetMachine(&B2D) + m, err := vbx.GetMachine(B2D.VM) if err != nil { logf("Failed to get machine %q: %s", B2D.VM, err) return 2 @@ -141,7 +338,7 @@ func cmdStop() int { // Forcefully power off the VM (equivalent to unplug power). Might corrupt disk // image. func cmdPoweroff() int { - m, err := driver.GetMachine(&B2D) + m, err := vbx.GetMachine(B2D.VM) if err != nil { logf("Failed to get machine %q: %s", B2D.VM, err) return 2 @@ -169,7 +366,7 @@ func cmdUpgrade() int { // Gracefully stop and then start the VM. func cmdRestart() int { - m, err := driver.GetMachine(&B2D) + m, err := vbx.GetMachine(B2D.VM) if err != nil { logf("Failed to get machine %q: %s", B2D.VM, err) return 2 @@ -183,7 +380,7 @@ func cmdRestart() int { // Forcefully reset (equivalent to cold boot) the VM. Might corrupt disk image. func cmdReset() int { - m, err := driver.GetMachine(&B2D) + m, err := vbx.GetMachine(B2D.VM) if err != nil { logf("Failed to get machine %q: %s", B2D.VM, err) return 2 @@ -197,9 +394,9 @@ func cmdReset() int { // Delete the VM and associated disk image. func cmdDelete() int { - m, err := driver.GetMachine(&B2D) + m, err := vbx.GetMachine(B2D.VM) if err != nil { - if err == driver.ErrMachineNotExist { + if err == vbx.ErrMachineNotExist { logf("Machine %q does not exist.", B2D.VM) return 0 } @@ -215,7 +412,7 @@ func cmdDelete() int { // Show detailed info of the VM. func cmdInfo() int { - m, err := driver.GetMachine(&B2D) + m, err := vbx.GetMachine(B2D.VM) if err != nil { logf("Failed to get machine %q: %s", B2D.VM, err) return 2 @@ -229,24 +426,24 @@ func cmdInfo() int { // Show the current state of the VM. func cmdStatus() int { - m, err := driver.GetMachine(&B2D) + m, err := vbx.GetMachine(B2D.VM) if err != nil { logf("Failed to get machine %q: %s", B2D.VM, err) return 2 } - fmt.Println(m.GetState()) + fmt.Println(m.State) return 0 } // Call the external SSH command to login into boot2docker VM. func cmdSSH() int { - m, err := driver.GetMachine(&B2D) + m, err := vbx.GetMachine(B2D.VM) if err != nil { logf("Failed to get machine %q: %s", B2D.VM, err) return 2 } - if m.GetState() != driver.Running { + if m.State != vbx.Running { logf("VM %q is not running.", B2D.VM) return 1 } @@ -263,7 +460,7 @@ func cmdSSH() int { "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", "-o", "LogLevel=quiet", // suppress "Warning: Permanently added '[localhost]:2022' (ECDSA) to the list of known hosts." - "-p", fmt.Sprintf("%d", m.GetSSHPort()), + "-p", fmt.Sprintf("%d", m.SSHPort), "-i", B2D.SSHKey, "docker@localhost", }, os.Args[i:]...) @@ -276,13 +473,13 @@ func cmdSSH() int { } func cmdIP() int { - m, err := driver.GetMachine(&B2D) + m, err := vbx.GetMachine(B2D.VM) if err != nil { logf("Failed to get machine %q: %s", B2D.VM, err) return 2 } - if m.GetState() != driver.Running { + if m.State != vbx.Running { logf("VM %q is not running.", B2D.VM) return 1 } @@ -291,7 +488,7 @@ func cmdIP() int { if B2D.Serial { for i := 1; i < 20; i++ { if runtime.GOOS != "windows" { - if IP = RequestIPFromSerialPort(m.GetSerialFile()); IP != "" { + if IP = RequestIPFromSerialPort(m.SerialFile); IP != "" { break } } @@ -312,13 +509,13 @@ func cmdIP() int { return 0 } -func RequestIPFromSSH(m driver.Machine) string { +func RequestIPFromSSH(m *vbx.Machine) string { // fall back to using the NAT port forwarded ssh out, err := cmd(B2D.SSH, "-v", // please leave in - this seems to improve the chance of success "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", - "-p", fmt.Sprintf("%d", m.GetSSHPort()), + "-p", fmt.Sprintf("%d", m.SSHPort), "-i", B2D.SSHKey, "docker@localhost", "ip addr show dev eth1", diff --git a/config.go b/config.go index 1208450..1578bb2 100644 --- a/config.go +++ b/config.go @@ -10,14 +10,50 @@ import ( "runtime" toml "github.com/BurntSushi/toml" - "github.com/boot2docker/boot2docker-cli/driver" + vbx "github.com/boot2docker/boot2docker-cli/virtualbox" flag "github.com/ogier/pflag" ) +// boot2docker config. +var B2D struct { + // NOTE: separate sections with blank lines so gofmt doesn't change + // indentation all the time. + + // Gereral flags. + Verbose bool + VBM string + + // basic config + SSH string // SSH client executable + SSHGen string // SSH keygen executable + SSHKey string // SSH key to send to the vm + VM string // virtual machine name + Dir string // boot2docker directory + ISO string // boot2docker ISO image path + VMDK string // base VMDK to use as persistent disk + DiskSize uint // VM disk image size (MB) + Memory uint // VM memory size (MB) + + // NAT network: port forwarding + SSHPort uint16 // host SSH port (forward to port 22 in VM) + DockerPort uint16 // host Docker port (forward to port 2375 in VM) + + // host-only network + HostIP net.IP + DHCPIP net.IP + NetMask net.IPMask + LowerIP net.IP + UpperIP net.IP + DHCPEnabled bool + + // Serial console pipe/socket + Serial bool + SerialFile string +} + var ( // Pattern to parse a key=value line in config profile. reFlagLine = regexp.MustCompile(`^\s*(\w+)\s*=\s*([^#;]+)`) - B2D = driver.MachineConfig{} ) func getCfgDir(name string) (string, error) { @@ -88,10 +124,8 @@ func config() (*flag.FlagSet, error) { } vbm = filepath.Join(p, "VBoxManage.exe") } - flags.BoolVarP(&B2D.Init, "init", "i", false, "auto initialize vm instance.") flags.StringVar(&B2D.VBM, "vbm", vbm, "path to VirtualBox management utility.") flags.BoolVarP(&B2D.Verbose, "verbose", "v", false, "display verbose command invocations.") - flags.StringVar(&B2D.Driver, "driver", "virtualbox", "hypervisor driver.") flags.StringVar(&B2D.SSH, "ssh", "ssh", "path to SSH client utility.") flags.StringVar(&B2D.SSHGen, "ssh-keygen", "ssh-keygen", "path to ssh-keygen utility.") @@ -140,6 +174,9 @@ func config() (*flag.FlagSet, error) { return nil, err } + vbx.Verbose = B2D.Verbose + vbx.VBM = B2D.VBM + if B2D.SerialFile == "" { if runtime.GOOS == "windows" { //SerialFile ~~ filepath.Join(dir, B2D.vm+".sock") diff --git a/driver/config.go b/driver/config.go deleted file mode 100644 index 91a1884..0000000 --- a/driver/config.go +++ /dev/null @@ -1,39 +0,0 @@ -package driver - -import "net" - -// Machine config. -type MachineConfig struct { - // Gereral flags. - Init bool - Verbose bool - VBM string - Driver string - - // basic config - SSH string // SSH client executable - SSHGen string // SSH keygen executable - SSHKey string // SSH key to send to the vm - VM string // virtual machine name - Dir string // boot2docker directory - ISO string // boot2docker ISO image path - VMDK string // base VMDK to use as persistent disk - DiskSize uint // VM disk image size (MB) - Memory uint // VM memory size (MB) - - // NAT network: port forwarding - SSHPort uint16 // host SSH port (forward to port 22 in VM) - DockerPort uint16 // host Docker port (forward to port 2375 in VM) - - // host-only network - HostIP net.IP - DHCPIP net.IP - NetMask net.IPMask - LowerIP net.IP - UpperIP net.IP - DHCPEnabled bool - - // Serial console pipe/socket - Serial bool - SerialFile string -} diff --git a/driver/dhcp.go b/driver/dhcp.go deleted file mode 100644 index f6b6316..0000000 --- a/driver/dhcp.go +++ /dev/null @@ -1,12 +0,0 @@ -package driver - -import "net" - -// DHCP server info. -type DHCP struct { - NetworkName string - IPv4 net.IPNet - LowerIP net.IP - UpperIP net.IP - Enabled bool -} diff --git a/driver/driver.go b/driver/driver.go deleted file mode 100644 index db4f7aa..0000000 --- a/driver/driver.go +++ /dev/null @@ -1,76 +0,0 @@ -package driver - -import ( - "errors" - "fmt" -) - -type InitFunc func(i *MachineConfig) (Machine, error) - -type MachineState string - -const ( - // Known ports - SSHPort = 22 - DockerPort = 2375 - - // VM states - Poweroff = MachineState("poweroff") - Running = MachineState("running") - Paused = MachineState("paused") - Saved = MachineState("saved") - Aborted = MachineState("aborted") -) - -// Machine represents a virtual machine instance -type Machine interface { - Start() error - Save() error - Pause() error - Stop() error - Refresh() error - Poweroff() error - Restart() error - Reset() error - Delete() error - Modify() error - AddNATPF(n int, name string, rule PFRule) error - DelNATPF(n int, name string) error - SetNIC(n int, nic NIC) error - AddStorageCtl(name string, ctl StorageController) error - DelStorageCtl(name string) error - AttachStorage(ctlName string, medium StorageMedium) error - GetState() MachineState - GetSerialFile() string - GetDockerPort() uint - GetSSHPort() uint -} - -var ( - // All registred machines - machines map[string]InitFunc - - ErrNotSupported = errors.New("machine not supported") - ErrMachineNotExist = errors.New("machine not exist") - ErrPrerequisites = errors.New("prerequisites for machine not satisfied (hypervisor installed?)") -) - -func init() { - machines = make(map[string]InitFunc) -} - -func Register(driver string, initFunc InitFunc) error { - if _, exists := machines[driver]; exists { - return fmt.Errorf("Driver already registered %s", driver) - } - machines[driver] = initFunc - - return nil -} - -func GetMachine(mc *MachineConfig) (Machine, error) { - if initFunc, exists := machines[mc.Driver]; exists { - return initFunc(mc) - } - return nil, ErrNotSupported -} diff --git a/dummy/machine.go b/dummy/machine.go deleted file mode 100644 index 6d6a46a..0000000 --- a/dummy/machine.go +++ /dev/null @@ -1,157 +0,0 @@ -package dummy - -import ( - "fmt" - - "github.com/boot2docker/boot2docker-cli/driver" -) - -func init() { - driver.Register("dummy", InitFunc) -} - -// Initialize the Machine. -func InitFunc(i *driver.MachineConfig) (driver.Machine, error) { - fmt.Printf("Init dummy %s\n", i.VM) - return &Machine{Name: i.VM, State: driver.Poweroff}, nil -} - -// Machine information. -type Machine struct { - Name string - UUID string - State driver.MachineState - CPUs uint - Memory uint // main memory (in MB) - VRAM uint // video memory (in MB) - CfgFile string - BaseFolder string - OSType string - BootOrder []string // max 4 slots, each in {none|floppy|dvd|disk|net} - DockerPort uint - SSHPort uint - SerialFile string -} - -// Refresh reloads the machine information. -func (m *Machine) Refresh() error { - fmt.Printf("Refresh %s: %s\n", m.Name, m.State) - return nil -} - -// Start starts the machine. -func (m *Machine) Start() error { - m.State = driver.Running - fmt.Printf("Start %s: %s\n", m.Name, m.State) - return nil -} - -// Suspend suspends the machine and saves its state to disk. -func (m *Machine) Save() error { - m.State = driver.Saved - fmt.Printf("Save %s: %s\n", m.Name, m.State) - return nil -} - -// Pause pauses the execution of the machine. -func (m *Machine) Pause() error { - m.State = driver.Paused - fmt.Printf("Pause %s: %s\n", m.Name, m.State) - return nil -} - -// Stop gracefully stops the machine. -func (m *Machine) Stop() error { - m.State = driver.Poweroff - fmt.Printf("Stop %s: %s\n", m.Name, m.State) - return nil -} - -// Poweroff forcefully stops the machine. State is lost and might corrupt the disk image. -func (m *Machine) Poweroff() error { - m.State = driver.Poweroff - fmt.Printf("Poweroff %s: %s\n", m.Name, m.State) - return nil -} - -// Restart gracefully restarts the machine. -func (m *Machine) Restart() error { - m.State = driver.Running - fmt.Printf("Restart %s: %s\n", m.Name, m.State) - return nil -} - -// Reset forcefully restarts the machine. State is lost and might corrupt the disk image. -func (m *Machine) Reset() error { - m.State = driver.Running - fmt.Printf("Reset %s: %s\n", m.Name, m.State) - return nil -} - -// Get current state -func (m *Machine) GetState() driver.MachineState { - return m.State -} - -// Get serial file -func (m *Machine) GetSerialFile() string { - return m.SerialFile -} - -// Get Docker port -func (m *Machine) GetDockerPort() uint { - return m.DockerPort -} - -// Get SSH port -func (m *Machine) GetSSHPort() uint { - return m.SSHPort -} - -// Delete deletes the machine and associated disk images. -func (m *Machine) Delete() error { - fmt.Printf("Delete %s: %s\n", m.Name, m.State) - return nil -} - -// Modify changes the settings of the machine. -func (m *Machine) Modify() error { - fmt.Printf("Modify %s: %s\n", m.Name, m.State) - return m.Refresh() -} - -// AddNATPF adds a NAT port forarding rule to the n-th NIC with the given name. -func (m *Machine) AddNATPF(n int, name string, rule driver.PFRule) error { - fmt.Println("Add NAT PF") - return nil -} - -// DelNATPF deletes the NAT port forwarding rule with the given name from the n-th NIC. -func (m *Machine) DelNATPF(n int, name string) error { - fmt.Println("Del NAT PF") - return nil -} - -// SetNIC set the n-th NIC. -func (m *Machine) SetNIC(n int, nic driver.NIC) error { - fmt.Println("Set NIC") - return nil -} - -// AddStorageCtl adds a storage controller with the given name. -func (m *Machine) AddStorageCtl(name string, ctl driver.StorageController) error { - fmt.Println("Add storage ctl") - return nil -} - -// DelStorageCtl deletes the storage controller with the given name. -func (m *Machine) DelStorageCtl(name string) error { - fmt.Println("Del storage ctl") - return nil -} - -// AttachStorage attaches a storage medium to the named storage controller. -func (m *Machine) AttachStorage(ctlName string, medium driver.StorageMedium) error { - fmt.Println("Attach storage") - return nil -} diff --git a/vbm.go b/vbm.go new file mode 100644 index 0000000..e1d9b72 --- /dev/null +++ b/vbm.go @@ -0,0 +1,107 @@ +package main + +import ( + "bytes" + "io" + "os" + "path/filepath" + + vbx "github.com/boot2docker/boot2docker-cli/virtualbox" +) + +// TODO: delete the hostonlyif and dhcpserver when we delete the vm! (need to +// reference count to make sure there are no other vms relying on them) + +// Get or create the hostonly network interface +func getHostOnlyNetworkInterface() (string, error) { + // Check if the interface/dhcp exists. + nets, err := vbx.HostonlyNets() + if err != nil { + return "", err + } + + dhcps, err := vbx.DHCPs() + if err != nil { + return "", err + } + + for _, n := range nets { + if dhcp, ok := dhcps[n.NetworkName]; ok { + if dhcp.IPv4.IP.Equal(B2D.DHCPIP) && + dhcp.IPv4.Mask.String() == B2D.NetMask.String() && + dhcp.LowerIP.Equal(B2D.LowerIP) && + dhcp.UpperIP.Equal(B2D.UpperIP) && + dhcp.Enabled == B2D.DHCPEnabled { + if B2D.Verbose { + logf("Reusing existing host-only network interface %q", n.Name) + } + return n.Name, nil + } + } + } + + // No existing host-only interface found. Create a new one. + if B2D.Verbose { + logf("Creating a new host-only network interface...") + } + hostonlyNet, err := vbx.CreateHostonlyNet() + if err != nil { + return "", err + } + hostonlyNet.IPv4.IP = B2D.HostIP + hostonlyNet.IPv4.Mask = B2D.NetMask + if err := hostonlyNet.Config(); err != nil { + return "", err + } + + // Create and add a DHCP server to the host-only network + dhcp := vbx.DHCP{} + dhcp.IPv4.IP = B2D.DHCPIP + dhcp.IPv4.Mask = B2D.NetMask + dhcp.LowerIP = B2D.LowerIP + dhcp.UpperIP = B2D.UpperIP + dhcp.Enabled = true + if err := vbx.AddHostonlyDHCP(hostonlyNet.Name, dhcp); err != nil { + return "", err + } + return hostonlyNet.Name, nil +} + +// Copy disk image from given source path to destination +func copyDiskImage(dst, src string) (err error) { + // Open source disk image + srcImg, err := os.Open(src) + if err != nil { + return err + } + closeSrcImg := func() { + if ee := srcImg.Close(); ee != nil { + err = ee + } + } + defer closeSrcImg() + dstImg, err := os.Create(dst) + if err != nil { + return err + } + closeDstImg := func() { + if ee := dstImg.Close(); ee != nil { + err = ee + } + } + defer closeDstImg() + _, err = io.Copy(dstImg, srcImg) + return err +} + +// Make a boot2docker VM disk image with the given size (in MB). +func makeDiskImage(dest string, size uint, initialBytes []byte) error { + // Create the dest dir. + if err := os.MkdirAll(filepath.Dir(dest), 0755); err != nil { + return err + } + // Fill in the magic string so boot2docker VM will detect this and format + // the disk upon first boot. + raw := bytes.NewReader(initialBytes) + return vbx.MakeDiskImage(dest, size, raw) +} diff --git a/virtualbox/dhcp.go b/virtualbox/dhcp.go index d686e8e..d18d7a4 100644 --- a/virtualbox/dhcp.go +++ b/virtualbox/dhcp.go @@ -4,11 +4,18 @@ import ( "bufio" "net" "strings" - - "github.com/boot2docker/boot2docker-cli/driver" ) -func addDHCP(kind, name string, d driver.DHCP) error { +// DHCP server info. +type DHCP struct { + NetworkName string + IPv4 net.IPNet + LowerIP net.IP + UpperIP net.IP + Enabled bool +} + +func addDHCP(kind, name string, d DHCP) error { command := "modify" // On some platforms (OSX), creating a hostonlyinterface adds a default dhcpserver @@ -38,29 +45,29 @@ func addDHCP(kind, name string, d driver.DHCP) error { } // AddInternalDHCP adds a DHCP server to an internal network. -func AddInternalDHCP(netname string, d driver.DHCP) error { +func AddInternalDHCP(netname string, d DHCP) error { return addDHCP("--netname", netname, d) } // AddHostonlyDHCP adds a DHCP server to a host-only network. -func AddHostonlyDHCP(ifname string, d driver.DHCP) error { +func AddHostonlyDHCP(ifname string, d DHCP) error { return addDHCP("--netname", "HostInterfaceNetworking-"+ifname, d) } // DHCPs gets all DHCP server settings in a map keyed by DHCP.NetworkName. -func DHCPs() (map[string]*driver.DHCP, error) { +func DHCPs() (map[string]*DHCP, error) { out, err := vbmOut("list", "dhcpservers") if err != nil { return nil, err } s := bufio.NewScanner(strings.NewReader(out)) - m := map[string]*driver.DHCP{} - dhcp := &driver.DHCP{} + m := map[string]*DHCP{} + dhcp := &DHCP{} for s.Scan() { line := s.Text() if line == "" { m[dhcp.NetworkName] = dhcp - dhcp = &driver.DHCP{} + dhcp = &DHCP{} continue } res := reColonLine.FindStringSubmatch(line) diff --git a/virtualbox/machine.go b/virtualbox/machine.go index cade564..70b1e86 100644 --- a/virtualbox/machine.go +++ b/virtualbox/machine.go @@ -1,20 +1,22 @@ package virtualbox import ( - "archive/tar" "bufio" - "bytes" "fmt" - "io/ioutil" - "net" - "os" "path/filepath" - "runtime" "strconv" "strings" "time" +) + +type MachineState string - "github.com/boot2docker/boot2docker-cli/driver" +const ( + Poweroff = MachineState("poweroff") + Running = MachineState("running") + Paused = MachineState("paused") + Saved = MachineState("saved") + Aborted = MachineState("aborted") ) type Flag int @@ -38,19 +40,6 @@ const ( F_accelerate3d ) -func init() { - driver.Register("virtualbox", InitFunc) -} - -// Initialize the Machine. -func InitFunc(mc *driver.MachineConfig) (driver.Machine, error) { - m, err := GetMachine(mc.VM) - if err != nil && mc.Init == true { - return CreateMachine(mc) - } - return m, err -} - // Convert bool to "on"/"off" func bool2string(b bool) string { if b { @@ -68,7 +57,7 @@ func (f Flag) Get(o Flag) string { type Machine struct { Name string UUID string - State driver.MachineState + State MachineState CPUs uint Memory uint // main memory (in MB) VRAM uint // video memory (in MB) @@ -99,13 +88,13 @@ func (m *Machine) Refresh() error { // Start starts the machine. func (m *Machine) Start() error { switch m.State { - case driver.Paused: + case Paused: return vbm("controlvm", m.Name, "resume") - case driver.Poweroff, driver.Saved, driver.Aborted: + case Poweroff, Saved, Aborted: return vbm("startvm", m.Name, "--type", "headless") } if err := m.Refresh(); err == nil { - if m.State != driver.Running { + if m.State != Running { return fmt.Errorf("Failed to start", m.Name) } } @@ -115,11 +104,11 @@ func (m *Machine) Start() error { // Suspend suspends the machine and saves its state to disk. func (m *Machine) Save() error { switch m.State { - case driver.Paused: + case Paused: if err := m.Start(); err != nil { return err } - case driver.Poweroff, driver.Aborted, driver.Saved: + case Poweroff, Aborted, Saved: return nil } return vbm("controlvm", m.Name, "savestate") @@ -128,7 +117,7 @@ func (m *Machine) Save() error { // Pause pauses the execution of the machine. func (m *Machine) Pause() error { switch m.State { - case driver.Paused, driver.Poweroff, driver.Aborted, driver.Saved: + case Paused, Poweroff, Aborted, Saved: return nil } return vbm("controlvm", m.Name, "pause") @@ -137,15 +126,15 @@ func (m *Machine) Pause() error { // Stop gracefully stops the machine. func (m *Machine) Stop() error { switch m.State { - case driver.Poweroff, driver.Aborted, driver.Saved: + case Poweroff, Aborted, Saved: return nil - case driver.Paused: + case Paused: if err := m.Start(); err != nil { return err } } - for m.State != driver.Poweroff { // busy wait until the machine is stopped + for m.State != Poweroff { // busy wait until the machine is stopped if err := vbm("controlvm", m.Name, "acpipowerbutton"); err != nil { return err } @@ -160,7 +149,7 @@ func (m *Machine) Stop() error { // Poweroff forcefully stops the machine. State is lost and might corrupt the disk image. func (m *Machine) Poweroff() error { switch m.State { - case driver.Poweroff, driver.Aborted, driver.Saved: + case Poweroff, Aborted, Saved: return nil } return vbm("controlvm", m.Name, "poweroff") @@ -169,7 +158,7 @@ func (m *Machine) Poweroff() error { // Restart gracefully restarts the machine. func (m *Machine) Restart() error { switch m.State { - case driver.Paused, driver.Saved: + case Paused, Saved: if err := m.Start(); err != nil { return err } @@ -183,7 +172,7 @@ func (m *Machine) Restart() error { // Reset forcefully restarts the machine. State is lost and might corrupt the disk image. func (m *Machine) Reset() error { switch m.State { - case driver.Paused, driver.Saved: + case Paused, Saved: if err := m.Start(); err != nil { return err } @@ -199,26 +188,6 @@ func (m *Machine) Delete() error { return vbm("unregistervm", m.Name, "--delete") } -// Get current state -func (m *Machine) GetState() driver.MachineState { - return m.State -} - -// Get serial file -func (m *Machine) GetSerialFile() string { - return m.SerialFile -} - -// Get Docker port -func (m *Machine) GetDockerPort() uint { - return m.DockerPort -} - -// Get SSH port -func (m *Machine) GetSSHPort() uint { - return m.SSHPort -} - // GetMachine finds a machine by its name or UUID. func GetMachine(id string) (*Machine, error) { stdout, stderr, err := vbmOutErr("showvminfo", id, "--machinereadable") @@ -250,7 +219,7 @@ func GetMachine(id string) (*Machine, error) { case "UUID": m.UUID = val case "VMState": - m.State = driver.MachineState(val) + m.State = MachineState(val) case "memory": n, err := strconv.ParseUint(val, 10, 32) if err != nil { @@ -325,8 +294,8 @@ func ListMachines() ([]string, error) { } // CreateMachine creates a new machine. If basefolder is empty, use default. -func CreateMachine(mc *driver.MachineConfig) (*Machine, error) { - if mc.VM == "" { +func CreateMachine(name, basefolder string) (*Machine, error) { + if name == "" { return nil, fmt.Errorf("machine name is empty") } @@ -336,140 +305,25 @@ func CreateMachine(mc *driver.MachineConfig) (*Machine, error) { return nil, err } for _, m := range machineNames { - if m == mc.VM { + if m == name { return nil, ErrMachineExist } } // Create and register the machine. - args := []string{"createvm", "--name", mc.VM, "--register"} + args := []string{"createvm", "--name", name, "--register"} + if basefolder != "" { + args = append(args, "--basefolder", basefolder) + } if err := vbm(args...); err != nil { return nil, err } - m, err := GetMachine(mc.VM) + m, err := GetMachine(name) if err != nil { return nil, err } - // Configure VM for Boot2docker - SetExtra(mc.VM, "VBoxInternal/CPUM/EnableHVP", "1") - m.OSType = "Linux26_64" - m.CPUs = uint(runtime.NumCPU()) - m.Memory = mc.Memory - m.SerialFile = mc.SerialFile - - m.Flag |= F_pae - m.Flag |= F_longmode // important: use x86-64 processor - m.Flag |= F_rtcuseutc - m.Flag |= F_acpi - m.Flag |= F_ioapic - m.Flag |= F_hpet - m.Flag |= F_hwvirtex - m.Flag |= F_vtxvpid - m.Flag |= F_largepages - m.Flag |= F_nestedpaging - - // Set VM boot order - m.BootOrder = []string{"dvd"} - if err := m.Modify(); err != nil { - return m, err - } - - // Set NIC #1 to use NAT - m.SetNIC(1, driver.NIC{Network: driver.NICNetNAT, Hardware: driver.VirtIO}) - pfRules := map[string]driver.PFRule{ - "ssh": {Proto: driver.PFTCP, HostIP: net.ParseIP("127.0.0.1"), HostPort: mc.SSHPort, GuestPort: driver.SSHPort}, - "docker": {Proto: driver.PFTCP, HostIP: net.ParseIP("127.0.0.1"), HostPort: mc.DockerPort, GuestPort: driver.DockerPort}, - } - - for name, rule := range pfRules { - if err := m.AddNATPF(1, name, rule); err != nil { - return m, err - } - } - - hostIFName, err := getHostOnlyNetworkInterface(mc) - if err != nil { - return m, err - } - - // Set NIC #2 to use host-only - if err := m.SetNIC(2, driver.NIC{Network: driver.NICNetHostonly, Hardware: driver.VirtIO, HostonlyAdapter: hostIFName}); err != nil { - return m, err - } - - // Set VM storage - if err := m.AddStorageCtl("SATA", driver.StorageController{SysBus: driver.SysBusSATA, HostIOCache: true, Bootable: true}); err != nil { - return m, err - } - - // Attach ISO image - if err := m.AttachStorage("SATA", driver.StorageMedium{Port: 0, Device: 0, DriveType: driver.DriveDVD, Medium: mc.ISO}); err != nil { - return m, err - } - - diskImg := filepath.Join(m.BaseFolder, fmt.Sprintf("%s.vmdk", mc.VM)) - if _, err := os.Stat(diskImg); err != nil { - if !os.IsNotExist(err) { - return m, err - } - - if mc.VMDK != "" { - if err := copyDiskImage(diskImg, mc.VMDK); err != nil { - return m, err - } - } else { - magicString := "boot2docker, please format-me" - - buf := new(bytes.Buffer) - tw := tar.NewWriter(buf) - - // magicString first so the automount script knows to format the disk - file := &tar.Header{Name: magicString, Size: int64(len(magicString))} - if err := tw.WriteHeader(file); err != nil { - return m, err - } - if _, err := tw.Write([]byte(magicString)); err != nil { - return m, err - } - // .ssh/key.pub => authorized_keys - file = &tar.Header{Name: ".ssh", Typeflag: tar.TypeDir, Mode: 0700} - if err := tw.WriteHeader(file); err != nil { - return m, err - } - pubKey, err := ioutil.ReadFile(mc.SSHKey + ".pub") - if err != nil { - return m, err - } - file = &tar.Header{Name: ".ssh/authorized_keys", Size: int64(len(pubKey)), Mode: 0644} - if err := tw.WriteHeader(file); err != nil { - return m, err - } - if _, err := tw.Write([]byte(pubKey)); err != nil { - return m, err - } - file = &tar.Header{Name: ".ssh/authorized_keys2", Size: int64(len(pubKey)), Mode: 0644} - if err := tw.WriteHeader(file); err != nil { - return m, err - } - if _, err := tw.Write([]byte(pubKey)); err != nil { - return m, err - } - if err := tw.Close(); err != nil { - return m, err - } - - if err := makeDiskImage(diskImg, mc.DiskSize, buf.Bytes()); err != nil { - return m, err - } - } - } - - if err := m.AttachStorage("SATA", driver.StorageMedium{Port: 1, Device: 0, DriveType: driver.DriveHDD, Medium: diskImg}); err != nil { - return m, err - } - return m, nil } @@ -525,7 +379,7 @@ func (m *Machine) Modify() error { } // AddNATPF adds a NAT port forarding rule to the n-th NIC with the given name. -func (m *Machine) AddNATPF(n int, name string, rule driver.PFRule) error { +func (m *Machine) AddNATPF(n int, name string, rule PFRule) error { return vbm("controlvm", m.Name, fmt.Sprintf("natpf%d", n), fmt.Sprintf("%s,%s", name, rule.Format())) } @@ -536,7 +390,7 @@ func (m *Machine) DelNATPF(n int, name string) error { } // SetNIC set the n-th NIC. -func (m *Machine) SetNIC(n int, nic driver.NIC) error { +func (m *Machine) SetNIC(n int, nic NIC) error { args := []string{"modifyvm", m.Name, fmt.Sprintf("--nic%d", n), string(nic.Network), fmt.Sprintf("--nictype%d", n), string(nic.Hardware), @@ -550,7 +404,7 @@ func (m *Machine) SetNIC(n int, nic driver.NIC) error { } // AddStorageCtl adds a storage controller with the given name. -func (m *Machine) AddStorageCtl(name string, ctl driver.StorageController) error { +func (m *Machine) AddStorageCtl(name string, ctl StorageController) error { args := []string{"storagectl", m.Name, "--name", name} if ctl.SysBus != "" { args = append(args, "--add", string(ctl.SysBus)) @@ -572,7 +426,7 @@ func (m *Machine) DelStorageCtl(name string) error { } // AttachStorage attaches a storage medium to the named storage controller. -func (m *Machine) AttachStorage(ctlName string, medium driver.StorageMedium) error { +func (m *Machine) AttachStorage(ctlName string, medium StorageMedium) error { return vbm("storageattach", m.Name, "--storagectl", ctlName, "--port", fmt.Sprintf("%d", medium.Port), "--device", fmt.Sprintf("%d", medium.Device), diff --git a/driver/nic.go b/virtualbox/nic.go similarity index 97% rename from driver/nic.go rename to virtualbox/nic.go index d6c7364..0569be5 100644 --- a/driver/nic.go +++ b/virtualbox/nic.go @@ -1,4 +1,4 @@ -package driver +package virtualbox // NIC represents a virtualized network interface card. type NIC struct { diff --git a/driver/pfrule.go b/virtualbox/pfrule.go similarity index 98% rename from driver/pfrule.go rename to virtualbox/pfrule.go index 18b8160..89b9de7 100644 --- a/driver/pfrule.go +++ b/virtualbox/pfrule.go @@ -1,4 +1,4 @@ -package driver +package virtualbox import ( "fmt" diff --git a/driver/storage.go b/virtualbox/storage.go similarity index 98% rename from driver/storage.go rename to virtualbox/storage.go index a229daf..ae129f4 100644 --- a/driver/storage.go +++ b/virtualbox/storage.go @@ -1,4 +1,4 @@ -package driver +package virtualbox // StorageController represents a virtualized storage controller. type StorageController struct { diff --git a/virtualbox/vbm.go b/virtualbox/vbm.go index e2d34df..dd0df6f 100644 --- a/virtualbox/vbm.go +++ b/virtualbox/vbm.go @@ -3,7 +3,6 @@ package virtualbox import ( "bytes" "errors" - "io" "log" "os" "os/exec" @@ -11,8 +10,6 @@ import ( "regexp" "runtime" "strings" - - "github.com/boot2docker/boot2docker-cli/driver" ) var ( @@ -89,89 +86,3 @@ func vbmOutErr(args ...string) (string, string, error) { } return stdout.String(), stderr.String(), err } - -// Get or create the hostonly network interface -func getHostOnlyNetworkInterface(mc *driver.MachineConfig) (string, error) { - // Check if the interface/dhcp exists. - nets, err := HostonlyNets() - if err != nil { - return "", err - } - - dhcps, err := DHCPs() - if err != nil { - return "", err - } - - for _, n := range nets { - if dhcp, ok := dhcps[n.NetworkName]; ok { - if dhcp.IPv4.IP.Equal(mc.DHCPIP) && - dhcp.IPv4.Mask.String() == mc.NetMask.String() && - dhcp.LowerIP.Equal(mc.LowerIP) && - dhcp.UpperIP.Equal(mc.UpperIP) && - dhcp.Enabled == mc.DHCPEnabled { - return n.Name, nil - } - } - } - - // No existing host-only interface found. Create a new one. - hostonlyNet, err := CreateHostonlyNet() - if err != nil { - return "", err - } - hostonlyNet.IPv4.IP = mc.HostIP - hostonlyNet.IPv4.Mask = mc.NetMask - if err := hostonlyNet.Config(); err != nil { - return "", err - } - - // Create and add a DHCP server to the host-only network - dhcp := driver.DHCP{} - dhcp.IPv4.IP = mc.DHCPIP - dhcp.IPv4.Mask = mc.NetMask - dhcp.LowerIP = mc.LowerIP - dhcp.UpperIP = mc.UpperIP - dhcp.Enabled = true - if err := AddHostonlyDHCP(hostonlyNet.Name, dhcp); err != nil { - return "", err - } - return hostonlyNet.Name, nil -} - -// Copy disk image from given source path to destination -func copyDiskImage(dst, src string) (err error) { - // Open source disk image - srcImg, err := os.Open(src) - if err != nil { - return err - } - defer func() { - if ee := srcImg.Close(); ee != nil { - err = ee - } - }() - dstImg, err := os.Create(dst) - if err != nil { - return err - } - defer func() { - if ee := dstImg.Close(); ee != nil { - err = ee - } - }() - _, err = io.Copy(dstImg, srcImg) - return err -} - -// Make a boot2docker VM disk image with the given size (in MB). -func makeDiskImage(dest string, size uint, initialBytes []byte) error { - // Create the dest dir. - if err := os.MkdirAll(filepath.Dir(dest), 0755); err != nil { - return err - } - // Fill in the magic string so boot2docker VM will detect this and format - // the disk upon first boot. - raw := bytes.NewReader(initialBytes) - return MakeDiskImage(dest, size, raw) -}