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