1/*
2Copyright (c) 2014-2016 VMware, Inc. All Rights Reserved.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package vm
18
19import (
20	"context"
21	"flag"
22	"fmt"
23
24	"github.com/vmware/govmomi/govc/cli"
25	"github.com/vmware/govmomi/govc/flags"
26	"github.com/vmware/govmomi/object"
27	"github.com/vmware/govmomi/vim25/soap"
28	"github.com/vmware/govmomi/vim25/types"
29)
30
31type power struct {
32	*flags.ClientFlag
33	*flags.SearchFlag
34
35	On       bool
36	Off      bool
37	Reset    bool
38	Reboot   bool
39	Shutdown bool
40	Suspend  bool
41	Force    bool
42	Multi    bool
43}
44
45func init() {
46	cli.Register("vm.power", &power{})
47}
48
49func (cmd *power) Register(ctx context.Context, f *flag.FlagSet) {
50	cmd.ClientFlag, ctx = flags.NewClientFlag(ctx)
51	cmd.ClientFlag.Register(ctx, f)
52
53	cmd.SearchFlag, ctx = flags.NewSearchFlag(ctx, flags.SearchVirtualMachines)
54	cmd.SearchFlag.Register(ctx, f)
55
56	f.BoolVar(&cmd.On, "on", false, "Power on")
57	f.BoolVar(&cmd.Off, "off", false, "Power off")
58	f.BoolVar(&cmd.Reset, "reset", false, "Power reset")
59	f.BoolVar(&cmd.Suspend, "suspend", false, "Power suspend")
60	f.BoolVar(&cmd.Reboot, "r", false, "Reboot guest")
61	f.BoolVar(&cmd.Shutdown, "s", false, "Shutdown guest")
62	f.BoolVar(&cmd.Force, "force", false, "Force (ignore state error and hard shutdown/reboot if tools unavailable)")
63	f.BoolVar(&cmd.Multi, "M", false, "Use Datacenter.PowerOnMultiVM method instead of VirtualMachine.PowerOnVM")
64}
65
66func (cmd *power) Process(ctx context.Context) error {
67	if err := cmd.ClientFlag.Process(ctx); err != nil {
68		return err
69	}
70	if err := cmd.SearchFlag.Process(ctx); err != nil {
71		return err
72	}
73	opts := []bool{cmd.On, cmd.Off, cmd.Reset, cmd.Suspend, cmd.Reboot, cmd.Shutdown}
74	selected := false
75
76	for _, opt := range opts {
77		if opt {
78			if selected {
79				return flag.ErrHelp
80			}
81			selected = opt
82		}
83	}
84
85	if !selected {
86		return flag.ErrHelp
87	}
88
89	return nil
90}
91
92func isToolsUnavailable(err error) bool {
93	if soap.IsSoapFault(err) {
94		soapFault := soap.ToSoapFault(err)
95		if _, ok := soapFault.VimFault().(types.ToolsUnavailable); ok {
96			return ok
97		}
98	}
99
100	return false
101}
102
103// this is annoying, but the likely use cases for Datacenter.PowerOnVM outside of this command would
104// use []types.ManagedObjectReference via ContainerView or field such as ResourcePool.Vm rather than the Finder.
105func vmReferences(vms []*object.VirtualMachine) []types.ManagedObjectReference {
106	refs := make([]types.ManagedObjectReference, len(vms))
107	for i, vm := range vms {
108		refs[i] = vm.Reference()
109	}
110	return refs
111}
112
113func (cmd *power) Run(ctx context.Context, f *flag.FlagSet) error {
114	vms, err := cmd.VirtualMachines(f.Args())
115	if err != nil {
116		return err
117	}
118
119	if cmd.On && cmd.Multi {
120		dc, derr := cmd.Datacenter()
121		if derr != nil {
122			return derr
123		}
124
125		task, derr := dc.PowerOnVM(ctx, vmReferences(vms))
126		if derr != nil {
127			return derr
128		}
129
130		msg := fmt.Sprintf("Powering on %d VMs...", len(vms))
131		if task == nil {
132			// running against ESX
133			fmt.Fprintf(cmd, "%s OK\n", msg)
134			return nil
135		}
136
137		logger := cmd.ProgressLogger(msg)
138		defer logger.Wait()
139
140		_, err = task.WaitForResult(ctx, logger)
141		return err
142	}
143
144	for _, vm := range vms {
145		var task *object.Task
146
147		switch {
148		case cmd.On:
149			fmt.Fprintf(cmd, "Powering on %s... ", vm.Reference())
150			task, err = vm.PowerOn(ctx)
151		case cmd.Off:
152			fmt.Fprintf(cmd, "Powering off %s... ", vm.Reference())
153			task, err = vm.PowerOff(ctx)
154		case cmd.Reset:
155			fmt.Fprintf(cmd, "Reset %s... ", vm.Reference())
156			task, err = vm.Reset(ctx)
157		case cmd.Suspend:
158			fmt.Fprintf(cmd, "Suspend %s... ", vm.Reference())
159			task, err = vm.Suspend(ctx)
160		case cmd.Reboot:
161			fmt.Fprintf(cmd, "Reboot guest %s... ", vm.Reference())
162			err = vm.RebootGuest(ctx)
163
164			if err != nil && cmd.Force && isToolsUnavailable(err) {
165				task, err = vm.Reset(ctx)
166			}
167		case cmd.Shutdown:
168			fmt.Fprintf(cmd, "Shutdown guest %s... ", vm.Reference())
169			err = vm.ShutdownGuest(ctx)
170
171			if err != nil && cmd.Force && isToolsUnavailable(err) {
172				task, err = vm.PowerOff(ctx)
173			}
174		}
175
176		if err != nil {
177			return err
178		}
179
180		if task != nil {
181			err = task.Wait(ctx)
182		}
183		if err == nil {
184			fmt.Fprintf(cmd, "OK\n")
185			continue
186		}
187
188		if cmd.Force {
189			fmt.Fprintf(cmd, "Error: %s\n", err)
190			continue
191		}
192
193		return err
194	}
195
196	return nil
197}
198