From b2715c4c1c1643ad0e091bacebf050fc93d45d7f Mon Sep 17 00:00:00 2001 From: Kobi Samoray Date: Tue, 9 Apr 2024 11:06:17 +0300 Subject: [PATCH 1/3] Add custom groups support to upgrade run resource Add the logic to create and update custom host groups while running the upgrade process. Signed-off-by: Kobi Samoray --- nsxt/resource_nsxt_edge_transport_node.go | 35 +--- nsxt/resource_nsxt_upgrade_run.go | 185 ++++++++++++++++++++-- nsxt/utils.go | 44 +++++ website/docs/r/upgrade_run.html.markdown | 9 +- 4 files changed, 230 insertions(+), 43 deletions(-) diff --git a/nsxt/resource_nsxt_edge_transport_node.go b/nsxt/resource_nsxt_edge_transport_node.go index 07f39a29a..2741a644b 100644 --- a/nsxt/resource_nsxt_edge_transport_node.go +++ b/nsxt/resource_nsxt_edge_transport_node.go @@ -303,23 +303,7 @@ func getEdgeNodeSettingsSchema() *schema.Schema { Required: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "advanced_configuration": { - Type: schema.TypeList, - Optional: true, - Description: "Advanced configuration", - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": { - Type: schema.TypeString, - Required: true, - }, - "value": { - Type: schema.TypeString, - Required: true, - }, - }, - }, - }, + "advanced_configuration": getKeyValuePairListSchema(), "allow_ssh_root_login": { Type: schema.TypeBool, Default: false, @@ -858,13 +842,7 @@ func getEdgeNodeSettingsFromSchema(s interface{}) (*model.EdgeNodeSettings, erro settings := s.([]interface{}) for _, settingIf := range settings { setting := settingIf.(map[string]interface{}) - var advCfg []model.KeyValuePair - for _, aci := range setting["advanced_configuration"].([]interface{}) { - ac := aci.(map[string]interface{}) - key := ac["key"].(string) - val := ac["value"].(string) - advCfg = append(advCfg, model.KeyValuePair{Key: &key, Value: &val}) - } + advCfg := getKeyValuePairListFromSchema(setting["advanced_configuration"]) allowSSHRootLogin := setting["allow_ssh_root_login"].(bool) dnsServers := interface2StringList(setting["dns_servers"].([]interface{})) enableSSH := setting["enable_ssh"].(bool) @@ -1255,14 +1233,7 @@ func resourceNsxtEdgeTransportNodeRead(d *schema.ResourceData, m interface{}) er func setEdgeNodeSettingsInSchema(d *schema.ResourceData, nodeSettings *model.EdgeNodeSettings) error { elem := getElemOrEmptyMapFromSchema(d, "node_settings") - var advCfg []map[string]interface{} - for _, kv := range nodeSettings.AdvancedConfiguration { - e := make(map[string]interface{}) - e["key"] = kv.Key - e["value"] = kv.Value - advCfg = append(advCfg, e) - } - elem["advanced_configuration"] = advCfg + elem["advanced_configuration"] = setKeyValueListForSchema(nodeSettings.AdvancedConfiguration) elem["allow_ssh_root_login"] = nodeSettings.AllowSshRootLogin elem["dns_servers"] = nodeSettings.DnsServers elem["enable_ssh"] = nodeSettings.EnableSsh diff --git a/nsxt/resource_nsxt_upgrade_run.go b/nsxt/resource_nsxt_upgrade_run.go index 68da107a1..9483c01d1 100644 --- a/nsxt/resource_nsxt_upgrade_run.go +++ b/nsxt/resource_nsxt_upgrade_run.go @@ -11,13 +11,18 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/vmware/vsphere-automation-sdk-go/lib/vapi/std/errors" + "github.com/vmware/vsphere-automation-sdk-go/runtime/bindings" "github.com/vmware/vsphere-automation-sdk-go/runtime/protocol/client" "github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp/nsx" "github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp/nsx/model" "github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp/nsx/upgrade" "github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp/nsx/upgrade/plan" + "golang.org/x/exp/slices" ) +const hostUpgradeUnitDefaultGroup = "Group 1 for ESXI" + // Order matters var upgradeComponentList = []string{ edgeUpgradeGroup, @@ -232,7 +237,8 @@ func getUpgradeGroupSchema(isHostGroup bool) *schema.Schema { "id": { Type: schema.TypeString, Description: "ID of upgrade unit group", - Required: true, + Required: !isHostGroup, + Optional: isHostGroup, }, "enabled": { Type: schema.TypeBool, @@ -255,6 +261,11 @@ func getUpgradeGroupSchema(isHostGroup bool) *schema.Schema { } if isHostGroup { + elemSchema["display_name"] = &schema.Schema{ + Type: schema.TypeString, + Description: "Name of upgrade unit group", + Optional: true, + } elemSchema["upgrade_mode"] = &schema.Schema{ Type: schema.TypeString, Description: "Upgrade mode", @@ -279,6 +290,15 @@ func getUpgradeGroupSchema(isHostGroup bool) *schema.Schema { Optional: true, Default: true, } + elemSchema["hosts"] = &schema.Schema{ + Type: schema.TypeList, + Description: "Hosts to be included in the upgrade group", + Optional: true, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + } } return &schema.Schema{ @@ -340,7 +360,7 @@ func upgradeRunCreateOrUpdate(d *schema.ResourceData, m interface{}) error { upgradeClientSet := newUpgradeClientSet(connector, d) log.Printf("[INFO] Updating UpgradeUnitGroup and UpgradePlanSetting.") - err := prepareUpgrade(upgradeClientSet, d) + err := prepareUpgrade(upgradeClientSet, d, m) if err != nil { return handleCreateError("NsxtUpgradeRun", id, err) } @@ -358,7 +378,7 @@ func upgradeRunCreateOrUpdate(d *schema.ResourceData, m interface{}) error { return resourceNsxtUpgradeRunRead(d, m) } -func prepareUpgrade(upgradeClientSet *upgradeClientSet, d *schema.ResourceData) error { +func prepareUpgrade(upgradeClientSet *upgradeClientSet, d *schema.ResourceData, m interface{}) error { for i := range upgradeComponentList { component := upgradeComponentList[i] // Customize MP upgrade is not allowed @@ -395,7 +415,7 @@ func prepareUpgrade(upgradeClientSet *upgradeClientSet, d *schema.ResourceData) return err } - err = updateUpgradeUnitGroups(upgradeClientSet, d, component) + err = updateUpgradeUnitGroups(upgradeClientSet, d, m, component) if err != nil { return err } @@ -490,7 +510,7 @@ func waitUpgradeForStatus(upgradeClientSet *upgradeClientSet, component *string, return nil } -func updateUpgradeUnitGroups(upgradeClientSet *upgradeClientSet, d *schema.ResourceData, component string) error { +func updateUpgradeUnitGroups(upgradeClientSet *upgradeClientSet, d *schema.ResourceData, m interface{}, component string) error { isBefore := false getReorderAfterReq := func(id string) model.ReorderRequest { return model.ReorderRequest{ @@ -503,9 +523,79 @@ func updateUpgradeUnitGroups(upgradeClientSet *upgradeClientSet, d *schema.Resou for _, groupI := range d.Get(componentToGroupKey[component]).([]interface{}) { group := groupI.(map[string]interface{}) groupID := group["id"].(string) - groupGet, err := upgradeClientSet.GroupClient.Get(groupID, nil) - if err != nil { - return err + var groupGet *model.UpgradeUnitGroup + var err error + isCreate := false + if groupID == "" { + groupName := group["display_name"].(string) + if groupName == "" { + return fmt.Errorf("couldn't find upgrade unit group without id or display_name") + } + // This is a custom group, try to find it by name + groupList, err := upgradeClientSet.GroupClient.List(nil, nil, nil, nil, nil, nil, nil, nil) + if err != nil { + return err + } + for i, group := range groupList.Results { + if *group.DisplayName == groupName { + if groupGet == nil { + groupID = *group.Id + groupGet = &groupList.Results[i] + } else { + return fmt.Errorf("upgrade group name %s is not unique", groupName) + } + } + } + if groupGet == nil { + // This is a new custom group, create an upgrade unit list + isCreate = true + var groupMembers []model.UpgradeUnit + if group["hosts"] != nil { + for _, h := range group["hosts"].([]interface{}) { + hostID := h.(string) + groupMembers = append(groupMembers, model.UpgradeUnit{Id: &hostID}) + } + } + typeHost := "HOST" + groupGet = &model.UpgradeUnitGroup{DisplayName: &groupName, UpgradeUnits: groupMembers, Type_: &typeHost} + } else { + // This custom group might be updated, compare the upgrade unit lists + nsxUnits := getUnitIDsFromUnits(groupGet.UpgradeUnits) + + // Find and remove upgrade units which have been removed from the list. This is done by assigning the + // upgrade unit to its default group + var schemaUnits []string + if group["hosts"] != nil { + schemaUnits = interface2StringList(group["hosts"].([]interface{})) + } + for _, nsxUnit := range nsxUnits { + if !slices.Contains(schemaUnits, nsxUnit) { + groupID, err := getHostDefaultUpgradeGroup(m, nsxUnit) + if isNotFoundError(err) { + return fmt.Errorf("couldn't find default group for host %s as default group was not found", nsxUnit) + } else if err != nil { + return handleUpdateError("Host Upgrade Group", nsxUnit, err) + } + err = addHostToGroup(m, groupID, nsxUnit, false) + if err != nil { + return handleUpdateError("Host Upgrade Group", nsxUnit, err) + } + } + } + // Replace the upgrade unit list + var groupMembers []model.UpgradeUnit + for _, h := range group["hosts"].([]interface{}) { + hostID := h.(string) + groupMembers = append(groupMembers, model.UpgradeUnit{Id: &hostID}) + } + groupGet.UpgradeUnits = groupMembers + } + } else { + group, err := upgradeClientSet.GroupClient.Get(groupID, nil) + if err != nil { + return err + } + groupGet = &group } enabled := group["enabled"].(bool) @@ -551,11 +641,14 @@ func updateUpgradeUnitGroups(upgradeClientSet *upgradeClientSet, d *schema.Resou groupGet.ExtendedConfiguration = extendConfig } - _, err = upgradeClientSet.GroupClient.Update(groupID, groupGet) + if isCreate { + _, err = upgradeClientSet.GroupClient.Create(*groupGet) + } else { + _, err = upgradeClientSet.GroupClient.Update(groupID, *groupGet) + } if err != nil { return err } - if preUpgradeGroupID != "" { err = upgradeClientSet.GroupClient.Reorder(groupID, getReorderAfterReq(preUpgradeGroupID)) if err != nil { @@ -722,3 +815,75 @@ func resourceNsxtUpgradeRunUpdate(d *schema.ResourceData, m interface{}) error { func resourceNsxtUpgradeRunDelete(d *schema.ResourceData, m interface{}) error { return nil } + +func getHostDefaultUpgradeGroup(m interface{}, hostID string) (string, error) { + connector := getPolicyConnector(m) + hostClient := nsx.NewTransportNodesClient(connector) + host, err := hostClient.Get(hostID) + if err != nil { + return "", err + } + converter := bindings.NewTypeConverter() + base, errs := converter.ConvertToGolang(host.NodeDeploymentInfo, model.HostNodeBindingType()) + if errs != nil { + return "", errs[0] + } + node := base.(model.HostNode) + + if node.ComputeCollectionId != nil { + return *node.ComputeCollectionId, nil + } + + // This host is not a part of a compute cluster: + // it should be assigned to the 'Group 1 for ESXI' group (this value is hardcoded in NSX) + groupClient := upgrade.NewUpgradeUnitGroupsClient(connector) + componentType := "HOST" + hostGroups, err := groupClient.List(&componentType, nil, nil, nil, nil, nil, nil, nil) + if err != nil { + return "", err + } + if hostGroups.Results != nil { + for _, group := range hostGroups.Results { + if group.DisplayName != nil && *group.DisplayName == hostUpgradeUnitDefaultGroup { + return *group.Id, nil + } + } + } + + return "", errors.NotFound{} +} + +func addHostToGroup(m interface{}, groupID, hostID string, isCreate bool) error { + connector := getPolicyConnector(m) + client := upgrade.NewUpgradeUnitGroupsClient(connector) + + doUpdate := func() error { + group, err := client.Get(groupID, nil) + if err != nil { + return err + } + + hostIDs := getUnitIDsFromUnits(group.UpgradeUnits) + if slices.Contains(hostIDs, hostID) { + // Host is already within the group + return nil + } + group.UpgradeUnits = append(group.UpgradeUnits, model.UpgradeUnit{Id: &hostID}) + _, err = client.Update(groupID, group) + if err != nil { + return err + } + return nil + } + commonProviderConfig := getCommonProviderConfig(m) + return retryUponPreconditionFailed(doUpdate, commonProviderConfig.MaxRetries) +} + +func getUnitIDsFromUnits(units []model.UpgradeUnit) []string { + var unitIDs []string + + for _, unit := range units { + unitIDs = append(unitIDs, *unit.Id) + } + return unitIDs +} diff --git a/nsxt/utils.go b/nsxt/utils.go index 59f2864a2..947979d83 100644 --- a/nsxt/utils.go +++ b/nsxt/utils.go @@ -800,3 +800,47 @@ func getGMTagsFromSchema(d *schema.ResourceData) []gm_model.Tag { func setGMTagsInSchema(d *schema.ResourceData, tags []gm_model.Tag) { setCustomizedGMTagsInSchema(d, tags, "tag") } + +func getKeyValuePairListSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Description: "Advanced configuration", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Required: true, + }, + "value": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + } +} + +func getKeyValuePairListFromSchema(kvIList interface{}) []mp_model.KeyValuePair { + var kvList []mp_model.KeyValuePair + if kvIList != nil { + for _, kv := range kvIList.([]interface{}) { + kvMap := kv.(map[string]interface{}) + key := kvMap["key"].(string) + val := kvMap["value"].(string) + kvList = append(kvList, mp_model.KeyValuePair{Key: &key, Value: &val}) + } + } + return kvList +} + +func setKeyValueListForSchema(kvList []mp_model.KeyValuePair) interface{} { + var kvIList []interface{} + for _, ec := range kvList { + kvMap := make(map[string]interface{}) + kvMap["key"] = ec.Key + kvMap["value"] = ec.Value + kvIList = append(kvIList, kvMap) + } + return kvIList +} diff --git a/website/docs/r/upgrade_run.html.markdown b/website/docs/r/upgrade_run.html.markdown index 9b4eec992..a308055c4 100644 --- a/website/docs/r/upgrade_run.html.markdown +++ b/website/docs/r/upgrade_run.html.markdown @@ -42,6 +42,11 @@ resource "nsxt_upgrade_run" "run1" { id = data.nsxt_host_upgrade_group.hg1.id parallel = true } + host_group { + display_name = "TEST123" + parallel = false + hosts = ["2fa96cdc-6b82-4284-a69a-18a21a6b6d0c"] + } edge_upgrade_setting { parallel = true @@ -66,7 +71,8 @@ The following arguments are supported: * `parallel` - (Optional) Upgrade method to specify whether upgrades of UpgradeUnits in this group are performed in parallel or serially. Default: True. * `pause_after_each_upgrade_unit` - (Optional) Flag to indicate whether upgrade should be paused after upgrade of each upgrade-unit. Default: False. * `host_group` - (Optional) HOST component upgrade unit group configurations. Groups will be reordered following the order they present in this field. - * `id` - (Required) ID of the upgrade unit group. + * `id` - (Optional) ID of the upgrade unit group. Should exist only for predefined groups. When creating a custom host group, the value is assigned by NSX. + * `display_name` - (Optional) The display name of the host group. Should be assigned only for custom host groups and must be unique. * `enabled` - (Optional) Flag to indicate whether upgrade of this group is enabled or not. Default: True. * `parallel` - (Optional) Upgrade method to specify whether upgrades of UpgradeUnits in this group are performed in parallel or serially. Default: True. * `pause_after_each_upgrade_unit` - (Optional) Flag to indicate whether upgrade should be paused after upgrade of each upgrade-unit. Default: False. @@ -74,6 +80,7 @@ The following arguments are supported: * `maintenance_mode_config_vsan_mode` - (Optional) Maintenance mode config of vsan mode. Supported values: `evacuate_all_data`, `ensure_object_accessibility`, `no_action`. * `maintenance_mode_config_evacuate_powered_off_vms` - (Optional) Maintenance mode config of whether evacuate powered off vms. * `rebootless_upgrade` - (Optional) Flag to indicate whether to use rebootless upgrade. Default: True. + * `hosts` - (Optional) The list of hosts to be associated with a custom group. * `edge_upgrade_setting` - (Optional) EDGE component upgrade plan setting. * `parallel` - (Optional) Upgrade Method to specify whether upgrades of UpgradeUnitGroups in this component are performed serially or in parallel. Default: True. * `post_upgrade_check` - (Optional) Flag to indicate whether run post upgrade check after upgrade. Default: True. From d235730eb1a51015f2e18a91adab90307bfeb159 Mon Sep 17 00:00:00 2001 From: Kobi Samoray Date: Sun, 19 May 2024 17:14:51 +0300 Subject: [PATCH 2/3] Use one function call to retrieve group list Signed-off-by: Kobi Samoray --- nsxt/resource_nsxt_upgrade_run.go | 58 +++++++++++++++++-------------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/nsxt/resource_nsxt_upgrade_run.go b/nsxt/resource_nsxt_upgrade_run.go index 9483c01d1..dcaf81eeb 100644 --- a/nsxt/resource_nsxt_upgrade_run.go +++ b/nsxt/resource_nsxt_upgrade_run.go @@ -568,9 +568,13 @@ func updateUpgradeUnitGroups(upgradeClientSet *upgradeClientSet, d *schema.Resou if group["hosts"] != nil { schemaUnits = interface2StringList(group["hosts"].([]interface{})) } + getHostDefaultUpgradeGroup, err := getHostDefaultUpgradeGroupGetter(m) + if err != nil { + return fmt.Errorf("failed to retrieve host upgrade groups, error is %v", err) + } for _, nsxUnit := range nsxUnits { if !slices.Contains(schemaUnits, nsxUnit) { - groupID, err := getHostDefaultUpgradeGroup(m, nsxUnit) + groupID, err := getHostDefaultUpgradeGroup(nsxUnit) if isNotFoundError(err) { return fmt.Errorf("couldn't find default group for host %s as default group was not found", nsxUnit) } else if err != nil { @@ -816,41 +820,43 @@ func resourceNsxtUpgradeRunDelete(d *schema.ResourceData, m interface{}) error { return nil } -func getHostDefaultUpgradeGroup(m interface{}, hostID string) (string, error) { +func getHostDefaultUpgradeGroupGetter(m interface{}) (func(string) (string, error), error) { connector := getPolicyConnector(m) hostClient := nsx.NewTransportNodesClient(connector) - host, err := hostClient.Get(hostID) - if err != nil { - return "", err - } - converter := bindings.NewTypeConverter() - base, errs := converter.ConvertToGolang(host.NodeDeploymentInfo, model.HostNodeBindingType()) - if errs != nil { - return "", errs[0] - } - node := base.(model.HostNode) - - if node.ComputeCollectionId != nil { - return *node.ComputeCollectionId, nil - } - - // This host is not a part of a compute cluster: - // it should be assigned to the 'Group 1 for ESXI' group (this value is hardcoded in NSX) groupClient := upgrade.NewUpgradeUnitGroupsClient(connector) componentType := "HOST" hostGroups, err := groupClient.List(&componentType, nil, nil, nil, nil, nil, nil, nil) if err != nil { - return "", err + return nil, err } - if hostGroups.Results != nil { - for _, group := range hostGroups.Results { - if group.DisplayName != nil && *group.DisplayName == hostUpgradeUnitDefaultGroup { - return *group.Id, nil + + return func(hostID string) (string, error) { + host, err := hostClient.Get(hostID) + if err != nil { + return "", err + } + converter := bindings.NewTypeConverter() + base, errs := converter.ConvertToGolang(host.NodeDeploymentInfo, model.HostNodeBindingType()) + if errs != nil { + return "", errs[0] + } + node := base.(model.HostNode) + + if node.ComputeCollectionId != nil { + return *node.ComputeCollectionId, nil + } + // This host is not a part of a compute cluster: + // it should be assigned to the 'Group 1 for ESXI' group (this value is hardcoded in NSX) + if hostGroups.Results != nil { + for _, group := range hostGroups.Results { + if group.DisplayName != nil && *group.DisplayName == hostUpgradeUnitDefaultGroup { + return *group.Id, nil + } } } - } - return "", errors.NotFound{} + return "", errors.NotFound{} + }, nil } func addHostToGroup(m interface{}, groupID, hostID string, isCreate bool) error { From 19961255773a308aecc8cef009d6796f3035257e Mon Sep 17 00:00:00 2001 From: Kobi Samoray Date: Mon, 3 Jun 2024 12:10:53 +0300 Subject: [PATCH 3/3] Host upgrade group cleanup when not in schema When a host upgrade group is deleted, clean it from NSX. Signed-off-by: Kobi Samoray --- nsxt/resource_nsxt_upgrade_run.go | 58 +++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/nsxt/resource_nsxt_upgrade_run.go b/nsxt/resource_nsxt_upgrade_run.go index dcaf81eeb..ad6ee4804 100644 --- a/nsxt/resource_nsxt_upgrade_run.go +++ b/nsxt/resource_nsxt_upgrade_run.go @@ -15,6 +15,7 @@ import ( "github.com/vmware/vsphere-automation-sdk-go/runtime/bindings" "github.com/vmware/vsphere-automation-sdk-go/runtime/protocol/client" "github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp/nsx" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp/nsx/fabric" "github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp/nsx/model" "github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp/nsx/upgrade" "github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp/nsx/upgrade/plan" @@ -519,6 +520,13 @@ func updateUpgradeUnitGroups(upgradeClientSet *upgradeClientSet, d *schema.Resou } } + groupList, err := upgradeClientSet.GroupClient.List(nil, nil, nil, nil, nil, nil, nil, nil) + if err != nil { + return err + } + groupIDMap := make(map[string]bool) + groupNameMap := make(map[string]bool) + preUpgradeGroupID := "" for _, groupI := range d.Get(componentToGroupKey[component]).([]interface{}) { group := groupI.(map[string]interface{}) @@ -531,11 +539,8 @@ func updateUpgradeUnitGroups(upgradeClientSet *upgradeClientSet, d *schema.Resou if groupName == "" { return fmt.Errorf("couldn't find upgrade unit group without id or display_name") } + groupNameMap[groupName] = true // This is a custom group, try to find it by name - groupList, err := upgradeClientSet.GroupClient.List(nil, nil, nil, nil, nil, nil, nil, nil) - if err != nil { - return err - } for i, group := range groupList.Results { if *group.DisplayName == groupName { if groupGet == nil { @@ -568,7 +573,7 @@ func updateUpgradeUnitGroups(upgradeClientSet *upgradeClientSet, d *schema.Resou if group["hosts"] != nil { schemaUnits = interface2StringList(group["hosts"].([]interface{})) } - getHostDefaultUpgradeGroup, err := getHostDefaultUpgradeGroupGetter(m) + getHostDefaultUpgradeGroup, err := getHostDefaultUpgradeGroupGetter(m, groupList) if err != nil { return fmt.Errorf("failed to retrieve host upgrade groups, error is %v", err) } @@ -595,6 +600,7 @@ func updateUpgradeUnitGroups(upgradeClientSet *upgradeClientSet, d *schema.Resou groupGet.UpgradeUnits = groupMembers } } else { + groupIDMap[groupID] = true group, err := upgradeClientSet.GroupClient.Get(groupID, nil) if err != nil { return err @@ -661,9 +667,37 @@ func updateUpgradeUnitGroups(upgradeClientSet *upgradeClientSet, d *schema.Resou } preUpgradeGroupID = groupID } + + // After group update is complete, check for empty custom host groups which aren't defined in the schema, and have no members - these can be deleted. + componentType := "HOST" + groupList, err = upgradeClientSet.GroupClient.List(&componentType, nil, nil, nil, nil, nil, nil, nil) + if err != nil { + return err + } + for _, group := range groupList.Results { + // Non-empty groups cannot be deleted anyway so skip + if *group.UpgradeUnitCount == 0 { + if !groupIDMap[*group.Id] && !groupNameMap[*group.DisplayName] && !isPredefinedGroup(m, group) { + err = upgradeClientSet.GroupClient.Delete(*group.Id) + if err != nil { + return err + } + } + } + } return nil } +func isPredefinedGroup(m interface{}, group model.UpgradeUnitGroup) bool { + if group.DisplayName != nil && *group.DisplayName == hostUpgradeUnitDefaultGroup && *group.Type_ == "HOST" { + return true + } + connector := getPolicyConnector(m) + client := fabric.NewComputeCollectionsClient(connector) + _, err := client.Get(*group.Id) + return !isNotFoundError(err) +} + func updateComponentUpgradePlanSetting(settingClient plan.SettingsClient, d *schema.ResourceData, component string) error { settingI := d.Get(componentToSettingKey[component]).([]interface{}) if len(settingI) == 0 { @@ -820,15 +854,9 @@ func resourceNsxtUpgradeRunDelete(d *schema.ResourceData, m interface{}) error { return nil } -func getHostDefaultUpgradeGroupGetter(m interface{}) (func(string) (string, error), error) { +func getHostDefaultUpgradeGroupGetter(m interface{}, groupList model.UpgradeUnitGroupListResult) (func(string) (string, error), error) { connector := getPolicyConnector(m) hostClient := nsx.NewTransportNodesClient(connector) - groupClient := upgrade.NewUpgradeUnitGroupsClient(connector) - componentType := "HOST" - hostGroups, err := groupClient.List(&componentType, nil, nil, nil, nil, nil, nil, nil) - if err != nil { - return nil, err - } return func(hostID string) (string, error) { host, err := hostClient.Get(hostID) @@ -847,9 +875,9 @@ func getHostDefaultUpgradeGroupGetter(m interface{}) (func(string) (string, erro } // This host is not a part of a compute cluster: // it should be assigned to the 'Group 1 for ESXI' group (this value is hardcoded in NSX) - if hostGroups.Results != nil { - for _, group := range hostGroups.Results { - if group.DisplayName != nil && *group.DisplayName == hostUpgradeUnitDefaultGroup { + if groupList.Results != nil { + for _, group := range groupList.Results { + if group.DisplayName != nil && *group.DisplayName == hostUpgradeUnitDefaultGroup && *group.Type_ == "HOST" { return *group.Id, nil } }