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