github.com/vmware/go-vcloud-director/v2@v2.24.0/govcd/vm_affinity_rule.go (about) 1 package govcd 2 3 /* 4 * Copyright 2021 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. 5 */ 6 7 import ( 8 "fmt" 9 "net/http" 10 11 "github.com/vmware/go-vcloud-director/v2/types/v56" 12 ) 13 14 // VmAffinityRule is the govcd structure to deal with VM affinity rules 15 type VmAffinityRule struct { 16 VmAffinityRule *types.VmAffinityRule 17 client *Client 18 } 19 20 // NewVmAffinityRule creates a new VM affinity rule 21 func NewVmAffinityRule(cli *Client) *VmAffinityRule { 22 return &VmAffinityRule{ 23 VmAffinityRule: new(types.VmAffinityRule), 24 client: cli, 25 } 26 } 27 28 // validPolarity validates the polarity passed as a string 29 // Accepted values are only 'Affinity' and 'Anti-Affinity' 30 func validPolarity(polarity string) bool { 31 return polarity == types.PolarityAffinity || polarity == types.PolarityAntiAffinity 32 } 33 34 // GetAllVmAffinityRuleList retrieves all VM affinity and anti-affinity rules 35 func (vdc *Vdc) GetAllVmAffinityRuleList() ([]*types.VmAffinityRule, error) { 36 37 affinityRules := new(types.VmAffinityRules) 38 39 href := vdc.getLinkHref("down", "application/vnd.vmware.vcloud.vmaffinityrules+xml") 40 if href == "" { 41 return nil, fmt.Errorf("no link with VM affinity rule found in VDC %s", vdc.Vdc.Name) 42 } 43 _, err := vdc.client.ExecuteRequest(href, http.MethodGet, 44 "", "error retrieving list of affinity rules: %s", nil, affinityRules) 45 if err != nil { 46 return nil, err 47 } 48 return affinityRules.VmAffinityRule, nil 49 50 } 51 52 // GetVmAffinityRuleList retrieves VM affinity rules 53 func (vdc *Vdc) GetVmAffinityRuleList() ([]*types.VmAffinityRule, error) { 54 return vdc.getSpecificVmAffinityRuleList(types.PolarityAffinity) 55 } 56 57 // GetVmAntiAffinityRuleList retrieves VM anti-affinity rules 58 func (vdc *Vdc) GetVmAntiAffinityRuleList() ([]*types.VmAffinityRule, error) { 59 return vdc.getSpecificVmAffinityRuleList(types.PolarityAntiAffinity) 60 } 61 62 // getSpecificVmAffinityRuleList retrieves specific VM affinity rules 63 func (vdc *Vdc) getSpecificVmAffinityRuleList(polarity string) ([]*types.VmAffinityRule, error) { 64 fullList, err := vdc.GetAllVmAffinityRuleList() 65 if err != nil { 66 return nil, err 67 } 68 69 var returnList []*types.VmAffinityRule 70 for _, rule := range fullList { 71 if rule.Polarity == polarity { 72 returnList = append(returnList, rule) 73 } 74 } 75 76 return returnList, nil 77 } 78 79 // GetVmAffinityRuleByHref finds a VM affinity or anti-affinity rule by HREF 80 func (vdc *Vdc) GetVmAffinityRuleByHref(href string) (*VmAffinityRule, error) { 81 82 affinityRule := NewVmAffinityRule(vdc.client) 83 84 _, err := vdc.client.ExecuteRequest(href, http.MethodGet, 85 "", "error retrieving affinity rule: %s", nil, affinityRule.VmAffinityRule) 86 if err != nil { 87 return nil, err 88 } 89 90 return affinityRule, nil 91 } 92 93 // GetVmAffinityRulesByName finds the rules with the given name 94 // Note that name does not have to be unique, so a search by name can match several items 95 // If polarity is indicated, the function retrieves only the rules with the given polarity 96 func (vdc *Vdc) GetVmAffinityRulesByName(name string, polarity string) ([]*VmAffinityRule, error) { 97 98 var returnList []*VmAffinityRule 99 ruleList, err := vdc.GetAllVmAffinityRuleList() 100 if err != nil { 101 return nil, err 102 } 103 for _, rule := range ruleList { 104 if rule.Name == name { 105 fullRule, err := vdc.GetVmAffinityRuleByHref(rule.HREF) 106 if err != nil { 107 return returnList, err 108 } 109 if (polarity != "" && polarity == rule.Polarity) || polarity == "" { 110 returnList = append(returnList, fullRule) 111 } 112 } 113 } 114 return returnList, nil 115 } 116 117 // GetVmAffinityRuleById retrieves a VM affinity or anti-affinity rule by ID 118 func (vdc *Vdc) GetVmAffinityRuleById(id string) (*VmAffinityRule, error) { 119 120 list, err := vdc.GetAllVmAffinityRuleList() 121 if err != nil { 122 return nil, err 123 } 124 for _, rule := range list { 125 if equalIds(id, rule.ID, rule.HREF) { 126 return vdc.GetVmAffinityRuleByHref(rule.HREF) 127 } 128 } 129 return nil, ErrorEntityNotFound 130 } 131 132 // GetVmAffinityRuleByNameOrId retrieves an affinity or anti-affinity rule by name or ID 133 // Given the possibility of a name identifying multiple items, this function may also fail 134 // when the search by name returns more than one item. 135 func (vdc *Vdc) GetVmAffinityRuleByNameOrId(identifier string) (*VmAffinityRule, error) { 136 getByName := func(name string, refresh bool) (interface{}, error) { 137 list, err := vdc.GetVmAffinityRulesByName(name, "") 138 if err != nil { 139 return nil, err 140 } 141 if len(list) == 0 { 142 return nil, ErrorEntityNotFound 143 } 144 if len(list) == 1 { 145 return list[0], nil 146 } 147 return nil, fmt.Errorf("more than one item matches the name '%s'", name) 148 } 149 getById := func(id string, refresh bool) (interface{}, error) { return vdc.GetVmAffinityRuleById(id) } 150 entity, err := getEntityByNameOrId(getByName, getById, identifier, false) 151 if entity == nil { 152 return nil, err 153 } 154 return entity.(*VmAffinityRule), err 155 } 156 157 // validateAffinityRule checks that a VM affinity rule has all the needed properties 158 // If checkVMs is true, then the function checks that all VMs in the internal list exist. 159 // The usual workflow is: 160 // 1. validation without VM checking 161 // 2. creation or update 162 // 3. if no error -> end 163 // 4. if error, validation with VM checks 164 // 4a. if validation error, it was a VM issue: return combined original error + validation error 165 // 4b. if no validation error, the failure was due to something else: return only original error 166 func validateAffinityRule(client *Client, affinityRuleDef *types.VmAffinityRule, checkVMs bool) (*types.VmAffinityRule, error) { 167 if affinityRuleDef == nil { 168 return nil, fmt.Errorf("empty definition given for a VM affinity rule") 169 } 170 if affinityRuleDef.Name == "" { 171 return nil, fmt.Errorf("no name given for a VM affinity rule") 172 } 173 if affinityRuleDef.Polarity == "" { 174 return nil, fmt.Errorf("no polarity given for a VM affinity rule") 175 } 176 if !validPolarity(affinityRuleDef.Polarity) { 177 return nil, fmt.Errorf("illegal polarity given (%s) for a VM affinity rule", affinityRuleDef.Polarity) 178 } 179 // Ensure the VMs in the list are different 180 var seenVms = make(map[string]bool) 181 var allVmMap = make(map[string]bool) 182 if checkVMs { 183 vmList, err := client.QueryVmList(types.VmQueryFilterOnlyDeployed) 184 if err != nil { 185 return nil, fmt.Errorf("error getting VM list : %s", err) 186 } 187 for _, vm := range vmList { 188 allVmMap[extractUuid(vm.HREF)] = true 189 } 190 } 191 for _, vmr := range affinityRuleDef.VmReferences { 192 if len(vmr.VMReference) == 0 { 193 continue 194 } 195 for _, vm := range vmr.VMReference { 196 if vm == nil { 197 continue 198 } 199 // The only mandatory field is the HREF 200 if vm.HREF == "" { 201 return nil, fmt.Errorf("empty VM HREF provided in VM list") 202 } 203 _, seen := seenVms[vm.HREF] 204 if seen { 205 return nil, fmt.Errorf("VM HREF %s used more than once", vm.HREF) 206 } 207 seenVms[vm.HREF] = true 208 209 if checkVMs { 210 // Checking that the VMs indicated exist. 211 // Without this check, if any of the VMs do not exist, we would get an ugly error that doesn't easily explain 212 // the nature of the problem, such as 213 // > "error instantiating a new VM affinity rule: API Error: 403: [ ... ] 214 // > Either you need some or all of the following rights [ORG_VDC_VM_VM_AFFINITY_EDIT] 215 // > to perform operations [VAPP_VM_EDIT_AFFINITY_RULE] for $OP_ID or the target entity is invalid" 216 217 _, vmInList := allVmMap[extractUuid(vm.HREF)] 218 if !vmInList { 219 return nil, fmt.Errorf("VM identified by '%s' not found ", vm.HREF) 220 } 221 } 222 } 223 } 224 if len(seenVms) < 2 { 225 return nil, fmt.Errorf("at least 2 VMs should be given for a VM Affinity Rule") 226 } 227 return affinityRuleDef, nil 228 } 229 230 // CreateVmAffinityRuleAsync creates a new VM affinity rule, and returns a task that handles the operation 231 func (vdc *Vdc) CreateVmAffinityRuleAsync(affinityRuleDef *types.VmAffinityRule) (Task, error) { 232 233 var err error 234 // We validate the input, without a strict check on the VMs 235 affinityRuleDef, err = validateAffinityRule(vdc.client, affinityRuleDef, false) 236 if err != nil { 237 return Task{}, fmt.Errorf("[CreateVmAffinityRuleAsync] %s", err) 238 } 239 240 affinityRuleDef.Xmlns = types.XMLNamespaceVCloud 241 242 href := vdc.getLinkHref("add", "application/vnd.vmware.vcloud.vmaffinityrule+xml") 243 if href == "" { 244 return Task{}, fmt.Errorf("no link with VM affinity rule found in VDC %s", vdc.Vdc.Name) 245 } 246 247 task, err := vdc.client.ExecuteTaskRequest(href, http.MethodPost, 248 "application/vnd.vmware.vcloud.vmaffinityrule+xml", "error instantiating a new VM affinity rule: %s", affinityRuleDef) 249 if err != nil { 250 // if we get any error, we repeat the validation 251 // with a strict check on VM existence. 252 _, validationErr := validateAffinityRule(vdc.client, affinityRuleDef, true) 253 if validationErr != nil { 254 // If we get any error from the validation now, it should be an invalid VM, 255 // so we combine the original error with the validation error 256 return Task{}, fmt.Errorf("%s - %s", err, validationErr) 257 } 258 // If the validation error is nil, we return just the original error 259 return Task{}, err 260 } 261 return task, err 262 } 263 264 // CreateVmAffinityRule is a wrap around CreateVmAffinityRuleAsync that handles the task and returns the finished object 265 func (vdc *Vdc) CreateVmAffinityRule(affinityRuleDef *types.VmAffinityRule) (*VmAffinityRule, error) { 266 267 task, err := vdc.CreateVmAffinityRuleAsync(affinityRuleDef) 268 if err != nil { 269 return nil, err 270 } 271 // The rule ID is the ID of the task owner (see Task definition in types.go) 272 ruleId := task.Task.Owner.ID 273 274 err = task.WaitTaskCompletion() 275 if err != nil { 276 return nil, err 277 } 278 279 // Retrieving the newly created rule using the ID from the task 280 vmAffinityRule, err := vdc.GetVmAffinityRuleById(ruleId) 281 if err != nil { 282 return nil, fmt.Errorf("error retrieving VmAffinityRule %s using ID %s: %s", affinityRuleDef.Name, ruleId, err) 283 } 284 return vmAffinityRule, nil 285 } 286 287 // Delete removes a VM affinity rule from vCD 288 func (vmar *VmAffinityRule) Delete() error { 289 290 if vmar == nil || vmar.VmAffinityRule == nil { 291 return fmt.Errorf("nil VM Affinity Rule passed for deletion") 292 } 293 294 if vmar.VmAffinityRule.HREF == "" { 295 return fmt.Errorf("VM Affinity Rule passed for deletion has no HREF") 296 } 297 298 deleteHref := vmar.VmAffinityRule.HREF 299 linkHref := vmar.getLinkHref("remove") 300 if linkHref != "" { 301 deleteHref = linkHref 302 } 303 304 deleteTask, err := vmar.client.ExecuteTaskRequest(deleteHref, http.MethodDelete, 305 "", "error removing VM Affinity Rule : %s", nil) 306 if err != nil { 307 return err 308 } 309 return deleteTask.WaitTaskCompletion() 310 } 311 312 // getLinkHref returns an HREF for a given value of Rel 313 func (vmar *VmAffinityRule) getLinkHref(rel string) string { 314 if vmar.VmAffinityRule.Link != nil { 315 for _, link := range vmar.VmAffinityRule.Link { 316 if link.Rel == rel { 317 return link.HREF 318 } 319 } 320 } 321 return "" 322 } 323 324 // Update modifies a VM affinity rule using as input 325 // the entity's internal data. 326 func (vmar *VmAffinityRule) Update() error { 327 var err error 328 var affinityRuleDef *types.VmAffinityRule 329 330 if vmar == nil || vmar.VmAffinityRule == nil { 331 return fmt.Errorf("nil VM Affinity Rule passed for update") 332 } 333 if vmar.VmAffinityRule.HREF == "" { 334 return fmt.Errorf("VM Affinity Rule passed for update has no HREF") 335 } 336 337 // We validate the input, without a strict check on the VMs 338 affinityRuleDef, err = validateAffinityRule(vmar.client, vmar.VmAffinityRule, false) 339 if err != nil { 340 return fmt.Errorf("[Update] %s", err) 341 } 342 vmar.VmAffinityRule = affinityRuleDef 343 344 updateRef := vmar.VmAffinityRule.HREF 345 linkHref := vmar.getLinkHref("edit") 346 if linkHref != "" { 347 updateRef = linkHref 348 } 349 350 vmar.VmAffinityRule.Link = nil 351 vmar.VmAffinityRule.VCloudExtension = nil 352 updateTask, err := vmar.client.ExecuteTaskRequest(updateRef, http.MethodPut, 353 "", "error updating VM Affinity Rule : %s", vmar.VmAffinityRule) 354 if err != nil { 355 // if we get any error, we repeat the validation 356 // with a strict check on VM existence. 357 _, validationErr := validateAffinityRule(vmar.client, affinityRuleDef, true) 358 // If we get any error from the validation now, it should be an invalid VM, 359 // so we combine the original error with the validation error 360 if validationErr != nil { 361 return fmt.Errorf("%s - %s", err, validationErr) 362 } 363 // If the validation error is nil, we return just the original error 364 return err 365 } 366 err = updateTask.WaitTaskCompletion() 367 if err != nil { 368 return err 369 } 370 return vmar.Refresh() 371 } 372 373 // Refresh gets a fresh copy of the VM affinity rule from vCD 374 func (vmar *VmAffinityRule) Refresh() error { 375 var newVmAffinityRule types.VmAffinityRule 376 _, err := vmar.client.ExecuteRequest(vmar.VmAffinityRule.HREF, http.MethodGet, 377 "", "error retrieving affinity rule: %v", nil, &newVmAffinityRule) 378 if err != nil { 379 return err 380 } 381 vmar.VmAffinityRule = &newVmAffinityRule 382 return nil 383 } 384 385 // SetEnabled is a shortcut to update only the IsEnabled property of a VM affinity rule 386 func (vmar *VmAffinityRule) SetEnabled(value bool) error { 387 if vmar.VmAffinityRule.IsEnabled != nil { 388 currentValue := *vmar.VmAffinityRule.IsEnabled 389 if currentValue == value { 390 return nil 391 } 392 } 393 vmar.VmAffinityRule.IsEnabled = &value 394 return vmar.Update() 395 } 396 397 // SetMandatory is a shortcut to update only the IsMandatory property of a VM affinity rule 398 func (vmar *VmAffinityRule) SetMandatory(value bool) error { 399 if vmar.VmAffinityRule.IsMandatory != nil { 400 currentValue := *vmar.VmAffinityRule.IsMandatory 401 if currentValue == value { 402 return nil 403 } 404 } 405 vmar.VmAffinityRule.IsMandatory = &value 406 return vmar.Update() 407 }