github.com/kimor79/packer@v0.8.7-0.20151221212622-d507b18eb4cf/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) Destroy() error { 108 return d.sh("vim-cmd", "vmsvc/destroy", d.vmId) 109 } 110 111 func (d *ESX5Driver) IsDestroyed() (bool, error) { 112 err := d.sh("test", "!", "-e", d.outputDir) 113 if err != nil { 114 return false, err 115 } 116 return true, err 117 } 118 119 func (d *ESX5Driver) UploadISO(localPath string, checksum string, checksumType string) (string, error) { 120 finalPath := d.cachePath(localPath) 121 if err := d.mkdir(filepath.ToSlash(filepath.Dir(finalPath))); err != nil { 122 return "", err 123 } 124 125 log.Printf("Verifying checksum of %s", finalPath) 126 if d.verifyChecksum(checksumType, checksum, finalPath) { 127 log.Println("Initial checksum matched, no upload needed.") 128 return finalPath, nil 129 } 130 131 if err := d.upload(finalPath, localPath); err != nil { 132 return "", err 133 } 134 135 return finalPath, nil 136 } 137 138 func (d *ESX5Driver) ToolsIsoPath(string) string { 139 return "" 140 } 141 142 func (d *ESX5Driver) ToolsInstall() error { 143 return d.sh("vim-cmd", "vmsvc/tools.install", d.vmId) 144 } 145 146 func (d *ESX5Driver) DhcpLeasesPath(string) string { 147 return "" 148 } 149 150 func (d *ESX5Driver) Verify() error { 151 checks := []func() error{ 152 d.connect, 153 d.checkSystemVersion, 154 d.checkGuestIPHackEnabled, 155 } 156 157 for _, check := range checks { 158 if err := check(); err != nil { 159 return err 160 } 161 } 162 163 return nil 164 } 165 166 func (d *ESX5Driver) HostIP() (string, error) { 167 conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", d.Host, d.Port)) 168 defer conn.Close() 169 if err != nil { 170 return "", err 171 } 172 173 host, _, err := net.SplitHostPort(conn.LocalAddr().String()) 174 return host, err 175 } 176 177 func (d *ESX5Driver) VNCAddress(portMin, portMax uint) (string, uint, error) { 178 var vncPort uint 179 180 //Process ports ESXi is listening on to determine which are available 181 //This process does best effort to detect ports that are unavailable, 182 //it will ignore any ports listened to by only localhost 183 r, err := d.esxcli("network", "ip", "connection", "list") 184 if err != nil { 185 err = fmt.Errorf("Could not retrieve network information for ESXi: %v", err) 186 return "", 0, err 187 } 188 189 listenPorts := make(map[string]bool) 190 for record, err := r.read(); record != nil && err == nil; record, err = r.read() { 191 if record["State"] == "LISTEN" { 192 splitAddress := strings.Split(record["LocalAddress"], ":") 193 if splitAddress[0] != "127.0.0.1" { 194 port := splitAddress[len(splitAddress)-1] 195 log.Printf("ESXi listening on address %s, port %s unavailable for VNC", record["LocalAddress"], port) 196 listenPorts[port] = true 197 } 198 } 199 } 200 201 for port := portMin; port <= portMax; port++ { 202 if _, ok := listenPorts[fmt.Sprintf("%d", port)]; ok { 203 log.Printf("Port %d in use", port) 204 continue 205 } 206 address := fmt.Sprintf("%s:%d", d.Host, port) 207 log.Printf("Trying address: %s...", address) 208 l, err := net.DialTimeout("tcp", address, 1*time.Second) 209 210 if err != nil { 211 if e, ok := err.(*net.OpError); ok { 212 if e.Timeout() { 213 log.Printf("Timeout connecting to: %s (check firewall rules)", address) 214 } else { 215 vncPort = port 216 break 217 } 218 } 219 } else { 220 defer l.Close() 221 } 222 } 223 224 if vncPort == 0 { 225 err := fmt.Errorf("Unable to find available VNC port between %d and %d", 226 portMin, portMax) 227 return d.Host, vncPort, err 228 } 229 230 return d.Host, vncPort, nil 231 } 232 233 func (d *ESX5Driver) CommHost(state multistep.StateBag) (string, error) { 234 config := state.Get("config").(*Config) 235 236 if address, ok := state.GetOk("vm_address"); ok { 237 return address.(string), nil 238 } 239 240 r, err := d.esxcli("network", "vm", "list") 241 if err != nil { 242 return "", err 243 } 244 245 record, err := r.find("Name", config.VMName) 246 if err != nil { 247 return "", err 248 } 249 wid := record["WorldID"] 250 if wid == "" { 251 return "", errors.New("VM WorldID not found") 252 } 253 254 r, err = d.esxcli("network", "vm", "port", "list", "-w", wid) 255 if err != nil { 256 return "", err 257 } 258 259 record, err = r.read() 260 if err != nil { 261 return "", err 262 } 263 264 if record["IPAddress"] == "0.0.0.0" { 265 return "", errors.New("VM network port found, but no IP address") 266 } 267 268 address := record["IPAddress"] 269 state.Put("vm_address", address) 270 return address, nil 271 } 272 273 //------------------------------------------------------------------- 274 // OutputDir implementation 275 //------------------------------------------------------------------- 276 277 func (d *ESX5Driver) DirExists() (bool, error) { 278 err := d.sh("test", "-e", d.outputDir) 279 return err == nil, nil 280 } 281 282 func (d *ESX5Driver) ListFiles() ([]string, error) { 283 stdout, err := d.ssh("ls -1p "+d.outputDir, nil) 284 if err != nil { 285 return nil, err 286 } 287 288 files := make([]string, 0, 10) 289 reader := bufio.NewReader(stdout) 290 for { 291 line, _, err := reader.ReadLine() 292 if err == io.EOF { 293 break 294 } 295 if line[len(line)-1] == '/' { 296 continue 297 } 298 299 files = append(files, filepath.ToSlash(filepath.Join(d.outputDir, string(line)))) 300 } 301 302 return files, nil 303 } 304 305 func (d *ESX5Driver) MkdirAll() error { 306 return d.mkdir(d.outputDir) 307 } 308 309 func (d *ESX5Driver) Remove(path string) error { 310 return d.sh("rm", path) 311 } 312 313 func (d *ESX5Driver) RemoveAll() error { 314 return d.sh("rm", "-rf", d.outputDir) 315 } 316 317 func (d *ESX5Driver) SetOutputDir(path string) { 318 d.outputDir = d.datastorePath(path) 319 } 320 321 func (d *ESX5Driver) String() string { 322 return d.outputDir 323 } 324 325 func (d *ESX5Driver) datastorePath(path string) string { 326 dirPath := filepath.Dir(path) 327 return filepath.ToSlash(filepath.Join("/vmfs/volumes", d.Datastore, dirPath, filepath.Base(path))) 328 } 329 330 func (d *ESX5Driver) cachePath(path string) string { 331 return filepath.ToSlash(filepath.Join("/vmfs/volumes", d.CacheDatastore, d.CacheDirectory, filepath.Base(path))) 332 } 333 334 func (d *ESX5Driver) connect() error { 335 address := fmt.Sprintf("%s:%d", d.Host, d.Port) 336 337 auth := []gossh.AuthMethod{ 338 gossh.Password(d.Password), 339 gossh.KeyboardInteractive( 340 ssh.PasswordKeyboardInteractive(d.Password)), 341 } 342 343 // TODO(dougm) KeyPath support 344 sshConfig := &ssh.Config{ 345 Connection: ssh.ConnectFunc("tcp", address), 346 SSHConfig: &gossh.ClientConfig{ 347 User: d.Username, 348 Auth: auth, 349 }, 350 } 351 352 comm, err := ssh.New(address, sshConfig) 353 if err != nil { 354 return err 355 } 356 357 d.comm = comm 358 return nil 359 } 360 361 func (d *ESX5Driver) checkSystemVersion() error { 362 r, err := d.esxcli("system", "version", "get") 363 if err != nil { 364 return err 365 } 366 367 record, err := r.read() 368 if err != nil { 369 return err 370 } 371 372 log.Printf("Connected to %s %s %s", record["Product"], 373 record["Version"], record["Build"]) 374 return nil 375 } 376 377 func (d *ESX5Driver) checkGuestIPHackEnabled() error { 378 r, err := d.esxcli("system", "settings", "advanced", "list", "-o", "/Net/GuestIPHack") 379 if err != nil { 380 return err 381 } 382 383 record, err := r.read() 384 if err != nil { 385 return err 386 } 387 388 if record["IntValue"] != "1" { 389 return errors.New( 390 "GuestIPHack is required, enable by running this on the ESX machine:\n" + 391 "esxcli system settings advanced set -o /Net/GuestIPHack -i 1") 392 } 393 394 return nil 395 } 396 397 func (d *ESX5Driver) mkdir(path string) error { 398 return d.sh("mkdir", "-p", path) 399 } 400 401 func (d *ESX5Driver) upload(dst, src string) error { 402 f, err := os.Open(src) 403 if err != nil { 404 return err 405 } 406 defer f.Close() 407 return d.comm.Upload(dst, f, nil) 408 } 409 410 func (d *ESX5Driver) verifyChecksum(ctype string, hash string, file string) bool { 411 if ctype == "none" { 412 if err := d.sh("stat", file); err != nil { 413 return false 414 } 415 } else { 416 stdin := bytes.NewBufferString(fmt.Sprintf("%s %s", hash, file)) 417 _, err := d.run(stdin, fmt.Sprintf("%ssum", ctype), "-c") 418 if err != nil { 419 return false 420 } 421 } 422 423 return true 424 } 425 426 func (d *ESX5Driver) ssh(command string, stdin io.Reader) (*bytes.Buffer, error) { 427 var stdout, stderr bytes.Buffer 428 429 cmd := &packer.RemoteCmd{ 430 Command: command, 431 Stdout: &stdout, 432 Stderr: &stderr, 433 Stdin: stdin, 434 } 435 436 err := d.comm.Start(cmd) 437 if err != nil { 438 return nil, err 439 } 440 441 cmd.Wait() 442 443 if cmd.ExitStatus != 0 { 444 err = fmt.Errorf("'%s'\n\nStdout: %s\n\nStderr: %s", 445 cmd.Command, stdout.String(), stderr.String()) 446 return nil, err 447 } 448 449 return &stdout, nil 450 } 451 452 func (d *ESX5Driver) run(stdin io.Reader, args ...string) (string, error) { 453 stdout, err := d.ssh(strings.Join(args, " "), stdin) 454 if err != nil { 455 return "", err 456 } 457 return stdout.String(), nil 458 } 459 460 func (d *ESX5Driver) sh(args ...string) error { 461 _, err := d.run(nil, args...) 462 return err 463 } 464 465 func (d *ESX5Driver) esxcli(args ...string) (*esxcliReader, error) { 466 stdout, err := d.ssh("esxcli --formatter csv "+strings.Join(args, " "), nil) 467 if err != nil { 468 return nil, err 469 } 470 r := csv.NewReader(bytes.NewReader(stdout.Bytes())) 471 r.TrailingComma = true 472 header, err := r.Read() 473 if err != nil { 474 return nil, err 475 } 476 return &esxcliReader{r, header}, nil 477 } 478 479 type esxcliReader struct { 480 cr *csv.Reader 481 header []string 482 } 483 484 func (r *esxcliReader) read() (map[string]string, error) { 485 fields, err := r.cr.Read() 486 487 if err != nil { 488 return nil, err 489 } 490 491 record := map[string]string{} 492 for i, v := range fields { 493 record[r.header[i]] = v 494 } 495 496 return record, nil 497 } 498 499 func (r *esxcliReader) find(key, val string) (map[string]string, error) { 500 for { 501 record, err := r.read() 502 if err != nil { 503 return nil, err 504 } 505 if record[key] == val { 506 return record, nil 507 } 508 } 509 }