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 }