From c749d80e9bcbe6cd02855e0dbc9838d59fbbe921 Mon Sep 17 00:00:00 2001 From: Jacob Salmela Date: Fri, 17 Nov 2023 14:06:47 -0600 Subject: [PATCH] merge flags for add cabinet Signed-off-by: Jacob Salmela --- cmd/blade/add_blade.go | 2 +- cmd/cabinet/add_cabinet.go | 59 +++------- cmd/cabinet/init.go | 9 +- cmd/node/init.go | 11 +- cmd/node/update_node.go | 21 +--- cmd/util.go | 13 +++ internal/domain/cabinet.go | 24 ++-- internal/domain/node.go | 11 +- internal/provider/csm/csv.go | 3 +- internal/provider/csm/metadata.go | 154 ++++++++++++++----------- internal/provider/csm/metadata_test.go | 12 +- internal/provider/csm/recommend.go | 26 ++++- internal/provider/interface.go | 15 ++- 13 files changed, 184 insertions(+), 176 deletions(-) diff --git a/cmd/blade/add_blade.go b/cmd/blade/add_blade.go index e348cf4a..4eba361f 100644 --- a/cmd/blade/add_blade.go +++ b/cmd/blade/add_blade.go @@ -53,7 +53,7 @@ var AddBladeCmd = &cobra.Command{ // addBlade adds a blade to the inventory func addBlade(cmd *cobra.Command, args []string) (err error) { if auto { - recommendations, err := root.D.Recommend(args[0]) + recommendations, err := root.D.Recommend(cmd, args, auto) if err != nil { return err } diff --git a/cmd/cabinet/add_cabinet.go b/cmd/cabinet/add_cabinet.go index 4ab1c5f0..90676ec7 100644 --- a/cmd/cabinet/add_cabinet.go +++ b/cmd/cabinet/add_cabinet.go @@ -31,9 +31,7 @@ import ( "sort" root "github.com/Cray-HPE/cani/cmd" - "github.com/Cray-HPE/cani/internal/domain" "github.com/Cray-HPE/cani/internal/provider" - "github.com/Cray-HPE/cani/internal/provider/csm" "github.com/Cray-HPE/cani/internal/tui" "github.com/Cray-HPE/cani/pkg/hardwaretypes" "github.com/rs/zerolog/log" @@ -51,51 +49,36 @@ var AddCabinetCmd = &cobra.Command{ // addCabinet adds a cabinet to the inventory func addCabinet(cmd *cobra.Command, args []string) (err error) { + var recommendations = provider.HardwareRecommendations{} + recommendations, err = root.D.Recommend(cmd, args, auto) + if err != nil { + return err + } + if auto { - recommendations, err := root.D.Recommend(args[0]) - if err != nil { - return err - } + // get hardware recommendations from the provider log.Info().Msgf("Querying inventory to suggest cabinet number and VLAN ID") - // set the vars to the recommendations - cabinetNumber = recommendations.CabinetOrdinal - vlanId = recommendations.ProviderMetadata[csm.ProviderMetadataVlanId].(int) - log.Debug().Msgf("Provider recommendations: %+v", recommendations) - log.Info().Msgf("Suggested cabinet number: %d", cabinetNumber) - log.Info().Msgf("Suggested VLAN ID: %d", vlanId) - if accept { - auto = true - } else { - // Prompt the user to confirm the suggestions - auto, err = tui.CustomConfirmation( - fmt.Sprintf("Would you like to accept the recommendations and add the %s", hardwaretypes.Cabinet)) + // Prompt the user to confirm the suggestions + if !accept { + accept, err = tui.CustomConfirmation(fmt.Sprintf("Would you like to accept the recommendations and add the %s", hardwaretypes.Cabinet)) if err != nil { return err } } // If the user chose not to accept the suggestions, exit - if !auto { + if !accept { log.Warn().Msgf("Aborted %s add", hardwaretypes.Cabinet) - fmt.Printf("\nAuto-generated values can be overridden by re-running the command with explicit values:\n") - fmt.Printf("\n\t%s %s %s %s --vlan-id %d --cabinet %d\n\n", cmd.Root().Name(), cmd.Parent().Name(), cmd.Name(), args[0], vlanId, cabinetNumber) - return nil } - } - // Push all the CLI flags that were provided into a generic map - // TODO Need to figure out how to specify to unset something - // Right now the build metadata function in the CSM provider will - // unset options if nil is passed in. - cabinetMetadata := map[string]interface{}{ - csm.ProviderMetadataVlanId: vlanId, + // log the provider recommendations to the screen + recommendations.Print() } // Add the cabinet to the inventory using domain methods - result, err := root.D.AddCabinet(cmd.Context(), args[0], cabinetNumber, cabinetMetadata) + result, err := root.D.AddCabinet(cmd, args, recommendations) if errors.Is(err, provider.ErrDataValidationFailure) { - // TODO the following should probably suggest commands to fix the issue? log.Error().Msgf("Inventory data validation errors encountered") for id, failedValidation := range result.ProviderValidationErrors { log.Error().Msgf(" %s: %s", id, failedValidation.Hardware.LocationPath.String()) @@ -110,20 +93,14 @@ func addCabinet(cmd *cobra.Command, args []string) (err error) { return err } - log.Info().Str("status", "SUCCESS").Msgf("%s %d was successfully staged to be added to the system", hardwaretypes.Cabinet, cabinetNumber) - - // Use a map to track already added nodes. - newNodes := []domain.HardwareLocationPair{} - for _, result := range result.AddedHardware { - // If the type is a Node if result.Hardware.Type == hardwaretypes.Cabinet { log.Debug().Msgf("%s added at %s with parent %s (%s)", result.Hardware.Type, result.Location.String(), hardwaretypes.System, result.Hardware.Parent) + log.Info().Str("status", "SUCCESS").Msgf("%s %d was successfully staged to be added to the system", hardwaretypes.Cabinet, recommendations.CabinetOrdinal) log.Info().Msgf("UUID: %s", result.Hardware.ID) - log.Info().Msgf("Cabinet Number: %d", cabinetNumber) - log.Info().Msgf("VLAN ID: %d", vlanId) - // Add the node to the map - newNodes = append(newNodes, result) + log.Info().Msgf("Cabinet Number: %d", *result.Hardware.LocationOrdinal) + // FIXME: make generic summary print function + log.Info().Msgf("VLAN ID: %d", result.Hardware.ProviderMetadata["csm"]["Cabinet"].(map[string]interface{})["HMNVlan"]) } } diff --git a/cmd/cabinet/init.go b/cmd/cabinet/init.go index 65eb791e..a62080e4 100644 --- a/cmd/cabinet/init.go +++ b/cmd/cabinet/init.go @@ -35,7 +35,6 @@ import ( var ( cabinetNumber int - vlanId int auto bool accept bool format string @@ -44,6 +43,8 @@ var ( ) func init() { + var err error + // Add variants to root commands root.AddCmd.AddCommand(AddCabinetCmd) root.ListCmd.AddCommand(ListCabinetCmd) @@ -54,10 +55,6 @@ func init() { // Cabinets AddCabinetCmd.Flags().IntVar(&cabinetNumber, "cabinet", 1001, "Cabinet number.") - // AddCabinetCmd.MarkFlagRequired("cabinet") - AddCabinetCmd.Flags().IntVar(&vlanId, "vlan-id", -1, "Vlan ID for the cabinet.") - // AddCabinetCmd.MarkFlagRequired("vlan-id") - AddCabinetCmd.MarkFlagsRequiredTogether("cabinet", "vlan-id") AddCabinetCmd.Flags().BoolVar(&auto, "auto", false, "Automatically recommend and assign required flags.") AddCabinetCmd.MarkFlagsMutuallyExclusive("auto") AddCabinetCmd.Flags().BoolVarP(&accept, "accept", "y", false, "Automatically accept recommended values.") @@ -67,7 +64,7 @@ func init() { // Merge CANI's command with the provider-specified command // this allows for CANI's operations to remain consistent, while adding provider config on top - err := root.MergeProviderCommand(AddCabinetCmd, ProviderAddCabinetCmd) + err = root.MergeProviderCommand(AddCabinetCmd, ProviderAddCabinetCmd) if err != nil { log.Error().Msgf("%+v", err) os.Exit(1) diff --git a/cmd/node/init.go b/cmd/node/init.go index 10801eab..c6fbf2f1 100644 --- a/cmd/node/init.go +++ b/cmd/node/init.go @@ -50,6 +50,7 @@ var ( ) func init() { + var err error // Add variants to root commands root.AddCmd.AddCommand(AddNodeCmd) root.ListCmd.AddCommand(ListNodeCmd) @@ -59,6 +60,7 @@ func init() { // Add a flag to show supported types AddNodeCmd.Flags().BoolP("list-supported-types", "L", false, "List supported hardware types.") + // TODO remove more cray-sauce AddNodeCmd.Flags().StringVar(&role, "role", "", "Role of the node") AddNodeCmd.Flags().StringVar(&subrole, "subrole", "", "Subrole of the node") AddNodeCmd.Flags().IntVar(&nid, "nid", 0, "NID of the node") @@ -70,13 +72,6 @@ func init() { UpdateNodeCmd.Flags().IntVar(&blade, "blade", 1, "Parent blade") UpdateNodeCmd.Flags().IntVar(&nodecard, "nodecard", 1, "Parent node card") UpdateNodeCmd.Flags().IntVar(&node, "node", 1, "Node to update") - - // CSM specific options - // TODO a thought, it might be neat if the options that CANI shows changes based on the active provider - UpdateNodeCmd.Flags().StringVar(&role, "role", "", "Role of the node") - UpdateNodeCmd.Flags().StringVar(&subrole, "subrole", "", "Subrole of the node") - UpdateNodeCmd.Flags().IntVar(&nid, "nid", 0, "NID of the node") - UpdateNodeCmd.Flags().StringVar(&alias, "alias", "", "Alias of the node") UpdateNodeCmd.Flags().StringVar(&nodeUuid, "uuid", "", "UUID of the node to update") UpdateNodeCmd.MarkFlagsRequiredTogether("cabinet", "chassis", "blade", "nodecard", "node") @@ -86,7 +81,7 @@ func init() { // Merge CANI's command with the provider-specified command // this allows for CANI's operations to remain consistent, while adding provider config on top - err := root.MergeProviderCommand(UpdateNodeCmd, ProviderUpdateNodeCmd) + err = root.MergeProviderCommand(UpdateNodeCmd, ProviderUpdateNodeCmd) if err != nil { log.Error().Msgf("%+v", err) os.Exit(1) diff --git a/cmd/node/update_node.go b/cmd/node/update_node.go index d82f5fd4..b5ed07f2 100644 --- a/cmd/node/update_node.go +++ b/cmd/node/update_node.go @@ -31,7 +31,6 @@ import ( root "github.com/Cray-HPE/cani/cmd" "github.com/Cray-HPE/cani/internal/provider" - "github.com/Cray-HPE/cani/internal/provider/csm" "github.com/google/uuid" "github.com/rs/zerolog/log" "github.com/spf13/cobra" @@ -47,24 +46,6 @@ var UpdateNodeCmd = &cobra.Command{ // updateNode updates a node to the inventory func updateNode(cmd *cobra.Command, args []string) (err error) { - // Push all the CLI flags that were provided into a generic map - // TODO Need to figure out how to specify to unset something - // Right now the build metadata function in the CSM provider will - // unset options if nil is passed in. - nodeMeta := map[string]interface{}{} - if cmd.Flags().Changed("role") { - nodeMeta[csm.ProviderMetadataRole] = role - } - if cmd.Flags().Changed("subrole") { - nodeMeta[csm.ProviderMetadataSubRole] = subrole - } - if cmd.Flags().Changed("alias") { - nodeMeta[csm.ProviderMetadataAlias] = alias - } - if cmd.Flags().Changed("nid") { - nodeMeta[csm.ProviderMetadataNID] = nid - } - // Remove the node from the inventory using domain methods if cmd.Flags().Changed("uuid") { // parse the passed in uuid @@ -87,7 +68,7 @@ func updateNode(cmd *cobra.Command, args []string) (err error) { } } - result, err := root.D.UpdateNode(cmd.Context(), cabinet, chassis, blade, nodecard, node, nodeMeta) + result, err := root.D.UpdateNode(cmd, args, cabinet, chassis, blade, nodecard, node) if errors.Is(err, provider.ErrDataValidationFailure) { // TODO the following should probably suggest commands to fix the issue? log.Error().Msgf("Inventory data validation errors encountered") diff --git a/cmd/util.go b/cmd/util.go index 65605e99..8176c9c3 100644 --- a/cmd/util.go +++ b/cmd/util.go @@ -69,7 +69,14 @@ func MergeProviderCommand(bootstrapCmd *cobra.Command, providerCmd *cobra.Comman case "add": providerCmd, err = csm.NewAddCabinetCommand() } + case "node": + // check for add/update variants + switch bootstrapCmd.Parent().Name() { + case "update": + providerCmd, err = csm.NewUpdateNodeCommand() + } } + default: log.Debug().Msgf("skipping provider: %s", provider) } @@ -104,6 +111,12 @@ func MergeProviderCommand(bootstrapCmd *cobra.Command, providerCmd *cobra.Comman case "add": err = csm.UpdateAddCabinetCommand(bootstrapCmd) } + case "node": + // check for add/update variants + switch bootstrapCmd.Parent().Name() { + case "update": + err = csm.UpdateUpdateNodeCommand(bootstrapCmd) + } } default: log.Debug().Msgf("skipping provider: %s", provider) diff --git a/internal/domain/cabinet.go b/internal/domain/cabinet.go index 5562e77e..238c3b5e 100644 --- a/internal/domain/cabinet.go +++ b/internal/domain/cabinet.go @@ -26,7 +26,6 @@ package domain import ( - "context" "errors" "fmt" @@ -35,15 +34,16 @@ import ( "github.com/Cray-HPE/cani/pkg/hardwaretypes" "github.com/google/uuid" "github.com/rs/zerolog/log" + "github.com/spf13/cobra" ) // AddCabinet adds a cabinet to the inventory -func (d *Domain) AddCabinet(ctx context.Context, deviceTypeSlug string, cabinetOrdinal int, metadata map[string]interface{}) (AddHardwareResult, error) { +func (d *Domain) AddCabinet(cmd *cobra.Command, args []string, recommendations provider.HardwareRecommendations) (AddHardwareResult, error) { // Validate provided cabinet exists // Craft the path to the cabinet cabinetLocationPath := inventory.LocationPath{ {HardwareType: hardwaretypes.System, Ordinal: 0}, - {HardwareType: hardwaretypes.Cabinet, Ordinal: cabinetOrdinal}, + {HardwareType: hardwaretypes.Cabinet, Ordinal: recommendations.CabinetOrdinal}, } // Check if the cabinet already exists @@ -58,7 +58,7 @@ func (d *Domain) AddCabinet(ctx context.Context, deviceTypeSlug string, cabinetO if exists { return AddHardwareResult{}, errors.Join( - fmt.Errorf("%s number %d is already in use", hardwaretypes.Cabinet, cabinetOrdinal), + fmt.Errorf("%s number %d is already in use", hardwaretypes.Cabinet, recommendations.CabinetOrdinal), fmt.Errorf("please re-run the command with an available %s number", hardwaretypes.Cabinet), ) } @@ -71,6 +71,7 @@ func (d *Domain) AddCabinet(ctx context.Context, deviceTypeSlug string, cabinetO ) } + deviceTypeSlug := args[0] // Verify the provided device type slug is a cabinet deviceType, err := d.hardwareTypeLibrary.GetDeviceType(deviceTypeSlug) if err != nil { @@ -81,7 +82,7 @@ func (d *Domain) AddCabinet(ctx context.Context, deviceTypeSlug string, cabinetO } // Generate a hardware build out using the system as a parent - hardwareBuildOutItems, err := inventory.GenerateDefaultHardwareBuildOut(d.hardwareTypeLibrary, deviceTypeSlug, cabinetOrdinal, system) + hardwareBuildOutItems, err := inventory.GenerateDefaultHardwareBuildOut(d.hardwareTypeLibrary, deviceTypeSlug, recommendations.CabinetOrdinal, system) if err != nil { return AddHardwareResult{}, errors.Join( fmt.Errorf("unable to build default hardware build out for %s", deviceTypeSlug), @@ -96,17 +97,14 @@ func (d *Domain) AddCabinet(ctx context.Context, deviceTypeSlug string, cabinetO hardware := inventory.NewHardwareFromBuildOut(hardwareBuildOut, inventory.HardwareStatusStaged) // Ask the inventory provider to craft a metadata object for this information - if err := d.externalInventoryProvider.BuildHardwareMetadata(&hardware, metadata); err != nil { + if err := d.externalInventoryProvider.BuildHardwareMetadata(&hardware, cmd, args, recommendations); err != nil { return AddHardwareResult{}, err } log.Debug().Any("id", hardware.ID).Msg("Hardware") log.Debug().Str("path", hardwareBuildOut.LocationPath.String()).Msg("Hardware Build out") - // TODO need a check to see if all the needed information exists, - // Things like role/subrole/nid/alias could be injected at a later time. - // Not sure how hard it would be to specify at this point in time. - // This command creates the physical information for a node, have another command for the logical part of the data + // Metadata is now set by the BuildHardwareMetadata so it can be added to the datastore if err := d.datastore.Add(&hardware); err != nil { return AddHardwareResult{}, errors.Join( fmt.Errorf("unable to add hardware to inventory datastore"), @@ -129,7 +127,7 @@ func (d *Domain) AddCabinet(ctx context.Context, deviceTypeSlug string, cabinetO // Validate the current state of CANI's inventory data against the provider plugin // for provider specific data. - if failedValidations, err := d.externalInventoryProvider.ValidateInternal(ctx, d.datastore, false); len(failedValidations) > 0 { + if failedValidations, err := d.externalInventoryProvider.ValidateInternal(cmd.Context(), d.datastore, false); len(failedValidations) > 0 { result.ProviderValidationErrors = failedValidations return result, provider.ErrDataValidationFailure } else if err != nil { @@ -154,14 +152,14 @@ func (d *Domain) RemoveCabinet(u uuid.UUID, recursion bool) error { return d.datastore.Flush() } -func (d *Domain) Recommend(deviceTypeSlug string) (recommendations provider.HardwareRecommendations, err error) { +func (d *Domain) Recommend(cmd *cobra.Command, args []string, auto bool) (recommendations provider.HardwareRecommendations, err error) { // Get the existing inventory inv, err := d.List() if err != nil { return recommendations, err } // Get recommendations from the CSM provider for the cabinet - recommendations, err = d.externalInventoryProvider.RecommendHardware(inv, deviceTypeSlug) + recommendations, err = d.externalInventoryProvider.RecommendHardware(inv, cmd, args, auto) if err != nil { return recommendations, err } diff --git a/internal/domain/node.go b/internal/domain/node.go index 87a18613..916ed3d1 100644 --- a/internal/domain/node.go +++ b/internal/domain/node.go @@ -26,7 +26,6 @@ package domain import ( - "context" "errors" "fmt" @@ -34,9 +33,10 @@ import ( "github.com/Cray-HPE/cani/internal/provider" "github.com/Cray-HPE/cani/pkg/hardwaretypes" "github.com/rs/zerolog/log" + "github.com/spf13/cobra" ) -func (d *Domain) UpdateNode(ctx context.Context, cabinet, chassis, slot, bmc, node int, metadata map[string]interface{}) (AddHardwareResult, error) { +func (d *Domain) UpdateNode(cmd *cobra.Command, args []string, cabinet, chassis, slot, bmc, node int) (AddHardwareResult, error) { // Get the node object from the datastore locationPath := inventory.LocationPath{ {HardwareType: hardwaretypes.System, Ordinal: 0}, @@ -54,12 +54,11 @@ func (d *Domain) UpdateNode(ctx context.Context, cabinet, chassis, slot, bmc, no log.Debug().Msgf("Found node at: %s with ID (%s)", locationPath, hw.ID) // Ask the inventory provider to craft a metadata object for this information - if err := d.externalInventoryProvider.BuildHardwareMetadata(&hw, metadata); err != nil { + err = d.externalInventoryProvider.NewHardwareMetadata(&hw, cmd, args) + if err != nil { return AddHardwareResult{}, err } - log.Debug().Any("metadata", hw.ProviderMetadata).Msg("Provider Properties") - // Push it back into the data store if err := d.datastore.Update(&hw); err != nil { return AddHardwareResult{}, err @@ -68,7 +67,7 @@ func (d *Domain) UpdateNode(ctx context.Context, cabinet, chassis, slot, bmc, no // Validate the current state of CANI's inventory data against the provider plugin // for provider specific data. var result AddHardwareResult - if failedValidations, err := d.externalInventoryProvider.ValidateInternal(ctx, d.datastore, false); len(failedValidations) > 0 { + if failedValidations, err := d.externalInventoryProvider.ValidateInternal(cmd.Context(), d.datastore, false); len(failedValidations) > 0 { result.ProviderValidationErrors = failedValidations return result, provider.ErrDataValidationFailure } else if err != nil { diff --git a/internal/provider/csm/csv.go b/internal/provider/csm/csv.go index 11776f78..6859125c 100644 --- a/internal/provider/csm/csv.go +++ b/internal/provider/csm/csv.go @@ -134,7 +134,8 @@ func (csm *CSM) SetFields(hw *inventory.Hardware, values map[string]string) (res } hw.SetProviderMetadata(inventory.CSMProvider, metadataRaw) } - } else if csmMetadata.Cabinet != nil { + } + if csmMetadata.Cabinet != nil { for key, value := range values { switch key { case "Vlan": diff --git a/internal/provider/csm/metadata.go b/internal/provider/csm/metadata.go index b3e6529c..378c9a2b 100644 --- a/internal/provider/csm/metadata.go +++ b/internal/provider/csm/metadata.go @@ -26,17 +26,19 @@ package csm import ( - "errors" "fmt" + "github.com/Cray-HPE/cani/cmd/taxonomy" "github.com/Cray-HPE/cani/internal/inventory" + "github.com/Cray-HPE/cani/internal/provider" "github.com/Cray-HPE/cani/pkg/hardwaretypes" "github.com/Cray-HPE/cani/pkg/pointers" "github.com/mitchellh/mapstructure" + "github.com/spf13/cobra" ) const ( - ProviderMetadataVlanId = "VlanID" + ProviderMetadataVlanId = "HMNVlan" ProviderMetadataRole = "Role" ProviderMetadataSubRole = "SubRole" ProviderMetadataAlias = "Alias" @@ -118,95 +120,111 @@ func EncodeProviderMetadata(metadata Metadata) (result map[string]interface{}, e return result, err } -func (csm *CSM) BuildHardwareMetadata(cHardware *inventory.Hardware, rawProperties map[string]interface{}) error { +func (csm *CSM) NewHardwareMetadata(hw *inventory.Hardware, cmd *cobra.Command, args []string) (err error) { + md, err := DecodeProviderMetadata(*hw) + if err != nil { + return err + } + // Get the flags and set the metdata accordingly + role, _ := cmd.Flags().GetString("role") + if cmd.Flags().Changed("role") { + md.Node.Role = &role + } + subrole, _ := cmd.Flags().GetString("subrole") + if cmd.Flags().Changed("subrole") { + md.Node.SubRole = &subrole + } + nid, _ := cmd.Flags().GetInt("nid") + if cmd.Flags().Changed("nid") { + md.Node.Nid = &nid + } + alias, _ := cmd.Flags().GetStringSlice("alias") + if cmd.Flags().Changed("alias") { + md.Node.Alias = alias + } + + metadata, err := EncodeProviderMetadata(md) + if err != nil { + return err + } + + hw.SetProviderMetadata(inventory.CSMProvider, metadata) + + return nil +} + +func (csm *CSM) BuildHardwareMetadata(cHardware *inventory.Hardware, cmd *cobra.Command, args []string, recommendations provider.HardwareRecommendations) error { if cHardware == nil { return fmt.Errorf("provided hardware is nil") } - metadata := Metadata{} - if cHardware.ProviderMetadata != nil { - var err error - metadata, err = DecodeProviderMetadata(*cHardware) + var metadata = map[string]interface{}{ + // string(hardwaretypes.Cabinet): map[string]interface{}{}, + // string(hardwaretypes.Node): map[string]interface{}{}, + } + switch cHardware.Type { + case hardwaretypes.Cabinet: + var vlan int + // if the flag is set, get the vlan there + if cmd.Flags().Changed("vlan-id") { + vlan, _ = cmd.Flags().GetInt("vlan-id") + // otherwise, get it from the recommendations + } else { + val, exists := recommendations.ProviderMetadata[ProviderMetadataVlanId] + if exists { + vlan = val.(int) + } + } + // check for the vlan limit + max, err := DetermineEndingVlanFromSlug(cHardware.DeviceTypeSlug, *csm.hardwareLibrary) if err != nil { - return errors.Join(fmt.Errorf("failed to decode CSM metadata from hardware (%v)", cHardware.ID), err) + return err } - } - switch cHardware.Type { - case hardwaretypes.Cabinet: - if metadata.Cabinet == nil { - // Create an cabinet metadata object it does not exist - metadata.Cabinet = &CabinetMetadata{} + // if the VLAN is greater than the max, fail + if vlan > max { + return fmt.Errorf("VLAN exceeds the provider's maximum range (%d). Please choose a valid VLAN", max) } - // Make changes to the node metadata - // The keys of rawProperties need to match what is defined in ./cmd/cabinet/add_cabinet.go - if vlanIDRaw, exists := rawProperties[ProviderMetadataVlanId]; exists { - // Check if the VLAN exceeds the valid range for the hardware - max, err := DetermineEndingVlanFromSlug(cHardware.DeviceTypeSlug, *csm.hardwareLibrary) - if err != nil { - return err - } - // if the VLAN is greater than the max, fail - if vlanIDRaw.(int) > max { - return fmt.Errorf("VLAN exceeds the provider's maximum range (%d). Please choose a valid VLAN", max) - } - if vlanIDRaw == nil { - metadata.Cabinet.HMNVlan = nil - } else { - metadata.Cabinet.HMNVlan = pointers.IntPtr(vlanIDRaw.(int)) - } + metadata[string(hardwaretypes.Cabinet)] = recommendations.ProviderMetadata + + if cHardware.ProviderMetadata == nil { + cHardware.ProviderMetadata = map[inventory.Provider]inventory.ProviderMetadataRaw{} } - case hardwaretypes.Node: - if metadata.Node == nil { - // Create an cabinet metadata object it does not exist - metadata.Node = &NodeMetadata{} + if cHardware.ProviderMetadata[taxonomy.CSM] == nil { + cHardware.ProviderMetadata[taxonomy.CSM] = map[string]interface{}{} } + // set the metadata + cHardware.ProviderMetadata[taxonomy.CSM] = metadata - // Make changes to the node metadata - // The keys of rawProperties need to match what is defined in ./cmd/node/update_node.go - if roleRaw, exists := rawProperties[ProviderMetadataRole]; exists { - if roleRaw == nil { - metadata.Node.Role = nil - } else { - metadata.Node.Role = pointers.StringPtr(roleRaw.(string)) - } + case hardwaretypes.Node: + role, _ := cmd.Flags().GetString("role") + subrole, _ := cmd.Flags().GetString("subrole") + nid, _ := cmd.Flags().GetInt("nid") + alias, _ := cmd.Flags().GetStringSlice("alias") + + md := map[string]interface{}{} + if role != "" { + md["role"] = role } - if subroleRaw, exists := rawProperties[ProviderMetadataSubRole]; exists { - if subroleRaw == nil { - metadata.Node.SubRole = nil - } else { - metadata.Node.SubRole = pointers.StringPtr(subroleRaw.(string)) - } + if subrole != "" { + md["subrole"] = subrole } - if nidRaw, exists := rawProperties[ProviderMetadataNID]; exists { - if nidRaw == nil { - metadata.Node.Nid = nil - } else { - metadata.Node.Nid = pointers.IntPtr(nidRaw.(int)) - } + if cmd.Flags().Changed("nid") { + md["nid"] = nid } - if aliasRaw, exists := rawProperties[ProviderMetadataAlias]; exists { - if aliasRaw == nil { - metadata.Node.Alias = nil - } else { - metadata.Node.Alias = []string{aliasRaw.(string)} - } + if alias != nil { + md["alias"] = alias } + metadata[string(hardwaretypes.Node)] = md default: // This hardware type doesn't have metadata for it right now return nil } - // Set the hardware metadata - metadataRaw, err := EncodeProviderMetadata(metadata) - if err != nil { - return errors.Join(fmt.Errorf("failed to encoder CSM Metadata for hardware (%v)", cHardware.ID), err) - } - - cHardware.SetProviderMetadata(inventory.CSMProvider, metadataRaw) + cHardware.SetProviderMetadata(inventory.CSMProvider, metadata) return nil } diff --git a/internal/provider/csm/metadata_test.go b/internal/provider/csm/metadata_test.go index bd022a2d..e00c5406 100644 --- a/internal/provider/csm/metadata_test.go +++ b/internal/provider/csm/metadata_test.go @@ -32,9 +32,11 @@ import ( "testing" "github.com/Cray-HPE/cani/internal/inventory" + "github.com/Cray-HPE/cani/internal/provider" "github.com/Cray-HPE/cani/pkg/hardwaretypes" "github.com/Cray-HPE/cani/pkg/pointers" "github.com/google/uuid" + "github.com/spf13/cobra" "github.com/stretchr/testify/suite" ) @@ -145,9 +147,13 @@ func (suite *BuildHardwareMetadataTestSuite) SetupSuite() { } func (suite *BuildHardwareMetadataTestSuite) TestCabinet() { - rawProperties := map[string]interface{}{ - ProviderMetadataVlanId: 1234, + recommendations := provider.HardwareRecommendations{ + ProviderMetadata: map[string]interface{}{ + ProviderMetadataVlanId: 1234, + }, } + cmd := &cobra.Command{} + args := []string{} hardware := inventory.Hardware{ ID: uuid.New(), @@ -158,7 +164,7 @@ func (suite *BuildHardwareMetadataTestSuite) TestCabinet() { Status: inventory.HardwareStatusStaged, } - err := suite.csm.BuildHardwareMetadata(&hardware, rawProperties) + err := suite.csm.BuildHardwareMetadata(&hardware, cmd, args, recommendations) suite.NoError(err) suite.NotNil(hardware.ProviderMetadata) diff --git a/internal/provider/csm/recommend.go b/internal/provider/csm/recommend.go index 5ed2b038..b12d64b2 100644 --- a/internal/provider/csm/recommend.go +++ b/internal/provider/csm/recommend.go @@ -35,9 +35,14 @@ import ( "github.com/Cray-HPE/cani/pkg/hardwaretypes" "github.com/google/uuid" "github.com/rs/zerolog/log" + "github.com/spf13/cobra" ) -func (csm *CSM) RecommendHardware(inv inventory.Inventory, deviceTypeSlug string) (recommended provider.HardwareRecommendations, err error) { +func (csm *CSM) RecommendHardware(inv inventory.Inventory, cmd *cobra.Command, args []string, auto bool) (recommended provider.HardwareRecommendations, err error) { + var deviceTypeSlug string + if cmd.Parent().Name() == "add" { + deviceTypeSlug = args[0] + } // loop through the existing inventory to check for vlans log.Debug().Msg("Checking existing hardware to find recommendations") deviceType, exists := csm.hardwareLibrary.DeviceTypes[deviceTypeSlug] @@ -48,10 +53,14 @@ func (csm *CSM) RecommendHardware(inv inventory.Inventory, deviceTypeSlug string switch deviceType.HardwareType { case hardwaretypes.Cabinet: - r, err := csm.recommendCabinet(inv, deviceTypeSlug) + r, err := csm.recommendCabinet(inv, cmd, args, auto) if err != nil { return recommended, err } + if !auto { + r.CabinetOrdinal, _ = cmd.Flags().GetInt("cabinet") + r.ProviderMetadata["HMNVlan"], _ = cmd.Flags().GetInt("vlan-id") + } recommended = r case hardwaretypes.NodeBlade: @@ -62,6 +71,12 @@ func (csm *CSM) RecommendHardware(inv inventory.Inventory, deviceTypeSlug string } recommended = r + case hardwaretypes.Node: + r, err := csm.recommendNode(inv, cmd, args, auto) + if err != nil { + return recommended, err + } + recommended = r default: // This function only handles cabinets and blades } @@ -70,7 +85,12 @@ func (csm *CSM) RecommendHardware(inv inventory.Inventory, deviceTypeSlug string return recommended, nil } -func (csm *CSM) recommendCabinet(inv inventory.Inventory, deviceTypeSlug string) (recommended provider.HardwareRecommendations, err error) { +func (csm *CSM) recommendNode(inv inventory.Inventory, cmd *cobra.Command, args []string, auto bool) (recommended provider.HardwareRecommendations, err error) { + return recommended, nil +} + +func (csm *CSM) recommendCabinet(inv inventory.Inventory, cmd *cobra.Command, args []string, auto bool) (recommended provider.HardwareRecommendations, err error) { + deviceTypeSlug := args[0] // slice to track existing vlans var existingVlans = []int{} // slice to track existing cabinets diff --git a/internal/provider/interface.go b/internal/provider/interface.go index 417ee904..126a763b 100644 --- a/internal/provider/interface.go +++ b/internal/provider/interface.go @@ -31,6 +31,7 @@ import ( "github.com/Cray-HPE/cani/internal/inventory" "github.com/google/uuid" + "github.com/rs/zerolog/log" "github.com/spf13/cobra" ) @@ -55,7 +56,7 @@ type InventoryProvider interface { Reconcile(ctx context.Context, datastore inventory.Datastore, dryrun bool, ignoreExternalValidation bool) error // RecommendHardware returns recommended settings for adding hardware based on the deviceTypeSlug - RecommendHardware(inv inventory.Inventory, deviceTypeSlug string) (HardwareRecommendations, error) + RecommendHardware(inv inventory.Inventory, cmd *cobra.Command, args []string, auto bool) (recommended HardwareRecommendations, err error) // SetProviderOptions are specific to the Provider. For example, supported Roles and SubRoles SetProviderOptions(cmd *cobra.Command, args []string) error @@ -74,7 +75,8 @@ type InventoryProvider interface { // Build metadata, and add ito the hardware object // This function could return the data to put into object - BuildHardwareMetadata(hw *inventory.Hardware, rawProperties map[string]interface{}) error + BuildHardwareMetadata(hw *inventory.Hardware, cmd *cobra.Command, args []string, recommendations HardwareRecommendations) error + NewHardwareMetadata(hw *inventory.Hardware, cmd *cobra.Command, args []string) error // Return values for the given fields from the hardware's metadata GetFields(hw *inventory.Hardware, fieldNames []string) (values []string, err error) @@ -86,10 +88,6 @@ type InventoryProvider interface { GetFieldMetadata() ([]FieldMetadata, error) } -// type SlsProvider interface { -// GetSlsJson(ctx context.Context, datastore inventory.Datastore, skipValidation bool) ([]byte, error) -// } - type HardwareValidationResult struct { Hardware inventory.Hardware Errors []string @@ -118,3 +116,8 @@ type FieldMetadata struct { Description string IsModifiable bool } + +func (r HardwareRecommendations) Print() { + log.Info().Msgf("Suggested cabinet number: %d", r.CabinetOrdinal) + log.Info().Msgf("Suggested VLAN ID: %d", r.ProviderMetadata["HMNVlan"]) +}