Skip to content

Commit

Permalink
Merge pull request #158 from Cray-HPE/solid-reaper
Browse files Browse the repository at this point in the history
generalize add cabinet workflow by moving cobra command down to provider layer
  • Loading branch information
jacobsalmela authored Nov 27, 2023
2 parents e1efb24 + c749d80 commit a911201
Show file tree
Hide file tree
Showing 13 changed files with 184 additions and 176 deletions.
2 changes: 1 addition & 1 deletion cmd/blade/add_blade.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
59 changes: 18 additions & 41 deletions cmd/cabinet/add_cabinet.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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())
Expand All @@ -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"])
}
}

Expand Down
9 changes: 3 additions & 6 deletions cmd/cabinet/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ import (

var (
cabinetNumber int
vlanId int
auto bool
accept bool
format string
Expand All @@ -44,6 +43,8 @@ var (
)

func init() {
var err error

// Add variants to root commands
root.AddCmd.AddCommand(AddCabinetCmd)
root.ListCmd.AddCommand(ListCabinetCmd)
Expand All @@ -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.")
Expand All @@ -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)
Expand Down
11 changes: 3 additions & 8 deletions cmd/node/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ var (
)

func init() {
var err error
// Add variants to root commands
root.AddCmd.AddCommand(AddNodeCmd)
root.ListCmd.AddCommand(ListNodeCmd)
Expand All @@ -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")
Expand All @@ -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")
Expand All @@ -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)
Expand Down
21 changes: 1 addition & 20 deletions cmd/node/update_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand All @@ -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")
Expand Down
13 changes: 13 additions & 0 deletions cmd/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
Expand Down
24 changes: 11 additions & 13 deletions internal/domain/cabinet.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
package domain

import (
"context"
"errors"
"fmt"

Expand All @@ -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
Expand All @@ -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),
)
}
Expand All @@ -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 {
Expand All @@ -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),
Expand All @@ -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"),
Expand All @@ -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 {
Expand All @@ -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
}
Expand Down
11 changes: 5 additions & 6 deletions internal/domain/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,17 @@
package domain

import (
"context"
"errors"
"fmt"

"github.com/Cray-HPE/cani/internal/inventory"
"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},
Expand All @@ -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
Expand All @@ -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 {
Expand Down
Loading

0 comments on commit a911201

Please sign in to comment.