1// Copyright 2016-2018 VMware, Inc. All Rights Reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package management 16 17import ( 18 "fmt" 19 "path" 20 21 "github.com/vmware/govmomi/find" 22 "github.com/vmware/govmomi/object" 23 "github.com/vmware/govmomi/vim25/types" 24 "github.com/vmware/vic/lib/config" 25 "github.com/vmware/vic/lib/install/validate" 26 "github.com/vmware/vic/lib/migration" 27 "github.com/vmware/vic/pkg/errors" 28 "github.com/vmware/vic/pkg/retry" 29 "github.com/vmware/vic/pkg/trace" 30 "github.com/vmware/vic/pkg/vsphere/compute" 31 "github.com/vmware/vic/pkg/vsphere/extraconfig" 32 "github.com/vmware/vic/pkg/vsphere/extraconfig/vmomi" 33 "github.com/vmware/vic/pkg/vsphere/tasks" 34 "github.com/vmware/vic/pkg/vsphere/vm" 35) 36 37const ( 38 vchIDType = "VirtualMachine" 39) 40 41func (d *Dispatcher) NewVCHFromID(id string) (*vm.VirtualMachine, error) { 42 defer trace.End(trace.Begin(id, d.op)) 43 44 var err error 45 46 moref := &types.ManagedObjectReference{ 47 Type: vchIDType, 48 Value: id, 49 } 50 ref, err := d.session.Finder.ObjectReference(d.op, *moref) 51 if err != nil { 52 if !isManagedObjectNotFoundError(err) { 53 err = errors.Errorf("Failed to query appliance (%q): %s", moref, err) 54 return nil, err 55 } 56 d.op.Debug("Appliance is not found") 57 return nil, fmt.Errorf("id %q could not be found", id) 58 } 59 ovm, ok := ref.(*object.VirtualMachine) 60 if !ok { 61 d.op.Errorf("Failed to find VM %q: %s", moref, err) 62 return nil, err 63 } 64 d.appliance = vm.NewVirtualMachine(d.op, d.session, ovm.Reference()) 65 66 // check if it's VCH 67 if ok, err = d.isVCH(d.appliance); err != nil { 68 d.op.Error(err) 69 return nil, err 70 } 71 if !ok { 72 err = errors.Errorf("Not a VCH") 73 d.op.Error(err) 74 return nil, err 75 } 76 d.vchPool, err = d.appliance.ResourcePool(d.op) 77 if err != nil { 78 d.op.Errorf("Failed to get VM parent resource pool: %s", err) 79 return nil, err 80 } 81 82 rp := compute.NewResourcePool(d.op, d.session, d.vchPool.Reference()) 83 if d.session.Cluster, err = rp.GetCluster(d.op); err != nil { 84 d.op.Debugf("Unable to get the cluster for the VCH's resource pool: %s", err) 85 } 86 d.InitDiagnosticLogsFromVCH(d.appliance) 87 return d.appliance, nil 88} 89 90func (d *Dispatcher) NewVCHFromComputePath(computePath string, name string, v *validate.Validator) (*vm.VirtualMachine, error) { 91 defer trace.End(trace.Begin(fmt.Sprintf("path %q, name %q", computePath, name), d.op)) 92 93 var err error 94 95 parent, err := v.ResourcePoolHelper(d.op, computePath) 96 if err != nil { 97 return nil, err 98 } 99 d.vchPoolPath = path.Join(parent.InventoryPath, name) 100 if d.isVC { 101 vapp, err := d.findVirtualApp(d.vchPoolPath) 102 if err != nil { 103 d.op.Errorf("Failed to get VCH virtual app %q: %s", d.vchPoolPath, err) 104 return nil, err 105 } 106 if vapp != nil { 107 d.vchPool = vapp.ResourcePool 108 } 109 } 110 if d.vchPool == nil { 111 d.vchPool, err = d.session.Finder.ResourcePool(d.op, d.vchPoolPath) 112 if err != nil { 113 // we didn't find the ResourcePool with a name matching the appliance, so 114 // lets look for just the resource pool -- this could be at the cluster level 115 d.vchPoolPath = parent.InventoryPath 116 d.vchPool, err = d.session.Finder.ResourcePool(d.op, d.vchPoolPath) 117 if err != nil { 118 d.op.Errorf("Failed to find VCH resource pool %q: %s", d.vchPoolPath, err) 119 return nil, err 120 } 121 } 122 } 123 124 // creating a pkg/vsphere resource pool for use of convenience method 125 rp := compute.NewResourcePool(d.op, d.session, d.vchPool.Reference()) 126 127 if d.session.Cluster, err = rp.GetCluster(d.op); err != nil { 128 d.op.Debugf("Unable to get the cluster for the VCH's resource pool: %s", err) 129 } 130 131 if d.appliance, err = rp.GetChildVM(d.op, name); err != nil { 132 d.op.Errorf("Failed to get VCH VM: %s", err) 133 return nil, err 134 } 135 if d.appliance == nil { 136 err = errors.Errorf("Didn't find VM %q in resource pool %q", name, rp.Reference()) 137 d.op.Error(err) 138 return nil, err 139 } 140 d.appliance.InventoryPath = path.Join(d.vchPoolPath, name) 141 142 // check if it's VCH 143 var ok bool 144 if ok, err = d.isVCH(d.appliance); err != nil { 145 d.op.Error(err) 146 return nil, err 147 } 148 if !ok { 149 err = errors.Errorf("Not a VCH") 150 d.op.Error(err) 151 return nil, err 152 } 153 154 d.InitDiagnosticLogsFromVCH(d.appliance) 155 return d.appliance, nil 156} 157 158// GetVCHConfig queries VCH configuration and decrypts secret information 159func (d *Dispatcher) GetVCHConfig(vm *vm.VirtualMachine) (*config.VirtualContainerHostConfigSpec, error) { 160 defer trace.End(trace.Begin("", d.op)) 161 162 //this is the appliance vm 163 mapConfig, err := vm.FetchExtraConfigBaseOptions(d.op) 164 if err != nil { 165 err = errors.Errorf("Failed to get VM extra config of %q: %s", vm.Reference(), err) 166 d.op.Error(err) 167 return nil, err 168 } 169 170 kv := vmomi.OptionValueMap(mapConfig) 171 vchConfig, err := d.decryptVCHConfig(vm, kv) 172 if err != nil { 173 err = errors.Errorf("Failed to decode VM configuration %q: %s", vm.Reference(), err) 174 d.op.Error(err) 175 return nil, err 176 } 177 178 if vchConfig.IsCreating() { 179 vmRef := vm.Reference() 180 vchConfig.SetMoref(&vmRef) 181 } 182 return vchConfig, nil 183} 184 185// GetNoSecretVCHConfig queries vch configure from vm configuration, without decrypting secret information 186// this method is used to accommodate old vch version without secret information 187func (d *Dispatcher) GetNoSecretVCHConfig(vm *vm.VirtualMachine) (*config.VirtualContainerHostConfigSpec, error) { 188 defer trace.End(trace.Begin("", d.op)) 189 190 //this is the appliance vm 191 mapConfig, err := vm.FetchExtraConfigBaseOptions(d.op) 192 if err != nil { 193 err = errors.Errorf("Failed to get VM extra config of %q: %s", vm.Reference(), err) 194 d.op.Error(err) 195 return nil, err 196 } 197 198 kv := vmomi.OptionValueMap(mapConfig) 199 vchConfig := &config.VirtualContainerHostConfigSpec{} 200 extraconfig.Decode(extraconfig.MapSource(kv), vchConfig) 201 202 if vchConfig.IsCreating() { 203 vmRef := vm.Reference() 204 vchConfig.SetMoref(&vmRef) 205 } 206 return vchConfig, nil 207} 208 209// FetchAndMigrateVCHConfig queries VCH guestinfo, and try to migrate older version data to latest if the data is old 210func (d *Dispatcher) FetchAndMigrateVCHConfig(vm *vm.VirtualMachine) (*config.VirtualContainerHostConfigSpec, error) { 211 defer trace.End(trace.Begin("", d.op)) 212 213 //this is the appliance vm 214 mapConfig, err := vm.FetchExtraConfigBaseOptions(d.op) 215 if err != nil { 216 err = errors.Errorf("Failed to get VM extra config of %q: %s", vm.Reference(), err) 217 return nil, err 218 } 219 220 kv := vmomi.OptionValueMap(mapConfig) 221 newMap, migrated, err := migration.MigrateApplianceConfig(d.op, d.session, kv) 222 if err != nil { 223 err = errors.Errorf("Failed to migrate config of %q: %s", vm.Reference(), err) 224 return nil, err 225 } 226 if !migrated { 227 d.op.Debugf("No need to migrate configuration for %q", vm.Reference()) 228 } 229 return d.decryptVCHConfig(vm, newMap) 230} 231 232// SearchVCHs searches for VCHs in one of the following areas: 233// 234// computePath: if a compute resource is provided then the search will be limited 235// to the resource pools of the compute resources found at the path 236// 237// datacenter: if no compute resource is provided then search across all compute 238// resources in the sessions datacenter. This requires the user to specify the datacenter 239// via the target flag 240// 241// all datacenters: if the other options aren't available then search across every compute 242// resource in each vSphere datacenter 243func (d *Dispatcher) SearchVCHs(computePath string) ([]*vm.VirtualMachine, error) { 244 defer trace.End(trace.Begin(computePath, d.op)) 245 if computePath != "" { 246 return d.search(computePath) 247 } 248 if d.session.Datacenter != nil { 249 return d.search(path.Join(d.session.Datacenter.InventoryPath, "...")) 250 } 251 252 dcs, err := d.session.Finder.DatacenterList(d.op, "*") 253 if err != nil { 254 err = errors.Errorf("Failed to get datacenter list: %s", err) 255 return nil, err 256 } 257 258 var vchs []*vm.VirtualMachine 259 for _, dc := range dcs { 260 d.session.Finder.SetDatacenter(dc) 261 dcVCHs, err := d.search(path.Join(dc.InventoryPath, "...")) 262 if err != nil { 263 // we will just warn for now 264 d.op.Warnf("Error searching the datacenter(%s): %s", dc.Name(), err) 265 continue 266 } 267 vchs = append(vchs, dcVCHs...) 268 } 269 return vchs, nil 270} 271 272// search finds the compute resources based on the search path and then retrieves 273// the resouce pools for that compute resource. Once a list of resource pools is 274// collected search will iterate over the pools and search for VCHs in each pool 275func (d *Dispatcher) search(searchPath string) ([]*vm.VirtualMachine, error) { 276 defer trace.End(trace.Begin(searchPath, d.op)) 277 var vchs []*vm.VirtualMachine 278 // find compute resources for the search path 279 resources, err := d.session.Finder.ComputeResourceList(d.op, searchPath) 280 if err != nil { 281 if _, ok := err.(*find.NotFoundError); ok { 282 return nil, nil 283 } 284 return nil, err 285 } 286 287 // iterate over clusters and search each resourcePool in the cluster 288 for _, c := range resources { 289 pools, err := d.listResourcePools(c.InventoryPath) 290 if err != nil { 291 e := fmt.Errorf("Unable to find resource pools for compute(%s): %s", c.Name(), err) 292 return nil, e 293 } 294 // now search the pools for VCHs 295 vms := d.searchResourcePools(pools) 296 vchs = append(vchs, vms...) 297 } 298 return vchs, nil 299} 300 301// listResourcePool returns a list of all resource pools under a compute path 302// The func will rety on an ObjectNotFound error because sometimes it's due to concurrent 303// operations on the resource pool 304func (d *Dispatcher) listResourcePools(searchPath string) ([]*object.ResourcePool, error) { 305 var err error 306 var pools []*object.ResourcePool 307 308 // under some circumstances, such as when there is concurrent vic-machine delete operation running in the background, 309 // listing resource pools might fail because some VCH pool is being destroyed at the same time. 310 // If that happens, we retry and list pools again 311 err = retry.Do(d.op, func() error { 312 pools, err = d.session.Finder.ResourcePoolList(d.op, path.Join(searchPath, "...")) 313 if _, ok := err.(*find.NotFoundError); ok { 314 return nil 315 } 316 return err 317 }, func(err error) bool { 318 return tasks.IsTransientError(d.op, err) || tasks.IsNotFoundError(err) 319 }) 320 321 return pools, err 322} 323 324// searchResourcePools searches for VCHs in the provided list of resource pools 325func (d *Dispatcher) searchResourcePools(pools []*object.ResourcePool) []*vm.VirtualMachine { 326 var vchs []*vm.VirtualMachine 327 // The number of resource pools found for the compute resource determines how 328 // to search for any VCHs. If there is only one pool then look at all VMs in the 329 // resource pool for VCH metadata. If there are multiple pools then look only at VMs 330 // that have the same name as the resource pool. 331 multiPool := false 332 if len(pools) > 1 { 333 multiPool = true 334 } 335 // iterate over the compute resources pools and search for VMs and vApps 336 for _, pool := range pools { 337 children, err := d.findVCHs(pool, multiPool) 338 // #nosec: Errors unhandled. 339 if err != nil { 340 d.op.Warnf("Failed to get VCH from resource pool %q: %s", pool.InventoryPath, err) 341 } else { 342 vchs = append(vchs, children...) 343 } 344 345 // search for a vApp 346 vappPath := path.Join(pool.InventoryPath, "*") 347 vapps, err := d.session.Finder.VirtualAppList(d.op, vappPath) 348 if err != nil { 349 if _, ok := err.(*find.NotFoundError); !ok { 350 d.op.Errorf("Failed to query vapp %q: %s", vappPath, err) 351 } 352 } 353 for _, vapp := range vapps { 354 children, err := d.findVCHs(vapp.ResourcePool, multiPool) 355 if err != nil { 356 d.op.Warnf("Failed to get VCH from vApp resource pool %q: %s", pool.InventoryPath, err) 357 continue 358 } 359 vchs = append(vchs, children...) 360 } 361 362 } 363 return vchs 364} 365 366// findVCHs finds any VCH in the specified pool. If the compute resource has multiple pools, then look 367// for any VMs with the same name as the resource pool. If the compute resource only had a single pool then 368// evaluate each VM in the pool for VCH metadata 369func (d *Dispatcher) findVCHs(pool *object.ResourcePool, multiPool bool) ([]*vm.VirtualMachine, error) { 370 defer trace.End(trace.Begin(pool.InventoryPath, d.op)) 371 372 // check if pool itself contains VCH vm. 373 var vms []*vm.VirtualMachine 374 var vchs []*vm.VirtualMachine 375 var err error 376 computeResource := compute.NewResourcePool(d.op, d.session, pool.Reference()) 377 // The compute resource had multiple pools, so the assumption is that any VCH that exists will be the same 378 // name as it's resource pool 379 if multiPool { 380 vm, err := computeResource.GetChildVM(d.op, pool.Name()) 381 if err != nil { 382 return nil, errors.Errorf("Failed to query children VM in resource pool %q: %s", pool.InventoryPath, err) 383 } 384 if vm != nil { 385 vms = append(vms, vm) 386 } 387 388 } else { 389 // We only had one pool, so we'll look at all the VMs in that pool 390 vms, err = computeResource.GetChildrenVMs(d.op) 391 if err != nil { 392 return nil, errors.Errorf("Failed to query children VM in resource pool %q: %s", pool.InventoryPath, err) 393 } 394 } 395 // iterate over VMs and determine if we've got any VCHs 396 for _, v := range vms { 397 // override the VM inventory path (which is folder based) 398 // for the resource pool path 399 v.InventoryPath = path.Join(pool.InventoryPath, v.Name()) 400 // #nosec: Errors unhandled. 401 if ok, _ := d.isVCH(v); ok { 402 d.op.Debugf("%q is VCH", v.InventoryPath) 403 vchs = append(vchs, v) 404 } 405 } 406 407 return vchs, nil 408} 409