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 26, 2024
1 parent 5308952 commit b2715c4
Show file tree
Hide file tree
Showing 4 changed files with 230 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 @@ -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)
Expand Down Expand Up @@ -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
Expand Down
185 changes: 175 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,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)
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
44 changes: 44 additions & 0 deletions nsxt/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Loading

0 comments on commit b2715c4

Please sign in to comment.