Skip to content

Commit

Permalink
Add custom groups support to upgrade run resource
Browse files Browse the repository at this point in the history
Add the logic to create and update custom host groups while running the
upgrade process.

Signed-off-by: Kobi Samoray <[email protected]>
  • Loading branch information
ksamoray committed May 9, 2024
1 parent 94a5c97 commit 8aeb442
Show file tree
Hide file tree
Showing 4 changed files with 226 additions and 43 deletions.
35 changes: 3 additions & 32 deletions nsxt/resource_nsxt_edge_transport_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -861,13 +845,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)
Expand Down Expand Up @@ -1260,14 +1238,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
Expand Down
181 changes: 171 additions & 10 deletions nsxt/resource_nsxt_upgrade_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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",
Expand All @@ -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{
Expand Down Expand Up @@ -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)
}
Expand All @@ -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
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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{
Expand All @@ -503,9 +523,76 @@ 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 handleDeleteError("Host Upgrade Group Binding", nsxUnit, err)
}
return addHostToGroup(m, groupID, nsxUnit, false)
}
}
// 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)
Expand Down Expand Up @@ -551,11 +638,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 {
Expand Down Expand Up @@ -722,3 +812,74 @@ 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) {
return fmt.Errorf("host %s already exists in group %s", hostID, groupID)
}
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
}
44 changes: 44 additions & 0 deletions nsxt/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -819,3 +819,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
}
9 changes: 8 additions & 1 deletion website/docs/r/upgrade_run.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -66,14 +71,16 @@ 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.
* `upgrade_mode` - (Optional) Upgrade mode. Supported values: `maintenance_mode`, `in_place`, `stage_in_vlcm`.
* `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.
Expand Down

0 comments on commit 8aeb442

Please sign in to comment.