1/*
2Copyright (c) 2017 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 simulator
18
19import (
20	"context"
21	"fmt"
22	"io/ioutil"
23	"os"
24	"path"
25
26	"github.com/vmware/govmomi/object"
27	"github.com/vmware/govmomi/simulator/esx"
28	"github.com/vmware/govmomi/simulator/vpx"
29	"github.com/vmware/govmomi/vim25/mo"
30	"github.com/vmware/govmomi/vim25/types"
31)
32
33type DelayConfig struct {
34	// Delay specifies the number of milliseconds to delay serving a SOAP call. 0 means no delay.
35	// This can be used to simulate a poorly performing vCenter or network lag.
36	Delay int
37
38	// Delay specifies the number of milliseconds to delay serving a specific method.
39	// Each entry in the map represents the name of a method and its associated delay in milliseconds,
40	// This can be used to simulate a poorly performing vCenter or network lag.
41	MethodDelay map[string]int
42
43	// DelayJitter defines the delay jitter as a coefficient of variation (stddev/mean).
44	// This can be used to simulate unpredictable delay. 0 means no jitter, i.e. all invocations get the same delay.
45	DelayJitter float64
46}
47
48// Model is used to populate a Model with an initial set of managed entities.
49// This is a simple helper for tests running against a simulator, to populate an inventory
50// with commonly used models.
51type Model struct {
52	Service *Service `json:"-"`
53
54	ServiceContent types.ServiceContent `json:"-"`
55	RootFolder     mo.Folder            `json:"-"`
56
57	// Autostart will power on Model created VMs when true
58	Autostart bool `json:"-"`
59
60	// Datacenter specifies the number of Datacenter entities to create
61	Datacenter int
62
63	// Portgroup specifies the number of DistributedVirtualPortgroup entities to create per Datacenter
64	Portgroup int
65
66	// Host specifies the number of standalone HostSystems entities to create per Datacenter
67	Host int `json:",omitempty"`
68
69	// Cluster specifies the number of ClusterComputeResource entities to create per Datacenter
70	Cluster int
71
72	// ClusterHost specifies the number of HostSystems entities to create within a Cluster
73	ClusterHost int `json:",omitempty"`
74
75	// Pool specifies the number of ResourcePool entities to create per Cluster
76	Pool int
77
78	// Datastore specifies the number of Datastore entities to create
79	// Each Datastore will have temporary local file storage and will be mounted
80	// on every HostSystem created by the ModelConfig
81	Datastore int
82
83	// Machine specifies the number of VirtualMachine entities to create per ResourcePool
84	Machine int
85
86	// Folder specifies the number of Datacenter to place within a Folder.
87	// This includes a folder for the Datacenter itself and its host, vm, network and datastore folders.
88	// All resources for the Datacenter are placed within these folders, rather than the top-level folders.
89	Folder int
90
91	// App specifies the number of VirtualApp to create per Cluster
92	App int
93
94	// Pod specifies the number of StoragePod to create per Cluster
95	Pod int
96
97	// Delay configurations
98	DelayConfig DelayConfig
99
100	// total number of inventory objects, set by Count()
101	total int
102
103	dirs []string
104}
105
106// ESX is the default Model for a standalone ESX instance
107func ESX() *Model {
108	return &Model{
109		ServiceContent: esx.ServiceContent,
110		RootFolder:     esx.RootFolder,
111		Autostart:      true,
112		Datastore:      1,
113		Machine:        2,
114		DelayConfig: DelayConfig{
115			Delay:       0,
116			DelayJitter: 0,
117			MethodDelay: nil,
118		},
119	}
120}
121
122// VPX is the default Model for a vCenter instance
123func VPX() *Model {
124	return &Model{
125		ServiceContent: vpx.ServiceContent,
126		RootFolder:     vpx.RootFolder,
127		Autostart:      true,
128		Datacenter:     1,
129		Portgroup:      1,
130		Host:           1,
131		Cluster:        1,
132		ClusterHost:    3,
133		Datastore:      1,
134		Machine:        2,
135		DelayConfig: DelayConfig{
136			Delay:       0,
137			DelayJitter: 0,
138			MethodDelay: nil,
139		},
140	}
141}
142
143// Count returns a Model with total number of each existing type
144func (m *Model) Count() Model {
145	count := Model{}
146
147	for ref, obj := range Map.objects {
148		if _, ok := obj.(mo.Entity); !ok {
149			continue
150		}
151
152		count.total++
153
154		switch ref.Type {
155		case "Datacenter":
156			count.Datacenter++
157		case "DistributedVirtualPortgroup":
158			count.Portgroup++
159		case "ClusterComputeResource":
160			count.Cluster++
161		case "Datastore":
162			count.Datastore++
163		case "HostSystem":
164			count.Host++
165		case "VirtualMachine":
166			count.Machine++
167		case "ResourcePool":
168			count.Pool++
169		case "VirtualApp":
170			count.App++
171		case "Folder":
172			count.Folder++
173		case "StoragePod":
174			count.Pod++
175		}
176	}
177
178	return count
179}
180
181func (*Model) fmtName(prefix string, num int) string {
182	return fmt.Sprintf("%s%d", prefix, num)
183}
184
185// Create populates the Model with the given ModelConfig
186func (m *Model) Create() error {
187	m.Service = New(NewServiceInstance(m.ServiceContent, m.RootFolder))
188
189	ctx := context.Background()
190	client := m.Service.client
191	root := object.NewRootFolder(client)
192
193	// After all hosts are created, this var is used to mount the host datastores.
194	var hosts []*object.HostSystem
195	hostMap := make(map[string][]*object.HostSystem)
196
197	// We need to defer VM creation until after the datastores are created.
198	var vms []func() error
199	// 1 DVS per DC, added to all hosts
200	var dvs *object.DistributedVirtualSwitch
201	// 1 NIC per VM, backed by a DVPG if Model.Portgroup > 0
202	vmnet := esx.EthernetCard.Backing
203
204	// addHost adds a cluster host or a stanalone host.
205	addHost := func(name string, f func(types.HostConnectSpec) (*object.Task, error)) (*object.HostSystem, error) {
206		spec := types.HostConnectSpec{
207			HostName: name,
208		}
209
210		task, err := f(spec)
211		if err != nil {
212			return nil, err
213		}
214
215		info, err := task.WaitForResult(context.Background(), nil)
216		if err != nil {
217			return nil, err
218		}
219
220		host := object.NewHostSystem(client, info.Result.(types.ManagedObjectReference))
221		hosts = append(hosts, host)
222
223		if dvs != nil {
224			config := &types.DVSConfigSpec{
225				Host: []types.DistributedVirtualSwitchHostMemberConfigSpec{{
226					Operation: string(types.ConfigSpecOperationAdd),
227					Host:      host.Reference(),
228				}},
229			}
230
231			_, _ = dvs.Reconfigure(ctx, config)
232		}
233
234		return host, nil
235	}
236
237	// addMachine returns a func to create a VM.
238	addMachine := func(prefix string, host *object.HostSystem, pool *object.ResourcePool, folders *object.DatacenterFolders) {
239		nic := esx.EthernetCard
240		nic.Backing = vmnet
241		ds := types.ManagedObjectReference{}
242
243		f := func() error {
244			for i := 0; i < m.Machine; i++ {
245				name := m.fmtName(prefix+"_VM", i)
246
247				config := types.VirtualMachineConfigSpec{
248					Name:    name,
249					GuestId: string(types.VirtualMachineGuestOsIdentifierOtherGuest),
250					Files: &types.VirtualMachineFileInfo{
251						VmPathName: "[LocalDS_0]",
252					},
253				}
254
255				if pool == nil {
256					pool, _ = host.ResourcePool(ctx)
257				}
258
259				var devices object.VirtualDeviceList
260
261				scsi, _ := devices.CreateSCSIController("pvscsi")
262				ide, _ := devices.CreateIDEController()
263				cdrom, _ := devices.CreateCdrom(ide.(*types.VirtualIDEController))
264				disk := devices.CreateDisk(scsi.(types.BaseVirtualController), ds,
265					config.Files.VmPathName+" "+path.Join(name, "disk1.vmdk"))
266				disk.CapacityInKB = 1024
267
268				devices = append(devices, scsi, cdrom, disk, &nic)
269
270				config.DeviceChange, _ = devices.ConfigSpec(types.VirtualDeviceConfigSpecOperationAdd)
271
272				task, err := folders.VmFolder.CreateVM(ctx, config, pool, host)
273				if err != nil {
274					return err
275				}
276
277				info, err := task.WaitForResult(ctx, nil)
278				if err != nil {
279					return err
280				}
281
282				vm := object.NewVirtualMachine(client, info.Result.(types.ManagedObjectReference))
283
284				if m.Autostart {
285					_, _ = vm.PowerOn(ctx)
286				}
287			}
288
289			return nil
290		}
291
292		vms = append(vms, f)
293	}
294
295	nfolder := 0
296
297	for ndc := 0; ndc < m.Datacenter; ndc++ {
298		dcName := m.fmtName("DC", ndc)
299		folder := root
300		fName := m.fmtName("F", nfolder)
301
302		// If Datacenter > Folder, don't create folders for the first N DCs.
303		if nfolder < m.Folder && ndc >= (m.Datacenter-m.Folder) {
304			f, err := folder.CreateFolder(ctx, fName)
305			if err != nil {
306				return err
307			}
308			folder = f
309		}
310
311		dc, err := folder.CreateDatacenter(ctx, dcName)
312		if err != nil {
313			return err
314		}
315
316		folders, err := dc.Folders(ctx)
317		if err != nil {
318			return err
319		}
320
321		if m.Pod > 0 {
322			for pod := 0; pod < m.Pod; pod++ {
323				_, _ = folders.DatastoreFolder.CreateStoragePod(ctx, m.fmtName(dcName+"_POD", pod))
324			}
325		}
326
327		if folder != root {
328			// Create sub-folders and use them to create any resources that follow
329			subs := []**object.Folder{&folders.DatastoreFolder, &folders.HostFolder, &folders.NetworkFolder, &folders.VmFolder}
330
331			for _, sub := range subs {
332				f, err := (*sub).CreateFolder(ctx, fName)
333				if err != nil {
334					return err
335				}
336
337				*sub = f
338			}
339
340			nfolder++
341		}
342
343		if m.Portgroup > 0 {
344			var spec types.DVSCreateSpec
345			spec.ConfigSpec = &types.VMwareDVSConfigSpec{}
346			spec.ConfigSpec.GetDVSConfigSpec().Name = m.fmtName("DVS", 0)
347
348			task, err := folders.NetworkFolder.CreateDVS(ctx, spec)
349			if err != nil {
350				return err
351			}
352
353			info, err := task.WaitForResult(ctx, nil)
354			if err != nil {
355				return err
356			}
357
358			dvs = object.NewDistributedVirtualSwitch(client, info.Result.(types.ManagedObjectReference))
359
360			for npg := 0; npg < m.Portgroup; npg++ {
361				name := m.fmtName(dcName+"_DVPG", npg)
362
363				task, err = dvs.AddPortgroup(ctx, []types.DVPortgroupConfigSpec{{Name: name}})
364				if err != nil {
365					return err
366				}
367
368				err = task.Wait(ctx)
369				if err != nil {
370					return err
371				}
372
373				// Use the 1st DVPG for the VMs eth0 backing
374				if npg == 0 {
375					// AddPortgroup_Task does not return the moid, so we look it up by name
376					net := Map.Get(folders.NetworkFolder.Reference()).(*Folder)
377					pg := Map.FindByName(name, net.ChildEntity)
378
379					vmnet, _ = object.NewDistributedVirtualPortgroup(client, pg.Reference()).EthernetCardBackingInfo(ctx)
380				}
381			}
382		}
383
384		for nhost := 0; nhost < m.Host; nhost++ {
385			name := m.fmtName(dcName+"_H", nhost)
386
387			host, err := addHost(name, func(spec types.HostConnectSpec) (*object.Task, error) {
388				return folders.HostFolder.AddStandaloneHost(ctx, spec, true, nil, nil)
389			})
390			if err != nil {
391				return err
392			}
393
394			addMachine(name, host, nil, folders)
395		}
396
397		for ncluster := 0; ncluster < m.Cluster; ncluster++ {
398			clusterName := m.fmtName(dcName+"_C", ncluster)
399
400			cluster, err := folders.HostFolder.CreateCluster(ctx, clusterName, types.ClusterConfigSpecEx{})
401			if err != nil {
402				return err
403			}
404
405			for nhost := 0; nhost < m.ClusterHost; nhost++ {
406				name := m.fmtName(clusterName+"_H", nhost)
407
408				_, err = addHost(name, func(spec types.HostConnectSpec) (*object.Task, error) {
409					return cluster.AddHost(ctx, spec, true, nil, nil)
410				})
411				if err != nil {
412					return err
413				}
414			}
415
416			pool, err := cluster.ResourcePool(ctx)
417			if err != nil {
418				return err
419			}
420
421			prefix := clusterName + "_RP"
422
423			addMachine(prefix+"0", nil, pool, folders)
424
425			for npool := 1; npool <= m.Pool; npool++ {
426				spec := types.DefaultResourceConfigSpec()
427
428				_, err = pool.Create(ctx, m.fmtName(prefix, npool), spec)
429				if err != nil {
430					return err
431				}
432			}
433
434			prefix = clusterName + "_APP"
435
436			for napp := 0; napp < m.App; napp++ {
437				rspec := types.DefaultResourceConfigSpec()
438				vspec := NewVAppConfigSpec()
439				name := m.fmtName(prefix, napp)
440
441				vapp, err := pool.CreateVApp(ctx, name, rspec, vspec, nil)
442				if err != nil {
443					return err
444				}
445
446				addMachine(name, nil, vapp.ResourcePool, folders)
447			}
448		}
449
450		hostMap[dcName] = hosts
451		hosts = nil
452	}
453
454	if m.ServiceContent.RootFolder == esx.RootFolder.Reference() {
455		// ESX model
456		host := object.NewHostSystem(client, esx.HostSystem.Reference())
457
458		dc := object.NewDatacenter(client, esx.Datacenter.Reference())
459		folders, err := dc.Folders(ctx)
460		if err != nil {
461			return err
462		}
463
464		hostMap[dc.Reference().Value] = append(hosts, host)
465
466		addMachine(host.Reference().Value, host, nil, folders)
467	}
468
469	for dc, dchosts := range hostMap {
470		for i := 0; i < m.Datastore; i++ {
471			err := m.createLocalDatastore(dc, m.fmtName("LocalDS_", i), dchosts)
472			if err != nil {
473				return err
474			}
475		}
476	}
477
478	for _, createVM := range vms {
479		err := createVM()
480		if err != nil {
481			return err
482		}
483	}
484
485	// Turn on delay AFTER we're done building the service content
486	m.Service.delay = &m.DelayConfig
487
488	return nil
489}
490
491func (m *Model) createLocalDatastore(dc string, name string, hosts []*object.HostSystem) error {
492	ctx := context.Background()
493	dir, err := ioutil.TempDir("", fmt.Sprintf("govcsim-%s-%s-", dc, name))
494	if err != nil {
495		return err
496	}
497
498	m.dirs = append(m.dirs, dir)
499
500	for _, host := range hosts {
501		dss, err := host.ConfigManager().DatastoreSystem(ctx)
502		if err != nil {
503			return err
504		}
505
506		_, err = dss.CreateLocalDatastore(ctx, name, dir)
507		if err != nil {
508			return err
509		}
510	}
511
512	return nil
513}
514
515// Remove cleans up items created by the Model, such as local datastore directories
516func (m *Model) Remove() {
517	for _, dir := range m.dirs {
518		_ = os.RemoveAll(dir)
519	}
520}
521