diff --git a/README.md b/README.md index d5c3d8d..01c3f69 100644 --- a/README.md +++ b/README.md @@ -111,3 +111,8 @@ Use kubectl apply to deliver SIP CRs and BaremetalHost CRDs to kubernetes cluste ``` # kustomize build config/samples | kubectl apply -f - ``` + +## Testing + +Run `make test` to execute a suite of unit and integration tests against the SIP +operator. diff --git a/config/crd/bases/airship.airshipit.org_sipclusters.yaml b/config/crd/bases/airship.airshipit.org_sipclusters.yaml index 86962f0..4c97696 100644 --- a/config/crd/bases/airship.airshipit.org_sipclusters.yaml +++ b/config/crd/bases/airship.airshipit.org_sipclusters.yaml @@ -34,6 +34,13 @@ spec: spec: description: SIPClusterSpec defines the desired state of SIPCluster properties: + config: + description: SIPClusterSpec defines the desired state of SIPCluster + properties: + cluster-name: + description: Cluster NAme to be used for labeling vBMH + type: string + type: object infra: additionalProperties: properties: @@ -41,8 +48,10 @@ spec: type: string nodeInterfaceId: type: string - nodePort: - type: integer + nodePorts: + items: + type: integer + type: array nodelabels: additionalProperties: type: string diff --git a/config/crd/bases/bmh.yaml b/config/crd/bases/bmh.yaml new file mode 100644 index 0000000..bb28fe9 --- /dev/null +++ b/config/crd/bases/bmh.yaml @@ -0,0 +1,570 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + labels: + clusterctl.cluster.x-k8s.io: "" + name: baremetalhosts.metal3.io +spec: + additionalPrinterColumns: + - JSONPath: .status.operationalStatus + description: Operational status + name: Status + type: string + - JSONPath: .status.provisioning.state + description: Provisioning status + name: Provisioning Status + type: string + - JSONPath: .spec.consumerRef.name + description: Consumer using this host + name: Consumer + type: string + - JSONPath: .spec.bmc.address + description: Address of management controller + name: BMC + type: string + - JSONPath: .status.hardwareProfile + description: The type of hardware detected + name: Hardware Profile + type: string + - JSONPath: .spec.online + description: Whether the host is online or not + name: Online + type: string + - JSONPath: .status.errorMessage + description: Most recent error + name: Error + type: string + group: metal3.io + names: + kind: BareMetalHost + listKind: BareMetalHostList + plural: baremetalhosts + shortNames: + - bmh + - bmhost + singular: baremetalhost + scope: Namespaced + subresources: + status: {} + validation: + openAPIV3Schema: + description: BareMetalHost is the Schema for the baremetalhosts API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: BareMetalHostSpec defines the desired state of BareMetalHost + properties: + bmc: + description: How do we connect to the BMC? + properties: + address: + description: Address holds the URL for accessing the controller + on the network. + type: string + credentialsName: + description: The name of the secret containing the BMC credentials + (requires keys "username" and "password"). + type: string + disableCertificateVerification: + description: DisableCertificateVerification disables verification + of server certificates when using HTTPS to connect to the BMC. + This is required when the server certificate is self-signed, but + is insecure because it allows a man-in-the-middle to intercept + the connection. + type: boolean + required: + - address + - credentialsName + type: object + bootMACAddress: + description: Which MAC address will PXE boot? This is optional for some + types, but required for libvirt VMs driven by vbmc. + pattern: '[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}' + type: string + bootMode: + description: Select the method of initializing the hardware during boot. + enum: + - UEFI + - legacy + type: string + consumerRef: + description: ConsumerRef can be used to store information about something + that is using a host. When it is not empty, the host is considered + "in use". + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: 'If referring to a piece of an object instead of an + entire object, this string should contain a valid JSON/Go field + access statement, such as desiredState.manifest.containers[2]. + For example, if the object reference is to a container within + a pod, this would take on a value like: "spec.containers{name}" + (where "name" refers to the name of the container that triggered + the event) or if no container name is specified "spec.containers[2]" + (container with index 2 in this pod). This syntax is chosen only + to have some well-defined way of referencing a part of an object. + TODO: this design is not final and this field is subject to change + in the future.' + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resourceVersion: + description: 'Specific resourceVersion to which this reference is + made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' + type: string + uid: + description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' + type: string + type: object + description: + description: Description is a human-entered text used to help identify + the host + type: string + externallyProvisioned: + description: ExternallyProvisioned means something else is managing + the image running on the host and the operator should only manage + the power status and hardware inventory inspection. If the Image field + is filled in, this field is ignored. + type: boolean + hardwareProfile: + description: What is the name of the hardware profile for this host? + It should only be necessary to set this when inspection cannot automatically + determine the profile. + type: string + image: + description: Image holds the details of the image to be provisioned. + properties: + checksum: + description: Checksum is the checksum for the image. + type: string + url: + description: URL is a location of an image to deploy. + type: string + required: + - checksum + - url + type: object + networkData: + description: NetworkData holds the reference to the Secret containing + content of network_data.json which is passed to Config Drive + properties: + name: + description: Name is unique within a namespace to reference a secret + resource. + type: string + namespace: + description: Namespace defines the space within which the secret + name must be unique. + type: string + type: object + online: + description: Should the server be online? + type: boolean + taints: + description: Taints is the full, authoritative list of taints to apply + to the corresponding Machine. This list will overwrite any modifications + made to the Machine on an ongoing basis. + items: + description: The node this Taint is attached to has the "effect" on + any pod that does not tolerate the Taint. + properties: + effect: + description: Required. The effect of the taint on pods that do + not tolerate the taint. Valid effects are NoSchedule, PreferNoSchedule + and NoExecute. + type: string + key: + description: Required. The taint key to be applied to a node. + type: string + timeAdded: + description: TimeAdded represents the time at which the taint + was added. It is only written for NoExecute taints. + format: date-time + type: string + value: + description: Required. The taint value corresponding to the taint + key. + type: string + required: + - effect + - key + type: object + type: array + userData: + description: UserData holds the reference to the Secret containing the + user data to be passed to the host before it boots. + properties: + name: + description: Name is unique within a namespace to reference a secret + resource. + type: string + namespace: + description: Namespace defines the space within which the secret + name must be unique. + type: string + type: object + required: + - online + type: object + status: + description: BareMetalHostStatus defines the observed state of BareMetalHost + properties: + errorMessage: + description: the last error message reported by the provisioning subsystem + type: string + errorType: + description: ErrorType indicates the type of failure encountered when + the OperationalStatus is OperationalStatusError + enum: + - registration error + - inspection error + - provisioning error + - power management error + type: string + goodCredentials: + description: the last credentials we were able to validate as working + properties: + credentials: + description: SecretReference represents a Secret Reference. It has + enough information to retrieve secret in any namespace + properties: + name: + description: Name is unique within a namespace to reference + a secret resource. + type: string + namespace: + description: Namespace defines the space within which the secret + name must be unique. + type: string + type: object + credentialsVersion: + type: string + type: object + hardware: + description: The hardware discovered to exist on the host. + properties: + cpu: + description: CPU describes one processor on the host. + properties: + arch: + type: string + clockMegahertz: + description: ClockSpeed is a clock speed in MHz + count: + type: integer + flags: + items: + type: string + type: array + model: + type: string + required: + - arch + - clockMegahertz + - count + - flags + - model + type: object + firmware: + description: Firmware describes the firmware on the host. + properties: + bios: + description: The BIOS for this firmware + properties: + date: + description: The release/build date for this BIOS + type: string + vendor: + description: The vendor name for this BIOS + type: string + version: + description: The version of the BIOS + type: string + required: + - date + - vendor + - version + type: object + required: + - bios + type: object + hostname: + type: string + nics: + items: + description: NIC describes one network interface on the host. + properties: + ip: + description: The IP address of the device + type: string + mac: + description: The device MAC addr + pattern: '[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}' + type: string + model: + description: The name of the model, e.g. "virt-io" + type: string + name: + description: The name of the NIC, e.g. "nic-1" + type: string + pxe: + description: Whether the NIC is PXE Bootable + type: boolean + speedGbps: + description: The speed of the device + type: integer + vlanId: + description: The untagged VLAN ID + format: int32 + type: integer + vlans: + description: The VLANs available + items: + description: VLAN represents the name and ID of a VLAN + properties: + id: + description: VLANID is a 12-bit 802.1Q VLAN identifier + format: int32 + type: integer + name: + type: string + required: + - id + type: object + type: array + required: + - ip + - mac + - model + - name + - pxe + - speedGbps + - vlanId + type: object + type: array + ramMebibytes: + type: integer + storage: + items: + description: Storage describes one storage device (disk, SSD, + etc.) on the host. + properties: + hctl: + description: The SCSI location of the device + type: string + model: + description: Hardware model + type: string + name: + description: A name for the disk, e.g. "disk 1 (boot)" + type: string + rotational: + description: Whether this disk represents rotational storage + type: boolean + serialNumber: + description: The serial number of the device + type: string + sizeBytes: + description: The size of the disk in Bytes + format: int64 + type: integer + vendor: + description: The name of the vendor of the device + type: string + wwn: + description: The WWN of the device + type: string + wwnVendorExtension: + description: The WWN Vendor extension of the device + type: string + wwnWithExtension: + description: The WWN with the extension + type: string + required: + - name + - rotational + - serialNumber + - sizeBytes + type: object + type: array + systemVendor: + description: HardwareSystemVendor stores details about the whole + hardware system. + properties: + manufacturer: + type: string + productName: + type: string + serialNumber: + type: string + required: + - manufacturer + - productName + - serialNumber + type: object + required: + - cpu + - firmware + - hostname + - nics + - ramMebibytes + - storage + - systemVendor + type: object + hardwareProfile: + description: The name of the profile matching the hardware details. + type: string + lastUpdated: + description: LastUpdated identifies when this status was last observed. + format: date-time + type: string + operationHistory: + description: OperationHistory holds information about operations performed + on this host. + properties: + deprovision: + description: OperationMetric contains metadata about an operation + (inspection, provisioning, etc.) used for tracking metrics. + properties: + end: + format: date-time + nullable: true + type: string + start: + format: date-time + nullable: true + type: string + type: object + inspect: + description: OperationMetric contains metadata about an operation + (inspection, provisioning, etc.) used for tracking metrics. + properties: + end: + format: date-time + nullable: true + type: string + start: + format: date-time + nullable: true + type: string + type: object + provision: + description: OperationMetric contains metadata about an operation + (inspection, provisioning, etc.) used for tracking metrics. + properties: + end: + format: date-time + nullable: true + type: string + start: + format: date-time + nullable: true + type: string + type: object + register: + description: OperationMetric contains metadata about an operation + (inspection, provisioning, etc.) used for tracking metrics. + properties: + end: + format: date-time + nullable: true + type: string + start: + format: date-time + nullable: true + type: string + type: object + type: object + operationalStatus: + description: OperationalStatus holds the status of the host + enum: + - "" + - OK + - discovered + - error + type: string + poweredOn: + description: indicator for whether or not the host is powered on + type: boolean + provisioning: + description: Information tracked by the provisioner. + properties: + ID: + description: The machine's UUID from the underlying provisioning + tool + type: string + image: + description: Image holds the details of the last image successfully + provisioned to the host. + properties: + checksum: + description: Checksum is the checksum for the image. + type: string + url: + description: URL is a location of an image to deploy. + type: string + required: + - checksum + - url + type: object + state: + description: An indiciator for what the provisioner is doing with + the host. + type: string + required: + - ID + - state + type: object + triedCredentials: + description: the last credentials we sent to the provisioning backend + properties: + credentials: + description: SecretReference represents a Secret Reference. It has + enough information to retrieve secret in any namespace + properties: + name: + description: Name is unique within a namespace to reference + a secret resource. + type: string + namespace: + description: Namespace defines the space within which the secret + name must be unique. + type: string + type: object + credentialsVersion: + type: string + type: object + required: + - errorMessage + - hardwareProfile + - operationHistory + - operationalStatus + - poweredOn + - provisioning + type: object + type: object + version: v1alpha1 + versions: + - name: v1alpha1 + served: true + storage: true diff --git a/pkg/controllers/sipcluster_controller_test.go b/pkg/controllers/sipcluster_controller_test.go new file mode 100644 index 0000000..c4e6926 --- /dev/null +++ b/pkg/controllers/sipcluster_controller_test.go @@ -0,0 +1,163 @@ +/* + + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controllers + +import ( + "context" + "fmt" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + metal3 "github.com/metal3-io/baremetal-operator/apis/metal3.io/v1alpha1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + + airshipv1 "sipcluster/pkg/api/v1" + "sipcluster/pkg/vbmh" +) + +var _ = Describe("SIPCluster controller", func() { + Context("When it detects a SIPCluster", func() { + It("Should schedule BMHs accordingly", func() { + By("Labelling nodes") + + // Create vBMH test objects + nodes := []string{"master", "master", "master", "worker", "worker", "worker", "worker"} + namespace := "default" + for node, role := range nodes { + vBMH, networkData := createBMH(node, namespace, role, 6) + Expect(k8sClient.Create(context.Background(), vBMH)).Should(Succeed()) + Expect(k8sClient.Create(context.Background(), networkData)).Should(Succeed()) + } + + // Create SIP cluster + clusterName := "subcluster-test1" + sipCluster := createSIPCluster(clusterName, namespace, 3, 4) + Expect(k8sClient.Create(context.Background(), sipCluster)).Should(Succeed()) + + // Poll BMHs until SIP has scheduled them to the SIP cluster + Eventually(func() error { + expectedLabels := map[string]string{ + vbmh.SipScheduleLabel: "true", + vbmh.SipClusterLabel: clusterName, + } + + var bmh metal3.BareMetalHost + for node := range nodes { + Expect(k8sClient.Get(context.Background(), types.NamespacedName{ + Name: fmt.Sprintf("node%d", node), + Namespace: namespace, + }, &bmh)).Should(Succeed()) + } + + return compareLabels(expectedLabels, bmh.GetLabels()) + }, 60, 5).Should(Succeed()) + }) + }) +}) + +func compareLabels(expected map[string]string, actual map[string]string) error { + for k, v := range expected { + value, exists := actual[k] + if !exists { + return fmt.Errorf("label %s=%s missing. Has labels %v", k, v, actual) + } + + if value != v { + return fmt.Errorf("label %s=%s does not match expected label %s=%s. Has labels %v", k, value, k, + v, actual) + } + } + + return nil +} + +func createBMH(node int, namespace string, role string, rack int) (*metal3.BareMetalHost, *corev1.Secret) { + rackLabel := fmt.Sprintf("r%d", rack) + networkDataName := fmt.Sprintf("node%d-network-data", node) + return &metal3.BareMetalHost{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("node%d", node), + Namespace: namespace, + Labels: map[string]string{ + "airshipit.org/vino-flavor": role, + vbmh.SipScheduleLabel: "false", + vbmh.RackLabel: rackLabel, + vbmh.ServerLabel: fmt.Sprintf("stl2%so%d", rackLabel, node), + }, + }, + Spec: metal3.BareMetalHostSpec{ + NetworkData: &corev1.SecretReference{ + Namespace: namespace, + Name: networkDataName, + }, + }, + }, &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: networkDataName, + Namespace: namespace, + }, + Data: map[string][]byte{ + "networkData": []byte("ewoKICAgICJsaW5rcyI6IFsKICAgICAgICB7CiAgICAgICAgICAgICJpZCI6ICJlbm80IiwKICAgICAgICAgICAgIm5hbWUiOiAiZW5vNCIsCiAgICAgICAgICAgICJ0eXBlIjogInBoeSIsCiAgICAgICAgICAgICJtdHUiOiAxNTAwCiAgICAgICAgfSwKICAgICAgICB7CiAgICAgICAgICAgICJpZCI6ICJlbnA1OXMwZjEiLAogICAgICAgICAgICAibmFtZSI6ICJlbnA1OXMwZjEiLAogICAgICAgICAgICAidHlwZSI6ICJwaHkiLAogICAgICAgICAgICAibXR1IjogOTEwMAogICAgICAgIH0sCiAgICAgICAgewogICAgICAgICAgICAiaWQiOiAiZW5wMjE2czBmMCIsCiAgICAgICAgICAgICJuYW1lIjogImVucDIxNnMwZjAiLAogICAgICAgICAgICAidHlwZSI6ICJwaHkiLAogICAgICAgICAgICAibXR1IjogOTEwMAogICAgICAgIH0sCiAgICAgICAgewogICAgICAgICAgICAiaWQiOiAiYm9uZDAiLAogICAgICAgICAgICAibmFtZSI6ICJib25kMCIsCiAgICAgICAgICAgICJ0eXBlIjogImJvbmQiLAogICAgICAgICAgICAiYm9uZF9saW5rcyI6IFsKICAgICAgICAgICAgICAgICJlbnA1OXMwZjEiLAogICAgICAgICAgICAgICAgImVucDIxNnMwZjAiCiAgICAgICAgICAgIF0sCiAgICAgICAgICAgICJib25kX21vZGUiOiAiODAyLjNhZCIsCiAgICAgICAgICAgICJib25kX3htaXRfaGFzaF9wb2xpY3kiOiAibGF5ZXIzKzQiLAogICAgICAgICAgICAiYm9uZF9taWltb24iOiAxMDAsCiAgICAgICAgICAgICJtdHUiOiA5MTAwCiAgICAgICAgfSwKICAgICAgICB7CiAgICAgICAgICAgICJpZCI6ICJib25kMC40MSIsCiAgICAgICAgICAgICJuYW1lIjogImJvbmQwLjQxIiwKICAgICAgICAgICAgInR5cGUiOiAidmxhbiIsCiAgICAgICAgICAgICJ2bGFuX2xpbmsiOiAiYm9uZDAiLAogICAgICAgICAgICAidmxhbl9pZCI6IDQxLAogICAgICAgICAgICAibXR1IjogOTEwMCwKICAgICAgICAgICAgInZsYW5fbWFjX2FkZHJlc3MiOiBudWxsCiAgICAgICAgfSwKICAgICAgICB7CiAgICAgICAgICAgICJpZCI6ICJib25kMC40MiIsCiAgICAgICAgICAgICJuYW1lIjogImJvbmQwLjQyIiwKICAgICAgICAgICAgInR5cGUiOiAidmxhbiIsCiAgICAgICAgICAgICJ2bGFuX2xpbmsiOiAiYm9uZDAiLAogICAgICAgICAgICAidmxhbl9pZCI6IDQyLAogICAgICAgICAgICAibXR1IjogOTEwMCwKICAgICAgICAgICAgInZsYW5fbWFjX2FkZHJlc3MiOiBudWxsCiAgICAgICAgfSwKICAgICAgICB7CiAgICAgICAgICAgICJpZCI6ICJib25kMC40NCIsCiAgICAgICAgICAgICJuYW1lIjogImJvbmQwLjQ0IiwKICAgICAgICAgICAgInR5cGUiOiAidmxhbiIsCiAgICAgICAgICAgICJ2bGFuX2xpbmsiOiAiYm9uZDAiLAogICAgICAgICAgICAidmxhbl9pZCI6IDQ0LAogICAgICAgICAgICAibXR1IjogOTEwMCwKICAgICAgICAgICAgInZsYW5fbWFjX2FkZHJlc3MiOiBudWxsCiAgICAgICAgfSwKICAgICAgICB7CiAgICAgICAgICAgICJpZCI6ICJib25kMC40NSIsCiAgICAgICAgICAgICJuYW1lIjogImJvbmQwLjQ1IiwKICAgICAgICAgICAgInR5cGUiOiAidmxhbiIsCiAgICAgICAgICAgICJ2bGFuX2xpbmsiOiAiYm9uZDAiLAogICAgICAgICAgICAidmxhbl9pZCI6IDQ1LAogICAgICAgICAgICAibXR1IjogOTEwMCwKICAgICAgICAgICAgInZsYW5fbWFjX2FkZHJlc3MiOiBudWxsCiAgICAgICAgfQogICAgXSwKICAgICJuZXR3b3JrcyI6IFsKICAgICAgICB7CiAgICAgICAgICAgICJpZCI6ICJvYW0taXB2NiIsCiAgICAgICAgICAgICJ0eXBlIjogImlwdjYiLAogICAgICAgICAgICAibGluayI6ICJib25kMC40MSIsCiAgICAgICAgICAgICJpcF9hZGRyZXNzIjogIjIwMDE6MTg5MDoxMDAxOjI5M2Q6OjE0MCIsCiAgICAgICAgICAgICJyb3V0ZXMiOiBbCiAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgIm5ldHdvcmsiOiAiOjovMCIsCiAgICAgICAgICAgICAgICAgICAgIm5ldG1hc2siOiAiOjovMCIsCiAgICAgICAgICAgICAgICAgICAgImdhdGV3YXkiOiAiMjAwMToxODkwOjEwMDE6MjkzZDo6MSIKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgXQogICAgICAgIH0sCiAgICAgICAgewogICAgICAgICAgICAiaWQiOiAib2FtLWlwdjQiLAogICAgICAgICAgICAidHlwZSI6ICJpcHY0IiwKICAgICAgICAgICAgImxpbmsiOiAiYm9uZDAuNDEiLAogICAgICAgICAgICAiaXBfYWRkcmVzcyI6ICIzMi42OC41MS4xNDAiLAogICAgICAgICAgICAibmV0bWFzayI6ICIyNTUuMjU1LjI1NS4xMjgiLAogICAgICAgICAgICAiZG5zX25hbWVzZXJ2ZXJzIjogWwogICAgICAgICAgICAgICAgIjEzNS4xODguMzQuMTI0IiwKICAgICAgICAgICAgICAgICIxMzUuMzguMjQ0LjE2IiwKICAgICAgICAgICAgICAgICIxMzUuMTg4LjM0Ljg0IgogICAgICAgICAgICBdLAogICAgICAgICAgICAicm91dGVzIjogWwogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICJuZXR3b3JrIjogIjAuMC4wLjAiLAogICAgICAgICAgICAgICAgICAgICJuZXRtYXNrIjogIjAuMC4wLjAiLAogICAgICAgICAgICAgICAgICAgICJnYXRld2F5IjogIjMyLjY4LjUxLjEyOSIKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgXQogICAgICAgIH0sCiAgICAgICAgewogICAgICAgICAgICAiaWQiOiAicHhlLWlwdjYiLAogICAgICAgICAgICAibGluayI6ICJlbm80IiwKICAgICAgICAgICAgInR5cGUiOiAiaXB2NiIsCiAgICAgICAgICAgICJpcF9hZGRyZXNzIjogImZkMDA6OTAwOjEwMDoxMzg6OjEyIgogICAgICAgIH0sCiAgICAgICAgewogICAgICAgICAgICAiaWQiOiAicHhlLWlwdjQiLAogICAgICAgICAgICAibGluayI6ICJlbm80IiwKICAgICAgICAgICAgInR5cGUiOiAiaXB2NCIsCiAgICAgICAgICAgICJpcF9hZGRyZXNzIjogIjE3Mi4zMC4wLjEyIiwKICAgICAgICAgICAgIm5ldG1hc2siOiAiMjU1LjI1NS4yNTUuMTI4IgogICAgICAgIH0sCiAgICAgICAgewogICAgICAgICAgICAiaWQiOiAic3RvcmFnZS1pcHY2IiwKICAgICAgICAgICAgImxpbmsiOiAiYm9uZDAuNDIiLAogICAgICAgICAgICAidHlwZSI6ICJpcHY2IiwKICAgICAgICAgICAgImlwX2FkZHJlc3MiOiAiZmQwMDo5MDA6MTAwOjEzOTo6MTYiCiAgICAgICAgfSwKICAgICAgICB7CiAgICAgICAgICAgICJpZCI6ICJzdG9yYWdlLWlwdjQiLAogICAgICAgICAgICAibGluayI6ICJib25kMC40MiIsCiAgICAgICAgICAgICJ0eXBlIjogImlwdjQiLAogICAgICAgICAgICAiaXBfYWRkcmVzcyI6ICIxNzIuMzEuMC4xNiIsCiAgICAgICAgICAgICJuZXRtYXNrIjogIjI1NS4yNTUuMjU1LjEyOCIKICAgICAgICB9LAogICAgICAgIHsKICAgICAgICAgICAgImlkIjogImtzbi1pcHY2IiwKICAgICAgICAgICAgImxpbmsiOiAiYm9uZDAuNDQiLAogICAgICAgICAgICAidHlwZSI6ICJpcHY2IiwKICAgICAgICAgICAgImlwX2FkZHJlc3MiOiAiZmQwMDo5MDA6MTAwOjEzYTo6MTIiCiAgICAgICAgfSwKICAgICAgICB7CiAgICAgICAgICAgICJpZCI6ICJrc24taXB2NCIsCiAgICAgICAgICAgICJsaW5rIjogImJvbmQwLjQ0IiwKICAgICAgICAgICAgInR5cGUiOiAiaXB2NCIsCiAgICAgICAgICAgICJpcF9hZGRyZXNzIjogIjE3Mi4yOS4wLjEyIiwKICAgICAgICAgICAgIm5ldG1hc2siOiAiMjU1LjI1NS4yNTUuMTI4IgogICAgICAgIH0KICAgIF0KfQo="), + }, + Type: corev1.SecretTypeOpaque, + } +} + +func createSIPCluster(name string, namespace string, masters int, workers int) *airshipv1.SIPCluster { + return &airshipv1.SIPCluster{ + TypeMeta: metav1.TypeMeta{ + Kind: "SIPCluster", + APIVersion: "airship.airshipit.org/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: airshipv1.SIPClusterSpec{ + Config: &airshipv1.SipConfig{ + ClusterName: name, + }, + Nodes: map[airshipv1.VmRoles]airshipv1.NodeSet{ + airshipv1.VmMaster: airshipv1.NodeSet{ + VmFlavor: "airshipit.org/vino-flavor=master", + Scheduling: []airshipv1.SchedulingOptions{ + airshipv1.ServerAntiAffinity, + }, + Count: &airshipv1.VmCount{ + Active: masters, + Standby: 0, + }, + }, + airshipv1.VmWorker: airshipv1.NodeSet{ + VmFlavor: "airshipit.org/vino-flavor=worker", + Scheduling: []airshipv1.SchedulingOptions{ + airshipv1.ServerAntiAffinity, + }, + Count: &airshipv1.VmCount{ + Active: workers, + Standby: 0, + }, + }, + }, + InfraServices: map[airshipv1.InfraService]airshipv1.InfraConfig{}, + }, + Status: airshipv1.SIPClusterStatus{}, + } +} diff --git a/pkg/controllers/suite_test.go b/pkg/controllers/suite_test.go index 17d3786..88e1442 100644 --- a/pkg/controllers/suite_test.go +++ b/pkg/controllers/suite_test.go @@ -20,16 +20,17 @@ import ( "path/filepath" "testing" + metal3 "github.com/metal3-io/baremetal-operator/apis/metal3.io/v1alpha1" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" + ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/envtest" "sigs.k8s.io/controller-runtime/pkg/envtest/printer" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" - airshipv1 "sipcluster/pkg/api/v1" // +kubebuilder:scaffold:imports ) @@ -54,7 +55,7 @@ var _ = BeforeSuite(func(done Done) { By("bootstrapping test environment") testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, + CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, } var err error @@ -65,10 +66,29 @@ var _ = BeforeSuite(func(done Done) { err = airshipv1.AddToScheme(scheme.Scheme) Expect(err).NotTo(HaveOccurred()) + err = metal3.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + // +kubebuilder:scaffold:scheme - k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{ + Scheme: scheme.Scheme, + }) Expect(err).ToNot(HaveOccurred()) + + err = (&SIPClusterReconciler{ + Client: k8sManager.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("SIPCluster"), + Scheme: scheme.Scheme, + }).SetupWithManager(k8sManager) + Expect(err).ToNot(HaveOccurred()) + + go func() { + err = k8sManager.Start(ctrl.SetupSignalHandler()) + Expect(err).ToNot(HaveOccurred()) + }() + + k8sClient = k8sManager.GetClient() Expect(k8sClient).ToNot(BeNil()) close(done)