1/*
2 * Copyright 2014 VMware, Inc.  All rights reserved.  Licensed under the Apache v2 License.
3 */
4
5package vmwarevcloudair
6
7import (
8	"fmt"
9	"io/ioutil"
10	"net"
11	"strconv"
12	"strings"
13
14	"github.com/vmware/govcloudair"
15
16	"github.com/docker/machine/libmachine/drivers"
17	"github.com/docker/machine/libmachine/log"
18	"github.com/docker/machine/libmachine/mcnflag"
19	"github.com/docker/machine/libmachine/mcnutils"
20	"github.com/docker/machine/libmachine/ssh"
21	"github.com/docker/machine/libmachine/state"
22)
23
24type Driver struct {
25	*drivers.BaseDriver
26	UserName     string
27	UserPassword string
28	ComputeID    string
29	VDCID        string
30	OrgVDCNet    string
31	EdgeGateway  string
32	PublicIP     string
33	Catalog      string
34	CatalogItem  string
35	DockerPort   int
36	CPUCount     int
37	MemorySize   int
38	VAppID       string
39}
40
41const (
42	defaultCatalog     = "Public Catalog"
43	defaultCatalogItem = "Ubuntu Server 12.04 LTS (amd64 20150127)"
44	defaultCpus        = 1
45	defaultMemory      = 2048
46	defaultSSHPort     = 22
47	defaultDockerPort  = 2376
48)
49
50// GetCreateFlags registers the flags this driver adds to
51// "docker hosts create"
52func (d *Driver) GetCreateFlags() []mcnflag.Flag {
53	return []mcnflag.Flag{
54		mcnflag.StringFlag{
55			EnvVar: "VCLOUDAIR_USERNAME",
56			Name:   "vmwarevcloudair-username",
57			Usage:  "vCloud Air username",
58		},
59		mcnflag.StringFlag{
60			EnvVar: "VCLOUDAIR_PASSWORD",
61			Name:   "vmwarevcloudair-password",
62			Usage:  "vCloud Air password",
63		},
64		mcnflag.StringFlag{
65			EnvVar: "VCLOUDAIR_COMPUTEID",
66			Name:   "vmwarevcloudair-computeid",
67			Usage:  "vCloud Air Compute ID (if using Dedicated Cloud)",
68		},
69		mcnflag.StringFlag{
70			EnvVar: "VCLOUDAIR_VDCID",
71			Name:   "vmwarevcloudair-vdcid",
72			Usage:  "vCloud Air VDC ID",
73		},
74		mcnflag.StringFlag{
75			EnvVar: "VCLOUDAIR_ORGVDCNETWORK",
76			Name:   "vmwarevcloudair-orgvdcnetwork",
77			Usage:  "vCloud Air Org VDC Network (Default is <vdcid>-default-routed)",
78		},
79		mcnflag.StringFlag{
80			EnvVar: "VCLOUDAIR_EDGEGATEWAY",
81			Name:   "vmwarevcloudair-edgegateway",
82			Usage:  "vCloud Air Org Edge Gateway (Default is <vdcid>)",
83		},
84		mcnflag.StringFlag{
85			EnvVar: "VCLOUDAIR_PUBLICIP",
86			Name:   "vmwarevcloudair-publicip",
87			Usage:  "vCloud Air Org Public IP to use",
88		},
89		mcnflag.StringFlag{
90			EnvVar: "VCLOUDAIR_CATALOG",
91			Name:   "vmwarevcloudair-catalog",
92			Usage:  "vCloud Air Catalog (default is Public Catalog)",
93			Value:  defaultCatalog,
94		},
95		mcnflag.StringFlag{
96			EnvVar: "VCLOUDAIR_CATALOGITEM",
97			Name:   "vmwarevcloudair-catalogitem",
98			Usage:  "vCloud Air Catalog Item (default is Ubuntu Precise)",
99			Value:  defaultCatalogItem,
100		},
101		mcnflag.IntFlag{
102			EnvVar: "VCLOUDAIR_CPU_COUNT",
103			Name:   "vmwarevcloudair-cpu-count",
104			Usage:  "vCloud Air VM Cpu Count (default 1)",
105			Value:  defaultCpus,
106		},
107		mcnflag.IntFlag{
108			EnvVar: "VCLOUDAIR_MEMORY_SIZE",
109			Name:   "vmwarevcloudair-memory-size",
110			Usage:  "vCloud Air VM Memory Size in MB (default 2048)",
111			Value:  defaultMemory,
112		},
113		mcnflag.IntFlag{
114			EnvVar: "VCLOUDAIR_SSH_PORT",
115			Name:   "vmwarevcloudair-ssh-port",
116			Usage:  "vCloud Air SSH port",
117			Value:  defaultSSHPort,
118		},
119		mcnflag.IntFlag{
120			EnvVar: "VCLOUDAIR_DOCKER_PORT",
121			Name:   "vmwarevcloudair-docker-port",
122			Usage:  "vCloud Air Docker port",
123			Value:  defaultDockerPort,
124		},
125	}
126}
127
128func NewDriver(hostName, storePath string) drivers.Driver {
129	return &Driver{
130		Catalog:     defaultCatalog,
131		CatalogItem: defaultCatalogItem,
132		CPUCount:    defaultCpus,
133		MemorySize:  defaultMemory,
134		DockerPort:  defaultDockerPort,
135		BaseDriver: &drivers.BaseDriver{
136			SSHPort:     defaultSSHPort,
137			MachineName: hostName,
138			StorePath:   storePath,
139		},
140	}
141}
142
143func (d *Driver) GetSSHHostname() (string, error) {
144	return d.GetIP()
145}
146
147// DriverName returns the name of the driver
148func (d *Driver) DriverName() string {
149	return "vmwarevcloudair"
150}
151
152func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error {
153
154	d.UserName = flags.String("vmwarevcloudair-username")
155	d.UserPassword = flags.String("vmwarevcloudair-password")
156	d.VDCID = flags.String("vmwarevcloudair-vdcid")
157	d.PublicIP = flags.String("vmwarevcloudair-publicip")
158	d.SetSwarmConfigFromFlags(flags)
159
160	// Check for required Params
161	if d.UserName == "" || d.UserPassword == "" || d.VDCID == "" || d.PublicIP == "" {
162		return fmt.Errorf("Please specify vcloudair mandatory params using options: -vmwarevcloudair-username -vmwarevcloudair-password -vmwarevcloudair-vdcid and -vmwarevcloudair-publicip")
163	}
164
165	// If ComputeID is not set we're using a VPC, hence setting ComputeID = VDCID
166	if flags.String("vmwarevcloudair-computeid") == "" {
167		d.ComputeID = flags.String("vmwarevcloudair-vdcid")
168	} else {
169		d.ComputeID = flags.String("vmwarevcloudair-computeid")
170	}
171
172	// If the Org VDC Network is empty, set it to the default routed network.
173	if flags.String("vmwarevcloudair-orgvdcnetwork") == "" {
174		d.OrgVDCNet = flags.String("vmwarevcloudair-vdcid") + "-default-routed"
175	} else {
176		d.OrgVDCNet = flags.String("vmwarevcloudair-orgvdcnetwork")
177	}
178
179	// If the Edge Gateway is empty, just set it to the default edge gateway.
180	if flags.String("vmwarevcloudair-edgegateway") == "" {
181		d.EdgeGateway = flags.String("vmwarevcloudair-vdcid")
182	} else {
183		d.EdgeGateway = flags.String("vmwarevcloudair-edgegateway")
184	}
185
186	d.Catalog = flags.String("vmwarevcloudair-catalog")
187	d.CatalogItem = flags.String("vmwarevcloudair-catalogitem")
188
189	d.DockerPort = flags.Int("vmwarevcloudair-docker-port")
190	d.SSHUser = "root"
191	d.SSHPort = flags.Int("vmwarevcloudair-ssh-port")
192	d.CPUCount = flags.Int("vmwarevcloudair-cpu-count")
193	d.MemorySize = flags.Int("vmwarevcloudair-memory-size")
194
195	return nil
196}
197
198func (d *Driver) GetURL() (string, error) {
199	if err := drivers.MustBeRunning(d); err != nil {
200		return "", err
201	}
202
203	return fmt.Sprintf("tcp://%s", net.JoinHostPort(d.PublicIP, strconv.Itoa(d.DockerPort))), nil
204}
205
206func (d *Driver) GetIP() (string, error) {
207	return d.PublicIP, nil
208}
209
210func (d *Driver) GetState() (state.State, error) {
211	p, err := govcloudair.NewClient()
212	if err != nil {
213		return state.Error, err
214	}
215
216	log.Debug("Connecting to vCloud Air to fetch vApp Status...")
217	// Authenticate to vCloud Air
218	v, err := p.Authenticate(d.UserName, d.UserPassword, d.ComputeID, d.VDCID)
219	if err != nil {
220		return state.Error, err
221	}
222
223	vapp, err := v.FindVAppByID(d.VAppID)
224	if err != nil {
225		return state.Error, err
226	}
227
228	status, err := vapp.GetStatus()
229	if err != nil {
230		return state.Error, err
231	}
232
233	if err = p.Disconnect(); err != nil {
234		return state.Error, err
235	}
236
237	switch status {
238	case "POWERED_ON":
239		return state.Running, nil
240	case "POWERED_OFF":
241		return state.Stopped, nil
242	}
243	return state.None, nil
244}
245
246func (d *Driver) Create() error {
247	key, err := d.createSSHKey()
248	if err != nil {
249		return err
250	}
251
252	p, err := govcloudair.NewClient()
253	if err != nil {
254		return err
255	}
256
257	log.Infof("Connecting to vCloud Air...")
258	// Authenticate to vCloud Air
259	v, err := p.Authenticate(d.UserName, d.UserPassword, d.ComputeID, d.VDCID)
260	if err != nil {
261		return err
262	}
263
264	// Find VDC Network
265	net, err := v.FindVDCNetwork(d.OrgVDCNet)
266	if err != nil {
267		return err
268	}
269
270	// Find our Edge Gateway
271	edge, err := v.FindEdgeGateway(d.EdgeGateway)
272	if err != nil {
273		return err
274	}
275
276	// Get the Org our VDC belongs to
277	org, err := v.GetVDCOrg()
278	if err != nil {
279		return err
280	}
281
282	// Find our Catalog
283	cat, err := org.FindCatalog(d.Catalog)
284	if err != nil {
285		return err
286	}
287
288	// Find our Catalog Item
289	cati, err := cat.FindCatalogItem(d.CatalogItem)
290	if err != nil {
291		return err
292	}
293
294	// Fetch the vApp Template in the Catalog Item
295	vapptemplate, err := cati.GetVAppTemplate()
296	if err != nil {
297		return err
298	}
299
300	// Create a new empty vApp
301	vapp := govcloudair.NewVApp(p)
302
303	log.Infof("Creating a new vApp: %s...", d.MachineName)
304	// Compose the vApp with ComposeVApp
305	task, err := vapp.ComposeVApp(net, vapptemplate, d.MachineName, "Container Host created with Docker Host")
306	if err != nil {
307		return err
308	}
309
310	// Wait for the creation to be completed
311	if err = task.WaitTaskCompletion(); err != nil {
312		return err
313	}
314
315	task, err = vapp.ChangeCPUcount(d.CPUCount)
316	if err != nil {
317		return err
318	}
319
320	if err = task.WaitTaskCompletion(); err != nil {
321		return err
322	}
323
324	task, err = vapp.ChangeMemorySize(d.MemorySize)
325	if err != nil {
326		return err
327	}
328
329	if err = task.WaitTaskCompletion(); err != nil {
330		return err
331	}
332
333	sshCustomScript := "echo \"" + strings.TrimSpace(key) + "\" > /root/.ssh/authorized_keys"
334
335	task, err = vapp.RunCustomizationScript(d.MachineName, sshCustomScript)
336	if err != nil {
337		return err
338	}
339
340	if err = task.WaitTaskCompletion(); err != nil {
341		return err
342	}
343
344	task, err = vapp.PowerOn()
345	if err != nil {
346		return err
347	}
348
349	log.Infof("Waiting for the VM to power on and run the customization script...")
350
351	if err = task.WaitTaskCompletion(); err != nil {
352		return err
353	}
354
355	log.Infof("Creating NAT and Firewall Rules on %s...", d.EdgeGateway)
356	task, err = edge.Create1to1Mapping(vapp.VApp.Children.VM[0].NetworkConnectionSection.NetworkConnection.IPAddress, d.PublicIP, d.MachineName)
357	if err != nil {
358		return err
359	}
360
361	if err = task.WaitTaskCompletion(); err != nil {
362		return err
363	}
364
365	log.Debugf("Disconnecting from vCloud Air...")
366
367	if err = p.Disconnect(); err != nil {
368		return err
369	}
370
371	// Set VAppID with ID of the created VApp
372	d.VAppID = vapp.VApp.ID
373
374	d.IPAddress, err = d.GetIP()
375	return err
376}
377
378func (d *Driver) Remove() error {
379	p, err := govcloudair.NewClient()
380	if err != nil {
381		return err
382	}
383
384	log.Infof("Connecting to vCloud Air...")
385	// Authenticate to vCloud Air
386	v, err := p.Authenticate(d.UserName, d.UserPassword, d.ComputeID, d.VDCID)
387	if err != nil {
388		return err
389	}
390
391	// Find our Edge Gateway
392	edge, err := v.FindEdgeGateway(d.EdgeGateway)
393	if err != nil {
394		return err
395	}
396
397	vapp, err := v.FindVAppByID(d.VAppID)
398	if err != nil {
399		log.Infof("Can't find the vApp, assuming it was deleted already...")
400		return nil
401	}
402
403	status, err := vapp.GetStatus()
404	if err != nil {
405		return err
406	}
407
408	log.Infof("Removing NAT and Firewall Rules on %s...", d.EdgeGateway)
409	task, err := edge.Remove1to1Mapping(vapp.VApp.Children.VM[0].NetworkConnectionSection.NetworkConnection.IPAddress, d.PublicIP)
410	if err != nil {
411		return err
412	}
413	if err = task.WaitTaskCompletion(); err != nil {
414		return err
415	}
416
417	if status == "POWERED_ON" {
418		// If it's powered on, power it off before deleting
419		log.Infof("Powering Off %s...", d.MachineName)
420		task, err = vapp.PowerOff()
421		if err != nil {
422			return err
423		}
424		if err = task.WaitTaskCompletion(); err != nil {
425			return err
426		}
427
428	}
429
430	log.Debugf("Undeploying %s...", d.MachineName)
431	task, err = vapp.Undeploy()
432	if err != nil {
433		return err
434	}
435	if err = task.WaitTaskCompletion(); err != nil {
436		return err
437	}
438
439	log.Infof("Deleting %s...", d.MachineName)
440	task, err = vapp.Delete()
441	if err != nil {
442		return err
443	}
444	if err = task.WaitTaskCompletion(); err != nil {
445		return err
446	}
447
448	err = p.Disconnect()
449	return err
450}
451
452func (d *Driver) Start() error {
453	p, err := govcloudair.NewClient()
454	if err != nil {
455		return err
456	}
457
458	log.Infof("Connecting to vCloud Air...")
459	// Authenticate to vCloud Air
460	v, err := p.Authenticate(d.UserName, d.UserPassword, d.ComputeID, d.VDCID)
461	if err != nil {
462		return err
463	}
464
465	vapp, err := v.FindVAppByID(d.VAppID)
466	if err != nil {
467		return err
468	}
469
470	status, err := vapp.GetStatus()
471	if err != nil {
472		return err
473	}
474
475	if status == "POWERED_OFF" {
476		log.Infof("Starting %s...", d.MachineName)
477		task, err := vapp.PowerOn()
478		if err != nil {
479			return err
480		}
481		if err = task.WaitTaskCompletion(); err != nil {
482			return err
483		}
484
485	}
486
487	if err = p.Disconnect(); err != nil {
488		return err
489	}
490
491	d.IPAddress, err = d.GetIP()
492	return err
493}
494
495func (d *Driver) Stop() error {
496	p, err := govcloudair.NewClient()
497	if err != nil {
498		return err
499	}
500
501	log.Infof("Connecting to vCloud Air...")
502	// Authenticate to vCloud Air
503	v, err := p.Authenticate(d.UserName, d.UserPassword, d.ComputeID, d.VDCID)
504	if err != nil {
505		return err
506	}
507
508	vapp, err := v.FindVAppByID(d.VAppID)
509	if err != nil {
510		return err
511	}
512
513	task, err := vapp.Shutdown()
514	if err != nil {
515		return err
516	}
517	if err = task.WaitTaskCompletion(); err != nil {
518		return err
519	}
520
521	if err = p.Disconnect(); err != nil {
522		return err
523	}
524
525	d.IPAddress = ""
526
527	return nil
528}
529
530func (d *Driver) Restart() error {
531	p, err := govcloudair.NewClient()
532	if err != nil {
533		return err
534	}
535
536	log.Infof("Connecting to vCloud Air...")
537	// Authenticate to vCloud Air
538	v, err := p.Authenticate(d.UserName, d.UserPassword, d.ComputeID, d.VDCID)
539	if err != nil {
540		return err
541	}
542
543	vapp, err := v.FindVAppByID(d.VAppID)
544	if err != nil {
545		return err
546	}
547
548	task, err := vapp.Reset()
549	if err != nil {
550		return err
551	}
552	if err = task.WaitTaskCompletion(); err != nil {
553		return err
554	}
555
556	if err = p.Disconnect(); err != nil {
557		return err
558	}
559
560	d.IPAddress, err = d.GetIP()
561	return err
562}
563
564func (d *Driver) Kill() error {
565	p, err := govcloudair.NewClient()
566	if err != nil {
567		return err
568	}
569
570	log.Infof("Connecting to vCloud Air...")
571	// Authenticate to vCloud Air
572	v, err := p.Authenticate(d.UserName, d.UserPassword, d.ComputeID, d.VDCID)
573	if err != nil {
574		return err
575	}
576
577	vapp, err := v.FindVAppByID(d.VAppID)
578	if err != nil {
579		return err
580	}
581
582	task, err := vapp.PowerOff()
583	if err != nil {
584		return err
585	}
586	if err = task.WaitTaskCompletion(); err != nil {
587		return err
588	}
589
590	if err = p.Disconnect(); err != nil {
591		return err
592	}
593
594	d.IPAddress = ""
595
596	return nil
597}
598
599// Helpers
600
601func generateVMName() string {
602	randomID := mcnutils.TruncateID(mcnutils.GenerateRandomID())
603	return fmt.Sprintf("docker-host-%s", randomID)
604}
605
606func (d *Driver) createSSHKey() (string, error) {
607	if err := ssh.GenerateSSHKey(d.GetSSHKeyPath()); err != nil {
608		return "", err
609	}
610
611	publicKey, err := ioutil.ReadFile(d.publicSSHKeyPath())
612	if err != nil {
613		return "", err
614	}
615
616	return string(publicKey), nil
617}
618
619func (d *Driver) publicSSHKeyPath() string {
620	return d.GetSSHKeyPath() + ".pub"
621}
622