github.com/vmware/govmomi@v0.51.0/cns/simulator/simulator.go (about) 1 // © Broadcom. All Rights Reserved. 2 // The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. 3 // SPDX-License-Identifier: Apache-2.0 4 5 package simulator 6 7 import ( 8 "context" 9 "slices" 10 "time" 11 12 "github.com/google/uuid" 13 14 "github.com/vmware/govmomi/cns" 15 "github.com/vmware/govmomi/cns/methods" 16 "github.com/vmware/govmomi/cns/types" 17 cnstypes "github.com/vmware/govmomi/cns/types" 18 pbmtypes "github.com/vmware/govmomi/pbm/types" 19 "github.com/vmware/govmomi/simulator" 20 "github.com/vmware/govmomi/vim25" 21 vim25methods "github.com/vmware/govmomi/vim25/methods" 22 "github.com/vmware/govmomi/vim25/soap" 23 vim25types "github.com/vmware/govmomi/vim25/types" 24 ) 25 26 func init() { 27 simulator.RegisterEndpoint(func(s *simulator.Service, r *simulator.Registry) { 28 if r.IsVPX() { 29 s.RegisterSDK(New()) 30 } 31 }) 32 } 33 34 func New() *simulator.Registry { 35 r := simulator.NewRegistry() 36 r.Namespace = cns.Namespace 37 r.Path = cns.Path 38 39 r.Put(&CnsVolumeManager{ 40 ManagedObjectReference: cns.CnsVolumeManagerInstance, 41 volumes: make(map[vim25types.ManagedObjectReference]map[cnstypes.CnsVolumeId]*cnstypes.CnsVolume), 42 attachments: make(map[cnstypes.CnsVolumeId]vim25types.ManagedObjectReference), 43 snapshots: make(map[cnstypes.CnsVolumeId]map[cnstypes.CnsSnapshotId]*cnstypes.CnsSnapshot), 44 }) 45 46 return r 47 } 48 49 type CnsVolumeManager struct { 50 vim25types.ManagedObjectReference 51 volumes map[vim25types.ManagedObjectReference]map[cnstypes.CnsVolumeId]*cnstypes.CnsVolume 52 attachments map[cnstypes.CnsVolumeId]vim25types.ManagedObjectReference 53 snapshots map[cnstypes.CnsVolumeId]map[cnstypes.CnsSnapshotId]*cnstypes.CnsSnapshot 54 } 55 56 const simulatorDiskUUID = "6000c298595bf4575739e9105b2c0c2d" 57 58 func (m *CnsVolumeManager) findDisk(vctx *simulator.Context, createSpec cnstypes.CnsVolumeCreateSpec) (*vim25types.VStorageObject, vim25types.BaseMethodFault) { 59 vsom := vctx.Map.VStorageObjectManager() 60 details := createSpec.BackingObjectDetails.(*cnstypes.CnsBlockBackingDetails) 61 62 for _, objs := range vsom.Catalog() { 63 for id, val := range objs { 64 if id.Id == details.BackingDiskId { 65 return &val.VStorageObject, nil 66 } 67 } 68 } 69 70 return nil, &vim25types.InvalidArgument{InvalidProperty: "VolumeId"} 71 } 72 73 func (m *CnsVolumeManager) createDisk(vctx *simulator.Context, createSpec cnstypes.CnsVolumeCreateSpec) (*vim25types.VStorageObject, vim25types.BaseMethodFault) { 74 vsom := vctx.Map.VStorageObjectManager() 75 76 if len(createSpec.Datastores) == 0 { 77 return nil, &vim25types.InvalidArgument{InvalidProperty: "createSpecs.datastores"} 78 } 79 80 // "Datastores to be considered for volume placement" - we'll just use the 1st for now 81 datastoreRef := createSpec.Datastores[0] 82 83 val := vsom.CreateDiskTask(vctx, &vim25types.CreateDisk_Task{ 84 This: vsom.Self, 85 Spec: vim25types.VslmCreateSpec{ 86 Name: createSpec.Name, 87 KeepAfterDeleteVm: vim25types.NewBool(true), 88 CapacityInMB: createSpec.BackingObjectDetails.GetCnsBackingObjectDetails().CapacityInMb, 89 Profile: createSpec.Profile, 90 BackingSpec: &vim25types.VslmCreateSpecDiskFileBackingSpec{ 91 VslmCreateSpecBackingSpec: vim25types.VslmCreateSpecBackingSpec{ 92 Datastore: datastoreRef, 93 }, 94 ProvisioningType: string(vim25types.BaseConfigInfoDiskFileBackingInfoProvisioningTypeThin), 95 }, 96 }, 97 }) 98 99 ref := val.(*vim25methods.CreateDisk_TaskBody).Res.Returnval 100 task := vctx.Map.Get(ref).(*simulator.Task) 101 task.Wait() 102 if task.Info.Error != nil { 103 return nil, task.Info.Error.Fault 104 } 105 106 return task.Info.Result.(*vim25types.VStorageObject), nil 107 } 108 109 func (m *CnsVolumeManager) CnsCreateVolume(ctx *simulator.Context, req *cnstypes.CnsCreateVolume) soap.HasFault { 110 vctx := ctx.For(vim25.Path) 111 vsom := vctx.Map.VStorageObjectManager() 112 113 task := simulator.CreateTask(m, "CnsCreateVolume", func(*simulator.Task) (vim25types.AnyType, vim25types.BaseMethodFault) { 114 if len(req.CreateSpecs) != 1 { 115 return nil, &vim25types.InvalidArgument{InvalidProperty: "InputSpec"} // Same as real VC, currently 116 } 117 118 var obj *vim25types.VStorageObject 119 var fault vim25types.BaseMethodFault 120 121 createSpec := req.CreateSpecs[0] 122 123 switch details := createSpec.BackingObjectDetails.(type) { 124 case *cnstypes.CnsBlockBackingDetails: 125 if details.BackingDiskId == "" { 126 obj, fault = m.createDisk(vctx, createSpec) 127 } else { 128 obj, fault = m.findDisk(vctx, createSpec) 129 } 130 if fault != nil { 131 return nil, fault 132 } 133 default: 134 return nil, &vim25types.InvalidArgument{InvalidProperty: "createSpecs.backingObjectDetails"} 135 } 136 137 datastoreRef := obj.Config.Backing.GetBaseConfigInfoBackingInfo().Datastore 138 datastore := vctx.Map.Get(datastoreRef).(*simulator.Datastore) 139 140 volumes, ok := m.volumes[datastore.Self] 141 if !ok { 142 volumes = make(map[cnstypes.CnsVolumeId]*cnstypes.CnsVolume) 143 m.volumes[datastore.Self] = volumes 144 } 145 146 policyId := "" 147 if len(createSpec.Profile) != 0 { 148 if profileSpec, ok := createSpec.Profile[0].(*vim25types.VirtualMachineDefinedProfileSpec); ok { 149 policyId = profileSpec.ProfileId 150 } 151 } 152 153 volume := &cnstypes.CnsVolume{ 154 VolumeId: cnstypes.CnsVolumeId(obj.Config.Id), 155 Name: createSpec.Name, 156 VolumeType: createSpec.VolumeType, 157 DatastoreUrl: datastore.Info.GetDatastoreInfo().Url, 158 Metadata: createSpec.Metadata, 159 BackingObjectDetails: &cnstypes.CnsBlockBackingDetails{ 160 CnsBackingObjectDetails: cnstypes.CnsBackingObjectDetails{ 161 CapacityInMb: obj.Config.CapacityInMB, 162 }, 163 BackingDiskId: obj.Config.Id.Id, 164 BackingDiskPath: obj.Config.Backing.(*vim25types.BaseConfigInfoDiskFileBackingInfo).FilePath, 165 }, 166 ComplianceStatus: string(pbmtypes.PbmComplianceStatusCompliant), 167 DatastoreAccessibilityStatus: string(pbmtypes.PbmHealthStatusForEntityGreen), 168 HealthStatus: string(pbmtypes.PbmHealthStatusForEntityGreen), 169 StoragePolicyId: policyId, 170 } 171 172 volumes[volume.VolumeId] = volume 173 174 operationResult := []cnstypes.BaseCnsVolumeOperationResult{ 175 &cnstypes.CnsVolumeCreateResult{ 176 CnsVolumeOperationResult: cnstypes.CnsVolumeOperationResult{ 177 VolumeId: volume.VolumeId, 178 }, 179 Name: volume.Name, 180 PlacementResults: []cnstypes.CnsPlacementResult{{ 181 Datastore: datastore.Reference(), 182 }}, 183 }, 184 } 185 186 cc := createSpec.Metadata.ContainerCluster 187 vsom.VCenterUpdateVStorageObjectMetadataExTask(vctx, &vim25types.VCenterUpdateVStorageObjectMetadataEx_Task{ 188 This: vsom.Self, 189 Id: obj.Config.Id, 190 Datastore: datastore.Self, 191 Metadata: []vim25types.KeyValue{ 192 {Key: "cns.containerCluster.clusterDistribution", Value: cc.ClusterDistribution}, 193 {Key: "cns.containerCluster.clusterFlavor", Value: cc.ClusterFlavor}, 194 {Key: "cns.containerCluster.clusterId", Value: cc.ClusterId}, 195 {Key: "cns.containerCluster.vSphereUser", Value: cc.VSphereUser}, 196 }, 197 }) 198 199 return &cnstypes.CnsVolumeOperationBatchResult{ 200 VolumeResults: operationResult, 201 }, nil 202 }) 203 204 return &methods.CnsCreateVolumeBody{ 205 Res: &cnstypes.CnsCreateVolumeResponse{ 206 Returnval: task.Run(vctx), 207 }, 208 } 209 } 210 211 func matchesDatastore(filter cnstypes.CnsQueryFilter, ds vim25types.ManagedObjectReference) bool { 212 if len(filter.Datastores) == 0 { 213 return true 214 } 215 return slices.Contains(filter.Datastores, ds) 216 } 217 218 func matchesID(filter cnstypes.CnsQueryFilter, volume *types.CnsVolume) bool { 219 if len(filter.VolumeIds) == 0 { 220 return true 221 } 222 return slices.Contains(filter.VolumeIds, volume.VolumeId) 223 } 224 225 func matchesName(filter cnstypes.CnsQueryFilter, volume *types.CnsVolume) bool { 226 if len(filter.Names) == 0 { 227 return true 228 } 229 return slices.Contains(filter.Names, volume.Name) 230 } 231 232 func matchesContainerCluster(filter cnstypes.CnsQueryFilter, volume *types.CnsVolume) bool { 233 if len(filter.ContainerClusterIds) == 0 { 234 return true 235 } 236 return slices.Contains(filter.ContainerClusterIds, volume.Metadata.ContainerCluster.ClusterId) 237 } 238 239 func matchesStoragePolicy(filter cnstypes.CnsQueryFilter, volume *types.CnsVolume) bool { 240 return filter.StoragePolicyId == "" || filter.StoragePolicyId == volume.StoragePolicyId 241 } 242 243 func matchesLabel(filter cnstypes.CnsQueryFilter, volume *types.CnsVolume) bool { 244 if len(filter.Labels) == 0 { 245 return true 246 } 247 248 for _, meta := range volume.Metadata.EntityMetadata { 249 for _, label := range meta.GetCnsEntityMetadata().Labels { 250 if slices.Contains(filter.Labels, label) { 251 return true 252 } 253 } 254 } 255 256 return false 257 } 258 259 func matchesComplianceStatus(filter cnstypes.CnsQueryFilter, volume *types.CnsVolume) bool { 260 return filter.ComplianceStatus == "" || filter.ComplianceStatus == volume.ComplianceStatus 261 } 262 263 func matchesHealth(filter cnstypes.CnsQueryFilter, volume *types.CnsVolume) bool { 264 return filter.HealthStatus == "" || filter.HealthStatus == volume.HealthStatus 265 } 266 267 func matchesFilter(filter cnstypes.CnsQueryFilter, volume *types.CnsVolume) bool { 268 matches := []func(cnstypes.CnsQueryFilter, *types.CnsVolume) bool{ 269 matchesID, 270 matchesName, 271 matchesContainerCluster, 272 matchesStoragePolicy, 273 matchesLabel, 274 matchesComplianceStatus, 275 matchesHealth, 276 } 277 278 for _, match := range matches { 279 if !match(filter, volume) { 280 return false 281 } 282 } 283 284 return true 285 } 286 287 func (m *CnsVolumeManager) queryVolume(filter cnstypes.CnsQueryFilter) []cnstypes.CnsVolume { 288 var matches []cnstypes.CnsVolume 289 290 for ds, volumes := range m.volumes { 291 if !matchesDatastore(filter, ds) { 292 continue 293 } 294 for _, volume := range volumes { 295 if matchesFilter(filter, volume) { 296 matches = append(matches, *volume) 297 } 298 } 299 } 300 301 return matches 302 } 303 304 // CnsQueryVolume simulates the query volumes implementation for CNSQuery API 305 func (m *CnsVolumeManager) CnsQueryVolume(ctx context.Context, req *cnstypes.CnsQueryVolume) soap.HasFault { 306 // TODO: paginate results using CnsCursor 307 return &methods.CnsQueryVolumeBody{ 308 Res: &cnstypes.CnsQueryVolumeResponse{ 309 Returnval: cnstypes.CnsQueryResult{ 310 Volumes: m.queryVolume(req.Filter), 311 }, 312 }, 313 } 314 } 315 316 // CnsQueryAllVolume simulates the query volumes implementation for CNSQueryAll API 317 func (m *CnsVolumeManager) CnsQueryAllVolume(ctx context.Context, req *cnstypes.CnsQueryAllVolume) soap.HasFault { 318 // Note we ignore req.Selection, which can limit fields to reduce response size 319 // This method is internal and does not paginate results using CnsCursor. 320 return &methods.CnsQueryAllVolumeBody{ 321 Res: &cnstypes.CnsQueryAllVolumeResponse{ 322 Returnval: cnstypes.CnsQueryResult{ 323 Volumes: m.queryVolume(req.Filter), 324 }, 325 }, 326 } 327 } 328 329 func (m *CnsVolumeManager) CnsDeleteVolume(ctx *simulator.Context, req *cnstypes.CnsDeleteVolume) soap.HasFault { 330 vctx := ctx.For(vim25.Path) 331 vsom := vctx.Map.VStorageObjectManager() 332 333 task := simulator.CreateTask(m, "CnsDeleteVolume", func(*simulator.Task) (vim25types.AnyType, vim25types.BaseMethodFault) { 334 if len(req.VolumeIds) != 1 { 335 return nil, &vim25types.InvalidArgument{InvalidProperty: "InputSpec"} // Same as real VC, currently 336 } 337 338 found := false 339 volumeId := req.VolumeIds[0] 340 res := &cnstypes.CnsVolumeOperationResult{ 341 VolumeId: volumeId, 342 } 343 344 for ds, volumes := range m.volumes { 345 if _, ok := volumes[volumeId]; ok { 346 found = true 347 delete(m.volumes[ds], volumeId) 348 349 if req.DeleteDisk { 350 val := vsom.DeleteVStorageObjectTask(vctx, &vim25types.DeleteVStorageObject_Task{ 351 This: vsom.Self, 352 Id: vim25types.ID(volumeId), 353 Datastore: ds, 354 }) 355 356 ref := val.(*vim25methods.DeleteVStorageObject_TaskBody).Res.Returnval 357 task := vctx.Map.Get(ref).(*simulator.Task) 358 task.Wait() 359 if task.Info.Error != nil { 360 res.Fault = task.Info.Error 361 } 362 } 363 } 364 } 365 366 if !found { 367 res.Fault = &vim25types.LocalizedMethodFault{Fault: new(vim25types.NotFound)} 368 } 369 370 return &cnstypes.CnsVolumeOperationBatchResult{ 371 VolumeResults: []cnstypes.BaseCnsVolumeOperationResult{res}, 372 }, nil 373 }) 374 375 return &methods.CnsDeleteVolumeBody{ 376 Res: &cnstypes.CnsDeleteVolumeResponse{ 377 Returnval: task.Run(vctx), 378 }, 379 } 380 } 381 382 // CnsUpdateVolumeMetadata simulates UpdateVolumeMetadata call for simulated vc 383 func (m *CnsVolumeManager) CnsUpdateVolumeMetadata(ctx *simulator.Context, req *cnstypes.CnsUpdateVolumeMetadata) soap.HasFault { 384 task := simulator.CreateTask(m, "CnsUpdateVolumeMetadata", func(*simulator.Task) (vim25types.AnyType, vim25types.BaseMethodFault) { 385 if len(req.UpdateSpecs) == 0 { 386 return nil, &vim25types.InvalidArgument{InvalidProperty: "CnsUpdateVolumeMetadataSpec"} 387 } 388 operationResult := []cnstypes.BaseCnsVolumeOperationResult{} 389 for _, updateSpecs := range req.UpdateSpecs { 390 for _, dsVolumes := range m.volumes { 391 for id, volume := range dsVolumes { 392 if id.Id == updateSpecs.VolumeId.Id { 393 volume.Metadata.EntityMetadata = updateSpecs.Metadata.EntityMetadata 394 operationResult = append(operationResult, &cnstypes.CnsVolumeOperationResult{ 395 VolumeId: volume.VolumeId, 396 }) 397 break 398 } 399 } 400 } 401 402 } 403 return &cnstypes.CnsVolumeOperationBatchResult{ 404 VolumeResults: operationResult, 405 }, nil 406 }) 407 return &methods.CnsUpdateVolumeBody{ 408 Res: &cnstypes.CnsUpdateVolumeMetadataResponse{ 409 Returnval: task.Run(ctx), 410 }, 411 } 412 } 413 414 // CnsAttachVolume simulates AttachVolume call for simulated vc 415 func (m *CnsVolumeManager) CnsAttachVolume(ctx *simulator.Context, req *cnstypes.CnsAttachVolume) soap.HasFault { 416 vctx := ctx.For(vim25.Path) 417 task := simulator.CreateTask(m, "CnsAttachVolume", func(task *simulator.Task) (vim25types.AnyType, vim25types.BaseMethodFault) { 418 if len(req.AttachSpecs) == 0 { 419 return nil, &vim25types.InvalidArgument{InvalidProperty: "CnsAttachVolumeSpec"} 420 } 421 operationResult := []cnstypes.BaseCnsVolumeOperationResult{} 422 for _, attachSpec := range req.AttachSpecs { 423 node := vctx.Map.Get(attachSpec.Vm).(*simulator.VirtualMachine) 424 if _, ok := m.attachments[attachSpec.VolumeId]; !ok { 425 m.attachments[attachSpec.VolumeId] = node.Self 426 } else { 427 return nil, &vim25types.ResourceInUse{ 428 Name: attachSpec.VolumeId.Id, 429 } 430 } 431 operationResult = append(operationResult, &cnstypes.CnsVolumeAttachResult{ 432 CnsVolumeOperationResult: cnstypes.CnsVolumeOperationResult{ 433 VolumeId: attachSpec.VolumeId, 434 }, 435 DiskUUID: simulatorDiskUUID, 436 }) 437 } 438 439 return &cnstypes.CnsVolumeOperationBatchResult{ 440 VolumeResults: operationResult, 441 }, nil 442 }) 443 444 return &methods.CnsAttachVolumeBody{ 445 Res: &cnstypes.CnsAttachVolumeResponse{ 446 Returnval: task.Run(vctx), 447 }, 448 } 449 } 450 451 // CnsDetachVolume simulates DetachVolume call for simulated vc 452 func (m *CnsVolumeManager) CnsDetachVolume(ctx *simulator.Context, req *cnstypes.CnsDetachVolume) soap.HasFault { 453 task := simulator.CreateTask(m, "CnsDetachVolume", func(*simulator.Task) (vim25types.AnyType, vim25types.BaseMethodFault) { 454 if len(req.DetachSpecs) == 0 { 455 return nil, &vim25types.InvalidArgument{InvalidProperty: "CnsDetachVolumeSpec"} 456 } 457 operationResult := []cnstypes.BaseCnsVolumeOperationResult{} 458 for _, detachSpec := range req.DetachSpecs { 459 if _, ok := m.attachments[detachSpec.VolumeId]; ok { 460 delete(m.attachments, detachSpec.VolumeId) 461 operationResult = append(operationResult, &cnstypes.CnsVolumeOperationResult{ 462 VolumeId: detachSpec.VolumeId, 463 }) 464 } else { 465 return nil, &vim25types.InvalidArgument{ 466 InvalidProperty: detachSpec.VolumeId.Id, 467 } 468 } 469 } 470 471 return &cnstypes.CnsVolumeOperationBatchResult{ 472 VolumeResults: operationResult, 473 }, nil 474 }) 475 return &methods.CnsDetachVolumeBody{ 476 Res: &cnstypes.CnsDetachVolumeResponse{ 477 Returnval: task.Run(ctx), 478 }, 479 } 480 } 481 482 // CnsExtendVolume simulates ExtendVolume call for simulated vc 483 func (m *CnsVolumeManager) CnsExtendVolume(ctx *simulator.Context, req *cnstypes.CnsExtendVolume) soap.HasFault { 484 task := simulator.CreateTask(m, "CnsExtendVolume", func(task *simulator.Task) (vim25types.AnyType, vim25types.BaseMethodFault) { 485 if len(req.ExtendSpecs) != 1 { 486 return nil, &vim25types.InvalidArgument{InvalidProperty: "InputSpec"} // Same as real VC, currently 487 } 488 489 found := false 490 spec := req.ExtendSpecs[0] 491 res := cnstypes.CnsVolumeOperationResult{ 492 VolumeId: spec.VolumeId, 493 } 494 495 for _, volumes := range m.volumes { 496 if volume, ok := volumes[spec.VolumeId]; ok { 497 found = true 498 volume.BackingObjectDetails.GetCnsBackingObjectDetails().CapacityInMb = spec.CapacityInMb 499 break 500 } 501 } 502 503 if !found { 504 res.Fault = &vim25types.LocalizedMethodFault{Fault: new(vim25types.NotFound)} 505 } 506 507 return &cnstypes.CnsVolumeOperationBatchResult{ 508 VolumeResults: []cnstypes.BaseCnsVolumeOperationResult{&res}, 509 }, nil 510 }) 511 512 return &methods.CnsExtendVolumeBody{ 513 Res: &cnstypes.CnsExtendVolumeResponse{ 514 Returnval: task.Run(ctx), 515 }, 516 } 517 } 518 519 func (m *CnsVolumeManager) CnsQueryVolumeInfo(ctx *simulator.Context, req *cnstypes.CnsQueryVolumeInfo) soap.HasFault { 520 vctx := ctx.For(vim25.Path) 521 vsom := vctx.Map.VStorageObjectManager() 522 523 task := simulator.CreateTask(m, "CnsQueryVolumeInfo", func(*simulator.Task) (vim25types.AnyType, vim25types.BaseMethodFault) { 524 var operationResult []cnstypes.BaseCnsVolumeOperationResult 525 526 for _, objs := range vsom.Catalog() { 527 for _, obj := range objs { 528 for _, volumeId := range req.VolumeIds { 529 if obj.Config.Id.Id == volumeId.Id { 530 operationResult = append(operationResult, &cnstypes.CnsQueryVolumeInfoResult{ 531 CnsVolumeOperationResult: cnstypes.CnsVolumeOperationResult{ 532 VolumeId: volumeId, 533 }, 534 VolumeInfo: &cnstypes.CnsBlockVolumeInfo{ 535 VStorageObject: obj.VStorageObject, 536 }, 537 }) 538 } 539 } 540 } 541 } 542 543 return &cnstypes.CnsVolumeOperationBatchResult{ 544 VolumeResults: operationResult, 545 }, nil 546 }) 547 548 return &methods.CnsQueryVolumeInfoBody{ 549 Res: &cnstypes.CnsQueryVolumeInfoResponse{ 550 Returnval: task.Run(vctx), 551 }, 552 } 553 } 554 555 func (m *CnsVolumeManager) CnsQueryAsync(ctx *simulator.Context, req *cnstypes.CnsQueryAsync) soap.HasFault { 556 task := simulator.CreateTask(m, "QueryVolumeAsync", func(*simulator.Task) (vim25types.AnyType, vim25types.BaseMethodFault) { 557 retVolumes := []cnstypes.CnsVolume{} 558 reqVolumeIds := make(map[string]bool) 559 isQueryFilter := false 560 561 if req.Filter.VolumeIds != nil { 562 isQueryFilter = true 563 } 564 // Create map of requested volume Ids in query request 565 for _, volumeID := range req.Filter.VolumeIds { 566 reqVolumeIds[volumeID.Id] = true 567 } 568 569 for _, dsVolumes := range m.volumes { 570 for _, volume := range dsVolumes { 571 if isQueryFilter { 572 if _, ok := reqVolumeIds[volume.VolumeId.Id]; ok { 573 retVolumes = append(retVolumes, *volume) 574 } 575 } else { 576 retVolumes = append(retVolumes, *volume) 577 } 578 } 579 } 580 operationResult := []cnstypes.BaseCnsVolumeOperationResult{} 581 operationResult = append(operationResult, &cnstypes.CnsAsyncQueryResult{ 582 QueryResult: cnstypes.CnsQueryResult{ 583 Volumes: retVolumes, 584 Cursor: cnstypes.CnsCursor{}, 585 }, 586 }) 587 588 return &cnstypes.CnsVolumeOperationBatchResult{ 589 VolumeResults: operationResult, 590 }, nil 591 }) 592 593 return &methods.CnsQueryAsyncBody{ 594 Res: &cnstypes.CnsQueryAsyncResponse{ 595 Returnval: task.Run(ctx), 596 }, 597 } 598 } 599 600 func (m *CnsVolumeManager) CnsCreateSnapshots(ctx *simulator.Context, req *cnstypes.CnsCreateSnapshots) soap.HasFault { 601 task := simulator.CreateTask(m, "CreateSnapshots", func(*simulator.Task) (vim25types.AnyType, vim25types.BaseMethodFault) { 602 if len(req.SnapshotSpecs) == 0 { 603 return nil, &vim25types.InvalidArgument{InvalidProperty: "CnsSnapshotCreateSpec"} 604 } 605 606 snapshotOperationResult := []cnstypes.BaseCnsVolumeOperationResult{} 607 for _, snapshotCreateSpec := range req.SnapshotSpecs { 608 for _, dsVolumes := range m.volumes { 609 for id := range dsVolumes { 610 if id.Id != snapshotCreateSpec.VolumeId.Id { 611 continue 612 } 613 snapshots, ok := m.snapshots[snapshotCreateSpec.VolumeId] 614 if !ok { 615 snapshots = make(map[cnstypes.CnsSnapshotId]*cnstypes.CnsSnapshot) 616 m.snapshots[snapshotCreateSpec.VolumeId] = snapshots 617 } 618 619 newSnapshot := &cnstypes.CnsSnapshot{ 620 SnapshotId: cnstypes.CnsSnapshotId{ 621 Id: uuid.New().String(), 622 }, 623 VolumeId: snapshotCreateSpec.VolumeId, 624 Description: snapshotCreateSpec.Description, 625 CreateTime: time.Now(), 626 } 627 snapshots[newSnapshot.SnapshotId] = newSnapshot 628 snapshotOperationResult = append(snapshotOperationResult, &cnstypes.CnsSnapshotCreateResult{ 629 CnsSnapshotOperationResult: cnstypes.CnsSnapshotOperationResult{ 630 CnsVolumeOperationResult: cnstypes.CnsVolumeOperationResult{ 631 VolumeId: newSnapshot.VolumeId, 632 }, 633 }, 634 Snapshot: *newSnapshot, 635 }) 636 } 637 } 638 } 639 640 return &cnstypes.CnsVolumeOperationBatchResult{ 641 VolumeResults: snapshotOperationResult, 642 }, nil 643 }) 644 645 return &methods.CnsCreateSnapshotsBody{ 646 Res: &cnstypes.CnsCreateSnapshotsResponse{ 647 Returnval: task.Run(ctx), 648 }, 649 } 650 } 651 652 func (m *CnsVolumeManager) CnsDeleteSnapshots(ctx *simulator.Context, req *cnstypes.CnsDeleteSnapshots) soap.HasFault { 653 task := simulator.CreateTask(m, "DeleteSnapshots", func(*simulator.Task) (vim25types.AnyType, vim25types.BaseMethodFault) { 654 snapshotOperationResult := []cnstypes.BaseCnsVolumeOperationResult{} 655 for _, snapshotDeleteSpec := range req.SnapshotDeleteSpecs { 656 for _, dsVolumes := range m.volumes { 657 for id := range dsVolumes { 658 if id.Id != snapshotDeleteSpec.VolumeId.Id { 659 continue 660 } 661 snapshots := m.snapshots[snapshotDeleteSpec.VolumeId] 662 snapshot, ok := snapshots[snapshotDeleteSpec.SnapshotId] 663 if ok { 664 delete(m.snapshots[snapshotDeleteSpec.VolumeId], snapshotDeleteSpec.SnapshotId) 665 snapshotOperationResult = append(snapshotOperationResult, &cnstypes.CnsSnapshotDeleteResult{ 666 CnsSnapshotOperationResult: cnstypes.CnsSnapshotOperationResult{ 667 CnsVolumeOperationResult: cnstypes.CnsVolumeOperationResult{ 668 VolumeId: snapshot.VolumeId, 669 }, 670 }, 671 SnapshotId: snapshot.SnapshotId, 672 }) 673 } 674 } 675 } 676 } 677 678 return &cnstypes.CnsVolumeOperationBatchResult{ 679 VolumeResults: snapshotOperationResult, 680 }, nil 681 }) 682 683 return &methods.CnsDeleteSnapshotBody{ 684 Res: &cnstypes.CnsDeleteSnapshotsResponse{ 685 Returnval: task.Run(ctx), 686 }, 687 } 688 } 689 690 func (m *CnsVolumeManager) CnsQuerySnapshots(ctx *simulator.Context, req *cnstypes.CnsQuerySnapshots) soap.HasFault { 691 task := simulator.CreateTask(m, "QuerySnapshots", func(*simulator.Task) (vim25types.AnyType, vim25types.BaseMethodFault) { 692 if len(req.SnapshotQueryFilter.SnapshotQuerySpecs) > 1 { 693 return nil, &vim25types.InvalidArgument{InvalidProperty: "CnsSnapshotQuerySpec"} 694 } 695 696 snapshotQueryResultEntries := []cnstypes.CnsSnapshotQueryResultEntry{} 697 checkVolumeExists := func(volumeId cnstypes.CnsVolumeId) bool { 698 for _, dsVolumes := range m.volumes { 699 for id := range dsVolumes { 700 if id.Id == volumeId.Id { 701 return true 702 } 703 } 704 } 705 return false 706 } 707 708 if req.SnapshotQueryFilter.SnapshotQuerySpecs == nil && len(req.SnapshotQueryFilter.SnapshotQuerySpecs) == 0 { 709 // return all snapshots if snapshotQuerySpecs is empty 710 for _, volSnapshots := range m.snapshots { 711 for _, snapshot := range volSnapshots { 712 snapshotQueryResultEntries = append(snapshotQueryResultEntries, cnstypes.CnsSnapshotQueryResultEntry{Snapshot: *snapshot}) 713 } 714 } 715 } else { 716 // snapshotQuerySpecs is not empty 717 isSnapshotQueryFilter := false 718 snapshotQuerySpec := req.SnapshotQueryFilter.SnapshotQuerySpecs[0] 719 if snapshotQuerySpec.SnapshotId != nil && (*snapshotQuerySpec.SnapshotId != cnstypes.CnsSnapshotId{}) { 720 isSnapshotQueryFilter = true 721 } 722 723 if !checkVolumeExists(snapshotQuerySpec.VolumeId) { 724 // volumeId in snapshotQuerySpecs does not exist 725 snapshotQueryResultEntries = append(snapshotQueryResultEntries, cnstypes.CnsSnapshotQueryResultEntry{ 726 Error: &vim25types.LocalizedMethodFault{ 727 Fault: cnstypes.CnsVolumeNotFoundFault{ 728 VolumeId: snapshotQuerySpec.VolumeId, 729 }, 730 }, 731 }) 732 } else { 733 // volumeId in snapshotQuerySpecs exists 734 for _, snapshot := range m.snapshots[snapshotQuerySpec.VolumeId] { 735 if isSnapshotQueryFilter && snapshot.SnapshotId.Id != (*snapshotQuerySpec.SnapshotId).Id { 736 continue 737 } 738 739 snapshotQueryResultEntries = append(snapshotQueryResultEntries, cnstypes.CnsSnapshotQueryResultEntry{Snapshot: *snapshot}) 740 } 741 742 if isSnapshotQueryFilter && len(snapshotQueryResultEntries) == 0 { 743 snapshotQueryResultEntries = append(snapshotQueryResultEntries, cnstypes.CnsSnapshotQueryResultEntry{ 744 Error: &vim25types.LocalizedMethodFault{ 745 Fault: cnstypes.CnsSnapshotNotFoundFault{ 746 VolumeId: snapshotQuerySpec.VolumeId, 747 SnapshotId: *snapshotQuerySpec.SnapshotId, 748 }, 749 }, 750 }) 751 } 752 } 753 } 754 755 return &cnstypes.CnsSnapshotQueryResult{ 756 Entries: snapshotQueryResultEntries, 757 }, nil 758 }) 759 760 return &methods.CnsQuerySnapshotsBody{ 761 Res: &cnstypes.CnsQuerySnapshotsResponse{ 762 Returnval: task.Run(ctx), 763 }, 764 } 765 }