From da04bc2fbeb08d0446823c634455bc4c09f323e4 Mon Sep 17 00:00:00 2001 From: Don Penney Date: Tue, 19 Nov 2024 17:58:28 -0500 Subject: [PATCH] Update dell-hwmgr adaptor to align with NodePool CRD changes Signed-off-by: Don Penney --- adaptors/dell-hwmgr/hwmgrclient/client.go | 77 +++++++-------------- adaptors/dell-hwmgr/node.go | 42 ++++++++--- adaptors/dell-hwmgr/nodepool.go | 9 +-- internal/controller/utils/nodepool_utils.go | 9 ++- 4 files changed, 72 insertions(+), 65 deletions(-) diff --git a/adaptors/dell-hwmgr/hwmgrclient/client.go b/adaptors/dell-hwmgr/hwmgrclient/client.go index cdfa8cf5..89c486de 100644 --- a/adaptors/dell-hwmgr/hwmgrclient/client.go +++ b/adaptors/dell-hwmgr/hwmgrclient/client.go @@ -32,16 +32,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" ) -// TODO: Define how these labels are specified. New field in NodePool? -// For now, we'll use an enum and label and hardcode a correlation to the nodegroup list order -const ( - ResourceSelectorControllerIdx = iota - ResourceSelectorWorkerIdx - - ResourceSelectorControllerLabel = "controller" // TODO: - ResourceSelectorWorkerLabel = "worker" -) - const ( RoleKey = "role" Tenant = "default_tenant" // TODO: Hardcoded, for now @@ -204,56 +194,39 @@ func ResourceGroupIdFromNodePool(nodepool *hwmgmtv1alpha1.NodePool) string { func ResourceGroupFromNodePool(nodepool *hwmgmtv1alpha1.NodePool) *hwmgrapi.CreateResourceGroupJSONRequestBody { rgId := ResourceGroupIdFromNodePool(nodepool) tenant := Tenant - // TODO: Get these values appropriately - resourceTypeId := "ResourceGroup~2.1.1" + resourceTypeId := utils.GetResourceTypeId(nodepool) description := "Resource Group managed by O-Cloud Hardware Manager Plugin" excludes := make(map[string]interface{}) roleKey := RoleKey - // TODO: What should the roles be? - controllerRole := ResourceSelectorControllerLabel - workerRole := ResourceSelectorWorkerLabel - rg := hwmgrapi.CreateResourceGroupJSONRequestBody{ - Tenant: &tenant, - ResourceGroup: &hwmgrapi.RhprotoResourceGroupObjectRequest{ - Description: &description, - Id: &rgId, - Name: &rgId, - ResourceTypeId: &resourceTypeId, - ResourceSelectors: &map[string]hwmgrapi.RhprotoResourceSelectorRequest{ - ResourceSelectorControllerLabel: { - RpId: &nodepool.Spec.NodeGroup[ResourceSelectorControllerIdx].Name, - ResourceProfileId: &nodepool.Spec.NodeGroup[ResourceSelectorControllerIdx].HwProfile, - NumResources: &nodepool.Spec.NodeGroup[ResourceSelectorControllerIdx].Size, - Filters: &hwmgrapi.RhprotoResourceSelectorFilter{ - Include: &hwmgrapi.RhprotoResourceSelectorFilterInclude{ - Labels: &[]hwmgrapi.RhprotoResourceSelectorFilterIncludeLabel{ - { - Key: &roleKey, - Value: &controllerRole, - }, - }, - }, - Exclude: &excludes, - }, - }, - ResourceSelectorWorkerLabel: { - RpId: &nodepool.Spec.NodeGroup[ResourceSelectorWorkerIdx].Name, - ResourceProfileId: &nodepool.Spec.NodeGroup[ResourceSelectorWorkerIdx].HwProfile, - NumResources: &nodepool.Spec.NodeGroup[ResourceSelectorWorkerIdx].Size, - Filters: &hwmgrapi.RhprotoResourceSelectorFilter{ - Include: &hwmgrapi.RhprotoResourceSelectorFilterInclude{ - Labels: &[]hwmgrapi.RhprotoResourceSelectorFilterIncludeLabel{ - { - Key: &roleKey, - Value: &workerRole, - }, - }, + resourceSelectors := make(map[string]hwmgrapi.RhprotoResourceSelectorRequest) + for _, nodegroup := range nodepool.Spec.NodeGroup { + resourceSelectors[nodegroup.Name] = hwmgrapi.RhprotoResourceSelectorRequest{ + RpId: &nodegroup.ResourcePoolId, + ResourceProfileId: &nodegroup.HwProfile, + NumResources: &nodegroup.Size, + Filters: &hwmgrapi.RhprotoResourceSelectorFilter{ + Include: &hwmgrapi.RhprotoResourceSelectorFilterInclude{ + Labels: &[]hwmgrapi.RhprotoResourceSelectorFilterIncludeLabel{ + { + Key: &roleKey, + Value: &nodegroup.Name, // TODO: This should be nodegroup.Role, but has to be nodegroup.Name for now }, - Exclude: &excludes, }, }, + Exclude: &excludes, }, + } + } + + rg := hwmgrapi.CreateResourceGroupJSONRequestBody{ + Tenant: &tenant, + ResourceGroup: &hwmgrapi.RhprotoResourceGroupObjectRequest{ + Description: &description, + Id: &rgId, + Name: &rgId, + ResourceTypeId: &resourceTypeId, + ResourceSelectors: &resourceSelectors, }, } diff --git a/adaptors/dell-hwmgr/node.go b/adaptors/dell-hwmgr/node.go index e2e9862b..1e356db6 100644 --- a/adaptors/dell-hwmgr/node.go +++ b/adaptors/dell-hwmgr/node.go @@ -26,6 +26,7 @@ import ( "github.com/openshift-kni/oran-hwmgr-plugin/internal/controller/utils" "github.com/openshift-kni/oran-hwmgr-plugin/internal/logging" hwmgmtv1alpha1 "github.com/openshift-kni/oran-o2ims/api/hardwaremanagement/v1alpha1" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/util/retry" @@ -58,8 +59,30 @@ type ExtensionInterface struct { Ports []ExtensionPort `json:"ports,omitempty"` } +func bmcSecretName(nodename string) string { + return fmt.Sprintf("%s-bmc-secret", nodename) +} + +// CheckBMCSecret creates the bmc-secret for a node +func (a *Adaptor) CheckBMCSecret(ctx context.Context, resource hwmgrapi.RhprotoResource) error { + nodename := *resource.Id + + a.Logger.InfoContext(ctx, "Checking bmc-secret:", "nodename", nodename) + + secretName := bmcSecretName(nodename) + + // TODO: For now, just check that the secret exists. Once the creds are accessible, we can create the secret + bmcSecret := &corev1.Secret{} + + if err := a.Get(ctx, types.NamespacedName{Name: secretName, Namespace: a.Namespace}, bmcSecret); err != nil { + return fmt.Errorf("failed to get secret %s: %w", secretName, err) + } + + return nil +} + // AllocateNode processes a NodePool CR, allocating a free node for each specified nodegroup as needed -func (a *Adaptor) AllocateNode(ctx context.Context, nodepool *hwmgmtv1alpha1.NodePool, resource hwmgrapi.RhprotoResource) (string, error) { +func (a *Adaptor) AllocateNode(ctx context.Context, nodepool *hwmgmtv1alpha1.NodePool, resource hwmgrapi.RhprotoResource, nodegroupName string) (string, error) { nodename := *resource.Id ctx = logging.AppendCtx(ctx, slog.String("nodenameCtx", nodename)) @@ -67,11 +90,11 @@ func (a *Adaptor) AllocateNode(ctx context.Context, nodepool *hwmgmtv1alpha1.Nod // if err := a.CreateBMCSecret(ctx, nodename, nodeinfo.BMC.UsernameBase64, nodeinfo.BMC.PasswordBase64); err != nil { // return fmt.Errorf("failed to create bmc-secret when allocating node %s: %w", nodename, err) // } - if err := a.ValidateNodeConfig(resource); err != nil { + if err := a.ValidateNodeConfig(ctx, resource); err != nil { return "", fmt.Errorf("failed to validate resource configuration: %w", err) } - if err := a.CreateNode(ctx, nodepool, resource); err != nil { + if err := a.CreateNode(ctx, nodepool, resource, nodegroupName); err != nil { return "", fmt.Errorf("failed to create allocated node (%s): %w", *resource.Id, err) } @@ -144,7 +167,7 @@ func (a *Adaptor) getNodeInterfaces(resource hwmgrapi.RhprotoResource) ([]*hwmgm } // ValidateNodeConfig performs basic data structure validation on the resource -func (a *Adaptor) ValidateNodeConfig(resource hwmgrapi.RhprotoResource) error { +func (a *Adaptor) ValidateNodeConfig(ctx context.Context, resource hwmgrapi.RhprotoResource) error { // Check required fields if resource.ResourceAttribute == nil || resource.ResourceAttribute.Compute == nil || @@ -154,6 +177,10 @@ func (a *Adaptor) ValidateNodeConfig(resource hwmgrapi.RhprotoResource) error { return fmt.Errorf("resource structure missing required resource attribute field") } + if err := a.CheckBMCSecret(ctx, resource); err != nil { + return fmt.Errorf("bmc-secret check failed: %w", err) + } + if _, err := a.parseExtensionInterfaces(resource); err != nil { return fmt.Errorf("invalid interface list: %w", err) } @@ -162,9 +189,8 @@ func (a *Adaptor) ValidateNodeConfig(resource hwmgrapi.RhprotoResource) error { } // CreateNode creates a Node CR with specified attributes -func (a *Adaptor) CreateNode(ctx context.Context, nodepool *hwmgmtv1alpha1.NodePool, resource hwmgrapi.RhprotoResource) error { +func (a *Adaptor) CreateNode(ctx context.Context, nodepool *hwmgmtv1alpha1.NodePool, resource hwmgrapi.RhprotoResource, nodegroupName string) error { nodename := *resource.Id - groupname := *resource.ResourcePoolId hwprofile := *resource.ResourceProfileID a.Logger.InfoContext(ctx, "Creating node") @@ -184,7 +210,7 @@ func (a *Adaptor) CreateNode(ctx context.Context, nodepool *hwmgmtv1alpha1.NodeP }, Spec: hwmgmtv1alpha1.NodeSpec{ NodePool: nodepool.Name, - GroupName: groupname, + GroupName: nodegroupName, HwProfile: hwprofile, }, } @@ -212,7 +238,7 @@ func (a *Adaptor) UpdateNodeStatus(ctx context.Context, resource hwmgrapi.Rhprot node.Status.BMC = &hwmgmtv1alpha1.BMC{ Address: IdracUrlPrefix + *resource.ResourceAttribute.Compute.Lom.IpAddress + IdracUrlSuffix, - CredentialsName: *resource.ResourceAttribute.Compute.Lom.Password, + CredentialsName: bmcSecretName(nodename), } node.Status.Hostname = *resource.Name // TODO: Define how the hostname is set diff --git a/adaptors/dell-hwmgr/nodepool.go b/adaptors/dell-hwmgr/nodepool.go index fcc1f4b9..d2fff6da 100644 --- a/adaptors/dell-hwmgr/nodepool.go +++ b/adaptors/dell-hwmgr/nodepool.go @@ -38,8 +38,9 @@ const ( // ValidateNodePool performs basic validation of the nodepool data func (a *Adaptor) ValidateNodePool(nodepool *hwmgmtv1alpha1.NodePool) error { - if len(nodepool.Spec.NodeGroup) != 2 { - return fmt.Errorf("nodepool %s invalid: Expected 2 entries in .spec.nodeGroup, got %d", nodepool.Name, len(nodepool.Spec.NodeGroup)) + resourceTypeId := utils.GetResourceTypeId(nodepool) + if resourceTypeId == "" { + return fmt.Errorf("nodepool %s is missing resourceTypeId in spec", nodepool.Name) } return nil @@ -185,13 +186,13 @@ func (a *Adaptor) HandleNodePoolProcessing( // TODO: Need to add validation to ensure the rg satisfies the nodepool // Create the Node CRs corresponding to the allocated resources - for _, resourceSelector := range *rg.ResourceSelectors { + for nodegroupName, resourceSelector := range *rg.ResourceSelectors { for _, node := range *resourceSelector.Resources { if slices.Contains(nodepool.Status.Properties.NodeNames, *node.Id) { a.Logger.InfoContext(ctx, "Node is already added", slog.String("nodename", *node.Id)) continue } - if nodename, err := a.AllocateNode(ctx, nodepool, node); err != nil { + if nodename, err := a.AllocateNode(ctx, nodepool, node, nodegroupName); err != nil { a.Logger.InfoContext(ctx, "Failed allocating node", slog.String("err", err.Error())) if err := utils.UpdateNodePoolStatusCondition(ctx, a.Client, nodepool, hwmgmtv1alpha1.Provisioned, hwmgmtv1alpha1.Failed, metav1.ConditionFalse, diff --git a/internal/controller/utils/nodepool_utils.go b/internal/controller/utils/nodepool_utils.go index 38c5c20b..0e784af3 100644 --- a/internal/controller/utils/nodepool_utils.go +++ b/internal/controller/utils/nodepool_utils.go @@ -28,7 +28,14 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) -const NodepoolFinalizer = "oran-hwmgr-plugin/nodepool-finalizer" +const ( + NodepoolFinalizer = "oran-hwmgr-plugin/nodepool-finalizer" + ResourceTypeIdKey = "resourceTypeId" +) + +func GetResourceTypeId(nodepool *hwmgmtv1alpha1.NodePool) string { + return nodepool.Spec.Extensions[ResourceTypeIdKey] +} func GetNodePoolProvisionedCondition(nodepool *hwmgmtv1alpha1.NodePool) *metav1.Condition { return meta.FindStatusCondition(