github.com/vmware/govmomi@v0.51.0/simulator/vstorage_object_manager.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 "log" 9 "net/url" 10 "os" 11 "strings" 12 "time" 13 14 "github.com/google/uuid" 15 16 "github.com/vmware/govmomi/object" 17 "github.com/vmware/govmomi/vim25/methods" 18 "github.com/vmware/govmomi/vim25/mo" 19 "github.com/vmware/govmomi/vim25/soap" 20 "github.com/vmware/govmomi/vim25/types" 21 ) 22 23 type VStorageObject struct { 24 types.VStorageObject 25 types.VStorageObjectSnapshotInfo 26 Metadata []types.KeyValue 27 } 28 29 type VcenterVStorageObjectManager struct { 30 mo.VcenterVStorageObjectManager 31 32 objects map[types.ManagedObjectReference]map[types.ID]*VStorageObject 33 } 34 35 func (m *VcenterVStorageObjectManager) init(*Registry) { 36 m.objects = make(map[types.ManagedObjectReference]map[types.ID]*VStorageObject) 37 } 38 39 func (m *VcenterVStorageObjectManager) object(ds types.ManagedObjectReference, id types.ID) *VStorageObject { 40 if objects, ok := m.objects[ds]; ok { 41 return objects[id] 42 } 43 return nil 44 } 45 46 func (m *VcenterVStorageObjectManager) Catalog() map[types.ManagedObjectReference]map[types.ID]*VStorageObject { 47 return m.objects 48 } 49 50 func (m *VcenterVStorageObjectManager) ListVStorageObject(req *types.ListVStorageObject) soap.HasFault { 51 body := &methods.ListVStorageObjectBody{ 52 Res: &types.ListVStorageObjectResponse{}, 53 } 54 55 if objects, ok := m.objects[req.Datastore]; ok { 56 for id := range objects { 57 body.Res.Returnval = append(body.Res.Returnval, id) 58 } 59 } 60 61 return body 62 } 63 64 func (m *VcenterVStorageObjectManager) RetrieveVStorageObject(ctx *Context, req *types.RetrieveVStorageObject) soap.HasFault { 65 body := new(methods.RetrieveVStorageObjectBody) 66 67 obj := m.object(req.Datastore, req.Id) 68 if obj == nil { 69 body.Fault_ = Fault("", new(types.NotFound)) 70 } else { 71 stat := m.statDatastoreBacking(ctx, req.Datastore, &req.Id) 72 if err := stat[req.Id]; err != nil { 73 body.Fault_ = Fault(err.Error(), new(types.NotFound)) 74 return body 75 } 76 body.Res = &types.RetrieveVStorageObjectResponse{ 77 Returnval: obj.VStorageObject, 78 } 79 } 80 81 return body 82 } 83 84 // statDatastoreBacking checks if object(s) backing file exists on the given datastore ref. 85 func (m *VcenterVStorageObjectManager) statDatastoreBacking(ctx *Context, ref types.ManagedObjectReference, id *types.ID) map[types.ID]error { 86 objs := m.objects[ref] // default to checking all objects 87 if id != nil { 88 // check for a specific object 89 objs = map[types.ID]*VStorageObject{ 90 *id: objs[*id], 91 } 92 } 93 res := make(map[types.ID]error, len(objs)) 94 ds := ctx.Map.Get(ref).(*Datastore) 95 dc := ctx.Map.getEntityDatacenter(ds) 96 fm := ctx.Map.FileManager() 97 98 for _, obj := range objs { 99 backing := obj.Config.Backing.(*types.BaseConfigInfoDiskFileBackingInfo) 100 file, _ := fm.resolve(ctx, &dc.Self, backing.FilePath) 101 _, res[obj.Config.Id] = os.Stat(file) 102 } 103 104 return res 105 } 106 107 func (m *VcenterVStorageObjectManager) ReconcileDatastoreInventoryTask(ctx *Context, req *types.ReconcileDatastoreInventory_Task) soap.HasFault { 108 task := CreateTask(m, "reconcileDatastoreInventory", func(*Task) (types.AnyType, types.BaseMethodFault) { 109 objs := m.objects[req.Datastore] 110 stat := m.statDatastoreBacking(ctx, req.Datastore, nil) 111 112 for id, err := range stat { 113 if os.IsNotExist(err) { 114 log.Printf("removing disk %s from inventory: %s", id.Id, err) 115 delete(objs, id) 116 } 117 } 118 119 return nil, nil 120 }) 121 122 return &methods.ReconcileDatastoreInventory_TaskBody{ 123 Res: &types.ReconcileDatastoreInventory_TaskResponse{ 124 Returnval: task.Run(ctx), 125 }, 126 } 127 } 128 129 func (m *VcenterVStorageObjectManager) RegisterDisk(ctx *Context, req *types.RegisterDisk) soap.HasFault { 130 body := new(methods.RegisterDiskBody) 131 132 invalid := func() soap.HasFault { 133 body.Fault_ = Fault("", &types.InvalidArgument{InvalidProperty: "path"}) 134 return body 135 } 136 137 u, err := url.Parse(req.Path) 138 if err != nil { 139 return invalid() 140 } 141 u.Path = strings.TrimPrefix(u.Path, folderPrefix) 142 143 ds, err := ctx.svc.findDatastore(u.Query()) 144 if err != nil { 145 return invalid() 146 } 147 148 st, err := os.Stat(ds.resolve(ctx, u.Path)) 149 if err != nil { 150 return invalid() 151 } 152 if st.IsDir() { 153 return invalid() 154 } 155 156 path := (&object.DatastorePath{Datastore: ds.Name, Path: u.Path}).String() 157 158 for _, obj := range m.objects[ds.Self] { 159 backing := obj.Config.Backing.(*types.BaseConfigInfoDiskFileBackingInfo) 160 if backing.FilePath == path { 161 return invalid() 162 } 163 } 164 165 creq := &types.CreateDisk_Task{ 166 Spec: types.VslmCreateSpec{ 167 Name: req.Name, 168 BackingSpec: &types.VslmCreateSpecDiskFileBackingSpec{ 169 VslmCreateSpecBackingSpec: types.VslmCreateSpecBackingSpec{ 170 Datastore: ds.Self, 171 Path: u.Path, 172 }, 173 }, 174 }, 175 } 176 177 obj, fault := m.createObject(ctx, creq, true) 178 if fault != nil { 179 body.Fault_ = Fault("", fault) 180 return body 181 } 182 183 body.Res = &types.RegisterDiskResponse{ 184 Returnval: *obj, 185 } 186 187 return body 188 } 189 190 func (m *VcenterVStorageObjectManager) createObject(ctx *Context, req *types.CreateDisk_Task, register bool) (*types.VStorageObject, types.BaseMethodFault) { 191 dir := "fcd" 192 ref := req.Spec.BackingSpec.GetVslmCreateSpecBackingSpec().Datastore 193 ds := ctx.Map.Get(ref).(*Datastore) 194 dc := ctx.Map.getEntityDatacenter(ds) 195 196 objects, ok := m.objects[ds.Self] 197 if !ok { 198 objects = make(map[types.ID]*VStorageObject) 199 m.objects[ds.Self] = objects 200 _ = os.MkdirAll(ds.resolve(ctx, dir), 0750) 201 } 202 203 id := uuid.New().String() 204 obj := types.VStorageObject{ 205 Config: types.VStorageObjectConfigInfo{ 206 BaseConfigInfo: types.BaseConfigInfo{ 207 Id: types.ID{ 208 Id: id, 209 }, 210 Name: req.Spec.Name, 211 CreateTime: time.Now(), 212 KeepAfterDeleteVm: req.Spec.KeepAfterDeleteVm, 213 RelocationDisabled: types.NewBool(false), 214 NativeSnapshotSupported: types.NewBool(false), 215 ChangedBlockTrackingEnabled: types.NewBool(false), 216 Iofilter: nil, 217 }, 218 CapacityInMB: req.Spec.CapacityInMB, 219 ConsumptionType: []string{"disk"}, 220 ConsumerId: nil, 221 }, 222 } 223 224 backing := req.Spec.BackingSpec.(*types.VslmCreateSpecDiskFileBackingSpec) 225 path := object.DatastorePath{ 226 Datastore: ds.Name, 227 Path: backing.Path, 228 } 229 if path.Path == "" { 230 path.Path = dir + "/" + id + ".vmdk" 231 } 232 233 if !register { 234 err := vdmCreateVirtualDisk(ctx, types.VirtualDeviceConfigSpecFileOperationCreate, &types.CreateVirtualDisk_Task{ 235 Datacenter: &dc.Self, 236 Name: path.String(), 237 Spec: &types.FileBackedVirtualDiskSpec{CapacityKb: req.Spec.CapacityInMB * 1024}, 238 }) 239 if err != nil { 240 return nil, err 241 } 242 } 243 244 obj.Config.Backing = &types.BaseConfigInfoDiskFileBackingInfo{ 245 BaseConfigInfoFileBackingInfo: types.BaseConfigInfoFileBackingInfo{ 246 BaseConfigInfoBackingInfo: types.BaseConfigInfoBackingInfo{ 247 Datastore: ds.Self, 248 }, 249 FilePath: path.String(), 250 BackingObjectId: uuid.New().String(), 251 Parent: nil, 252 DeltaSizeInMB: 0, 253 }, 254 ProvisioningType: backing.ProvisioningType, 255 } 256 257 objects[obj.Config.Id] = &VStorageObject{VStorageObject: obj} 258 259 return &obj, nil 260 261 } 262 263 func (m *VcenterVStorageObjectManager) CreateDiskTask(ctx *Context, req *types.CreateDisk_Task) soap.HasFault { 264 task := CreateTask(m, "createDisk", func(*Task) (types.AnyType, types.BaseMethodFault) { 265 return m.createObject(ctx, req, false) 266 }) 267 268 return &methods.CreateDisk_TaskBody{ 269 Res: &types.CreateDisk_TaskResponse{ 270 Returnval: task.Run(ctx), 271 }, 272 } 273 } 274 275 func (m *VcenterVStorageObjectManager) DeleteVStorageObjectTask(ctx *Context, req *types.DeleteVStorageObject_Task) soap.HasFault { 276 task := CreateTask(m, "deleteDisk", func(*Task) (types.AnyType, types.BaseMethodFault) { 277 obj := m.object(req.Datastore, req.Id) 278 if obj == nil { 279 return nil, &types.InvalidArgument{} 280 } 281 282 if len(obj.Config.ConsumerId) != 0 { 283 return nil, &types.InvalidState{} 284 } 285 286 backing := obj.Config.Backing.(*types.BaseConfigInfoDiskFileBackingInfo) 287 ds := ctx.Map.Get(req.Datastore).(*Datastore) 288 dc := ctx.Map.getEntityDatacenter(ds) 289 dm := ctx.Map.VirtualDiskManager() 290 dm.DeleteVirtualDiskTask(ctx, &types.DeleteVirtualDisk_Task{ 291 Name: backing.FilePath, 292 Datacenter: &dc.Self, 293 }) 294 295 delete(m.objects[req.Datastore], req.Id) 296 297 return nil, nil 298 }) 299 300 return &methods.DeleteVStorageObject_TaskBody{ 301 Res: &types.DeleteVStorageObject_TaskResponse{ 302 Returnval: task.Run(ctx), 303 }, 304 } 305 } 306 307 func (m *VcenterVStorageObjectManager) RetrieveSnapshotInfo(req *types.RetrieveSnapshotInfo) soap.HasFault { 308 body := new(methods.RetrieveSnapshotInfoBody) 309 310 obj := m.object(req.Datastore, req.Id) 311 if obj == nil { 312 body.Fault_ = Fault("", new(types.InvalidArgument)) 313 } else { 314 body.Res = &types.RetrieveSnapshotInfoResponse{ 315 Returnval: obj.VStorageObjectSnapshotInfo, 316 } 317 } 318 319 return body 320 } 321 322 func (m *VcenterVStorageObjectManager) VStorageObjectCreateSnapshotTask(ctx *Context, req *types.VStorageObjectCreateSnapshot_Task) soap.HasFault { 323 task := CreateTask(m, "createSnapshot", func(*Task) (types.AnyType, types.BaseMethodFault) { 324 obj := m.object(req.Datastore, req.Id) 325 if obj == nil { 326 return nil, new(types.InvalidArgument) 327 } 328 329 snapshot := types.VStorageObjectSnapshotInfoVStorageObjectSnapshot{ 330 Id: &types.ID{ 331 Id: uuid.New().String(), 332 }, 333 BackingObjectId: uuid.New().String(), 334 CreateTime: time.Now(), 335 Description: req.Description, 336 } 337 obj.Snapshots = append(obj.Snapshots, snapshot) 338 339 return snapshot.Id, nil 340 }) 341 342 return &methods.VStorageObjectCreateSnapshot_TaskBody{ 343 Res: &types.VStorageObjectCreateSnapshot_TaskResponse{ 344 Returnval: task.Run(ctx), 345 }, 346 } 347 } 348 349 func (m *VcenterVStorageObjectManager) ExtendDiskTask(ctx *Context, req *types.ExtendDisk_Task) soap.HasFault { 350 task := CreateTask(m, "extendDisk", func(*Task) (types.AnyType, types.BaseMethodFault) { 351 obj := m.object(req.Datastore, req.Id) 352 if obj == nil { 353 return nil, new(types.InvalidArgument) 354 } 355 356 obj.Config.CapacityInMB = req.NewCapacityInMB 357 return nil, nil 358 }) 359 return &methods.ExtendDisk_TaskBody{ 360 Res: &types.ExtendDisk_TaskResponse{ 361 Returnval: task.Run(ctx), 362 }, 363 } 364 } 365 366 func (m *VcenterVStorageObjectManager) DeleteSnapshotTask(ctx *Context, req *types.DeleteSnapshot_Task) soap.HasFault { 367 task := CreateTask(m, "deleteSnapshot", func(*Task) (types.AnyType, types.BaseMethodFault) { 368 obj := m.object(req.Datastore, req.Id) 369 if obj != nil { 370 for i := range obj.Snapshots { 371 if *obj.Snapshots[i].Id == req.SnapshotId { 372 obj.Snapshots = append(obj.Snapshots[:i], obj.Snapshots[i+1:]...) 373 return nil, nil 374 } 375 } 376 } 377 return nil, new(types.InvalidArgument) 378 }) 379 380 return &methods.DeleteSnapshot_TaskBody{ 381 Res: &types.DeleteSnapshot_TaskResponse{ 382 Returnval: task.Run(ctx), 383 }, 384 } 385 } 386 387 func (m *VcenterVStorageObjectManager) tagID(id types.ID) types.ManagedObjectReference { 388 return types.ManagedObjectReference{ 389 Type: "fcd", 390 Value: id.Id, 391 } 392 } 393 394 func (m *VcenterVStorageObjectManager) AttachTagToVStorageObject(ctx *Context, req *types.AttachTagToVStorageObject) soap.HasFault { 395 body := new(methods.AttachTagToVStorageObjectBody) 396 ref := m.tagID(req.Id) 397 398 err := ctx.Map.tagManager.AttachTag(ref, types.VslmTagEntry{ 399 ParentCategoryName: req.Category, 400 TagName: req.Tag, 401 }) 402 403 if err == nil { 404 body.Res = new(types.AttachTagToVStorageObjectResponse) 405 } else { 406 body.Fault_ = Fault("", err) 407 } 408 409 return body 410 } 411 412 func (m *VcenterVStorageObjectManager) DetachTagFromVStorageObject(ctx *Context, req *types.DetachTagFromVStorageObject) soap.HasFault { 413 body := new(methods.DetachTagFromVStorageObjectBody) 414 ref := m.tagID(req.Id) 415 416 err := ctx.Map.tagManager.DetachTag(ref, types.VslmTagEntry{ 417 ParentCategoryName: req.Category, 418 TagName: req.Tag, 419 }) 420 421 if err == nil { 422 body.Res = new(types.DetachTagFromVStorageObjectResponse) 423 } else { 424 body.Fault_ = Fault("", err) 425 } 426 427 return body 428 } 429 430 func (m *VcenterVStorageObjectManager) ListVStorageObjectsAttachedToTag(ctx *Context, req *types.ListVStorageObjectsAttachedToTag) soap.HasFault { 431 body := new(methods.ListVStorageObjectsAttachedToTagBody) 432 433 refs, err := ctx.Map.tagManager.AttachedObjects(types.VslmTagEntry{ 434 ParentCategoryName: req.Category, 435 TagName: req.Tag, 436 }) 437 438 if err == nil { 439 body.Res = new(types.ListVStorageObjectsAttachedToTagResponse) 440 for _, ref := range refs { 441 body.Res.Returnval = append(body.Res.Returnval, types.ID{Id: ref.Value}) 442 } 443 } else { 444 body.Fault_ = Fault("", err) 445 } 446 447 return body 448 } 449 450 func (m *VcenterVStorageObjectManager) ListTagsAttachedToVStorageObject(ctx *Context, req *types.ListTagsAttachedToVStorageObject) soap.HasFault { 451 body := new(methods.ListTagsAttachedToVStorageObjectBody) 452 ref := m.tagID(req.Id) 453 454 tags, err := ctx.Map.tagManager.AttachedTags(ref) 455 456 if err == nil { 457 body.Res = &types.ListTagsAttachedToVStorageObjectResponse{ 458 Returnval: tags, 459 } 460 } else { 461 body.Fault_ = Fault("", err) 462 } 463 464 return body 465 } 466 467 func (m *VcenterVStorageObjectManager) VCenterUpdateVStorageObjectMetadataExTask(ctx *Context, req *types.VCenterUpdateVStorageObjectMetadataEx_Task) soap.HasFault { 468 task := CreateTask(m, "updateVStorageObjectMetadataEx", func(*Task) (types.AnyType, types.BaseMethodFault) { 469 obj := m.object(req.Datastore, req.Id) 470 if obj == nil { 471 return nil, new(types.InvalidArgument) 472 } 473 474 var metadata []types.KeyValue 475 476 remove := func(key string) bool { 477 for _, dk := range req.DeleteKeys { 478 if key == dk { 479 return true 480 } 481 } 482 return false 483 } 484 485 for _, kv := range obj.Metadata { 486 if !remove(kv.Key) { 487 metadata = append(metadata, kv) 488 } 489 } 490 491 update := func(kv types.KeyValue) bool { 492 for i := range obj.Metadata { 493 if obj.Metadata[i].Key == kv.Key { 494 obj.Metadata[i] = kv 495 return true 496 } 497 } 498 return false 499 } 500 501 for _, kv := range req.Metadata { 502 if !update(kv) { 503 metadata = append(metadata, kv) 504 } 505 } 506 507 obj.Metadata = metadata 508 509 return nil, nil 510 }) 511 512 return &methods.VCenterUpdateVStorageObjectMetadataEx_TaskBody{ 513 Res: &types.VCenterUpdateVStorageObjectMetadataEx_TaskResponse{ 514 Returnval: task.Run(ctx), 515 }, 516 } 517 }