github.com/tonnydourado/packer@v0.6.1-0.20140701134019-5d0cd9676a37/builder/vmware/iso/driver_esx5.go (about)

     1  package iso
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	gossh "code.google.com/p/go.crypto/ssh"
     7  	"encoding/csv"
     8  	"errors"
     9  	"fmt"
    10  	"github.com/mitchellh/multistep"
    11  	"github.com/mitchellh/packer/communicator/ssh"
    12  	"github.com/mitchellh/packer/packer"
    13  	"io"
    14  	"log"
    15  	"net"
    16  	"os"
    17  	"path/filepath"
    18  	"strings"
    19  	"syscall"
    20  	"time"
    21  )
    22  
    23  // ESX5 driver talks to an ESXi5 hypervisor remotely over SSH to build
    24  // virtual machines. This driver can only manage one machine at a time.
    25  type ESX5Driver struct {
    26  	Host      string
    27  	Port      uint
    28  	Username  string
    29  	Password  string
    30  	Datastore string
    31  
    32  	comm      packer.Communicator
    33  	outputDir string
    34  	vmId      string
    35  }
    36  
    37  func (d *ESX5Driver) Clone(dst, src string) error {
    38  	return errors.New("Cloning is not supported with the ESX driver.")
    39  }
    40  
    41  func (d *ESX5Driver) CompactDisk(diskPathLocal string) error {
    42  	return nil
    43  }
    44  
    45  func (d *ESX5Driver) CreateDisk(diskPathLocal string, size string, typeId string) error {
    46  	diskPath := d.datastorePath(diskPathLocal)
    47  	return d.sh("vmkfstools", "-c", size, "-d", typeId, "-a", "lsilogic", diskPath)
    48  }
    49  
    50  func (d *ESX5Driver) IsRunning(string) (bool, error) {
    51  	state, err := d.run(nil, "vim-cmd", "vmsvc/power.getstate", d.vmId)
    52  	if err != nil {
    53  		return false, err
    54  	}
    55  	return strings.Contains(state, "Powered on"), nil
    56  }
    57  
    58  func (d *ESX5Driver) Start(vmxPathLocal string, headless bool) error {
    59  	return d.sh("vim-cmd", "vmsvc/power.on", d.vmId)
    60  }
    61  
    62  func (d *ESX5Driver) Stop(vmxPathLocal string) error {
    63  	return d.sh("vim-cmd", "vmsvc/power.off", d.vmId)
    64  }
    65  
    66  func (d *ESX5Driver) Register(vmxPathLocal string) error {
    67  	vmxPath := filepath.Join(d.outputDir, filepath.Base(vmxPathLocal))
    68  	if err := d.upload(vmxPath, vmxPathLocal); err != nil {
    69  		return err
    70  	}
    71  	r, err := d.run(nil, "vim-cmd", "solo/registervm", vmxPath)
    72  	if err != nil {
    73  		return err
    74  	}
    75  	d.vmId = strings.TrimRight(r, "\n")
    76  	return nil
    77  }
    78  
    79  func (d *ESX5Driver) SuppressMessages(vmxPath string) error {
    80  	return nil
    81  }
    82  
    83  func (d *ESX5Driver) Unregister(vmxPathLocal string) error {
    84  	return d.sh("vim-cmd", "vmsvc/unregister", d.vmId)
    85  }
    86  
    87  func (d *ESX5Driver) UploadISO(localPath string) (string, error) {
    88  	cacheRoot, _ := filepath.Abs(".")
    89  	targetFile, err := filepath.Rel(cacheRoot, localPath)
    90  	if err != nil {
    91  		return "", err
    92  	}
    93  
    94  	finalPath := d.datastorePath(targetFile)
    95  	if err := d.mkdir(filepath.Dir(finalPath)); err != nil {
    96  		return "", err
    97  	}
    98  
    99  	if err := d.upload(finalPath, localPath); err != nil {
   100  		return "", err
   101  	}
   102  
   103  	return finalPath, nil
   104  }
   105  
   106  func (d *ESX5Driver) ToolsIsoPath(string) string {
   107  	return ""
   108  }
   109  
   110  func (d *ESX5Driver) ToolsInstall() error {
   111  	return d.sh("vim-cmd", "vmsvc/tools.install", d.vmId)
   112  }
   113  
   114  func (d *ESX5Driver) DhcpLeasesPath(string) string {
   115  	return ""
   116  }
   117  
   118  func (d *ESX5Driver) Verify() error {
   119  	checks := []func() error{
   120  		d.connect,
   121  		d.checkSystemVersion,
   122  		d.checkGuestIPHackEnabled,
   123  	}
   124  
   125  	for _, check := range checks {
   126  		if err := check(); err != nil {
   127  			return err
   128  		}
   129  	}
   130  
   131  	return nil
   132  }
   133  
   134  func (d *ESX5Driver) HostIP() (string, error) {
   135  	conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", d.Host, d.Port))
   136  	defer conn.Close()
   137  	if err != nil {
   138  		return "", err
   139  	}
   140  
   141  	host, _, err := net.SplitHostPort(conn.LocalAddr().String())
   142  	return host, err
   143  }
   144  
   145  func (d *ESX5Driver) VNCAddress(portMin, portMax uint) (string, uint) {
   146  	var vncPort uint
   147  	// TODO(dougm) use esxcli network ip connection list
   148  	for port := portMin; port <= portMax; port++ {
   149  		address := fmt.Sprintf("%s:%d", d.Host, port)
   150  		log.Printf("Trying address: %s...", address)
   151  		l, err := net.DialTimeout("tcp", address, 1*time.Second)
   152  
   153  		if err == nil {
   154  			log.Printf("%s in use", address)
   155  			l.Close()
   156  		} else if e, ok := err.(*net.OpError); ok {
   157  			if e.Err == syscall.ECONNREFUSED {
   158  				// then port should be available for listening
   159  				vncPort = port
   160  				break
   161  			} else if e.Timeout() {
   162  				log.Printf("Timeout connecting to: %s (check firewall rules)", address)
   163  			}
   164  		}
   165  	}
   166  
   167  	return d.Host, vncPort
   168  }
   169  
   170  func (d *ESX5Driver) SSHAddress(state multistep.StateBag) (string, error) {
   171  	config := state.Get("config").(*config)
   172  
   173  	if address, ok := state.GetOk("vm_address"); ok {
   174  		return address.(string), nil
   175  	}
   176  
   177  	r, err := d.esxcli("network", "vm", "list")
   178  	if err != nil {
   179  		return "", err
   180  	}
   181  
   182  	record, err := r.find("Name", config.VMName)
   183  	if err != nil {
   184  		return "", err
   185  	}
   186  	wid := record["WorldID"]
   187  	if wid == "" {
   188  		return "", errors.New("VM WorldID not found")
   189  	}
   190  
   191  	r, err = d.esxcli("network", "vm", "port", "list", "-w", wid)
   192  	if err != nil {
   193  		return "", err
   194  	}
   195  
   196  	record, err = r.read()
   197  	if err != nil {
   198  		return "", err
   199  	}
   200  
   201  	if record["IPAddress"] == "0.0.0.0" {
   202  		return "", errors.New("VM network port found, but no IP address")
   203  	}
   204  
   205  	address := fmt.Sprintf("%s:%d", record["IPAddress"], config.SSHPort)
   206  	state.Put("vm_address", address)
   207  	return address, nil
   208  }
   209  
   210  //-------------------------------------------------------------------
   211  // OutputDir implementation
   212  //-------------------------------------------------------------------
   213  
   214  func (d *ESX5Driver) DirExists() (bool, error) {
   215  	err := d.sh("test", "-e", d.outputDir)
   216  	return err == nil, nil
   217  }
   218  
   219  func (d *ESX5Driver) ListFiles() ([]string, error) {
   220  	stdout, err := d.ssh("ls -1p "+d.outputDir, nil)
   221  	if err != nil {
   222  		return nil, err
   223  	}
   224  
   225  	files := make([]string, 0, 10)
   226  	reader := bufio.NewReader(stdout)
   227  	for {
   228  		line, _, err := reader.ReadLine()
   229  		if err == io.EOF {
   230  			break
   231  		}
   232  		if line[len(line)-1] == '/' {
   233  			continue
   234  		}
   235  
   236  		files = append(files, filepath.Join(d.outputDir, string(line)))
   237  	}
   238  
   239  	return files, nil
   240  }
   241  
   242  func (d *ESX5Driver) MkdirAll() error {
   243  	return d.mkdir(d.outputDir)
   244  }
   245  
   246  func (d *ESX5Driver) Remove(path string) error {
   247  	return d.sh("rm", path)
   248  }
   249  
   250  func (d *ESX5Driver) RemoveAll() error {
   251  	return d.sh("rm", "-rf", d.outputDir)
   252  }
   253  
   254  func (d *ESX5Driver) SetOutputDir(path string) {
   255  	d.outputDir = d.datastorePath(path)
   256  }
   257  
   258  func (d *ESX5Driver) String() string {
   259  	return d.outputDir
   260  }
   261  
   262  func (d *ESX5Driver) datastorePath(path string) string {
   263  	baseDir := filepath.Base(filepath.Dir(path))
   264  	return filepath.Join("/vmfs/volumes", d.Datastore, baseDir, filepath.Base(path))
   265  }
   266  
   267  func (d *ESX5Driver) connect() error {
   268  	address := fmt.Sprintf("%s:%d", d.Host, d.Port)
   269  
   270  	auth := []gossh.AuthMethod{
   271  		gossh.Password(d.Password),
   272  		gossh.KeyboardInteractive(
   273  			ssh.PasswordKeyboardInteractive(d.Password)),
   274  	}
   275  
   276  	// TODO(dougm) KeyPath support
   277  	sshConfig := &ssh.Config{
   278  		Connection: ssh.ConnectFunc("tcp", address),
   279  		SSHConfig: &gossh.ClientConfig{
   280  			User: d.Username,
   281  			Auth: auth,
   282  		},
   283  		NoPty: true,
   284  	}
   285  
   286  	comm, err := ssh.New(address, sshConfig)
   287  	if err != nil {
   288  		return err
   289  	}
   290  
   291  	d.comm = comm
   292  	return nil
   293  }
   294  
   295  func (d *ESX5Driver) checkSystemVersion() error {
   296  	r, err := d.esxcli("system", "version", "get")
   297  	if err != nil {
   298  		return err
   299  	}
   300  
   301  	record, err := r.read()
   302  	if err != nil {
   303  		return err
   304  	}
   305  
   306  	log.Printf("Connected to %s %s %s", record["Product"],
   307  		record["Version"], record["Build"])
   308  	return nil
   309  }
   310  
   311  func (d *ESX5Driver) checkGuestIPHackEnabled() error {
   312  	r, err := d.esxcli("system", "settings", "advanced", "list", "-o", "/Net/GuestIPHack")
   313  	if err != nil {
   314  		return err
   315  	}
   316  
   317  	record, err := r.read()
   318  	if err != nil {
   319  		return err
   320  	}
   321  
   322  	if record["IntValue"] != "1" {
   323  		return errors.New(
   324  			"GuestIPHack is required, enable by running this on the ESX machine:\n" +
   325  				"esxcli system settings advanced set -o /Net/GuestIPHack -i 1")
   326  	}
   327  
   328  	return nil
   329  }
   330  
   331  func (d *ESX5Driver) mkdir(path string) error {
   332  	return d.sh("mkdir", "-p", path)
   333  }
   334  
   335  func (d *ESX5Driver) upload(dst, src string) error {
   336  	f, err := os.Open(src)
   337  	if err != nil {
   338  		return err
   339  	}
   340  	defer f.Close()
   341  	return d.comm.Upload(dst, f)
   342  }
   343  
   344  func (d *ESX5Driver) verifyChecksum(ctype string, hash string, file string) bool {
   345  	stdin := bytes.NewBufferString(fmt.Sprintf("%s  %s", hash, file))
   346  	_, err := d.run(stdin, fmt.Sprintf("%ssum", ctype), "-c")
   347  	if err != nil {
   348  		return false
   349  	}
   350  	return true
   351  }
   352  
   353  func (d *ESX5Driver) ssh(command string, stdin io.Reader) (*bytes.Buffer, error) {
   354  	var stdout, stderr bytes.Buffer
   355  
   356  	cmd := &packer.RemoteCmd{
   357  		Command: command,
   358  		Stdout:  &stdout,
   359  		Stderr:  &stderr,
   360  		Stdin:   stdin,
   361  	}
   362  
   363  	err := d.comm.Start(cmd)
   364  	if err != nil {
   365  		return nil, err
   366  	}
   367  
   368  	cmd.Wait()
   369  
   370  	if cmd.ExitStatus != 0 {
   371  		err = fmt.Errorf("'%s'\n\nStdout: %s\n\nStderr: %s",
   372  			cmd.Command, stdout.String(), stderr.String())
   373  		return nil, err
   374  	}
   375  
   376  	return &stdout, nil
   377  }
   378  
   379  func (d *ESX5Driver) run(stdin io.Reader, args ...string) (string, error) {
   380  	stdout, err := d.ssh(strings.Join(args, " "), stdin)
   381  	if err != nil {
   382  		return "", err
   383  	}
   384  	return stdout.String(), nil
   385  }
   386  
   387  func (d *ESX5Driver) sh(args ...string) error {
   388  	_, err := d.run(nil, args...)
   389  	return err
   390  }
   391  
   392  func (d *ESX5Driver) esxcli(args ...string) (*esxcliReader, error) {
   393  	stdout, err := d.ssh("esxcli --formatter csv "+strings.Join(args, " "), nil)
   394  	if err != nil {
   395  		return nil, err
   396  	}
   397  	r := csv.NewReader(bytes.NewReader(stdout.Bytes()))
   398  	r.TrailingComma = true
   399  	header, err := r.Read()
   400  	if err != nil {
   401  		return nil, err
   402  	}
   403  	return &esxcliReader{r, header}, nil
   404  }
   405  
   406  type esxcliReader struct {
   407  	cr     *csv.Reader
   408  	header []string
   409  }
   410  
   411  func (r *esxcliReader) read() (map[string]string, error) {
   412  	fields, err := r.cr.Read()
   413  
   414  	if err != nil {
   415  		return nil, err
   416  	}
   417  
   418  	record := map[string]string{}
   419  	for i, v := range fields {
   420  		record[r.header[i]] = v
   421  	}
   422  
   423  	return record, nil
   424  }
   425  
   426  func (r *esxcliReader) find(key, val string) (map[string]string, error) {
   427  	for {
   428  		record, err := r.read()
   429  		if err != nil {
   430  			return nil, err
   431  		}
   432  		if record[key] == val {
   433  			return record, nil
   434  		}
   435  	}
   436  }