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  }