Add BMC authentication data to SIP machines

This change retrieves and stores BMC authentication information for each
BaremetalHost object represented as a machine. This will enable SIP's
Jump Host service to create a Secret with BMC information for each node
in a sub-cluster.

Signed-off-by: Drew Walters <andrew.walters@att.com>
Change-Id: Ie11fa95f694f050b4e61866aaf81ae6410147fc1
This commit is contained in:
Drew Walters 2021-02-12 16:42:47 +00:00
parent 9fcfae7107
commit 00c45a4444
5 changed files with 175 additions and 12 deletions

View File

@ -54,10 +54,19 @@ var _ = Describe("SIPCluster controller", func() {
// Create vBMH test objects
nodes := []airshipv1.VMRole{airshipv1.VMControlPlane, airshipv1.VMControlPlane, airshipv1.VMControlPlane,
airshipv1.VMWorker, airshipv1.VMWorker, airshipv1.VMWorker, airshipv1.VMWorker}
bmcUsername := "root"
bmcPassword := "test"
for node, role := range nodes {
vBMH, networkData := testutil.CreateBMH(node, testNamespace, role, 6)
bmcSecret := testutil.CreateBMCAuthSecret(vBMH.Name, vBMH.Namespace, bmcUsername,
bmcPassword)
vBMH.Spec.BMC.CredentialsName = bmcSecret.Name
Expect(k8sClient.Create(context.Background(), bmcSecret)).Should(Succeed())
Expect(k8sClient.Create(context.Background(), vBMH)).Should(Succeed())
Expect(k8sClient.Create(context.Background(), networkData)).Should(Succeed())
}
// Create SIP cluster

View File

@ -54,3 +54,13 @@ type ErrorNetworkDataNotFound struct {
func (e ErrorNetworkDataNotFound) Error() string {
return fmt.Sprintf("vBMH Host %v does not define NetworkData, but is required for scheduling.", e.BMH)
}
// ErrMalformedManagementCredentials occurs when a BMC credentials secret does not contain username and password fields.
type ErrMalformedManagementCredentials struct {
SecretName string
}
func (e ErrMalformedManagementCredentials) Error() string {
return fmt.Sprintf("secret %s contains malformed management credentials. Must contain '%s' and '%s' fields.",
e.SecretName, keyBMCUsername, keyBMCPassword)
}

View File

@ -78,6 +78,12 @@ const (
SipNodeTypeLabel = BaseAirshipSelector + "/" + SipNodeTypeLabelName
)
// Keys used to retrieve credentials from the BMC credentials secret
const (
keyBMCUsername = "username"
keyBMCPassword = "password"
)
// MAchine represents an individual BMH CR, and the appropriate
// attributes required to manage the SIP Cluster scheduling and
// rocesing needs about thhem
@ -118,6 +124,8 @@ type MachineData struct {
// Collect all IP's for the interfaces defined
// In the list of Services
IPOnInterface map[string]string
BMCUsername string
BMCPassword string
}
// MachineList contains the list of Scheduled or ToBeScheduled machines
@ -428,6 +436,26 @@ func (ml *MachineList) Extrapolate(sip airshipv1.SIPCluster, c client.Client) bo
ml.ReadyForScheduleCount[machine.VMRole]--
extrapolateSuccess = false
}
// Retrieve BMC credentials
mgmtCredsSecret := &corev1.Secret{}
err = c.Get(context.Background(), client.ObjectKey{
Namespace: bmh.Namespace,
Name: bmh.Spec.BMC.CredentialsName,
}, mgmtCredsSecret)
if err != nil {
machine.ScheduleStatus = UnableToSchedule
ml.ReadyForScheduleCount[machine.VMRole]--
extrapolateSuccess = false
}
// Parse BMC credentials from Secret
err = ml.getMangementCredentials(machine, mgmtCredsSecret)
if err != nil {
machine.ScheduleStatus = UnableToSchedule
ml.ReadyForScheduleCount[machine.VMRole]--
extrapolateSuccess = false
}
}
fmt.Printf("Schedule.Extrapolate extrapolateSuccess:%t\n", extrapolateSuccess)
return extrapolateSuccess
@ -623,6 +651,23 @@ func (ml *MachineList) getIP(machine *Machine, networkDataSecret *corev1.Secret,
return nil
}
// getManagementCredentials retrieves BMC credentials from a Kubernetes secret.
func (ml *MachineList) getMangementCredentials(machine *Machine, secret *corev1.Secret) error {
username, exists := secret.Data[keyBMCUsername]
if !exists {
return ErrMalformedManagementCredentials{SecretName: secret.Name}
}
machine.Data.BMCUsername = string(username)
password, exists := secret.Data[keyBMCPassword]
if !exists {
return ErrMalformedManagementCredentials{SecretName: secret.Name}
}
machine.Data.BMCPassword = string(password)
return nil
}
/*
ScheduleSet is a simple object to encapsulate data that
helps our poor man scheduler

View File

@ -95,12 +95,23 @@ var _ = Describe("MachineList", func() {
It("Should retrieve the BMH IP from the BMH's NetworkData secret when infra services are defined", func() {
// Create a BMH with a NetworkData secret
bmh, secret := testutil.CreateBMH(1, "default", airshipv1.VMControlPlane, 6)
bmh, networkData := testutil.CreateBMH(1, "default", airshipv1.VMControlPlane, 6)
// Create BMH and NetworkData secret
var objsToApply []runtime.Object
objsToApply = append(objsToApply, bmh)
objsToApply = append(objsToApply, networkData)
// Create BMC credential secret
username := "root"
password := "test"
bmcSecret := testutil.CreateBMCAuthSecret(bmh.Name, bmh.Namespace, username, password)
bmh.Spec.BMC.CredentialsName = bmcSecret.Name
objsToApply = append(objsToApply, bmcSecret)
m, err := NewMachine(*bmh, airshipv1.VMControlPlane, NotScheduled)
Expect(err).To(BeNil())
var objs []runtime.Object
objs = append(objs, bmh)
objs = append(objs, secret)
ml := &MachineList{
NamespacedName: types.NamespacedName{
@ -126,21 +137,31 @@ var _ = Describe("MachineList", func() {
},
},
}
k8sClient := mockClient.NewFakeClient(objs...)
k8sClient := mockClient.NewFakeClient(objsToApply...)
Expect(ml.Extrapolate(*sipCluster, k8sClient)).To(BeTrue())
// NOTE(drewwalters96): Interface data is b64 encoded in the testutil convenience function.
Expect(ml.Machines[bmh.Name].Data.IPOnInterface).To(Equal(map[string]string{"oam-ipv4": "32.68.51.139"}))
})
It("Should not retrieve the BMH IP from the BMH's NetworkData secret if no infraServices are defined", func() {
// Create a BMH with a NetworkData secret
bmh, secret := testutil.CreateBMH(1, "default", airshipv1.VMControlPlane, 6)
bmh, networkData := testutil.CreateBMH(1, "default", airshipv1.VMControlPlane, 6)
// Create BMH and NetworkData secret
var objsToApply []runtime.Object
objsToApply = append(objsToApply, bmh)
objsToApply = append(objsToApply, networkData)
// Create BMC credential secret
username := "root"
password := "test"
bmcSecret := testutil.CreateBMCAuthSecret(bmh.Name, bmh.Namespace, username, password)
bmh.Spec.BMC.CredentialsName = bmcSecret.Name
objsToApply = append(objsToApply, bmcSecret)
m, err := NewMachine(*bmh, airshipv1.VMControlPlane, NotScheduled)
Expect(err).To(BeNil())
var objs []runtime.Object
objs = append(objs, bmh)
objs = append(objs, secret)
ml := &MachineList{
NamespacedName: types.NamespacedName{
@ -153,10 +174,69 @@ var _ = Describe("MachineList", func() {
Log: ctrl.Log.WithName("controllers").WithName("SIPCluster"),
}
k8sClient := mockClient.NewFakeClient(objs...)
sipCluster := testutil.CreateSIPCluster("subcluster-1", "default", 1, 3)
sipCluster.Spec.Services = airshipv1.SIPClusterServices{
LoadBalancer: []airshipv1.SIPClusterService{
{
Image: "haproxy:latest",
NodeLabels: map[string]string{
"test": "true",
},
NodePort: 30000,
NodeInterface: "oam-ipv4",
},
},
}
k8sClient := mockClient.NewFakeClient(objsToApply...)
Expect(ml.Extrapolate(*sipCluster, k8sClient)).To(BeTrue())
Expect(len(ml.Machines[bmh.Name].Data.IPOnInterface)).To(Equal(0))
Expect(ml.Machines[bmh.Name].Data.BMCUsername).To(Equal(username))
Expect(ml.Machines[bmh.Name].Data.BMCPassword).To(Equal(password))
})
It("Should not process a BMH when its BMC secret is missing", func() {
var objsToApply []runtime.Object
// Create BMH and NetworkData secret
bmh, networkData := testutil.CreateBMH(1, "default", "master", 6)
objsToApply = append(objsToApply, bmh)
objsToApply = append(objsToApply, networkData)
bmh.Spec.BMC.CredentialsName = "foo-does-not-exist"
m, err := NewMachine(*bmh, airshipv1.VMControlPlane, NotScheduled)
Expect(err).To(BeNil())
ml := &MachineList{
NamespacedName: types.NamespacedName{
Name: "vbmh",
Namespace: "default",
},
Machines: map[string]*Machine{
bmh.Name: m,
},
ReadyForScheduleCount: map[airshipv1.VMRole]int{
airshipv1.VMControlPlane: 1,
airshipv1.VMWorker: 0,
},
Log: ctrl.Log.WithName("controllers").WithName("SIPCluster"),
}
sipCluster := testutil.CreateSIPCluster("subcluster-1", "default", 1, 3)
sipCluster.Spec.Services = airshipv1.SIPClusterServices{
LoadBalancer: []airshipv1.SIPClusterService{
{
Image: "haproxy:latest",
NodeLabels: map[string]string{
"test": "true",
},
NodePort: 30000,
NodeInterface: "oam-ipv4",
},
},
}
k8sClient := mockClient.NewFakeClient(objsToApply...)
Expect(ml.Extrapolate(*sipCluster, k8sClient)).To(BeFalse())
})
It("Should not retrieve the BMH IP if it has been previously extrapolated", func() {

View File

@ -256,3 +256,22 @@ func CreateSIPCluster(name string, namespace string, controlPlanes int, workers
Status: airshipv1.SIPClusterStatus{},
}
}
// CreateBMCAuthSecret creates a K8s Secret that matches the Metal3.io BaremetalHost credential format for use in test
// cases.
func CreateBMCAuthSecret(nodeName string, namespace string, username string, password string) *corev1.Secret {
return &corev1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("%s-bmc-credentials", nodeName),
Namespace: namespace,
},
Data: map[string][]byte{
"username": []byte(username),
"password": []byte(password),
},
}
}