diff --git a/.github/workflows/kustom.yaml b/.github/workflows/kustom.yaml new file mode 100644 index 000000000..0c6e0d434 --- /dev/null +++ b/.github/workflows/kustom.yaml @@ -0,0 +1,36 @@ +name: Kustomize Build +on: + workflow_dispatch: + push: + branches: + - main + pull_request: + branches: + - main + paths: + - config/samples/** +jobs: + kustomize: + runs-on: ubuntu-latest + steps: + - name: Install Go + uses: actions/setup-go@v3 + with: + go-version: 1.20.x + - uses: actions/checkout@v4 + with: + # this fetches all branches. Needed because we need gh-pages branch for deploy to work + fetch-depth: 0 + - name: download kustomize + run: | + mkdir bin + LINK=https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh + curl -Ss $LINK | bash -s -- 5.0.1 ./bin + - name: kustomize build + run: | + cd config/samples/dataplane + + for d in */ ; do + echo "=============== $d ===============" + ../../../bin/kustomize build --load-restrictor LoadRestrictionsNone "$d" + done diff --git a/Dockerfile b/Dockerfile index aae778daa..c9d6afc64 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,6 +27,8 @@ RUN if [ ! -f $CACHITO_ENV_FILE ]; then go mod download ; fi # Build manager RUN if [ -f $CACHITO_ENV_FILE ] ; then source $CACHITO_ENV_FILE ; fi ; env ${GO_BUILD_EXTRA_ENV_ARGS} go build ${GO_BUILD_EXTRA_ARGS} -a -o ${DEST_ROOT}/manager main.go +RUN cp -r config/services ${DEST_ROOT}/services + # Use distroless as minimal base image to package the manager binary # Refer to https://github.com/GoogleContainerTools/distroless for more details FROM $OPERATOR_BASE_IMAGE @@ -56,13 +58,17 @@ LABEL com.redhat.component="${IMAGE_COMPONENT}" \ io.openshift.tags="${IMAGE_TAGS}" ### DO NOT EDIT LINES ABOVE -ENV USER_UID=$USER_ID +ENV USER_UID=$USER_ID \ + OPERATOR_SERVICES=/usr/share/openstack-operator/services/ WORKDIR / # Install operator binary to WORKDIR COPY --from=builder ${DEST_ROOT}/manager . +# Install services +COPY --from=builder ${DEST_ROOT}/services ${OPERATOR_SERVICES} + USER $USER_ID ENV PATH="/:${PATH}" diff --git a/Makefile b/Makefile index 7d1914eb9..3fa82b938 100644 --- a/Makefile +++ b/Makefile @@ -137,7 +137,7 @@ test: manifests generate gowork fmt vet envtest ginkgo ## Run tests. source hack/export_related_images.sh && \ KUBEBUILDER_ASSETS="$(shell $(ENVTEST) -v debug --bin-dir $(LOCALBIN) use $(ENVTEST_K8S_VERSION) -p path)" \ OPERATOR_TEMPLATES="$(PWD)/templates" \ - $(GINKGO) --trace --cover --coverpkg=../../pkg/openstack,../../pkg/openstackclient,../../pkg/util,../../controllers,../../apis/client/v1beta1,../../apis/core/v1beta1 --coverprofile cover.out --covermode=atomic ${PROC_CMD} $(GINKGO_ARGS) ./tests/... ./apis/client/... + $(GINKGO) --trace --cover --coverpkg=../../pkg/openstack,../../pkg/openstackclient,../../pkg/util,../../controllers,../../apis/client/v1beta1,../../apis/core/v1beta1,../../apis/dataplane/v1beta1 --coverprofile cover.out --covermode=atomic ${PROC_CMD} $(GINKGO_ARGS) ./tests/... ./apis/client/... ##@ Build diff --git a/OWNERS b/OWNERS index c23a45061..564982606 100644 --- a/OWNERS +++ b/OWNERS @@ -2,7 +2,9 @@ approvers: - ci-approvers - openstack-approvers + - dataplane-approvers reviewers: - ci-approvers - openstack-approvers + - dataplane-approvers diff --git a/OWNERS_ALIASES b/OWNERS_ALIASES index b70792ba3..e803395f4 100644 --- a/OWNERS_ALIASES +++ b/OWNERS_ALIASES @@ -10,3 +10,11 @@ aliases: - dprince - olliewalsh - stuggi + dataplane-approvers: + - fao89 + - fultonj + - rebtoor + - slagle + - bshephar + - rabi + - jpodivin diff --git a/PROJECT b/PROJECT index 27e77b65b..6112f528e 100644 --- a/PROJECT +++ b/PROJECT @@ -46,4 +46,35 @@ resources: defaulting: true validation: true webhookVersion: v1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: openstack.org + group: dataplane + kind: OpenStackDataPlaneNodeSet + path: github.com/openstack-k8s-operators/openstack-operator/apis/dataplane/v1beta1 + version: v1beta1 + webhooks: + defaulting: true + validation: true + webhookVersion: v1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: openstack.org + group: dataplane + kind: OpenStackDataPlaneService + path: github.com/openstack-k8s-operators/openstack-operator/apis/dataplane/v1beta1 + version: v1beta1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: openstack.org + group: dataplane + kind: OpenStackDataPlaneDeployment + path: github.com/openstack-k8s-operators/openstack-operator/apis/dataplane/v1beta1 + version: v1beta1 version: "3" diff --git a/apis/bases/dataplane.openstack.org_openstackdataplanedeployments.yaml b/apis/bases/dataplane.openstack.org_openstackdataplanedeployments.yaml new file mode 100644 index 000000000..b06f69dd8 --- /dev/null +++ b/apis/bases/dataplane.openstack.org_openstackdataplanedeployments.yaml @@ -0,0 +1,150 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: openstackdataplanedeployments.dataplane.openstack.org +spec: + group: dataplane.openstack.org + names: + kind: OpenStackDataPlaneDeployment + listKind: OpenStackDataPlaneDeploymentList + plural: openstackdataplanedeployments + shortNames: + - osdpd + - osdpdeployment + - osdpdeployments + singular: openstackdataplanedeployment + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: NodeSets + jsonPath: .spec.nodeSets + name: NodeSets + type: string + - description: Status + jsonPath: .status.conditions[0].status + name: Status + type: string + - description: Message + jsonPath: .status.conditions[0].message + name: Message + type: string + name: v1beta1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + ansibleExtraVars: + x-kubernetes-preserve-unknown-fields: true + ansibleLimit: + type: string + ansibleSkipTags: + type: string + ansibleTags: + type: string + deploymentRequeueTime: + default: 15 + minimum: 1 + type: integer + nodeSets: + items: + type: string + type: array + servicesOverride: + items: + type: string + type: array + required: + - deploymentRequeueTime + - nodeSets + type: object + x-kubernetes-validations: + - message: OpenStackDataPlaneDeployment Spec is immutable + rule: self == oldSelf + status: + properties: + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + type: string + reason: + type: string + severity: + type: string + status: + type: string + type: + type: string + required: + - lastTransitionTime + - status + - type + type: object + type: array + configMapHashes: + additionalProperties: + type: string + type: object + containerImages: + additionalProperties: + type: string + type: object + deployed: + type: boolean + deployedVersion: + type: string + nodeSetConditions: + additionalProperties: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + type: string + reason: + type: string + severity: + type: string + status: + type: string + type: + type: string + required: + - lastTransitionTime + - status + - type + type: object + type: array + type: object + nodeSetHashes: + additionalProperties: + type: string + type: object + observedGeneration: + format: int64 + type: integer + secretHashes: + additionalProperties: + type: string + type: object + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/apis/bases/dataplane.openstack.org_openstackdataplanenodesets.yaml b/apis/bases/dataplane.openstack.org_openstackdataplanenodesets.yaml new file mode 100644 index 000000000..05ce3f662 --- /dev/null +++ b/apis/bases/dataplane.openstack.org_openstackdataplanenodesets.yaml @@ -0,0 +1,2047 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: openstackdataplanenodesets.dataplane.openstack.org +spec: + group: dataplane.openstack.org + names: + kind: OpenStackDataPlaneNodeSet + listKind: OpenStackDataPlaneNodeSetList + plural: openstackdataplanenodesets + shortNames: + - osdpns + - osdpnodeset + - osdpnodesets + singular: openstackdataplanenodeset + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: Status + jsonPath: .status.conditions[0].status + name: Status + type: string + - description: Message + jsonPath: .status.conditions[0].message + name: Message + type: string + name: v1beta1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + baremetalSetTemplate: + properties: + agentImageUrl: + type: string + apacheImageUrl: + type: string + automatedCleaningMode: + default: metadata + enum: + - metadata + - disabled + type: string + baremetalHosts: + additionalProperties: + properties: + ctlPlaneIP: + type: string + networkData: + properties: + name: + type: string + namespace: + type: string + type: object + x-kubernetes-map-type: atomic + preprovisioningNetworkDataName: + type: string + userData: + properties: + name: + type: string + namespace: + type: string + type: object + x-kubernetes-map-type: atomic + type: object + type: object + bmhLabelSelector: + additionalProperties: + type: string + type: object + bmhNamespace: + default: openshift-machine-api + type: string + bootstrapDns: + items: + type: string + type: array + cloudUserName: + default: cloud-admin + type: string + ctlplaneGateway: + type: string + ctlplaneInterface: + type: string + ctlplaneNetmask: + default: 255.255.255.0 + type: string + deploymentSSHSecret: + type: string + dnsSearchDomains: + items: + type: string + type: array + domainName: + type: string + hardwareReqs: + properties: + cpuReqs: + properties: + arch: + enum: + - x86_64 + - ppc64le + type: string + countReq: + properties: + count: + minimum: 1 + type: integer + exactMatch: + type: boolean + type: object + mhzReq: + properties: + exactMatch: + type: boolean + mhz: + minimum: 1 + type: integer + type: object + type: object + diskReqs: + properties: + gbReq: + properties: + exactMatch: + type: boolean + gb: + minimum: 1 + type: integer + type: object + ssdReq: + properties: + exactMatch: + type: boolean + ssd: + type: boolean + type: object + type: object + memReqs: + properties: + gbReq: + properties: + exactMatch: + type: boolean + gb: + minimum: 1 + type: integer + type: object + type: object + type: object + networkData: + properties: + name: + type: string + namespace: + type: string + type: object + x-kubernetes-map-type: atomic + osContainerImageUrl: + type: string + osImage: + type: string + passwordSecret: + properties: + name: + type: string + namespace: + type: string + type: object + x-kubernetes-map-type: atomic + provisionServerName: + type: string + provisioningInterface: + type: string + userData: + properties: + name: + type: string + namespace: + type: string + type: object + x-kubernetes-map-type: atomic + required: + - cloudUserName + - ctlplaneInterface + - deploymentSSHSecret + type: object + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + networkAttachments: + items: + type: string + type: array + nodeTemplate: + properties: + ansible: + properties: + ansibleHost: + type: string + ansiblePort: + type: integer + ansibleUser: + type: string + ansibleVars: + x-kubernetes-preserve-unknown-fields: true + ansibleVarsFrom: + items: + properties: + configMapRef: + properties: + name: + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + prefix: + type: string + secretRef: + properties: + name: + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + type: object + type: array + type: object + ansibleSSHPrivateKeySecret: + type: string + extraMounts: + items: + properties: + extraVolType: + type: string + mounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + propagation: + items: + type: string + type: array + volumes: + items: + properties: + awsElasticBlockStore: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + azureDisk: + properties: + cachingMode: + type: string + diskName: + type: string + diskURI: + type: string + fsType: + type: string + kind: + type: string + readOnly: + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + properties: + readOnly: + type: boolean + secretName: + type: string + shareName: + type: string + required: + - secretName + - shareName + type: object + cephfs: + properties: + monitors: + items: + type: string + type: array + path: + type: string + readOnly: + type: boolean + secretFile: + type: string + secretRef: + properties: + name: + type: string + type: object + x-kubernetes-map-type: atomic + user: + type: string + required: + - monitors + type: object + cinder: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + x-kubernetes-map-type: atomic + volumeID: + type: string + required: + - volumeID + type: object + configMap: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + csi: + properties: + driver: + type: string + fsType: + type: string + nodePublishSecretRef: + properties: + name: + type: string + type: object + x-kubernetes-map-type: atomic + readOnly: + type: boolean + volumeAttributes: + additionalProperties: + type: string + type: object + required: + - driver + type: object + downwardAPI: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + type: object + emptyDir: + properties: + medium: + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + ephemeral: + properties: + volumeClaimTemplate: + properties: + metadata: + type: object + spec: + properties: + accessModes: + items: + type: string + type: array + dataSource: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + dataSourceRef: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + required: + - kind + - name + type: object + resources: + properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + storageClassName: + type: string + volumeMode: + type: string + volumeName: + type: string + type: object + required: + - spec + type: object + type: object + fc: + properties: + fsType: + type: string + lun: + format: int32 + type: integer + readOnly: + type: boolean + targetWWNs: + items: + type: string + type: array + wwids: + items: + type: string + type: array + type: object + flexVolume: + properties: + driver: + type: string + fsType: + type: string + options: + additionalProperties: + type: string + type: object + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + x-kubernetes-map-type: atomic + required: + - driver + type: object + flocker: + properties: + datasetName: + type: string + datasetUUID: + type: string + type: object + gcePersistentDisk: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + pdName: + type: string + readOnly: + type: boolean + required: + - pdName + type: object + gitRepo: + properties: + directory: + type: string + repository: + type: string + revision: + type: string + required: + - repository + type: object + glusterfs: + properties: + endpoints: + type: string + path: + type: string + readOnly: + type: boolean + required: + - endpoints + - path + type: object + hostPath: + properties: + path: + type: string + type: + type: string + required: + - path + type: object + iscsi: + properties: + chapAuthDiscovery: + type: boolean + chapAuthSession: + type: boolean + fsType: + type: string + initiatorName: + type: string + iqn: + type: string + iscsiInterface: + type: string + lun: + format: int32 + type: integer + portals: + items: + type: string + type: array + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + x-kubernetes-map-type: atomic + targetPortal: + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + type: string + nfs: + properties: + path: + type: string + readOnly: + type: boolean + server: + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + properties: + claimName: + type: string + readOnly: + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + properties: + fsType: + type: string + pdID: + type: string + required: + - pdID + type: object + portworxVolume: + properties: + fsType: + type: string + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + projected: + properties: + defaultMode: + format: int32 + type: integer + sources: + items: + properties: + configMap: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + downwardAPI: + properties: + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + type: object + secret: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + serviceAccountToken: + properties: + audience: + type: string + expirationSeconds: + format: int64 + type: integer + path: + type: string + required: + - path + type: object + type: object + type: array + type: object + quobyte: + properties: + group: + type: string + readOnly: + type: boolean + registry: + type: string + tenant: + type: string + user: + type: string + volume: + type: string + required: + - registry + - volume + type: object + rbd: + properties: + fsType: + type: string + image: + type: string + keyring: + type: string + monitors: + items: + type: string + type: array + pool: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + x-kubernetes-map-type: atomic + user: + type: string + required: + - image + - monitors + type: object + scaleIO: + properties: + fsType: + type: string + gateway: + type: string + protectionDomain: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + x-kubernetes-map-type: atomic + sslEnabled: + type: boolean + storageMode: + type: string + storagePool: + type: string + system: + type: string + volumeName: + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + optional: + type: boolean + secretName: + type: string + type: object + storageos: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + x-kubernetes-map-type: atomic + volumeName: + type: string + volumeNamespace: + type: string + type: object + vsphereVolume: + properties: + fsType: + type: string + storagePolicyID: + type: string + storagePolicyName: + type: string + volumePath: + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + required: + - mounts + - volumes + type: object + type: array + managementNetwork: + default: ctlplane + type: string + networkData: + properties: + name: + type: string + namespace: + type: string + type: object + x-kubernetes-map-type: atomic + networks: + items: + properties: + defaultRoute: + type: boolean + fixedIP: + type: string + name: + pattern: ^[a-zA-Z0-9][a-zA-Z0-9\-_]*[a-zA-Z0-9]$ + type: string + subnetName: + pattern: ^[a-zA-Z0-9][a-zA-Z0-9\-_]*[a-zA-Z0-9]$ + type: string + required: + - name + - subnetName + type: object + type: array + userData: + properties: + name: + type: string + namespace: + type: string + type: object + x-kubernetes-map-type: atomic + required: + - ansibleSSHPrivateKeySecret + type: object + nodes: + additionalProperties: + properties: + ansible: + properties: + ansibleHost: + type: string + ansiblePort: + type: integer + ansibleUser: + type: string + ansibleVars: + x-kubernetes-preserve-unknown-fields: true + ansibleVarsFrom: + items: + properties: + configMapRef: + properties: + name: + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + prefix: + type: string + secretRef: + properties: + name: + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + type: object + type: array + type: object + extraMounts: + items: + properties: + extraVolType: + type: string + mounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + propagation: + items: + type: string + type: array + volumes: + items: + properties: + awsElasticBlockStore: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + azureDisk: + properties: + cachingMode: + type: string + diskName: + type: string + diskURI: + type: string + fsType: + type: string + kind: + type: string + readOnly: + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + properties: + readOnly: + type: boolean + secretName: + type: string + shareName: + type: string + required: + - secretName + - shareName + type: object + cephfs: + properties: + monitors: + items: + type: string + type: array + path: + type: string + readOnly: + type: boolean + secretFile: + type: string + secretRef: + properties: + name: + type: string + type: object + x-kubernetes-map-type: atomic + user: + type: string + required: + - monitors + type: object + cinder: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + x-kubernetes-map-type: atomic + volumeID: + type: string + required: + - volumeID + type: object + configMap: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + csi: + properties: + driver: + type: string + fsType: + type: string + nodePublishSecretRef: + properties: + name: + type: string + type: object + x-kubernetes-map-type: atomic + readOnly: + type: boolean + volumeAttributes: + additionalProperties: + type: string + type: object + required: + - driver + type: object + downwardAPI: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + type: object + emptyDir: + properties: + medium: + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + ephemeral: + properties: + volumeClaimTemplate: + properties: + metadata: + type: object + spec: + properties: + accessModes: + items: + type: string + type: array + dataSource: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + dataSourceRef: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + required: + - kind + - name + type: object + resources: + properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + storageClassName: + type: string + volumeMode: + type: string + volumeName: + type: string + type: object + required: + - spec + type: object + type: object + fc: + properties: + fsType: + type: string + lun: + format: int32 + type: integer + readOnly: + type: boolean + targetWWNs: + items: + type: string + type: array + wwids: + items: + type: string + type: array + type: object + flexVolume: + properties: + driver: + type: string + fsType: + type: string + options: + additionalProperties: + type: string + type: object + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + x-kubernetes-map-type: atomic + required: + - driver + type: object + flocker: + properties: + datasetName: + type: string + datasetUUID: + type: string + type: object + gcePersistentDisk: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + pdName: + type: string + readOnly: + type: boolean + required: + - pdName + type: object + gitRepo: + properties: + directory: + type: string + repository: + type: string + revision: + type: string + required: + - repository + type: object + glusterfs: + properties: + endpoints: + type: string + path: + type: string + readOnly: + type: boolean + required: + - endpoints + - path + type: object + hostPath: + properties: + path: + type: string + type: + type: string + required: + - path + type: object + iscsi: + properties: + chapAuthDiscovery: + type: boolean + chapAuthSession: + type: boolean + fsType: + type: string + initiatorName: + type: string + iqn: + type: string + iscsiInterface: + type: string + lun: + format: int32 + type: integer + portals: + items: + type: string + type: array + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + x-kubernetes-map-type: atomic + targetPortal: + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + type: string + nfs: + properties: + path: + type: string + readOnly: + type: boolean + server: + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + properties: + claimName: + type: string + readOnly: + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + properties: + fsType: + type: string + pdID: + type: string + required: + - pdID + type: object + portworxVolume: + properties: + fsType: + type: string + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + projected: + properties: + defaultMode: + format: int32 + type: integer + sources: + items: + properties: + configMap: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + downwardAPI: + properties: + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + type: object + secret: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + serviceAccountToken: + properties: + audience: + type: string + expirationSeconds: + format: int64 + type: integer + path: + type: string + required: + - path + type: object + type: object + type: array + type: object + quobyte: + properties: + group: + type: string + readOnly: + type: boolean + registry: + type: string + tenant: + type: string + user: + type: string + volume: + type: string + required: + - registry + - volume + type: object + rbd: + properties: + fsType: + type: string + image: + type: string + keyring: + type: string + monitors: + items: + type: string + type: array + pool: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + x-kubernetes-map-type: atomic + user: + type: string + required: + - image + - monitors + type: object + scaleIO: + properties: + fsType: + type: string + gateway: + type: string + protectionDomain: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + x-kubernetes-map-type: atomic + sslEnabled: + type: boolean + storageMode: + type: string + storagePool: + type: string + system: + type: string + volumeName: + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + optional: + type: boolean + secretName: + type: string + type: object + storageos: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + x-kubernetes-map-type: atomic + volumeName: + type: string + volumeNamespace: + type: string + type: object + vsphereVolume: + properties: + fsType: + type: string + storagePolicyID: + type: string + storagePolicyName: + type: string + volumePath: + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + required: + - mounts + - volumes + type: object + type: array + hostName: + type: string + managementNetwork: + type: string + networkData: + properties: + name: + type: string + namespace: + type: string + type: object + x-kubernetes-map-type: atomic + networks: + items: + properties: + defaultRoute: + type: boolean + fixedIP: + type: string + name: + pattern: ^[a-zA-Z0-9][a-zA-Z0-9\-_]*[a-zA-Z0-9]$ + type: string + subnetName: + pattern: ^[a-zA-Z0-9][a-zA-Z0-9\-_]*[a-zA-Z0-9]$ + type: string + required: + - name + - subnetName + type: object + type: array + preprovisioningNetworkDataName: + type: string + userData: + properties: + name: + type: string + namespace: + type: string + type: object + x-kubernetes-map-type: atomic + type: object + type: object + preProvisioned: + type: boolean + secretMaxSize: + default: 1048576 + type: integer + services: + default: + - download-cache + - bootstrap + - configure-network + - validate-network + - install-os + - configure-os + - ssh-known-hosts + - run-os + - reboot-os + - install-certs + - ovn + - neutron-metadata + - libvirt + - nova + - telemetry + items: + type: string + type: array + tags: + items: + type: string + type: array + tlsEnabled: + default: true + type: boolean + required: + - nodeTemplate + - nodes + type: object + status: + properties: + allHostnames: + additionalProperties: + additionalProperties: + type: string + type: object + type: object + allIPs: + additionalProperties: + additionalProperties: + type: string + type: object + type: object + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + type: string + reason: + type: string + severity: + type: string + status: + type: string + type: + type: string + required: + - lastTransitionTime + - status + - type + type: object + type: array + configHash: + type: string + configMapHashes: + additionalProperties: + type: string + type: object + containerImages: + additionalProperties: + type: string + type: object + ctlplaneSearchDomain: + type: string + deployed: + type: boolean + deployedConfigHash: + type: string + deployedVersion: + type: string + deploymentStatuses: + additionalProperties: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + type: string + reason: + type: string + severity: + type: string + status: + type: string + type: + type: string + required: + - lastTransitionTime + - status + - type + type: object + type: array + type: object + dnsClusterAddresses: + items: + type: string + type: array + observedGeneration: + format: int64 + type: integer + secretHashes: + additionalProperties: + type: string + type: object + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/apis/bases/dataplane.openstack.org_openstackdataplaneservices.yaml b/apis/bases/dataplane.openstack.org_openstackdataplaneservices.yaml new file mode 100644 index 000000000..1bfdf9a48 --- /dev/null +++ b/apis/bases/dataplane.openstack.org_openstackdataplaneservices.yaml @@ -0,0 +1,133 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: openstackdataplaneservices.dataplane.openstack.org +spec: + group: dataplane.openstack.org + names: + kind: OpenStackDataPlaneService + listKind: OpenStackDataPlaneServiceList + plural: openstackdataplaneservices + shortNames: + - osdps + - osdpservice + - osdpservices + singular: openstackdataplaneservice + scope: Namespaced + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + addCertMounts: + default: false + type: boolean + caCerts: + type: string + configMaps: + items: + type: string + type: array + containerImageFields: + items: + type: string + type: array + deployOnAllNodeSets: + type: boolean + openStackAnsibleEERunnerImage: + type: string + play: + type: string + playbook: + type: string + secrets: + items: + type: string + type: array + tlsCert: + properties: + contents: + items: + type: string + type: array + issuer: + type: string + keyUsages: + items: + enum: + - signing + - digital signature + - content commitment + - key encipherment + - key agreement + - data encipherment + - cert sign + - crl sign + - encipher only + - decipher only + - any + - server auth + - client auth + - code signing + - email protection + - s/mime + - ipsec end system + - ipsec tunnel + - ipsec user + - timestamping + - ocsp signing + - microsoft sgc + - netscape sgc + type: string + type: array + networks: + items: + pattern: ^[a-zA-Z0-9][a-zA-Z0-9\-_]*[a-zA-Z0-9]$ + type: string + type: array + required: + - contents + type: object + type: object + status: + properties: + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + type: string + reason: + type: string + severity: + type: string + status: + type: string + type: + type: string + required: + - lastTransitionTime + - status + - type + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/apis/dataplane/v1beta1/common.go b/apis/dataplane/v1beta1/common.go new file mode 100644 index 000000000..0bf0a1cb5 --- /dev/null +++ b/apis/dataplane/v1beta1/common.go @@ -0,0 +1,163 @@ +/* +Copyright 2023. + +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 v1beta1 + +import ( + "encoding/json" + + infranetworkv1 "github.com/openstack-k8s-operators/infra-operator/apis/network/v1beta1" + "github.com/openstack-k8s-operators/lib-common/modules/storage" + corev1 "k8s.io/api/core/v1" +) + +// AnsibleVarsFromSource represents the source of a set of ConfigMaps/Secrets +type AnsibleVarsFromSource struct { + // An optional identifier to prepend to each key in the ConfigMap. Must be a C_IDENTIFIER. + // +optional + Prefix string `json:"prefix,omitempty" protobuf:"bytes,1,opt,name=prefix"` + // The ConfigMap to select from + // +optional + ConfigMapRef *corev1.ConfigMapEnvSource `json:"configMapRef,omitempty" protobuf:"bytes,2,opt,name=configMapRef"` + // The Secret to select from + // +optional + SecretRef *corev1.SecretEnvSource `json:"secretRef,omitempty" protobuf:"bytes,3,opt,name=secretRef"` +} + +// AnsibleOpts defines a logical grouping of Ansible related configuration options. +type AnsibleOpts struct { + // AnsibleUser SSH user for Ansible connection + // +kubebuilder:validation:Optional + AnsibleUser string `json:"ansibleUser"` + + // AnsibleHost SSH host for Ansible connection + // +kubebuilder:validation:Optional + AnsibleHost string `json:"ansibleHost,omitempty"` + + // AnsiblePort SSH port for Ansible connection + // +kubebuilder:validation:Optional + // +operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors={"urn:alm:descriptor:com.tectonic.ui:number"} + AnsiblePort int `json:"ansiblePort,omitempty"` + + // AnsibleVars for configuring ansible + // +kubebuilder:pruning:PreserveUnknownFields + // +kubebuilder:validation:Schemaless + AnsibleVars map[string]json.RawMessage `json:"ansibleVars,omitempty"` + + // AnsibleVarsFrom is a list of sources to populate ansible variables from. + // Values defined by an AnsibleVars with a duplicate key take precedence. + // +kubebuilder:validation:Optional + AnsibleVarsFrom []AnsibleVarsFromSource `json:"ansibleVarsFrom,omitempty"` +} + +// NodeSection defines the top level attributes inherited by nodes in the CR. +type NodeSection struct { + // HostName - node name + // +kubebuilder:validation:Optional + HostName string `json:"hostName,omitempty"` + + // Networks - Instance networks + // +kubebuilder:validation:Optional + Networks []infranetworkv1.IPSetNetwork `json:"networks,omitempty"` + + // ManagementNetwork - Name of network to use for management (SSH/Ansible) + // +kubebuilder:validation:Optional + ManagementNetwork string `json:"managementNetwork,omitempty"` + + // Ansible is the group of Ansible related configuration options. + // +kubebuilder:validation:Optional + Ansible AnsibleOpts `json:"ansible,omitempty"` + + // ExtraMounts containing files which can be mounted into an Ansible Execution Pod + // +kubebuilder:validation:Optional + ExtraMounts []storage.VolMounts `json:"extraMounts,omitempty"` + + // UserData node specific user-data + // +kubebuilder:validation:Optional + UserData *corev1.SecretReference `json:"userData,omitempty"` + + // NetworkData node specific network-data + // +kubebuilder:validation:Optional + NetworkData *corev1.SecretReference `json:"networkData,omitempty"` + + // +kubebuilder:validation:Optional + // PreprovisioningNetworkDataName - NetworkData secret name in the local namespace for pre-provisioing + PreprovisioningNetworkDataName string `json:"preprovisioningNetworkDataName,omitempty"` +} + +// NodeTemplate is a specification of the node attributes that override top level attributes. +type NodeTemplate struct { + // AnsibleSSHPrivateKeySecret Name of a private SSH key secret containing + // private SSH key for connecting to node. + // The named secret must be of the form: + // Secret.data.ssh-privatekey: + // + // +kubebuilder:validation:Required + // +operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors={"urn:alm:descriptor:io.kubernetes:Secret"} + AnsibleSSHPrivateKeySecret string `json:"ansibleSSHPrivateKeySecret"` + + // Networks - Instance networks + // +kubebuilder:validation:Optional + Networks []infranetworkv1.IPSetNetwork `json:"networks,omitempty"` + + // ManagementNetwork - Name of network to use for management (SSH/Ansible) + // +kubebuilder:validation:Optional + // +kubebuilder:default=ctlplane + ManagementNetwork string `json:"managementNetwork"` + + // Ansible is the group of Ansible related configuration options. + // +kubebuilder:validation:Optional + Ansible AnsibleOpts `json:"ansible,omitempty"` + + // ExtraMounts containing files which can be mounted into an Ansible Execution Pod + // +kubebuilder:validation:Optional + ExtraMounts []storage.VolMounts `json:"extraMounts,omitempty"` + + // UserData node specific user-data + // +kubebuilder:validation:Optional + UserData *corev1.SecretReference `json:"userData,omitempty"` + + // NetworkData node specific network-data + // +kubebuilder:validation:Optional + NetworkData *corev1.SecretReference `json:"networkData,omitempty"` +} + +// AnsibleEESpec is a specification of the ansible EE attributes +type AnsibleEESpec struct { + // NetworkAttachments is a list of NetworkAttachment resource names to pass to the ansibleee resource + // which allows to connect the ansibleee runner to the given network + NetworkAttachments []string `json:"networkAttachments"` + // OpenStackAnsibleEERunnerImage image to use as the ansibleEE runner image + OpenStackAnsibleEERunnerImage string `json:"openStackAnsibleEERunnerImage,omitempty"` + // AnsibleTags for ansible execution + AnsibleTags string `json:"ansibleTags,omitempty"` + // AnsibleLimit for ansible execution + AnsibleLimit string `json:"ansibleLimit,omitempty"` + // AnsibleSkipTags for ansible execution + AnsibleSkipTags string `json:"ansibleSkipTags,omitempty"` + // ExtraVars for ansible execution + ExtraVars map[string]json.RawMessage `json:"extraVars,omitempty"` + // ExtraMounts containing files which can be mounted into an Ansible Execution Pod + ExtraMounts []storage.VolMounts `json:"extraMounts,omitempty"` + // Env is a list containing the environment variables to pass to the pod + Env []corev1.EnvVar `json:"env,omitempty"` + // DNSConfig for setting dnsservers + DNSConfig *corev1.PodDNSConfig `json:"dnsConfig,omitempty"` + // ServiceAccountName allows to specify what ServiceAccountName do we want + // the ansible execution run with. Without specifying, it will run with + // default serviceaccount + ServiceAccountName string +} diff --git a/apis/dataplane/v1beta1/conditions.go b/apis/dataplane/v1beta1/conditions.go new file mode 100644 index 000000000..d54b9f4bd --- /dev/null +++ b/apis/dataplane/v1beta1/conditions.go @@ -0,0 +1,109 @@ +/* +Copyright 2023. + +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 v1beta1 + +import ( + "github.com/openstack-k8s-operators/lib-common/modules/common/condition" +) + +const ( + // DataPlaneNodeSetErrorMessage error + DataPlaneNodeSetErrorMessage = "DataPlaneNodeSet error occurred %s" + + // ServiceReadyCondition Status=True condition indicates if the + // service is finished and successful. + ServiceReadyCondition condition.Type = "ServiceReady" + + // ServiceReadyMessage ready + ServiceReadyMessage = "%s service ready" + + // ServiceReadyWaitingMessage not yet ready + ServiceReadyWaitingMessage = "%s service not yet ready" + + // ServiceErrorMessage error + ServiceErrorMessage = "Service error occurred %s" + + // SetupReadyCondition - Overall setup condition + SetupReadyCondition condition.Type = "SetupReady" + + // NodeSetReadyMessage - NodeSet Ready + NodeSetReadyMessage = "NodeSet Ready" + + // NodeSetBareMetalProvisionReadyCondition Status=True condition indicates + // all baremetal nodes provisioned for the NodeSet. + NodeSetBareMetalProvisionReadyCondition condition.Type = "NodeSetBaremetalProvisionReady" + + // NodeSetBaremetalProvisionReadyMessage ready + NodeSetBaremetalProvisionReadyMessage = "NodeSetBaremetalProvisionReady ready" + + // NodeSetBaremetalProvisionReadyWaitingMessage not yet ready + NodeSetBaremetalProvisionReadyWaitingMessage = "NodeSetBaremetalProvisionReady not yet ready" + + // NodeSetBaremetalProvisionErrorMessage error + NodeSetBaremetalProvisionErrorMessage = "NodeSetBaremetalProvisionReady error occurred" + + // NodeSetIPReservationReadyCondition Status=True condition indicates + // IPSets reserved for all nodes in a NodeSet. + NodeSetIPReservationReadyCondition condition.Type = "NodeSetIPReservationReady" + + // NodeSetIPReservationReadyMessage ready + NodeSetIPReservationReadyMessage = "NodeSetIPReservationReady ready" + + // NodeSetIPReservationReadyWaitingMessage not yet ready + NodeSetIPReservationReadyWaitingMessage = "NodeSetIPReservationReady not yet ready" + + // NodeSetIPReservationReadyErrorMessage error + NodeSetIPReservationReadyErrorMessage = "NodeSetIPReservationReady error occurred" + + // NodeSetDNSDataReadyCondition Status=True condition indicates + // DNSData created for the NodeSet. + NodeSetDNSDataReadyCondition condition.Type = "NodeSetDNSDataReady" + + // NodeSetDNSDataReadyMessage ready + NodeSetDNSDataReadyMessage = "NodeSetDNSDataReady ready" + + // NodeSetDNSDataReadyWaitingMessage not yet ready + NodeSetDNSDataReadyWaitingMessage = "NodeSetDNSDataReady not yet ready" + + // NodeSetDNSDataReadyErrorMessage error + NodeSetDNSDataReadyErrorMessage = "NodeSetDNSDataReady error occurred" + + // InputReadyWaitingMessage not yet ready + InputReadyWaitingMessage = "Waiting for input %s, not yet ready" + + // NodeSetDeploymentReadyCondition Status=True condition indicates if the + // NodeSet Deployment is finished and successful. + NodeSetDeploymentReadyCondition condition.Type = "NodeSetDeploymentReady" + + // NodeSetDeploymentReadyMessage ready + NodeSetDeploymentReadyMessage = "Deployment ready for NodeSet" + + // NodeSetDeploymentReadyWaitingMessage not yet ready + NodeSetDeploymentReadyWaitingMessage = "Deployment not yet ready for NodeSet" + + // NodeSetDeploymentErrorMessage error + NodeSetDeploymentErrorMessage = "Deployment error occurred %s for NodeSet" + + // NodeSetServiceDeploymentReadyMessage ready + NodeSetServiceDeploymentReadyMessage = "%s Deployment ready" + + // NodeSetServiceDeploymentReadyWaitingMessage not yet ready + NodeSetServiceDeploymentReadyWaitingMessage = "%s Deployment not yet ready" + + // NodeSetServiceDeploymentErrorMessage error + NodeSetServiceDeploymentErrorMessage = "%s Deployment error occurred" +) diff --git a/apis/dataplane/v1beta1/groupversion_info.go b/apis/dataplane/v1beta1/groupversion_info.go new file mode 100644 index 000000000..8d652e550 --- /dev/null +++ b/apis/dataplane/v1beta1/groupversion_info.go @@ -0,0 +1,36 @@ +/* +Copyright 2023. + +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 v1beta1 contains API Schema definitions for the dataplane v1beta1 API group +// +kubebuilder:object:generate=true +// +groupName=dataplane.openstack.org +package v1beta1 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + // GroupVersion is group version used to register these objects + GroupVersion = schema.GroupVersion{Group: "dataplane.openstack.org", Version: "v1beta1"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme +) diff --git a/apis/dataplane/v1beta1/openstackdataplanedeployment_types.go b/apis/dataplane/v1beta1/openstackdataplanedeployment_types.go new file mode 100644 index 000000000..b73e38444 --- /dev/null +++ b/apis/dataplane/v1beta1/openstackdataplanedeployment_types.go @@ -0,0 +1,151 @@ +/* +Copyright 2023. + +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 v1beta1 + +import ( + "encoding/json" + + "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// OpenStackDataPlaneDeploymentSpec defines the desired state of OpenStackDataPlaneDeployment +type OpenStackDataPlaneDeploymentSpec struct { + + // +kubebuilder:validation:Required + // NodeSets is the list of NodeSets deployed + NodeSets []string `json:"nodeSets"` + + // AnsibleTags for ansible execution + // +kubebuilder:validation:Optional + AnsibleTags string `json:"ansibleTags,omitempty"` + + // AnsibleLimit for ansible execution + // +kubebuilder:validation:Optional + AnsibleLimit string `json:"ansibleLimit,omitempty"` + + // AnsibleSkipTags for ansible execution + // +kubebuilder:validation:Optional + AnsibleSkipTags string `json:"ansibleSkipTags,omitempty"` + + // +kubebuilder:validation:Optional + // AnsibleExtraVars for ansible execution + // +kubebuilder:pruning:PreserveUnknownFields + // +kubebuilder:validation:Schemaless + AnsibleExtraVars map[string]json.RawMessage `json:"ansibleExtraVars,omitempty"` + + // +kubebuilder:validation:Optional + // ServicesOverride list + ServicesOverride []string `json:"servicesOverride,omitempty"` + + // Time before the deployment is requeued in seconds + // +kubebuilder:validation:Minimum:=1 + // +kubebuilder:default:=15 + DeploymentRequeueTime int `json:"deploymentRequeueTime"` +} + +// OpenStackDataPlaneDeploymentStatus defines the observed state of OpenStackDataPlaneDeployment +type OpenStackDataPlaneDeploymentStatus struct { + // +operator-sdk:csv:customresourcedefinitions:type=status,xDescriptors={"urn:alm:descriptor:io.kubernetes.conditions"} + // Conditions + Conditions condition.Conditions `json:"conditions,omitempty" optional:"true"` + + // NodeSetConditions + NodeSetConditions map[string]condition.Conditions `json:"nodeSetConditions,omitempty" optional:"true"` + + // +operator-sdk:csv:customresourcedefinitions:type=status,xDescriptors={"urn:alm:descriptor:com.tectonic.ui:booleanSwitch"} + // Deployed + Deployed bool `json:"deployed,omitempty" optional:"true"` + + // ConfigMapHashes + ConfigMapHashes map[string]string `json:"configMapHashes,omitempty" optional:"true"` + + // SecretHashes + SecretHashes map[string]string `json:"secretHashes,omitempty" optional:"true"` + + // NodeSetHashes + NodeSetHashes map[string]string `json:"nodeSetHashes,omitempty" optional:"true"` + + //ObservedGeneration - the most recent generation observed for this Deployment. If the observed generation is less than the spec generation, then the controller has not processed the latest changes. + ObservedGeneration int64 `json:"observedGeneration,omitempty"` + + // DeployedVersion + DeployedVersion string `json:"deployedVersion,omitempty"` + + // ContainerImages + ContainerImages map[string]string `json:"containerImages,omitempty"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+operator-sdk:csv:customresourcedefinitions:displayName="OpenStack Data Plane Deployments" +//+kubebuilder:resource:shortName=osdpd;osdpdeployment;osdpdeployments +//+kubebuilder:printcolumn:name="NodeSets",type="string",JSONPath=".spec.nodeSets",description="NodeSets" +//+kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.conditions[0].status",description="Status" +//+kubebuilder:printcolumn:name="Message",type="string",JSONPath=".status.conditions[0].message",description="Message" + +// OpenStackDataPlaneDeployment is the Schema for the openstackdataplanedeployments API +// OpenStackDataPlaneDeployment name must be a valid RFC1123 as it is used in labels +type OpenStackDataPlaneDeployment struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="OpenStackDataPlaneDeployment Spec is immutable" + Spec OpenStackDataPlaneDeploymentSpec `json:"spec,omitempty"` + Status OpenStackDataPlaneDeploymentStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// OpenStackDataPlaneDeploymentList contains a list of OpenStackDataPlaneDeployment +type OpenStackDataPlaneDeploymentList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []OpenStackDataPlaneDeployment `json:"items"` +} + +func init() { + SchemeBuilder.Register(&OpenStackDataPlaneDeployment{}, &OpenStackDataPlaneDeploymentList{}) +} + +// IsReady - returns true if the OpenStackDataPlaneDeployment is ready +func (instance OpenStackDataPlaneDeployment) IsReady() bool { + return instance.Status.Conditions.IsTrue(condition.ReadyCondition) +} + +// InitConditions - Initializes Status Conditons +func (instance *OpenStackDataPlaneDeployment) InitConditions() { + instance.Status.Conditions = condition.Conditions{} + + cl := condition.CreateList( + condition.UnknownCondition(condition.DeploymentReadyCondition, condition.InitReason, condition.InitReason), + condition.UnknownCondition(condition.InputReadyCondition, condition.InitReason, condition.InitReason), + ) + instance.Status.Conditions.Init(&cl) + instance.Status.NodeSetConditions = make(map[string]condition.Conditions) + if instance.Spec.NodeSets != nil { + for _, nodeSet := range instance.Spec.NodeSets { + nsConds := condition.Conditions{} + nsConds.Set(condition.UnknownCondition( + NodeSetDeploymentReadyCondition, condition.InitReason, condition.InitReason)) + instance.Status.NodeSetConditions[nodeSet] = nsConds + + } + } + + instance.Status.Deployed = false +} diff --git a/apis/dataplane/v1beta1/openstackdataplanedeployment_webhook.go b/apis/dataplane/v1beta1/openstackdataplanedeployment_webhook.go new file mode 100644 index 000000000..d82983546 --- /dev/null +++ b/apis/dataplane/v1beta1/openstackdataplanedeployment_webhook.go @@ -0,0 +1,126 @@ +/* +Copyright 2024. + +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 v1beta1 + +import ( + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/validation/field" + ctrl "sigs.k8s.io/controller-runtime" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +var openstackdataplanedeploymentlog = logf.Log.WithName("openstackdataplanedeployment-resource") + +// SetupWebhookWithManager sets up the webhook with the Manager +func (r *OpenStackDataPlaneDeployment) SetupWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr).For(r).Complete() +} + +// TODO(user): EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! + +// +kubebuilder:webhook:path=/mutate-dataplane-openstack-org-v1beta1-openstackdataplanedeployment,mutating=true,failurePolicy=fail,sideEffects=None,groups=dataplane.openstack.org,resources=openstackdataplanedeployments,verbs=create;update,versions=v1beta1,name=mopenstackdataplanedeployment.kb.io,admissionReviewVersions=v1 + +var _ webhook.Defaulter = &OpenStackDataPlaneDeployment{} + +// Default implements webhook.Defaulter so a webhook will be registered for the type +func (r *OpenStackDataPlaneDeployment) Default() { + + openstackdataplanedeploymentlog.Info("default", "name", r.Name) + r.Spec.Default() +} + +// Default - set defaults for this OpenStackDataPlaneDeployment +func (spec *OpenStackDataPlaneDeploymentSpec) Default() { + +} + +// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. +// +kubebuilder:webhook:path=/validate-dataplane-openstack-org-v1beta1-openstackdataplanedeployment,mutating=false,failurePolicy=fail,sideEffects=None,groups=dataplane.openstack.org,resources=openstackdataplanedeployments,verbs=create;update,versions=v1beta1,name=vopenstackdataplanedeployment.kb.io,admissionReviewVersions=v1 + +var _ webhook.Validator = &OpenStackDataPlaneDeployment{} + +func (r *OpenStackDataPlaneDeployment) ValidateCreate() (admission.Warnings, error) { + + openstackdataplanedeploymentlog.Info("validate create", "name", r.Name) + + errors := r.Spec.ValidateCreate() + if len(errors) != 0 { + openstackdataplanedeploymentlog.Info("validation failed", "name", r.Name) + + return nil, apierrors.NewInvalid( + schema.GroupKind{Group: "dataplane.openstack.org", Kind: "OpenStackDataPlaneDeployment"}, + r.Name, + errors) + } + + return nil, nil +} + +func (r *OpenStackDataPlaneDeploymentSpec) ValidateCreate() field.ErrorList { + // TODO(user): fill in your validation logic upon object creation. + + return field.ErrorList{} +} + +func (r *OpenStackDataPlaneDeployment) ValidateUpdate(original runtime.Object) (admission.Warnings, error) { + openstackdataplanedeploymentlog.Info("validate update", "name", r.Name) + + errors := r.Spec.ValidateUpdate() + + if len(errors) != 0 { + openstackdataplanedeploymentlog.Info("validation failed", "name", r.Name) + + return nil, apierrors.NewInvalid( + schema.GroupKind{Group: "dataplane.openstack.org", Kind: "OpenStackDataPlaneDeployment"}, + r.Name, + errors) + } + + return nil, nil +} + +func (r *OpenStackDataPlaneDeploymentSpec) ValidateUpdate() field.ErrorList { + // TODO(user): fill in your validation logic upon object update. + + return field.ErrorList{} +} + +func (r *OpenStackDataPlaneDeployment) ValidateDelete() (admission.Warnings, error) { + openstackdataplanedeploymentlog.Info("validate delete", "name", r.Name) + + errors := r.Spec.ValidateDelete() + + if len(errors) != 0 { + openstackdataplanedeploymentlog.Info("validation failed", "name", r.Name) + + return nil, apierrors.NewInvalid( + schema.GroupKind{Group: "dataplane.openstack.org", Kind: "OpenStackDataPlaneDeployment"}, + r.Name, + errors) + } + return nil, nil +} + +func (r *OpenStackDataPlaneDeploymentSpec) ValidateDelete() field.ErrorList { + // TODO(user): fill in your validation logic upon object creation. + + return field.ErrorList{} +} diff --git a/apis/dataplane/v1beta1/openstackdataplanenodeset.go b/apis/dataplane/v1beta1/openstackdataplanenodeset.go new file mode 100644 index 000000000..317f8c641 --- /dev/null +++ b/apis/dataplane/v1beta1/openstackdataplanenodeset.go @@ -0,0 +1,31 @@ +/* +Copyright 2023. + +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 v1beta1 + +import ( + "regexp" +) + +// NodeHostNameIsFQDN Helper to check if a hostname is fqdn +func NodeHostNameIsFQDN(hostname string) bool { + // Regular expression to match a valid FQDN + // This regex assumes that the hostname and domain name segments only contain letters, digits, hyphens, and periods. + regex := `^([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.[a-zA-Z]{2,}$` + + match, _ := regexp.MatchString(regex, hostname) + return match +} diff --git a/apis/dataplane/v1beta1/openstackdataplanenodeset_types.go b/apis/dataplane/v1beta1/openstackdataplanenodeset_types.go new file mode 100644 index 000000000..60d7e1fc1 --- /dev/null +++ b/apis/dataplane/v1beta1/openstackdataplanenodeset_types.go @@ -0,0 +1,284 @@ +/* +Copyright 2023. + +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 v1beta1 + +import ( + "fmt" + + "golang.org/x/exp/slices" + + infranetworkv1 "github.com/openstack-k8s-operators/infra-operator/apis/network/v1beta1" + condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + "github.com/openstack-k8s-operators/lib-common/modules/common/util" + baremetalv1 "github.com/openstack-k8s-operators/openstack-baremetal-operator/api/v1beta1" + openstackv1 "github.com/openstack-k8s-operators/openstack-operator/apis/core/v1beta1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// OpenStackDataPlaneNodeSetSpec defines the desired state of OpenStackDataPlaneNodeSet +type OpenStackDataPlaneNodeSetSpec struct { + // +kubebuilder:validation:Optional + // BaremetalSetTemplate Template for BaremetalSet for the NodeSet + BaremetalSetTemplate baremetalv1.OpenStackBaremetalSetSpec `json:"baremetalSetTemplate,omitempty"` + + // +kubebuilder:validation:Required + // NodeTemplate - node attributes specific to nodes defined by this resource. These + // attributes can be overriden at the individual node level, else take their defaults + // from valus in this section. + NodeTemplate NodeTemplate `json:"nodeTemplate"` + + // Nodes - Map of Node Names and node specific data. Values here override defaults in the + // upper level section. + // +kubebuilder:validation:Required + Nodes map[string]NodeSection `json:"nodes"` + + // SecretMaxSize - Maximum size in bytes of a Kubernetes secret. This size is currently situated around + // 1 MiB (nearly 1 MB). + // +kubebuilder:validation:Optional + // +kubebuilder:default=1048576 + SecretMaxSize int `json:"secretMaxSize" yaml:"secretMaxSize"` + + // +kubebuilder:validation:Optional + // + // +operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors={"urn:alm:descriptor:com.tectonic.ui:booleanSwitch"} + // PreProvisioned - Set to true if the nodes have been Pre Provisioned. + PreProvisioned bool `json:"preProvisioned,omitempty"` + + // Env is a list containing the environment variables to pass to the pod + // +kubebuilder:validation:Optional + Env []corev1.EnvVar `json:"env,omitempty"` + + // +kubebuilder:validation:Optional + // NetworkAttachments is a list of NetworkAttachment resource names to pass to the ansibleee resource + // which allows to connect the ansibleee runner to the given network + NetworkAttachments []string `json:"networkAttachments,omitempty"` + + // +kubebuilder:validation:Optional + // +kubebuilder:default={download-cache,bootstrap,configure-network,validate-network,install-os,configure-os,ssh-known-hosts,run-os,reboot-os,install-certs,ovn,neutron-metadata,libvirt,nova,telemetry} + // Services list + Services []string `json:"services"` + + // TLSEnabled - Whether the node set has TLS enabled. + // +kubebuilder:validation:Optional + // +kubebuilder:default=true + // +operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors={"urn:alm:descriptor:com.tectonic.ui:booleanSwitch"} + TLSEnabled bool `json:"tlsEnabled" yaml:"tlsEnabled"` + + // Tags - Additional tags for NodeSet + // +kubebuilder:validation:Optional + Tags []string `json:"tags,omitempty"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+operator-sdk:csv:customresourcedefinitions:displayName="OpenStack Data Plane NodeSet" +//+kubebuilder:resource:shortName=osdpns;osdpnodeset;osdpnodesets +//+kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.conditions[0].status",description="Status" +//+kubebuilder:printcolumn:name="Message",type="string",JSONPath=".status.conditions[0].message",description="Message" + +// OpenStackDataPlaneNodeSet is the Schema for the openstackdataplanenodesets API +// OpenStackDataPlaneNodeSet name must be a valid RFC1123 as it is used in labels +type OpenStackDataPlaneNodeSet struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec OpenStackDataPlaneNodeSetSpec `json:"spec,omitempty"` + Status OpenStackDataPlaneNodeSetStatus `json:"status,omitempty"` +} + +// OpenStackDataPlaneNodeSetStatus defines the observed state of OpenStackDataPlaneNodeSet +type OpenStackDataPlaneNodeSetStatus struct { + // +operator-sdk:csv:customresourcedefinitions:type=status,xDescriptors={"urn:alm:descriptor:io.kubernetes.conditions"} + // Conditions + Conditions condition.Conditions `json:"conditions,omitempty" optional:"true"` + + // +operator-sdk:csv:customresourcedefinitions:type=status,xDescriptors={"urn:alm:descriptor:com.tectonic.ui:booleanSwitch"} + // Deployed + Deployed bool `json:"deployed,omitempty" optional:"true"` + + // DeploymentStatuses + DeploymentStatuses map[string]condition.Conditions `json:"deploymentStatuses,omitempty" optional:"true"` + + // DNSClusterAddresses + DNSClusterAddresses []string `json:"dnsClusterAddresses,omitempty" optional:"true"` + + // CtlplaneSearchDomain + CtlplaneSearchDomain string `json:"ctlplaneSearchDomain,omitempty" optional:"true"` + + // AllHostnames + AllHostnames map[string]map[infranetworkv1.NetNameStr]string `json:"allHostnames,omitempty" optional:"true"` + + // AllIPs + AllIPs map[string]map[infranetworkv1.NetNameStr]string `json:"allIPs,omitempty" optional:"true"` + + // ConfigMapHashes + ConfigMapHashes map[string]string `json:"configMapHashes,omitempty" optional:"true"` + + // SecretHashes + SecretHashes map[string]string `json:"secretHashes,omitempty" optional:"true"` + + // ConfigHash - holds the curret hash of the NodeTemplate and Node sections of the struct. + // This hash is used to determine when new Ansible executions are required to roll + // out config changes. + ConfigHash string `json:"configHash,omitempty"` + + // DeployedConfigHash - holds the hash of the NodeTemplate and Node sections of the struct + // that was last deployed. + // This hash is used to determine when new Ansible executions are required to roll + // out config changes. + DeployedConfigHash string `json:"deployedConfigHash,omitempty"` + + //ObservedGeneration - the most recent generation observed for this NodeSet. If the observed generation is less than the spec generation, then the controller has not processed the latest changes. + ObservedGeneration int64 `json:"observedGeneration,omitempty"` + + // DeployedVersion + DeployedVersion string `json:"deployedVersion,omitempty"` + + // ContainerImages + ContainerImages map[string]string `json:"containerImages,omitempty" optional:"true"` +} + +//+kubebuilder:object:root=true + +// OpenStackDataPlaneNodeSetList contains a list of OpenStackDataPlaneNodeSets +type OpenStackDataPlaneNodeSetList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []OpenStackDataPlaneNodeSet `json:"items"` +} + +func init() { + SchemeBuilder.Register(&OpenStackDataPlaneNodeSet{}, &OpenStackDataPlaneNodeSetList{}) +} + +// IsReady - returns true if the DataPlane is ready +func (instance OpenStackDataPlaneNodeSet) IsReady() bool { + return instance.Status.Conditions.IsTrue(condition.ReadyCondition) +} + +// InitConditions - Initializes Status Conditons +func (instance *OpenStackDataPlaneNodeSet) InitConditions() { + instance.Status.Conditions = condition.Conditions{} + instance.Status.DeploymentStatuses = make(map[string]condition.Conditions) + + cl := condition.CreateList( + condition.UnknownCondition(condition.DeploymentReadyCondition, condition.InitReason, condition.InitReason), + condition.UnknownCondition(condition.InputReadyCondition, condition.InitReason, condition.InitReason), + condition.UnknownCondition(SetupReadyCondition, condition.InitReason, condition.InitReason), + condition.UnknownCondition(NodeSetIPReservationReadyCondition, condition.InitReason, condition.InitReason), + condition.UnknownCondition(NodeSetDNSDataReadyCondition, condition.InitReason, condition.InitReason), + condition.UnknownCondition(condition.ServiceAccountReadyCondition, condition.InitReason, condition.InitReason), + ) + + // Only set Baremetal related conditions if we have baremetal hosts included in the + // baremetalSetTemplate. + if len(instance.Spec.BaremetalSetTemplate.BaremetalHosts) > 0 { + cl = append(cl, *condition.UnknownCondition(NodeSetBareMetalProvisionReadyCondition, condition.InitReason, condition.InitReason)) + } + + instance.Status.Conditions.Init(&cl) + instance.Status.Deployed = false +} + +// GetAnsibleEESpec - get the fields that will be passed to AEE +func (instance OpenStackDataPlaneNodeSet) GetAnsibleEESpec() AnsibleEESpec { + return AnsibleEESpec{ + NetworkAttachments: instance.Spec.NetworkAttachments, + ExtraMounts: instance.Spec.NodeTemplate.ExtraMounts, + Env: instance.Spec.Env, + ServiceAccountName: instance.Name, + } +} + +// ContainerImageDefaults - the hardcoded defaults which are the last fallback +// if no values are set elsewhere. +var ContainerImageDefaults = openstackv1.ContainerImages{ + ContainerTemplate: openstackv1.ContainerTemplate{ + EdpmFrrImage: getStrPtr("quay.io/podified-antelope-centos9/openstack-frr:current-podified"), + EdpmIscsidImage: getStrPtr("quay.io/podified-antelope-centos9/openstack-iscsid:current-podified"), + EdpmLogrotateCrondImage: getStrPtr("quay.io/podified-antelope-centos9/openstack-cron:current-podified"), + EdpmNeutronMetadataAgentImage: getStrPtr("quay.io/podified-antelope-centos9/openstack-neutron-metadata-agent-ovn:current-podified"), + EdpmNeutronSriovAgentImage: getStrPtr("quay.io/podified-antelope-centos9/openstack-neutron-sriov-agent:current-podified"), + EdpmMultipathdImage: getStrPtr("quay.io/podified-antelope-centos9/openstack-multipathd:current-podified"), + NovaComputeImage: getStrPtr("quay.io/podified-antelope-centos9/openstack-nova-compute:current-podified"), + OvnControllerImage: getStrPtr("quay.io/podified-antelope-centos9/openstack-ovn-controller:current-podified"), + EdpmOvnBgpAgentImage: getStrPtr("quay.io/podified-antelope-centos9/openstack-ovn-bgp-agent:current-podified"), + CeilometerComputeImage: getStrPtr("quay.io/podified-antelope-centos9/openstack-telemetry-ceilometer-compute:current-podified"), + CeilometerIpmiImage: getStrPtr("quay.io/podified-antelope-centos9/openstack-telemetry-ceilometer-ipmi:current-podified"), + EdpmNodeExporterImage: getStrPtr("quay.io/podified-antelope-centos9/openstack-telemetry-node-exporter:current-podified"), + }} + +// ContainerImages - the values if no OpenStackVersion is used +var ContainerImages openstackv1.ContainerImages + +// SetupDefaults - initializes any CRD field defaults based on environment variables +// called from main.go +func SetupDefaults() { + // Acquire environmental defaults and initialize dataplane defaults with them + ContainerImages = openstackv1.ContainerImages{ + ContainerTemplate: openstackv1.ContainerTemplate{ + EdpmFrrImage: getImageDefault("RELATED_IMAGE_EDPM_FRR_IMAGE_URL_DEFAULT", ContainerImageDefaults.EdpmFrrImage), + EdpmIscsidImage: getImageDefault("RELATED_IMAGE_EDPM_ISCSID_IMAGE_URL_DEFAULT", ContainerImageDefaults.EdpmIscsidImage), + EdpmLogrotateCrondImage: getImageDefault("RELATED_IMAGE_EDPM_LOGROTATE_CROND_IMAGE_URL_DEFAULT", ContainerImageDefaults.EdpmLogrotateCrondImage), + EdpmMultipathdImage: getImageDefault("RELATED_IMAGE_EDPM_MULTIPATHD_IMAGE_URL_DEFAULT", ContainerImageDefaults.EdpmMultipathdImage), + EdpmNeutronMetadataAgentImage: getImageDefault("RELATED_IMAGE_EDPM_NEUTRON_METADATA_AGENT_IMAGE_URL_DEFAULT", ContainerImageDefaults.EdpmNeutronMetadataAgentImage), + EdpmNeutronSriovAgentImage: getImageDefault("RELATED_IMAGE_EDPM_NEUTRON_SRIOV_AGENT_IMAGE_URL_DEFAULT", ContainerImageDefaults.EdpmNeutronSriovAgentImage), + EdpmNodeExporterImage: getImageDefault("RELATED_IMAGE_EDPM_NODE_EXPORTER_IMAGE_URL_DEFAULT", ContainerImageDefaults.EdpmNodeExporterImage), + EdpmOvnBgpAgentImage: getImageDefault("RELATED_IMAGE_EDPM_OVN_BGP_AGENT_IMAGE_URL_DEFAULT", ContainerImageDefaults.EdpmOvnBgpAgentImage), + CeilometerComputeImage: getImageDefault("RELATED_IMAGE_CEILOMETER_COMPUTE_IMAGE_URL_DEFAULT", ContainerImageDefaults.CeilometerComputeImage), + CeilometerIpmiImage: getImageDefault("RELATED_IMAGE_CEILOMETER_IPMI_IMAGE_URL_DEFAULT", ContainerImageDefaults.CeilometerIpmiImage), + NovaComputeImage: getImageDefault("RELATED_IMAGE_NOVA_COMPUTE_IMAGE_URL_DEFAULT", ContainerImageDefaults.NovaComputeImage), + OvnControllerImage: getImageDefault("RELATED_IMAGE_OVN_CONTROLLER_AGENT_IMAGE_URL_DEFAULT", ContainerImageDefaults.OvnControllerImage), + }, + } +} + +func getImageDefault(envVar string, defaultImage *string) *string { + d := util.GetEnvVar(envVar, *defaultImage) + return &d +} + +func getStrPtr(in string) *string { + return &in +} + +// duplicateNodeCheck checks the NodeSetList for pre-existing nodes. If the user is trying to redefine an +// existing node, we will return an error and block resource creation. +func (r *OpenStackDataPlaneNodeSetSpec) duplicateNodeCheck(nodeSetList *OpenStackDataPlaneNodeSetList) (errors field.ErrorList) { + existingNodeNames := make([]string, 0) + for _, existingNode := range nodeSetList.Items { + for _, node := range existingNode.Spec.Nodes { + existingNodeNames = append(existingNodeNames, node.HostName) + if node.Ansible.AnsibleHost != "" { + existingNodeNames = append(existingNodeNames, node.Ansible.AnsibleHost) + } + } + } + + for _, newNodeName := range r.Nodes { + if slices.Contains(existingNodeNames, newNodeName.HostName) || slices.Contains(existingNodeNames, newNodeName.Ansible.AnsibleHost) { + errors = append(errors, field.Invalid( + field.NewPath("spec").Child("nodes"), + newNodeName, + fmt.Sprintf("node %s already exists in another cluster", newNodeName.HostName))) + } + } + + return +} diff --git a/apis/dataplane/v1beta1/openstackdataplanenodeset_webhook.go b/apis/dataplane/v1beta1/openstackdataplanenodeset_webhook.go new file mode 100644 index 000000000..d6f02866d --- /dev/null +++ b/apis/dataplane/v1beta1/openstackdataplanenodeset_webhook.go @@ -0,0 +1,226 @@ +/* +Copyright 2023. + +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 v1beta1 + +import ( + "context" + "fmt" + "reflect" + "strings" + + "github.com/go-playground/validator/v10" + baremetalv1 "github.com/openstack-k8s-operators/openstack-baremetal-operator/api/v1beta1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/validation/field" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +// Client needed for API calls (manager's client, set by first SetupWebhookWithManager() call +// to any particular webhook) +var webhookClient client.Client + +// log is for logging in this package. +var openstackdataplanenodesetlog = logf.Log.WithName("openstackdataplanenodeset-resource") + +// SetupWebhookWithManager sets up the webhook with the Manager +func (r *OpenStackDataPlaneNodeSet) SetupWebhookWithManager(mgr ctrl.Manager) error { + if webhookClient == nil { + webhookClient = mgr.GetClient() + } + + return ctrl.NewWebhookManagedBy(mgr). + For(r). + Complete() +} + +// TODO(user): EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! + +//+kubebuilder:webhook:path=/mutate-dataplane-openstack-org-v1beta1-openstackdataplanenodeset,mutating=true,failurePolicy=fail,sideEffects=None,groups=dataplane.openstack.org,resources=openstackdataplanenodesets,verbs=create;update,versions=v1beta1,name=mopenstackdataplanenodeset.kb.io,admissionReviewVersions=v1 + +var _ webhook.Defaulter = &OpenStackDataPlaneNodeSet{} + +// Default implements webhook.Defaulter so a webhook will be registered for the type +func (r *OpenStackDataPlaneNodeSet) Default() { + openstackdataplanenodesetlog.Info("default", "name", r.Name) + r.Spec.Default() +} + +// Default - set defaults for this OpenStackDataPlaneNodeSet Spec +func (spec *OpenStackDataPlaneNodeSetSpec) Default() { + domain := spec.BaremetalSetTemplate.DomainName + for nodeName, node := range spec.Nodes { + if node.HostName == "" { + node.HostName = nodeName + } + if !spec.PreProvisioned { + if !NodeHostNameIsFQDN(node.HostName) && domain != "" { + node.HostName = strings.Join([]string{nodeName, domain}, ".") + } + } + spec.Nodes[nodeName] = *node.DeepCopy() + } + + if !spec.PreProvisioned { + spec.NodeTemplate.Ansible.AnsibleUser = spec.BaremetalSetTemplate.CloudUserName + if spec.BaremetalSetTemplate.DeploymentSSHSecret == "" { + spec.BaremetalSetTemplate.DeploymentSSHSecret = spec.NodeTemplate.AnsibleSSHPrivateKeySecret + } + nodeSetHostMap := make(map[string]baremetalv1.InstanceSpec) + for _, node := range spec.Nodes { + instanceSpec := baremetalv1.InstanceSpec{} + instanceSpec.UserData = node.UserData + instanceSpec.NetworkData = node.NetworkData + instanceSpec.PreprovisioningNetworkDataName = node.PreprovisioningNetworkDataName + nodeSetHostMap[node.HostName] = instanceSpec + } + spec.BaremetalSetTemplate.BaremetalHosts = nodeSetHostMap + } else if spec.NodeTemplate.Ansible.AnsibleUser == "" { + spec.NodeTemplate.Ansible.AnsibleUser = "cloud-admin" + } +} + +// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. +//+kubebuilder:webhook:path=/validate-dataplane-openstack-org-v1beta1-openstackdataplanenodeset,mutating=false,failurePolicy=fail,sideEffects=None,groups=dataplane.openstack.org,resources=openstackdataplanenodesets,verbs=create;update,versions=v1beta1,name=vopenstackdataplanenodeset.kb.io,admissionReviewVersions=v1 + +var _ webhook.Validator = &OpenStackDataPlaneNodeSet{} + +// ValidateCreate implements webhook.Validator so a webhook will be registered for the type +func (r *OpenStackDataPlaneNodeSet) ValidateCreate() (admission.Warnings, error) { + openstackdataplanenodesetlog.Info("validate create", "name", r.Name) + + var errors field.ErrorList + + nodeSetList := &OpenStackDataPlaneNodeSetList{} + opts := &client.ListOptions{ + Namespace: r.ObjectMeta.Namespace, + } + + err := webhookClient.List(context.TODO(), nodeSetList, opts) + if err != nil { + return nil, err + } + // Check if OpenStackDataPlaneNodeSet name matches RFC1123 for use in labels + validate := validator.New() + if err = validate.Var(r.Name, "hostname_rfc1123"); err != nil { + openstackdataplanenodesetlog.Error(err, "Error validating OpenStackDataPlaneNodeSet name, name must follow RFC1123") + errors = append(errors, field.Invalid( + field.NewPath("Name"), + r.Name, + fmt.Sprintf("Error validating OpenStackDataPlaneNodeSet name %s, name must follow RFC1123", r.Name))) + } + + errors = append(errors, r.Spec.ValidateCreate(nodeSetList)...) + + if len(errors) > 0 { + openstackdataplanenodesetlog.Info("validation failed", "name", r.Name) + + return nil, apierrors.NewInvalid( + schema.GroupKind{Group: "dataplane.openstack.org", Kind: "OpenStackDataPlaneNodeSet"}, + r.Name, + errors) + } + return nil, nil +} + +func (r *OpenStackDataPlaneNodeSetSpec) ValidateCreate(nodeSetList *OpenStackDataPlaneNodeSetList) field.ErrorList { + var errors field.ErrorList + // Currently, this check is only valid for PreProvisioned nodes. Since we can't possibly + // have duplicates in Baremetal Deployments, we can exit early here for Baremetal NodeSets. + // If this is the first NodeSet being created, then there can be no duplicates + // we can exit early here. + if r.PreProvisioned && len(nodeSetList.Items) != 0 { + errors = append(errors, r.duplicateNodeCheck(nodeSetList)...) + } + + return errors + +} + +// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type +func (r *OpenStackDataPlaneNodeSet) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { + openstackdataplanenodesetlog.Info("validate update", "name", r.Name) + oldNodeSet, ok := old.(*OpenStackDataPlaneNodeSet) + if !ok { + return nil, apierrors.NewInternalError( + fmt.Errorf("expected a OpenStackDataPlaneNodeSet object, but got %T", oldNodeSet)) + } + + errors := r.Spec.ValidateUpdate(&oldNodeSet.Spec) + + if errors != nil { + openstackdataplanenodesetlog.Info("validation failed", "name", r.Name) + return nil, apierrors.NewInvalid( + schema.GroupKind{Group: "dataplane.openstack.org", Kind: "OpenStackDataPlaneNodeSet"}, + r.Name, + errors, + ) + } + + return nil, nil +} + +func (r *OpenStackDataPlaneNodeSetSpec) ValidateUpdate(oldSpec *OpenStackDataPlaneNodeSetSpec) field.ErrorList { + + var errors field.ErrorList + // Some changes to the baremetalSetTemplate after the initial deployment would necessitate + // a redeploy of the node. Thus we should block these changes and require the user to + // delete and redeploy should they wish to make such changes after the initial deploy. + // If the BaremetalSetTemplate is changed, we will offload the parsing of these details + // to the openstack-baremetal-operator webhook to avoid duplicating logic. + if !reflect.DeepEqual(r.BaremetalSetTemplate, oldSpec.BaremetalSetTemplate) { + + // Call openstack-baremetal-operator webhook Validate() to parse changes + err := r.BaremetalSetTemplate.Validate(oldSpec.BaremetalSetTemplate) + if err != nil { + errors = append(errors, field.Forbidden( + field.NewPath("spec.baremetalSetTemplate"), + fmt.Sprintf("%s", err))) + } + } + + return errors +} + +// ValidateDelete implements webhook.Validator so a webhook will be registered for the type +func (r *OpenStackDataPlaneNodeSet) ValidateDelete() (admission.Warnings, error) { + openstackdataplanenodesetlog.Info("validate delete", "name", r.Name) + errors := r.Spec.ValidateDelete() + + if len(errors) != 0 { + openstackdataplanenodesetlog.Info("validation failed", "name", r.Name) + + return nil, apierrors.NewInvalid( + schema.GroupKind{Group: "dataplane.openstack.org", Kind: "OpenStackDataPlaneNodeSet"}, + r.Name, + errors, + ) + } + return nil, nil +} + +func (r *OpenStackDataPlaneNodeSetSpec) ValidateDelete() field.ErrorList { + // TODO(user): fill in your validation logic upon object deletion. + + return field.ErrorList{} + +} diff --git a/apis/dataplane/v1beta1/openstackdataplaneservice_types.go b/apis/dataplane/v1beta1/openstackdataplaneservice_types.go new file mode 100644 index 000000000..1228a28fb --- /dev/null +++ b/apis/dataplane/v1beta1/openstackdataplaneservice_types.go @@ -0,0 +1,143 @@ +/* +Copyright 2023. + +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 v1beta1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + certmgrv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + infranetworkv1 "github.com/openstack-k8s-operators/infra-operator/apis/network/v1beta1" + "github.com/openstack-k8s-operators/lib-common/modules/common/condition" +) + +// OpenstackDataPlaneServiceCert defines the property of a TLS cert issued for +// a dataplane service +type OpenstackDataPlaneServiceCert struct { + // Contents of the certificate + // This is a list of strings for properties that are needed in the cert + // +kubebuilder:validation:Required + Contents []string `json:"contents"` + + // Networks to include in SNI for the cert + // +kubebuilder:validation:Optional + Networks []infranetworkv1.NetNameStr `json:"networks,omitempty"` + + // Issuer is the label for the issuer to issue the cert + // Only one issuer should have this label + // +kubebuilder:validation:Optional + Issuer string `json:"issuer,omitempty"` + + // KeyUsages to be added to the issued cert + // +kubebuilder:validation:Optional + KeyUsages []certmgrv1.KeyUsage `json:"keyUsages,omitempty" yaml:"keyUsages,omitempty"` +} + +// OpenStackDataPlaneServiceSpec defines the desired state of OpenStackDataPlaneService +type OpenStackDataPlaneServiceSpec struct { + // Play is an inline playbook contents that ansible will run on execution. + Play string `json:"play,omitempty"` + + // Playbook is a path to the playbook that ansible will run on this execution + Playbook string `json:"playbook,omitempty"` + + // ConfigMaps list of ConfigMap names to mount as ExtraMounts for the OpenStackAnsibleEE + // +kubebuilder:validation:Optional + ConfigMaps []string `json:"configMaps,omitempty" yaml:"configMaps,omitempty"` + + // Secrets list of Secret names to mount as ExtraMounts for the OpenStackAnsibleEE + // +kubebuilder:validation:Optional + Secrets []string `json:"secrets,omitempty"` + + // OpenStackAnsibleEERunnerImage image to use as the ansibleEE runner image + // +kubebuilder:validation:Optional + OpenStackAnsibleEERunnerImage string `json:"openStackAnsibleEERunnerImage,omitempty" yaml:"openStackAnsibleEERunnerImage,omitempty"` + + // TLSCert tls certs to be generated + // +kubebuilder:validation:Optional + TLSCert *OpenstackDataPlaneServiceCert `json:"tlsCert,omitempty" yaml:"tlsCert,omitempty"` + + // CACerts - Secret containing the CA certificate chain + // +kubebuilder:validation:Optional + CACerts string `json:"caCerts,omitempty" yaml:"caCerts,omitempty"` + + // AddCertMounts - Whether to add cert mounts + // +kubebuilder:validation:Optional + // +kubebuilder:default=false + // +operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors={"urn:alm:descriptor:com.tectonic.ui:booleanSwitch"} + AddCertMounts bool `json:"addCertMounts" yaml:"addCertMounts"` + + // DeployOnAllNodeSets - should the service be deploy across all nodesets + // This will override default target of a service play, setting it to 'all'. + // +kubebuilder:validation:Optional + DeployOnAllNodeSets bool `json:"deployOnAllNodeSets,omitempty" yaml:"deployOnAllNodeSets,omitempty"` + + // ContainerImageFields - list of container image fields names that this + // service deploys. The field names should match the + // ContainerImages struct field names from + // github.com/openstack-k8s-operators/openstack-operator/apis/core/v1beta1 + ContainerImageFields []string `json:"containerImageFields,omitempty" yaml:"containerImageFields,omitempty"` +} + +// OpenStackDataPlaneServiceStatus defines the observed state of OpenStackDataPlaneService +type OpenStackDataPlaneServiceStatus struct { + // +operator-sdk:csv:customresourcedefinitions:type=status,xDescriptors={"urn:alm:descriptor:io.kubernetes.conditions"} + // Conditions + Conditions condition.Conditions `json:"conditions,omitempty" optional:"true"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +// +kubebuilder:resource:shortName=osdps;osdpservice;osdpservices +//+operator-sdk:csv:customresourcedefinitions:displayName="OpenStack Data Plane Service" + +// OpenStackDataPlaneService is the Schema for the openstackdataplaneservices API +// OpenStackDataPlaneService name must be a valid RFC1123 as it is used in labels +type OpenStackDataPlaneService struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec OpenStackDataPlaneServiceSpec `json:"spec,omitempty"` + Status OpenStackDataPlaneServiceStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// OpenStackDataPlaneServiceList contains a list of OpenStackDataPlaneService +type OpenStackDataPlaneServiceList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []OpenStackDataPlaneService `json:"items"` +} + +func init() { + SchemeBuilder.Register(&OpenStackDataPlaneService{}, &OpenStackDataPlaneServiceList{}) +} + +// IsReady - returns true if service is ready to serve requests +func (instance OpenStackDataPlaneService) IsReady() bool { + return instance.Status.Conditions.IsTrue(condition.ReadyCondition) +} + +// InitConditions - Initializes Status Conditons +func (instance OpenStackDataPlaneService) InitConditions() { + if instance.Status.Conditions == nil { + instance.Status.Conditions = condition.Conditions{} + } + cl := condition.CreateList(condition.UnknownCondition(condition.ReadyCondition, condition.InitReason, condition.InitReason)) + // initialize conditions used later as Status=Unknown + instance.Status.Conditions.Init(&cl) +} diff --git a/apis/dataplane/v1beta1/openstackdataplaneservice_webhook.go b/apis/dataplane/v1beta1/openstackdataplaneservice_webhook.go new file mode 100644 index 000000000..3a1050970 --- /dev/null +++ b/apis/dataplane/v1beta1/openstackdataplaneservice_webhook.go @@ -0,0 +1,125 @@ +/* +Copyright 2024. + +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 v1beta1 + +import ( + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/validation/field" + ctrl "sigs.k8s.io/controller-runtime" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +var openstackdataplaneservicelog = logf.Log.WithName("openstackdataplaneservice-resource") + +// SetupWebhookWithManager sets up the webhook with the Manager +func (r *OpenStackDataPlaneService) SetupWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr).For(r).Complete() +} + +// TODO(user): EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! + +// +kubebuilder:webhook:path=/mutate-dataplane-openstack-org-v1beta1-openstackdataplaneservice,mutating=true,failurePolicy=fail,sideEffects=None,groups=dataplane.openstack.org,resources=openstackdataplaneservices,verbs=create;update,versions=v1beta1,name=mopenstackdataplaneservice.kb.io,admissionReviewVersions=v1 + +var _ webhook.Defaulter = &OpenStackDataPlaneService{} + +// Default implements webhook.Defaulter so a webhook will be registered for the type +func (r *OpenStackDataPlaneService) Default() { + + openstackdataplaneservicelog.Info("default", "name", r.Name) + r.Spec.Default() +} + +// Default - set defaults for this OpenStackDataPlaneDeployment +func (spec *OpenStackDataPlaneServiceSpec) Default() { + +} + +// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. +// +kubebuilder:webhook:path=/validate-dataplane-openstack-org-v1beta1-openstackdataplaneservice,mutating=false,failurePolicy=fail,sideEffects=None,groups=dataplane.openstack.org,resources=openstackdataplaneservices,verbs=create;update,versions=v1beta1,name=vopenstackdataplaneservice.kb.io,admissionReviewVersions=v1 + +var _ webhook.Validator = &OpenStackDataPlaneService{} + +func (r *OpenStackDataPlaneService) ValidateCreate() (admission.Warnings, error) { + + openstackdataplaneservicelog.Info("validate create", "name", r.Name) + + errors := r.Spec.ValidateCreate() + + if len(errors) != 0 { + openstackdataplaneservicelog.Info("validation failed", "name", r.Name) + return nil, apierrors.NewInvalid( + schema.GroupKind{Group: "dataplane.openstack.org", Kind: "OpenStackDataPlaneService"}, + r.Name, + errors, + ) + } + + return nil, nil +} + +func (r *OpenStackDataPlaneServiceSpec) ValidateCreate() field.ErrorList { + // TODO(user): fill in your validation logic upon object creation. + + return field.ErrorList{} +} + +func (r *OpenStackDataPlaneService) ValidateUpdate(original runtime.Object) (admission.Warnings, error) { + openstackdataplaneservicelog.Info("validate update", "name", r.Name) + errors := r.Spec.ValidateUpdate() + + if len(errors) != 0 { + openstackdataplaneservicelog.Info("validation failed", "name", r.Name) + return nil, apierrors.NewInvalid( + schema.GroupKind{Group: "dataplane.openstack.org", Kind: "OpenStackDataPlaneService"}, + r.Name, + errors, + ) + } + return nil, nil +} + +func (r *OpenStackDataPlaneServiceSpec) ValidateUpdate() field.ErrorList { + // TODO(user): fill in your validation logic upon object creation. + + return field.ErrorList{} +} + +func (r *OpenStackDataPlaneService) ValidateDelete() (admission.Warnings, error) { + openstackdataplaneservicelog.Info("validate delete", "name", r.Name) + + errors := r.Spec.ValidateDelete() + + if len(errors) != 0 { + openstackdataplaneservicelog.Info("validation failed", "name", r.Name) + return nil, apierrors.NewInvalid( + schema.GroupKind{Group: "dataplane.openstack.org", Kind: "OpenStackDataPlaneService"}, + r.Name, + errors, + ) + } + return nil, nil +} + +func (r *OpenStackDataPlaneServiceSpec) ValidateDelete() field.ErrorList { + // TODO(user): fill in your validation logic upon object creation. + + return field.ErrorList{} +} diff --git a/apis/dataplane/v1beta1/zz_generated.deepcopy.go b/apis/dataplane/v1beta1/zz_generated.deepcopy.go new file mode 100644 index 000000000..d14245dbb --- /dev/null +++ b/apis/dataplane/v1beta1/zz_generated.deepcopy.go @@ -0,0 +1,744 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 2022. + +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. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "encoding/json" + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + networkv1beta1 "github.com/openstack-k8s-operators/infra-operator/apis/network/v1beta1" + "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + "github.com/openstack-k8s-operators/lib-common/modules/storage" + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AnsibleEESpec) DeepCopyInto(out *AnsibleEESpec) { + *out = *in + if in.NetworkAttachments != nil { + in, out := &in.NetworkAttachments, &out.NetworkAttachments + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ExtraVars != nil { + in, out := &in.ExtraVars, &out.ExtraVars + *out = make(map[string]json.RawMessage, len(*in)) + for key, val := range *in { + var outVal []byte + if val == nil { + (*out)[key] = nil + } else { + in, out := &val, &outVal + *out = make(json.RawMessage, len(*in)) + copy(*out, *in) + } + (*out)[key] = outVal + } + } + if in.ExtraMounts != nil { + in, out := &in.ExtraMounts, &out.ExtraMounts + *out = make([]storage.VolMounts, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Env != nil { + in, out := &in.Env, &out.Env + *out = make([]v1.EnvVar, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.DNSConfig != nil { + in, out := &in.DNSConfig, &out.DNSConfig + *out = new(v1.PodDNSConfig) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AnsibleEESpec. +func (in *AnsibleEESpec) DeepCopy() *AnsibleEESpec { + if in == nil { + return nil + } + out := new(AnsibleEESpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AnsibleOpts) DeepCopyInto(out *AnsibleOpts) { + *out = *in + if in.AnsibleVars != nil { + in, out := &in.AnsibleVars, &out.AnsibleVars + *out = make(map[string]json.RawMessage, len(*in)) + for key, val := range *in { + var outVal []byte + if val == nil { + (*out)[key] = nil + } else { + in, out := &val, &outVal + *out = make(json.RawMessage, len(*in)) + copy(*out, *in) + } + (*out)[key] = outVal + } + } + if in.AnsibleVarsFrom != nil { + in, out := &in.AnsibleVarsFrom, &out.AnsibleVarsFrom + *out = make([]AnsibleVarsFromSource, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AnsibleOpts. +func (in *AnsibleOpts) DeepCopy() *AnsibleOpts { + if in == nil { + return nil + } + out := new(AnsibleOpts) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AnsibleVarsFromSource) DeepCopyInto(out *AnsibleVarsFromSource) { + *out = *in + if in.ConfigMapRef != nil { + in, out := &in.ConfigMapRef, &out.ConfigMapRef + *out = new(v1.ConfigMapEnvSource) + (*in).DeepCopyInto(*out) + } + if in.SecretRef != nil { + in, out := &in.SecretRef, &out.SecretRef + *out = new(v1.SecretEnvSource) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AnsibleVarsFromSource. +func (in *AnsibleVarsFromSource) DeepCopy() *AnsibleVarsFromSource { + if in == nil { + return nil + } + out := new(AnsibleVarsFromSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NodeSection) DeepCopyInto(out *NodeSection) { + *out = *in + if in.Networks != nil { + in, out := &in.Networks, &out.Networks + *out = make([]networkv1beta1.IPSetNetwork, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + in.Ansible.DeepCopyInto(&out.Ansible) + if in.ExtraMounts != nil { + in, out := &in.ExtraMounts, &out.ExtraMounts + *out = make([]storage.VolMounts, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.UserData != nil { + in, out := &in.UserData, &out.UserData + *out = new(v1.SecretReference) + **out = **in + } + if in.NetworkData != nil { + in, out := &in.NetworkData, &out.NetworkData + *out = new(v1.SecretReference) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeSection. +func (in *NodeSection) DeepCopy() *NodeSection { + if in == nil { + return nil + } + out := new(NodeSection) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NodeTemplate) DeepCopyInto(out *NodeTemplate) { + *out = *in + if in.Networks != nil { + in, out := &in.Networks, &out.Networks + *out = make([]networkv1beta1.IPSetNetwork, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + in.Ansible.DeepCopyInto(&out.Ansible) + if in.ExtraMounts != nil { + in, out := &in.ExtraMounts, &out.ExtraMounts + *out = make([]storage.VolMounts, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.UserData != nil { + in, out := &in.UserData, &out.UserData + *out = new(v1.SecretReference) + **out = **in + } + if in.NetworkData != nil { + in, out := &in.NetworkData, &out.NetworkData + *out = new(v1.SecretReference) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeTemplate. +func (in *NodeTemplate) DeepCopy() *NodeTemplate { + if in == nil { + return nil + } + out := new(NodeTemplate) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OpenStackDataPlaneDeployment) DeepCopyInto(out *OpenStackDataPlaneDeployment) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenStackDataPlaneDeployment. +func (in *OpenStackDataPlaneDeployment) DeepCopy() *OpenStackDataPlaneDeployment { + if in == nil { + return nil + } + out := new(OpenStackDataPlaneDeployment) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *OpenStackDataPlaneDeployment) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OpenStackDataPlaneDeploymentList) DeepCopyInto(out *OpenStackDataPlaneDeploymentList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]OpenStackDataPlaneDeployment, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenStackDataPlaneDeploymentList. +func (in *OpenStackDataPlaneDeploymentList) DeepCopy() *OpenStackDataPlaneDeploymentList { + if in == nil { + return nil + } + out := new(OpenStackDataPlaneDeploymentList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *OpenStackDataPlaneDeploymentList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OpenStackDataPlaneDeploymentSpec) DeepCopyInto(out *OpenStackDataPlaneDeploymentSpec) { + *out = *in + if in.NodeSets != nil { + in, out := &in.NodeSets, &out.NodeSets + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.AnsibleExtraVars != nil { + in, out := &in.AnsibleExtraVars, &out.AnsibleExtraVars + *out = make(map[string]json.RawMessage, len(*in)) + for key, val := range *in { + var outVal []byte + if val == nil { + (*out)[key] = nil + } else { + in, out := &val, &outVal + *out = make(json.RawMessage, len(*in)) + copy(*out, *in) + } + (*out)[key] = outVal + } + } + if in.ServicesOverride != nil { + in, out := &in.ServicesOverride, &out.ServicesOverride + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenStackDataPlaneDeploymentSpec. +func (in *OpenStackDataPlaneDeploymentSpec) DeepCopy() *OpenStackDataPlaneDeploymentSpec { + if in == nil { + return nil + } + out := new(OpenStackDataPlaneDeploymentSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OpenStackDataPlaneDeploymentStatus) DeepCopyInto(out *OpenStackDataPlaneDeploymentStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make(condition.Conditions, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.NodeSetConditions != nil { + in, out := &in.NodeSetConditions, &out.NodeSetConditions + *out = make(map[string]condition.Conditions, len(*in)) + for key, val := range *in { + var outVal []condition.Condition + if val == nil { + (*out)[key] = nil + } else { + in, out := &val, &outVal + *out = make(condition.Conditions, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + (*out)[key] = outVal + } + } + if in.ConfigMapHashes != nil { + in, out := &in.ConfigMapHashes, &out.ConfigMapHashes + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.SecretHashes != nil { + in, out := &in.SecretHashes, &out.SecretHashes + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.NodeSetHashes != nil { + in, out := &in.NodeSetHashes, &out.NodeSetHashes + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.ContainerImages != nil { + in, out := &in.ContainerImages, &out.ContainerImages + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenStackDataPlaneDeploymentStatus. +func (in *OpenStackDataPlaneDeploymentStatus) DeepCopy() *OpenStackDataPlaneDeploymentStatus { + if in == nil { + return nil + } + out := new(OpenStackDataPlaneDeploymentStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OpenStackDataPlaneNodeSet) DeepCopyInto(out *OpenStackDataPlaneNodeSet) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenStackDataPlaneNodeSet. +func (in *OpenStackDataPlaneNodeSet) DeepCopy() *OpenStackDataPlaneNodeSet { + if in == nil { + return nil + } + out := new(OpenStackDataPlaneNodeSet) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *OpenStackDataPlaneNodeSet) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OpenStackDataPlaneNodeSetList) DeepCopyInto(out *OpenStackDataPlaneNodeSetList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]OpenStackDataPlaneNodeSet, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenStackDataPlaneNodeSetList. +func (in *OpenStackDataPlaneNodeSetList) DeepCopy() *OpenStackDataPlaneNodeSetList { + if in == nil { + return nil + } + out := new(OpenStackDataPlaneNodeSetList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *OpenStackDataPlaneNodeSetList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OpenStackDataPlaneNodeSetSpec) DeepCopyInto(out *OpenStackDataPlaneNodeSetSpec) { + *out = *in + in.BaremetalSetTemplate.DeepCopyInto(&out.BaremetalSetTemplate) + in.NodeTemplate.DeepCopyInto(&out.NodeTemplate) + if in.Nodes != nil { + in, out := &in.Nodes, &out.Nodes + *out = make(map[string]NodeSection, len(*in)) + for key, val := range *in { + (*out)[key] = *val.DeepCopy() + } + } + if in.Env != nil { + in, out := &in.Env, &out.Env + *out = make([]v1.EnvVar, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.NetworkAttachments != nil { + in, out := &in.NetworkAttachments, &out.NetworkAttachments + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Services != nil { + in, out := &in.Services, &out.Services + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Tags != nil { + in, out := &in.Tags, &out.Tags + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenStackDataPlaneNodeSetSpec. +func (in *OpenStackDataPlaneNodeSetSpec) DeepCopy() *OpenStackDataPlaneNodeSetSpec { + if in == nil { + return nil + } + out := new(OpenStackDataPlaneNodeSetSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OpenStackDataPlaneNodeSetStatus) DeepCopyInto(out *OpenStackDataPlaneNodeSetStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make(condition.Conditions, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.DeploymentStatuses != nil { + in, out := &in.DeploymentStatuses, &out.DeploymentStatuses + *out = make(map[string]condition.Conditions, len(*in)) + for key, val := range *in { + var outVal []condition.Condition + if val == nil { + (*out)[key] = nil + } else { + in, out := &val, &outVal + *out = make(condition.Conditions, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + (*out)[key] = outVal + } + } + if in.DNSClusterAddresses != nil { + in, out := &in.DNSClusterAddresses, &out.DNSClusterAddresses + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.AllHostnames != nil { + in, out := &in.AllHostnames, &out.AllHostnames + *out = make(map[string]map[networkv1beta1.NetNameStr]string, len(*in)) + for key, val := range *in { + var outVal map[networkv1beta1.NetNameStr]string + if val == nil { + (*out)[key] = nil + } else { + in, out := &val, &outVal + *out = make(map[networkv1beta1.NetNameStr]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + (*out)[key] = outVal + } + } + if in.AllIPs != nil { + in, out := &in.AllIPs, &out.AllIPs + *out = make(map[string]map[networkv1beta1.NetNameStr]string, len(*in)) + for key, val := range *in { + var outVal map[networkv1beta1.NetNameStr]string + if val == nil { + (*out)[key] = nil + } else { + in, out := &val, &outVal + *out = make(map[networkv1beta1.NetNameStr]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + (*out)[key] = outVal + } + } + if in.ConfigMapHashes != nil { + in, out := &in.ConfigMapHashes, &out.ConfigMapHashes + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.SecretHashes != nil { + in, out := &in.SecretHashes, &out.SecretHashes + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.ContainerImages != nil { + in, out := &in.ContainerImages, &out.ContainerImages + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenStackDataPlaneNodeSetStatus. +func (in *OpenStackDataPlaneNodeSetStatus) DeepCopy() *OpenStackDataPlaneNodeSetStatus { + if in == nil { + return nil + } + out := new(OpenStackDataPlaneNodeSetStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OpenStackDataPlaneService) DeepCopyInto(out *OpenStackDataPlaneService) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenStackDataPlaneService. +func (in *OpenStackDataPlaneService) DeepCopy() *OpenStackDataPlaneService { + if in == nil { + return nil + } + out := new(OpenStackDataPlaneService) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *OpenStackDataPlaneService) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OpenStackDataPlaneServiceList) DeepCopyInto(out *OpenStackDataPlaneServiceList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]OpenStackDataPlaneService, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenStackDataPlaneServiceList. +func (in *OpenStackDataPlaneServiceList) DeepCopy() *OpenStackDataPlaneServiceList { + if in == nil { + return nil + } + out := new(OpenStackDataPlaneServiceList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *OpenStackDataPlaneServiceList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OpenStackDataPlaneServiceSpec) DeepCopyInto(out *OpenStackDataPlaneServiceSpec) { + *out = *in + if in.ConfigMaps != nil { + in, out := &in.ConfigMaps, &out.ConfigMaps + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Secrets != nil { + in, out := &in.Secrets, &out.Secrets + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.TLSCert != nil { + in, out := &in.TLSCert, &out.TLSCert + *out = new(OpenstackDataPlaneServiceCert) + (*in).DeepCopyInto(*out) + } + if in.ContainerImageFields != nil { + in, out := &in.ContainerImageFields, &out.ContainerImageFields + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenStackDataPlaneServiceSpec. +func (in *OpenStackDataPlaneServiceSpec) DeepCopy() *OpenStackDataPlaneServiceSpec { + if in == nil { + return nil + } + out := new(OpenStackDataPlaneServiceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OpenStackDataPlaneServiceStatus) DeepCopyInto(out *OpenStackDataPlaneServiceStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make(condition.Conditions, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenStackDataPlaneServiceStatus. +func (in *OpenStackDataPlaneServiceStatus) DeepCopy() *OpenStackDataPlaneServiceStatus { + if in == nil { + return nil + } + out := new(OpenStackDataPlaneServiceStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OpenstackDataPlaneServiceCert) DeepCopyInto(out *OpenstackDataPlaneServiceCert) { + *out = *in + if in.Contents != nil { + in, out := &in.Contents, &out.Contents + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Networks != nil { + in, out := &in.Networks, &out.Networks + *out = make([]networkv1beta1.NetNameStr, len(*in)) + copy(*out, *in) + } + if in.KeyUsages != nil { + in, out := &in.KeyUsages, &out.KeyUsages + *out = make([]certmanagerv1.KeyUsage, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenstackDataPlaneServiceCert. +func (in *OpenstackDataPlaneServiceCert) DeepCopy() *OpenstackDataPlaneServiceCert { + if in == nil { + return nil + } + out := new(OpenstackDataPlaneServiceCert) + in.DeepCopyInto(out) + return out +} diff --git a/apis/go.mod b/apis/go.mod index 9365b6a3c..8f1ce131a 100644 --- a/apis/go.mod +++ b/apis/go.mod @@ -21,6 +21,7 @@ require ( github.com/openstack-k8s-operators/neutron-operator/api v0.3.1-0.20240416230751-051151b6f9e0 github.com/openstack-k8s-operators/nova-operator/api v0.3.1-0.20240417151820-72ec42d52670 github.com/openstack-k8s-operators/octavia-operator/api v0.3.1-0.20240417135623-fc90a6fe7f86 + github.com/openstack-k8s-operators/openstack-baremetal-operator/api v0.3.1-0.20240417104943-679bfaa3afc1 github.com/openstack-k8s-operators/ovn-operator/api v0.3.1-0.20240415222517-eb816e08cb4a github.com/openstack-k8s-operators/placement-operator/api v0.3.1-0.20240417101529-887edc53c417 github.com/openstack-k8s-operators/swift-operator/api v0.3.1-0.20240417131140-2e30bb18b13b @@ -30,7 +31,7 @@ require ( github.com/rhobs/observability-operator v0.0.28 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 // indirect + golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 golang.org/x/tools v0.20.0 // indirect k8s.io/api v0.28.9 k8s.io/apimachinery v0.28.9 @@ -38,6 +39,11 @@ require ( sigs.k8s.io/controller-runtime v0.16.5 ) +require ( + github.com/cert-manager/cert-manager v1.13.5 + github.com/go-playground/validator/v10 v10.19.0 +) + require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect @@ -45,11 +51,14 @@ require ( github.com/emicklei/go-restful/v3 v3.11.2 // indirect github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/zapr v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.20.2 // indirect github.com/go-openapi/jsonreference v0.20.4 // indirect github.com/go-openapi/swag v0.22.9 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect @@ -63,7 +72,10 @@ require ( github.com/imdario/mergo v0.3.16 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/leodido/go-urn v1.4.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect + github.com/metal3-io/baremetal-operator/apis v0.5.1 // indirect + github.com/metal3-io/baremetal-operator/pkg/hardwareutils v0.4.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect @@ -76,6 +88,7 @@ require ( github.com/prometheus/procfs v0.12.0 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect github.com/spf13/pflag v1.0.5 // indirect + golang.org/x/crypto v0.22.0 // indirect golang.org/x/net v0.24.0 // indirect golang.org/x/oauth2 v0.16.0 // indirect golang.org/x/sys v0.19.0 // indirect @@ -93,6 +106,7 @@ require ( k8s.io/klog/v2 v2.120.1 // indirect k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect k8s.io/utils v0.0.0-20240310230437-4693a0247e57 // indirect + sigs.k8s.io/gateway-api v0.8.0 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect sigs.k8s.io/yaml v1.4.0 // indirect diff --git a/apis/go.sum b/apis/go.sum index 851738726..f6d101290 100644 --- a/apis/go.sum +++ b/apis/go.sum @@ -1,5 +1,7 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cert-manager/cert-manager v1.13.5 h1:kSO9FnOQEuIox5FbtZnxWSSlYUV+7nBprL+U43YSnO0= +github.com/cert-manager/cert-manager v1.13.5/go.mod h1:4+c8TrwH3Q809Zxv6f8sgVbzdNcgOCl3sd55pr7r/EI= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -13,6 +15,8 @@ github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0 github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= @@ -23,6 +27,13 @@ github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdX github.com/go-openapi/jsonreference v0.20.4/go.mod h1:5pZJyJP2MnYCpoeoMAql78cCHauHj0V9Lhc506VOpw4= github.com/go-openapi/swag v0.22.9 h1:XX2DssF+mQKM2DHsbgZK74y/zj4mo9I99+89xUmuZCE= github.com/go-openapi/swag v0.22.9/go.mod h1:3/OXnFfnMAwBD099SwYRk7GD3xOrr1iL7d/XNLXVVwE= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4= +github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -58,8 +69,14 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/metal3-io/baremetal-operator/apis v0.5.1 h1:l6VCuM2nSYMsdir3mocXvF80F7HnTXVZ7NNIoMEYbio= +github.com/metal3-io/baremetal-operator/apis v0.5.1/go.mod h1:Q3MHes59mRabjHM6ARoHfgd2uXUjJIytl3/uflzhyew= +github.com/metal3-io/baremetal-operator/pkg/hardwareutils v0.4.0 h1:AnA8XLLp3RKYjjlB4KI0fyPSDN/d5gb3ZtM2cVyxwOc= +github.com/metal3-io/baremetal-operator/pkg/hardwareutils v0.4.0/go.mod h1:399nvdaqoU9rTI25UdFw2EWcVjmJPpeZPIhfDAIx/XU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -107,6 +124,8 @@ github.com/openstack-k8s-operators/nova-operator/api v0.3.1-0.20240417151820-72e github.com/openstack-k8s-operators/nova-operator/api v0.3.1-0.20240417151820-72ec42d52670/go.mod h1:hyowLr4alfdMPfkBhqW6r1Eah7l45lGsznmJW6YF38Y= github.com/openstack-k8s-operators/octavia-operator/api v0.3.1-0.20240417135623-fc90a6fe7f86 h1:70Hcy4sOAEJcX9yomgLTUTcWdujA4BLSeMxp1wP56kA= github.com/openstack-k8s-operators/octavia-operator/api v0.3.1-0.20240417135623-fc90a6fe7f86/go.mod h1:EZymlUAhQzGNIAGrpGZ5P6oqfq2IhqY2lNPKLG9iKh8= +github.com/openstack-k8s-operators/openstack-baremetal-operator/api v0.3.1-0.20240417104943-679bfaa3afc1 h1:w8vZ9nWh9/gHcHzTKWyIs+4jENLFcl2t1+w3B+2jhi8= +github.com/openstack-k8s-operators/openstack-baremetal-operator/api v0.3.1-0.20240417104943-679bfaa3afc1/go.mod h1:ZIHfxt2oUMEhL8UdH1bId7sak3lGYLlRuthRrLUDPd0= github.com/openstack-k8s-operators/ovn-operator/api v0.3.1-0.20240415222517-eb816e08cb4a h1:DMc6a3vW2Hn51SflutJggKjWx1z1k49NcHkRsjieEs8= github.com/openstack-k8s-operators/ovn-operator/api v0.3.1-0.20240415222517-eb816e08cb4a/go.mod h1:BUPvHv4+dcNNT50Kvkj9ae8FX9VBht8+7wtpiBWUbm0= github.com/openstack-k8s-operators/placement-operator/api v0.3.1-0.20240417101529-887edc53c417 h1:7I3V+nw8bd/k2qJMKsdzKO6rYTBkAU3rvQTSpqAgzco= @@ -155,6 +174,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 h1:ESSUROHIBHg7USnszlcdmjBEwdMj9VUvU+OPk4yl2mc= golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -245,6 +266,8 @@ k8s.io/utils v0.0.0-20240310230437-4693a0247e57 h1:gbqbevonBh57eILzModw6mrkbwM0g k8s.io/utils v0.0.0-20240310230437-4693a0247e57/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/controller-runtime v0.16.5 h1:yr1cEJbX08xsTW6XEIzT13KHHmIyX8Umvme2cULvFZw= sigs.k8s.io/controller-runtime v0.16.5/go.mod h1:j7bialYoSn142nv9sCOJmQgDXQXxnroFU4VnX/brVJ0= +sigs.k8s.io/gateway-api v0.8.0 h1:isQQ3Jx2qFP7vaA3ls0846F0Amp9Eq14P08xbSwVbQg= +sigs.k8s.io/gateway-api v0.8.0/go.mod h1:okOnjPNBFbIS/Rw9kAhuIUaIkLhTKEu+ARIuXk2dgaM= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= diff --git a/cmd/csv-merger/csv-merger.go b/cmd/csv-merger/csv-merger.go index 2b9d22346..e5d9efb5f 100644 --- a/cmd/csv-merger/csv-merger.go +++ b/cmd/csv-merger/csv-merger.go @@ -79,7 +79,6 @@ var ( rabbitmqCsv = flag.String("rabbitmq-csv", "", "RabbitMQ CSV filename") infraCsv = flag.String("infra-csv", "", "Infra CSV filename") ansibleEECsv = flag.String("ansibleee-csv", "", "Ansible EE CSV filename") - dataplaneCsv = flag.String("dataplane-csv", "", "Data plane CSV filename") novaCsv = flag.String("nova-csv", "", "Nova CSV filename") heatCsv = flag.String("heat-csv", "", "Heat CSV filename") neutronCsv = flag.String("neutron-csv", "", "Neutron CSV filename") @@ -127,7 +126,6 @@ func main() { *rabbitmqCsv, *infraCsv, *ansibleEECsv, - *dataplaneCsv, *novaCsv, *neutronCsv, *manilaCsv, diff --git a/config/crd/bases/dataplane.openstack.org_openstackdataplanedeployments.yaml b/config/crd/bases/dataplane.openstack.org_openstackdataplanedeployments.yaml new file mode 100644 index 000000000..b06f69dd8 --- /dev/null +++ b/config/crd/bases/dataplane.openstack.org_openstackdataplanedeployments.yaml @@ -0,0 +1,150 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: openstackdataplanedeployments.dataplane.openstack.org +spec: + group: dataplane.openstack.org + names: + kind: OpenStackDataPlaneDeployment + listKind: OpenStackDataPlaneDeploymentList + plural: openstackdataplanedeployments + shortNames: + - osdpd + - osdpdeployment + - osdpdeployments + singular: openstackdataplanedeployment + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: NodeSets + jsonPath: .spec.nodeSets + name: NodeSets + type: string + - description: Status + jsonPath: .status.conditions[0].status + name: Status + type: string + - description: Message + jsonPath: .status.conditions[0].message + name: Message + type: string + name: v1beta1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + ansibleExtraVars: + x-kubernetes-preserve-unknown-fields: true + ansibleLimit: + type: string + ansibleSkipTags: + type: string + ansibleTags: + type: string + deploymentRequeueTime: + default: 15 + minimum: 1 + type: integer + nodeSets: + items: + type: string + type: array + servicesOverride: + items: + type: string + type: array + required: + - deploymentRequeueTime + - nodeSets + type: object + x-kubernetes-validations: + - message: OpenStackDataPlaneDeployment Spec is immutable + rule: self == oldSelf + status: + properties: + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + type: string + reason: + type: string + severity: + type: string + status: + type: string + type: + type: string + required: + - lastTransitionTime + - status + - type + type: object + type: array + configMapHashes: + additionalProperties: + type: string + type: object + containerImages: + additionalProperties: + type: string + type: object + deployed: + type: boolean + deployedVersion: + type: string + nodeSetConditions: + additionalProperties: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + type: string + reason: + type: string + severity: + type: string + status: + type: string + type: + type: string + required: + - lastTransitionTime + - status + - type + type: object + type: array + type: object + nodeSetHashes: + additionalProperties: + type: string + type: object + observedGeneration: + format: int64 + type: integer + secretHashes: + additionalProperties: + type: string + type: object + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/dataplane.openstack.org_openstackdataplanenodesets.yaml b/config/crd/bases/dataplane.openstack.org_openstackdataplanenodesets.yaml new file mode 100644 index 000000000..05ce3f662 --- /dev/null +++ b/config/crd/bases/dataplane.openstack.org_openstackdataplanenodesets.yaml @@ -0,0 +1,2047 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: openstackdataplanenodesets.dataplane.openstack.org +spec: + group: dataplane.openstack.org + names: + kind: OpenStackDataPlaneNodeSet + listKind: OpenStackDataPlaneNodeSetList + plural: openstackdataplanenodesets + shortNames: + - osdpns + - osdpnodeset + - osdpnodesets + singular: openstackdataplanenodeset + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: Status + jsonPath: .status.conditions[0].status + name: Status + type: string + - description: Message + jsonPath: .status.conditions[0].message + name: Message + type: string + name: v1beta1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + baremetalSetTemplate: + properties: + agentImageUrl: + type: string + apacheImageUrl: + type: string + automatedCleaningMode: + default: metadata + enum: + - metadata + - disabled + type: string + baremetalHosts: + additionalProperties: + properties: + ctlPlaneIP: + type: string + networkData: + properties: + name: + type: string + namespace: + type: string + type: object + x-kubernetes-map-type: atomic + preprovisioningNetworkDataName: + type: string + userData: + properties: + name: + type: string + namespace: + type: string + type: object + x-kubernetes-map-type: atomic + type: object + type: object + bmhLabelSelector: + additionalProperties: + type: string + type: object + bmhNamespace: + default: openshift-machine-api + type: string + bootstrapDns: + items: + type: string + type: array + cloudUserName: + default: cloud-admin + type: string + ctlplaneGateway: + type: string + ctlplaneInterface: + type: string + ctlplaneNetmask: + default: 255.255.255.0 + type: string + deploymentSSHSecret: + type: string + dnsSearchDomains: + items: + type: string + type: array + domainName: + type: string + hardwareReqs: + properties: + cpuReqs: + properties: + arch: + enum: + - x86_64 + - ppc64le + type: string + countReq: + properties: + count: + minimum: 1 + type: integer + exactMatch: + type: boolean + type: object + mhzReq: + properties: + exactMatch: + type: boolean + mhz: + minimum: 1 + type: integer + type: object + type: object + diskReqs: + properties: + gbReq: + properties: + exactMatch: + type: boolean + gb: + minimum: 1 + type: integer + type: object + ssdReq: + properties: + exactMatch: + type: boolean + ssd: + type: boolean + type: object + type: object + memReqs: + properties: + gbReq: + properties: + exactMatch: + type: boolean + gb: + minimum: 1 + type: integer + type: object + type: object + type: object + networkData: + properties: + name: + type: string + namespace: + type: string + type: object + x-kubernetes-map-type: atomic + osContainerImageUrl: + type: string + osImage: + type: string + passwordSecret: + properties: + name: + type: string + namespace: + type: string + type: object + x-kubernetes-map-type: atomic + provisionServerName: + type: string + provisioningInterface: + type: string + userData: + properties: + name: + type: string + namespace: + type: string + type: object + x-kubernetes-map-type: atomic + required: + - cloudUserName + - ctlplaneInterface + - deploymentSSHSecret + type: object + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + networkAttachments: + items: + type: string + type: array + nodeTemplate: + properties: + ansible: + properties: + ansibleHost: + type: string + ansiblePort: + type: integer + ansibleUser: + type: string + ansibleVars: + x-kubernetes-preserve-unknown-fields: true + ansibleVarsFrom: + items: + properties: + configMapRef: + properties: + name: + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + prefix: + type: string + secretRef: + properties: + name: + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + type: object + type: array + type: object + ansibleSSHPrivateKeySecret: + type: string + extraMounts: + items: + properties: + extraVolType: + type: string + mounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + propagation: + items: + type: string + type: array + volumes: + items: + properties: + awsElasticBlockStore: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + azureDisk: + properties: + cachingMode: + type: string + diskName: + type: string + diskURI: + type: string + fsType: + type: string + kind: + type: string + readOnly: + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + properties: + readOnly: + type: boolean + secretName: + type: string + shareName: + type: string + required: + - secretName + - shareName + type: object + cephfs: + properties: + monitors: + items: + type: string + type: array + path: + type: string + readOnly: + type: boolean + secretFile: + type: string + secretRef: + properties: + name: + type: string + type: object + x-kubernetes-map-type: atomic + user: + type: string + required: + - monitors + type: object + cinder: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + x-kubernetes-map-type: atomic + volumeID: + type: string + required: + - volumeID + type: object + configMap: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + csi: + properties: + driver: + type: string + fsType: + type: string + nodePublishSecretRef: + properties: + name: + type: string + type: object + x-kubernetes-map-type: atomic + readOnly: + type: boolean + volumeAttributes: + additionalProperties: + type: string + type: object + required: + - driver + type: object + downwardAPI: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + type: object + emptyDir: + properties: + medium: + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + ephemeral: + properties: + volumeClaimTemplate: + properties: + metadata: + type: object + spec: + properties: + accessModes: + items: + type: string + type: array + dataSource: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + dataSourceRef: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + required: + - kind + - name + type: object + resources: + properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + storageClassName: + type: string + volumeMode: + type: string + volumeName: + type: string + type: object + required: + - spec + type: object + type: object + fc: + properties: + fsType: + type: string + lun: + format: int32 + type: integer + readOnly: + type: boolean + targetWWNs: + items: + type: string + type: array + wwids: + items: + type: string + type: array + type: object + flexVolume: + properties: + driver: + type: string + fsType: + type: string + options: + additionalProperties: + type: string + type: object + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + x-kubernetes-map-type: atomic + required: + - driver + type: object + flocker: + properties: + datasetName: + type: string + datasetUUID: + type: string + type: object + gcePersistentDisk: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + pdName: + type: string + readOnly: + type: boolean + required: + - pdName + type: object + gitRepo: + properties: + directory: + type: string + repository: + type: string + revision: + type: string + required: + - repository + type: object + glusterfs: + properties: + endpoints: + type: string + path: + type: string + readOnly: + type: boolean + required: + - endpoints + - path + type: object + hostPath: + properties: + path: + type: string + type: + type: string + required: + - path + type: object + iscsi: + properties: + chapAuthDiscovery: + type: boolean + chapAuthSession: + type: boolean + fsType: + type: string + initiatorName: + type: string + iqn: + type: string + iscsiInterface: + type: string + lun: + format: int32 + type: integer + portals: + items: + type: string + type: array + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + x-kubernetes-map-type: atomic + targetPortal: + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + type: string + nfs: + properties: + path: + type: string + readOnly: + type: boolean + server: + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + properties: + claimName: + type: string + readOnly: + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + properties: + fsType: + type: string + pdID: + type: string + required: + - pdID + type: object + portworxVolume: + properties: + fsType: + type: string + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + projected: + properties: + defaultMode: + format: int32 + type: integer + sources: + items: + properties: + configMap: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + downwardAPI: + properties: + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + type: object + secret: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + serviceAccountToken: + properties: + audience: + type: string + expirationSeconds: + format: int64 + type: integer + path: + type: string + required: + - path + type: object + type: object + type: array + type: object + quobyte: + properties: + group: + type: string + readOnly: + type: boolean + registry: + type: string + tenant: + type: string + user: + type: string + volume: + type: string + required: + - registry + - volume + type: object + rbd: + properties: + fsType: + type: string + image: + type: string + keyring: + type: string + monitors: + items: + type: string + type: array + pool: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + x-kubernetes-map-type: atomic + user: + type: string + required: + - image + - monitors + type: object + scaleIO: + properties: + fsType: + type: string + gateway: + type: string + protectionDomain: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + x-kubernetes-map-type: atomic + sslEnabled: + type: boolean + storageMode: + type: string + storagePool: + type: string + system: + type: string + volumeName: + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + optional: + type: boolean + secretName: + type: string + type: object + storageos: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + x-kubernetes-map-type: atomic + volumeName: + type: string + volumeNamespace: + type: string + type: object + vsphereVolume: + properties: + fsType: + type: string + storagePolicyID: + type: string + storagePolicyName: + type: string + volumePath: + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + required: + - mounts + - volumes + type: object + type: array + managementNetwork: + default: ctlplane + type: string + networkData: + properties: + name: + type: string + namespace: + type: string + type: object + x-kubernetes-map-type: atomic + networks: + items: + properties: + defaultRoute: + type: boolean + fixedIP: + type: string + name: + pattern: ^[a-zA-Z0-9][a-zA-Z0-9\-_]*[a-zA-Z0-9]$ + type: string + subnetName: + pattern: ^[a-zA-Z0-9][a-zA-Z0-9\-_]*[a-zA-Z0-9]$ + type: string + required: + - name + - subnetName + type: object + type: array + userData: + properties: + name: + type: string + namespace: + type: string + type: object + x-kubernetes-map-type: atomic + required: + - ansibleSSHPrivateKeySecret + type: object + nodes: + additionalProperties: + properties: + ansible: + properties: + ansibleHost: + type: string + ansiblePort: + type: integer + ansibleUser: + type: string + ansibleVars: + x-kubernetes-preserve-unknown-fields: true + ansibleVarsFrom: + items: + properties: + configMapRef: + properties: + name: + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + prefix: + type: string + secretRef: + properties: + name: + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + type: object + type: array + type: object + extraMounts: + items: + properties: + extraVolType: + type: string + mounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + propagation: + items: + type: string + type: array + volumes: + items: + properties: + awsElasticBlockStore: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + azureDisk: + properties: + cachingMode: + type: string + diskName: + type: string + diskURI: + type: string + fsType: + type: string + kind: + type: string + readOnly: + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + properties: + readOnly: + type: boolean + secretName: + type: string + shareName: + type: string + required: + - secretName + - shareName + type: object + cephfs: + properties: + monitors: + items: + type: string + type: array + path: + type: string + readOnly: + type: boolean + secretFile: + type: string + secretRef: + properties: + name: + type: string + type: object + x-kubernetes-map-type: atomic + user: + type: string + required: + - monitors + type: object + cinder: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + x-kubernetes-map-type: atomic + volumeID: + type: string + required: + - volumeID + type: object + configMap: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + csi: + properties: + driver: + type: string + fsType: + type: string + nodePublishSecretRef: + properties: + name: + type: string + type: object + x-kubernetes-map-type: atomic + readOnly: + type: boolean + volumeAttributes: + additionalProperties: + type: string + type: object + required: + - driver + type: object + downwardAPI: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + type: object + emptyDir: + properties: + medium: + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + ephemeral: + properties: + volumeClaimTemplate: + properties: + metadata: + type: object + spec: + properties: + accessModes: + items: + type: string + type: array + dataSource: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + dataSourceRef: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + required: + - kind + - name + type: object + resources: + properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + storageClassName: + type: string + volumeMode: + type: string + volumeName: + type: string + type: object + required: + - spec + type: object + type: object + fc: + properties: + fsType: + type: string + lun: + format: int32 + type: integer + readOnly: + type: boolean + targetWWNs: + items: + type: string + type: array + wwids: + items: + type: string + type: array + type: object + flexVolume: + properties: + driver: + type: string + fsType: + type: string + options: + additionalProperties: + type: string + type: object + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + x-kubernetes-map-type: atomic + required: + - driver + type: object + flocker: + properties: + datasetName: + type: string + datasetUUID: + type: string + type: object + gcePersistentDisk: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + pdName: + type: string + readOnly: + type: boolean + required: + - pdName + type: object + gitRepo: + properties: + directory: + type: string + repository: + type: string + revision: + type: string + required: + - repository + type: object + glusterfs: + properties: + endpoints: + type: string + path: + type: string + readOnly: + type: boolean + required: + - endpoints + - path + type: object + hostPath: + properties: + path: + type: string + type: + type: string + required: + - path + type: object + iscsi: + properties: + chapAuthDiscovery: + type: boolean + chapAuthSession: + type: boolean + fsType: + type: string + initiatorName: + type: string + iqn: + type: string + iscsiInterface: + type: string + lun: + format: int32 + type: integer + portals: + items: + type: string + type: array + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + x-kubernetes-map-type: atomic + targetPortal: + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + type: string + nfs: + properties: + path: + type: string + readOnly: + type: boolean + server: + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + properties: + claimName: + type: string + readOnly: + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + properties: + fsType: + type: string + pdID: + type: string + required: + - pdID + type: object + portworxVolume: + properties: + fsType: + type: string + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + projected: + properties: + defaultMode: + format: int32 + type: integer + sources: + items: + properties: + configMap: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + downwardAPI: + properties: + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + type: object + secret: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + serviceAccountToken: + properties: + audience: + type: string + expirationSeconds: + format: int64 + type: integer + path: + type: string + required: + - path + type: object + type: object + type: array + type: object + quobyte: + properties: + group: + type: string + readOnly: + type: boolean + registry: + type: string + tenant: + type: string + user: + type: string + volume: + type: string + required: + - registry + - volume + type: object + rbd: + properties: + fsType: + type: string + image: + type: string + keyring: + type: string + monitors: + items: + type: string + type: array + pool: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + x-kubernetes-map-type: atomic + user: + type: string + required: + - image + - monitors + type: object + scaleIO: + properties: + fsType: + type: string + gateway: + type: string + protectionDomain: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + x-kubernetes-map-type: atomic + sslEnabled: + type: boolean + storageMode: + type: string + storagePool: + type: string + system: + type: string + volumeName: + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + optional: + type: boolean + secretName: + type: string + type: object + storageos: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + x-kubernetes-map-type: atomic + volumeName: + type: string + volumeNamespace: + type: string + type: object + vsphereVolume: + properties: + fsType: + type: string + storagePolicyID: + type: string + storagePolicyName: + type: string + volumePath: + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + required: + - mounts + - volumes + type: object + type: array + hostName: + type: string + managementNetwork: + type: string + networkData: + properties: + name: + type: string + namespace: + type: string + type: object + x-kubernetes-map-type: atomic + networks: + items: + properties: + defaultRoute: + type: boolean + fixedIP: + type: string + name: + pattern: ^[a-zA-Z0-9][a-zA-Z0-9\-_]*[a-zA-Z0-9]$ + type: string + subnetName: + pattern: ^[a-zA-Z0-9][a-zA-Z0-9\-_]*[a-zA-Z0-9]$ + type: string + required: + - name + - subnetName + type: object + type: array + preprovisioningNetworkDataName: + type: string + userData: + properties: + name: + type: string + namespace: + type: string + type: object + x-kubernetes-map-type: atomic + type: object + type: object + preProvisioned: + type: boolean + secretMaxSize: + default: 1048576 + type: integer + services: + default: + - download-cache + - bootstrap + - configure-network + - validate-network + - install-os + - configure-os + - ssh-known-hosts + - run-os + - reboot-os + - install-certs + - ovn + - neutron-metadata + - libvirt + - nova + - telemetry + items: + type: string + type: array + tags: + items: + type: string + type: array + tlsEnabled: + default: true + type: boolean + required: + - nodeTemplate + - nodes + type: object + status: + properties: + allHostnames: + additionalProperties: + additionalProperties: + type: string + type: object + type: object + allIPs: + additionalProperties: + additionalProperties: + type: string + type: object + type: object + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + type: string + reason: + type: string + severity: + type: string + status: + type: string + type: + type: string + required: + - lastTransitionTime + - status + - type + type: object + type: array + configHash: + type: string + configMapHashes: + additionalProperties: + type: string + type: object + containerImages: + additionalProperties: + type: string + type: object + ctlplaneSearchDomain: + type: string + deployed: + type: boolean + deployedConfigHash: + type: string + deployedVersion: + type: string + deploymentStatuses: + additionalProperties: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + type: string + reason: + type: string + severity: + type: string + status: + type: string + type: + type: string + required: + - lastTransitionTime + - status + - type + type: object + type: array + type: object + dnsClusterAddresses: + items: + type: string + type: array + observedGeneration: + format: int64 + type: integer + secretHashes: + additionalProperties: + type: string + type: object + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/dataplane.openstack.org_openstackdataplaneservices.yaml b/config/crd/bases/dataplane.openstack.org_openstackdataplaneservices.yaml new file mode 100644 index 000000000..1bfdf9a48 --- /dev/null +++ b/config/crd/bases/dataplane.openstack.org_openstackdataplaneservices.yaml @@ -0,0 +1,133 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: openstackdataplaneservices.dataplane.openstack.org +spec: + group: dataplane.openstack.org + names: + kind: OpenStackDataPlaneService + listKind: OpenStackDataPlaneServiceList + plural: openstackdataplaneservices + shortNames: + - osdps + - osdpservice + - osdpservices + singular: openstackdataplaneservice + scope: Namespaced + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + addCertMounts: + default: false + type: boolean + caCerts: + type: string + configMaps: + items: + type: string + type: array + containerImageFields: + items: + type: string + type: array + deployOnAllNodeSets: + type: boolean + openStackAnsibleEERunnerImage: + type: string + play: + type: string + playbook: + type: string + secrets: + items: + type: string + type: array + tlsCert: + properties: + contents: + items: + type: string + type: array + issuer: + type: string + keyUsages: + items: + enum: + - signing + - digital signature + - content commitment + - key encipherment + - key agreement + - data encipherment + - cert sign + - crl sign + - encipher only + - decipher only + - any + - server auth + - client auth + - code signing + - email protection + - s/mime + - ipsec end system + - ipsec tunnel + - ipsec user + - timestamping + - ocsp signing + - microsoft sgc + - netscape sgc + type: string + type: array + networks: + items: + pattern: ^[a-zA-Z0-9][a-zA-Z0-9\-_]*[a-zA-Z0-9]$ + type: string + type: array + required: + - contents + type: object + type: object + status: + properties: + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + type: string + reason: + type: string + severity: + type: string + status: + type: string + type: + type: string + required: + - lastTransitionTime + - status + - type + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index cface3851..d4049952f 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -5,6 +5,9 @@ resources: - bases/core.openstack.org_openstackcontrolplanes.yaml - bases/core.openstack.org_openstackversions.yaml - bases/client.openstack.org_openstackclients.yaml +- bases/dataplane.openstack.org_openstackdataplanenodesets.yaml +- bases/dataplane.openstack.org_openstackdataplaneservices.yaml +- bases/dataplane.openstack.org_openstackdataplanedeployments.yaml #+kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: diff --git a/config/default/manager_default_images.yaml b/config/default/manager_default_images.yaml index 6d4b8c21c..96b19acbf 100644 --- a/config/default/manager_default_images.yaml +++ b/config/default/manager_default_images.yaml @@ -15,3 +15,21 @@ spec: value: quay.io/podified-antelope-centos9/openstack-rabbitmq:current-podified - name: RELATED_IMAGE_OPENSTACK_CLIENT_IMAGE_URL_DEFAULT value: quay.io/podified-antelope-centos9/openstack-openstackclient:current-podified + - name: RELATED_IMAGE_EDPM_FRR_IMAGE_URL_DEFAULT + value: quay.io/podified-antelope-centos9/openstack-frr:current-podified + - name: RELATED_IMAGE_EDPM_ISCSID_IMAGE_URL_DEFAULT + value: quay.io/podified-antelope-centos9/openstack-iscsid:current-podified + - name: RELATED_IMAGE_EDPM_LOGROTATE_CROND_IMAGE_URL_DEFAULT + value: quay.io/podified-antelope-centos9/openstack-cron:current-podified + - name: RELATED_IMAGE_EDPM_OVN_CONTROLLER_AGENT_IMAGE_URL_DEFAULT + value: quay.io/podified-antelope-centos9/openstack-ovn-controller:current-podified + - name: RELATED_IMAGE_EDPM_NEUTRON_METADATA_AGENT_IMAGE_URL_DEFAULT + value: quay.io/podified-antelope-centos9/openstack-neutron-metadata-agent-ovn:current-podified + - name: RELATED_IMAGE_EDPM_NEUTRON_SRIOV_AGENT_IMAGE_URL_DEFAULT + value: quay.io/podified-antelope-centos9/openstack-neutron-sriov-agent:current-podified + - name: RELATED_IMAGE_EDPM_OVN_BGP_AGENT_IMAGE_URL_DEFAULT + value: quay.io/podified-antelope-centos9/openstack-ovn-bgp-agent:current-podified + - name: RELATED_IMAGE_EDPM_NODE_EXPORTER_IMAGE_URL_DEFAULT + value: quay.io/prometheus/node-exporter:v1.5.0 + - name: RELATED_IMAGE_EDPM_MULTIPATHD_IMAGE_URL_DEFAULT + value: quay.io/podified-antelope-centos9/openstack-multipathd:current-podified diff --git a/config/manifests/bases/openstack-operator.clusterserviceversion.yaml b/config/manifests/bases/openstack-operator.clusterserviceversion.yaml index 0d1629107..ca4e2de5c 100644 --- a/config/manifests/bases/openstack-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/openstack-operator.clusterserviceversion.yaml @@ -461,6 +461,90 @@ spec: displayName: TLS path: tls version: v1beta1 + - description: OpenStackDataPlaneDeployment is the Schema for the openstackdataplanedeployments + API OpenStackDataPlaneDeployment name must be a valid RFC1123 as it is used + in labels + displayName: OpenStack Data Plane Deployments + kind: OpenStackDataPlaneDeployment + name: openstackdataplanedeployments.dataplane.openstack.org + statusDescriptors: + - description: Conditions + displayName: Conditions + path: conditions + x-descriptors: + - urn:alm:descriptor:io.kubernetes.conditions + - description: Deployed + displayName: Deployed + path: deployed + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + version: v1beta1 + - description: OpenStackDataPlaneNodeSet is the Schema for the openstackdataplanenodesets + API OpenStackDataPlaneNodeSet name must be a valid RFC1123 as it is used in + labels + displayName: OpenStack Data Plane NodeSet + kind: OpenStackDataPlaneNodeSet + name: openstackdataplanenodesets.dataplane.openstack.org + specDescriptors: + - description: AnsiblePort SSH port for Ansible connection + displayName: Ansible Port + path: nodeTemplate.ansible.ansiblePort + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:number + - description: 'AnsibleSSHPrivateKeySecret Name of a private SSH key secret + containing private SSH key for connecting to node. The named secret must + be of the form: Secret.data.ssh-privatekey: ' + displayName: Ansible SSHPrivate Key Secret + path: nodeTemplate.ansibleSSHPrivateKeySecret + x-descriptors: + - urn:alm:descriptor:io.kubernetes:Secret + - description: AnsiblePort SSH port for Ansible connection + displayName: Ansible Port + path: nodes.ansible.ansiblePort + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:number + - description: PreProvisioned - Set to true if the nodes have been Pre Provisioned. + displayName: Pre Provisioned + path: preProvisioned + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: TLSEnabled - Whether the node set has TLS enabled. + displayName: TLSEnabled + path: tlsEnabled + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + statusDescriptors: + - description: Conditions + displayName: Conditions + path: conditions + x-descriptors: + - urn:alm:descriptor:io.kubernetes.conditions + - description: Deployed + displayName: Deployed + path: deployed + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + version: v1beta1 + - description: OpenStackDataPlaneService is the Schema for the openstackdataplaneservices + API OpenStackDataPlaneService name must be a valid RFC1123 as it is used in + labels + displayName: OpenStack Data Plane Service + kind: OpenStackDataPlaneService + name: openstackdataplaneservices.dataplane.openstack.org + specDescriptors: + - description: AddCertMounts - Whether to add cert mounts + displayName: Add Cert Mounts + path: addCertMounts + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + statusDescriptors: + - description: Conditions + displayName: Conditions + path: conditions + x-descriptors: + - urn:alm:descriptor:io.kubernetes.conditions + version: v1beta1 - description: OpenStackVersion is the Schema for the openstackversionupdates API displayName: OpenStack Version diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 460182909..fb4904f40 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -5,6 +5,54 @@ metadata: creationTimestamp: null name: manager-role rules: +- apiGroups: + - "" + resources: + - imagestreamimages + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - imagestreammappings + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - imagestreams + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - imagestreams/layers + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - imagestreamtags + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - imagetags + verbs: + - get + - list + - watch - apiGroups: - "" resources: @@ -17,6 +65,12 @@ rules: - patch - update - watch +- apiGroups: + - "" + resources: + - projects + verbs: + - get - apiGroups: - "" resources: @@ -27,6 +81,18 @@ rules: - list - update - watch +- apiGroups: + - ansibleee.openstack.org + resources: + - openstackansibleees + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - barbican.openstack.org resources: @@ -39,6 +105,42 @@ rules: - patch - update - watch +- apiGroups: + - baremetal.openstack.org + resources: + - openstackbaremetalsets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - baremetal.openstack.org + resources: + - openstackbaremetalsets/finalizers + verbs: + - update +- apiGroups: + - baremetal.openstack.org + resources: + - openstackbaremetalsets/status + verbs: + - get +- apiGroups: + - batch + resources: + - jobs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - cert-manager.io resources: @@ -106,8 +208,12 @@ rules: resources: - configmaps verbs: + - create + - delete - get - list + - patch + - update - watch - apiGroups: - "" @@ -126,8 +232,13 @@ rules: resources: - services verbs: + - create + - delete - get - list + - patch + - update + - watch - apiGroups: - core.openstack.org resources: @@ -180,6 +291,73 @@ rules: - get - patch - update +- apiGroups: + - dataplane.openstack.org + resources: + - openstackdataplanedeployments + verbs: + - create + - delete + - get + - list + - watch +- apiGroups: + - dataplane.openstack.org + resources: + - openstackdataplanedeployments/finalizers + verbs: + - update +- apiGroups: + - dataplane.openstack.org + resources: + - openstackdataplanedeployments/status + verbs: + - get + - patch + - update +- apiGroups: + - dataplane.openstack.org + resources: + - openstackdataplanenodesets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - dataplane.openstack.org + resources: + - openstackdataplanenodesets/finalizers + verbs: + - update +- apiGroups: + - dataplane.openstack.org + resources: + - openstackdataplanenodesets/status + verbs: + - get + - patch + - update +- apiGroups: + - dataplane.openstack.org + resources: + - openstackdataplaneservices + verbs: + - create + - get + - list + - patch + - update + - watch +- apiGroups: + - dataplane.openstack.org + resources: + - openstackdataplaneservices/finalizers + verbs: + - update - apiGroups: - designate.openstack.org resources: @@ -192,6 +370,18 @@ rules: - patch - update - watch +- apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - glance.openstack.org resources: @@ -228,6 +418,52 @@ rules: - patch - update - watch +- apiGroups: + - image.openshift.io + resources: + - imagestreamimages + verbs: + - get + - list + - watch +- apiGroups: + - image.openshift.io + resources: + - imagestreammappings + verbs: + - get + - list + - watch +- apiGroups: + - image.openshift.io + resources: + - imagestreams + verbs: + - get + - list + - watch +- apiGroups: + - image.openshift.io + resources: + - imagestreams/layers + verbs: + - get +- apiGroups: + - image.openshift.io + resources: + - imagestreamtags + verbs: + - get + - list + - watch +- apiGroups: + - image.openshift.io + resources: + - imagetags + verbs: + - get + - list + - watch - apiGroups: - ironic.openstack.org resources: @@ -240,6 +476,14 @@ rules: - patch - update - watch +- apiGroups: + - k8s.cni.cncf.io + resources: + - network-attachment-definitions + verbs: + - get + - list + - watch - apiGroups: - keystone.openstack.org resources: @@ -288,6 +532,30 @@ rules: - patch - update - watch +- apiGroups: + - network.openstack.org + resources: + - dnsdata + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - network.openstack.org + resources: + - dnsdata/finalizers + verbs: + - update +- apiGroups: + - network.openstack.org + resources: + - dnsdata/status + verbs: + - get - apiGroups: - network.openstack.org resources: @@ -300,6 +568,44 @@ rules: - patch - update - watch +- apiGroups: + - network.openstack.org + resources: + - dnsmasqs/status + verbs: + - get +- apiGroups: + - network.openstack.org + resources: + - ipsets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - network.openstack.org + resources: + - ipsets/finalizers + verbs: + - update +- apiGroups: + - network.openstack.org + resources: + - ipsets/status + verbs: + - get +- apiGroups: + - network.openstack.org + resources: + - netconfigs + verbs: + - get + - list + - watch - apiGroups: - neutron.openstack.org resources: @@ -384,6 +690,12 @@ rules: - patch - update - watch +- apiGroups: + - project.openshift.io + resources: + - projects + verbs: + - get - apiGroups: - rabbitmq.com resources: @@ -404,6 +716,7 @@ rules: - create - get - list + - patch - update - watch - apiGroups: diff --git a/config/samples/dataplane/README.md b/config/samples/dataplane/README.md new file mode 100644 index 000000000..f44f7f276 --- /dev/null +++ b/config/samples/dataplane/README.md @@ -0,0 +1,6 @@ +# Kustomize examples + +Requires [OpenShift CLI](https://docs.openshift.com/container-platform/4.14/cli_reference/openshift_cli/getting-started-cli.html#installing-openshift-cli) (oc) 4.14 or higher +``` +oc kustomize --load-restrictor LoadRestrictionsNone examples/ +``` diff --git a/config/samples/dataplane/baremetal/kustomization.yaml b/config/samples/dataplane/baremetal/kustomization.yaml new file mode 100644 index 000000000..19ed67a1b --- /dev/null +++ b/config/samples/dataplane/baremetal/kustomization.yaml @@ -0,0 +1,68 @@ +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component + +namespace: openstack +nameSuffix: -ipam + +components: +- ../base + +resources: + - values.yaml + +replacements: +# OpenStackDataPlaneNodeSet customizations +- source: + kind: DataPlaneConfig + name: edpm-baremetal-values + fieldPath: data.nodeset.nodetemplate.ansible.vars.edpm_sshd_allowed_ranges + targets: + - select: + kind: OpenStackDataPlaneNodeSet + fieldPaths: + - spec.nodeTemplate.ansible.ansibleVars.edpm_sshd_allowed_ranges + options: + create: true +- source: + kind: DataPlaneConfig + name: edpm-baremetal-values + fieldPath: data.nodeset.baremetalsettemplate + targets: + - select: + kind: OpenStackDataPlaneNodeSet + fieldPaths: + - spec.baremetalSetTemplate + options: + create: true +- source: + kind: DataPlaneConfig + name: edpm-baremetal-values + fieldPath: data.preProvisioned + targets: + - select: + kind: OpenStackDataPlaneNodeSet + fieldPaths: + - spec.preProvisioned + options: + create: true +- source: + kind: DataPlaneConfig + name: edpm-baremetal-values + fieldPath: data.nodeset.nodetemplate.networks + targets: + - select: + kind: OpenStackDataPlaneNodeSet + fieldPaths: + - spec.nodeTemplate.networks + options: + create: true + +patches: +- target: + kind: OpenStackDataPlaneNodeSet + name: .* + patch: |- + - op: remove + path: /spec/nodes/edpm-compute-0/ansible + - op: remove + path: /spec/nodes/edpm-compute-0/networks diff --git a/config/samples/dataplane/baremetal/values.yaml b/config/samples/dataplane/baremetal/values.yaml new file mode 100644 index 000000000..18b5f9c35 --- /dev/null +++ b/config/samples/dataplane/baremetal/values.yaml @@ -0,0 +1,33 @@ +# local-config: referenced, but not emitted by kustomize +apiVersion: v1 +kind: DataPlaneConfig +metadata: + name: edpm-baremetal-values + annotations: + config.kubernetes.io/local-config: "true" +data: + preProvisioned: false + nodeset: + baremetalsettemplate: + bmhLabelSelector: + app: openstack + ctlplaneInterface: enp1s0 + cloudUserName: cloud-admin + nodetemplate: + ansible: + vars: + # edpm firewall, change the allowed CIDR if needed + edpm_sshd_allowed_ranges: ['192.168.111.0/24'] + networks: + - defaultRoute: true + name: ctlplane + subnetName: subnet1 + - name: internalapi + subnetName: subnet1 + - name: storage + subnetName: subnet1 + - name: tenant + subnetName: subnet1 + nodes: + edpm-compute-0: + hostName: edpm-compute-0 diff --git a/config/samples/dataplane/base/config/nodesetname.yaml b/config/samples/dataplane/base/config/nodesetname.yaml new file mode 100644 index 000000000..317abd17c --- /dev/null +++ b/config/samples/dataplane/base/config/nodesetname.yaml @@ -0,0 +1,10 @@ +# This file is for teaching kustomize how to substitute OpenStackDataPlaneNodeSet name reference in OpenStackDataPlaneDeployment +nameReference: +- kind: OpenStackDataPlaneNodeSet + version: v1beta1 + group: dataplane.openstack.org + fieldSpecs: + - kind: OpenStackDataPlaneDeployment + version: v1beta1 + group: dataplane.openstack.org + path: spec/nodeSets diff --git a/config/samples/dataplane/base/config/varsfromname.yaml b/config/samples/dataplane/base/config/varsfromname.yaml new file mode 100644 index 000000000..35be02c95 --- /dev/null +++ b/config/samples/dataplane/base/config/varsfromname.yaml @@ -0,0 +1,13 @@ +# This file is for teaching kustomize how to substitute ansibleVarsFrom name reference in OpenStackDataPlaneNodeSet +nameReference: +- kind: ConfigMap + version: v1 + fieldSpecs: + - kind: OpenStackDataPlaneNodeSet + version: v1beta1 + group: dataplane.openstack.org + path: spec/nodeTemplate/ansible/ansibleVarsFrom/configMapRef/name + - kind: OpenStackDataPlaneNodeSet + version: v1beta1 + group: dataplane.openstack.org + path: spec/nodeTemplate/ansible/ansibleVarsFrom/secretRef/name diff --git a/config/samples/dataplane/base/files/nic-config.j2 b/config/samples/dataplane/base/files/nic-config.j2 new file mode 100644 index 000000000..9fb986c1e --- /dev/null +++ b/config/samples/dataplane/base/files/nic-config.j2 @@ -0,0 +1,31 @@ +--- +{% set mtu_list = [ctlplane_mtu] %} +{% for network in nodeset_networks %} +{{ mtu_list.append(lookup('vars', networks_lower[network] ~ '_mtu')) }} +{%- endfor %} +{% set min_viable_mtu = mtu_list | max %} +network_config: +- type: ovs_bridge + name: {{ neutron_physical_bridge_name }} + mtu: {{ min_viable_mtu }} + use_dhcp: false + dns_servers: {{ ctlplane_dns_nameservers }} + domain: {{ dns_search_domains }} + addresses: + - ip_netmask: {{ ctlplane_ip }}/{{ ctlplane_cidr }} + routes: {{ ctlplane_host_routes }} + members: + - type: interface + name: nic1 + mtu: {{ min_viable_mtu }} + # force the MAC address of the bridge to this interface + primary: true +{% for network in nodeset_networks %} + - type: vlan + mtu: {{ lookup('vars', networks_lower[network] ~ '_mtu') }} + vlan_id: {{ lookup('vars', networks_lower[network] ~ '_vlan_id') }} + addresses: + - ip_netmask: + {{ lookup('vars', networks_lower[network] ~ '_ip') }}/{{ lookup('vars', networks_lower[network] ~ '_cidr') }} + routes: {{ lookup('vars', networks_lower[network] ~ '_host_routes') }} +{% endfor %} diff --git a/config/samples/dataplane/base/kustomization.yaml b/config/samples/dataplane/base/kustomization.yaml new file mode 100644 index 000000000..2b66b3e30 --- /dev/null +++ b/config/samples/dataplane/base/kustomization.yaml @@ -0,0 +1,25 @@ +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component + +resources: +- ../../dataplane_v1beta1_openstackdataplanenodeset.yaml +- ../../dataplane_v1beta1_openstackdataplanedeployment.yaml + +namespace: openstack + +configMapGenerator: +- name: network-config-template + files: + - network_config_template=files/nic-config.j2 + options: + disableNameSuffixHash: true +- name: neutron-edpm + literals: + - physical_bridge_name=br-ex + - public_interface_name=eth0 + options: + disableNameSuffixHash: true + +configurations: +- config/nodesetname.yaml +- config/varsfromname.yaml diff --git a/config/samples/dataplane/bgp/kustomization.yaml b/config/samples/dataplane/bgp/kustomization.yaml new file mode 100644 index 000000000..bbb833661 --- /dev/null +++ b/config/samples/dataplane/bgp/kustomization.yaml @@ -0,0 +1,53 @@ +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component + +namespace: openstack + +components: +- ../preprovisioned # for baremetal nodes, replace with baremetal + +resources: + - values.yaml + +replacements: +# OpenStackDataPlaneNodeSet customizations +- source: + kind: DataPlaneConfig + name: edpm-bgp-values + fieldPath: data.nodeset.nodetemplate.ansible.vars + targets: + - select: + kind: OpenStackDataPlaneNodeSet + fieldPaths: + - spec.nodeTemplate.ansible.ansibleVars + options: + create: true +- source: + kind: DataPlaneConfig + name: edpm-bgp-values + fieldPath: data.nodeset.services + targets: + - select: + kind: OpenStackDataPlaneNodeSet + fieldPaths: + - spec.services + options: + create: true + +patches: +- target: + kind: OpenStackDataPlaneNodeSet + name: .* + patch: |- + - op: copy + from: /spec/nodes/edpm-compute-0 + path: /spec/nodes/edpm-compute-1 + - op: replace + path: /spec/nodes/edpm-compute-1/ansible/ansibleHost + value: 192.168.122.101 + - op: replace + path: /spec/nodes/edpm-compute-1/hostName + value: edpm-compute-1 + - op: replace + path: /spec/nodes/edpm-compute-1/networks/0/fixedIP + value: 192.168.122.101 diff --git a/config/samples/dataplane/bgp/values.yaml b/config/samples/dataplane/bgp/values.yaml new file mode 100644 index 000000000..571373634 --- /dev/null +++ b/config/samples/dataplane/bgp/values.yaml @@ -0,0 +1,104 @@ +# local-config: referenced, but not emitted by kustomize +apiVersion: v1 +kind: DataPlaneConfig +metadata: + name: edpm-bgp-values + annotations: + config.kubernetes.io/local-config: "true" +data: + nodeset: + nodetemplate: + ansible: + vars: + # CHANGEME -- see https://access.redhat.com/solutions/253273 + # edpm_bootstrap_command: | + # subscription-manager register --username {{ subscription_manager_username }} --password {{ subscription_manager_password }} + # podman login -u {{ registry_username }} -p {{ registry_password }} registry.redhat.io + timesync_ntp_servers: + - hostname: pool.ntp.org + # edpm_network_config + # Default nic config template for a EDPM compute node + # These vars are edpm_network_config role vars + edpm_network_config_hide_sensitive_logs: false + edpm_network_config_template: | + --- + {% set mtu_list = [ctlplane_mtu] %} + {% for network in nodeset_networks %} + {{ mtu_list.append(lookup('vars', networks_lower[network] ~ '_mtu')) }} + {%- endfor %} + {% set min_viable_mtu = mtu_list | max %} + network_config: + - type: interface + name: nic1 + mtu: {{ ctlplane_mtu }} + dns_servers: {{ ctlplane_dns_nameservers }} + domain: {{ dns_search_domains }} + use_dhcp: false + addresses: + - ip_netmask: {{ ctlplane_ip }}/{{ ctlplane_cidr }} + {% for network in nodeset_networks %} + {% if lookup('vars', networks_lower[network] ~ '_vlan_id', default='') %} + - type: vlan + device: nic1 + mtu: {{ lookup('vars', networks_lower[network] ~ '_mtu') }} + vlan_id: {{ lookup('vars', networks_lower[network] ~ '_vlan_id') }} + addresses: + - ip_netmask: + {{ lookup('vars', networks_lower[network] ~ '_ip') }}/{{ lookup('vars', networks_lower[network] ~ '_cidr') }} + routes: {{ lookup('vars', networks_lower[network] ~ '_host_routes') }} + {% endif %} + {%- endfor %} + - type: ovs_bridge + name: {{ neutron_physical_bridge_name }} + use_dhcp: false + - type: interface + name: nic2 + use_dhcp: false + addresses: + - ip_netmask: {{ lookup('vars', 'bgpnet1_ip') }}/30 + - type: interface + name: nic3 + use_dhcp: false + addresses: + - ip_netmask: {{ lookup('vars', 'bgpnet2_ip') }}/30 + - type: interface + name: lo + addresses: + - ip_netmask: {{ lookup('vars', 'bgpmainnet_ip') }}/32 + - ip_netmask: {{ lookup('vars', 'bgpmainnet6_ip') }}/128 + + # These vars are for the network config templates themselves and are + # considered EDPM network defaults. + neutron_physical_bridge_name: br-ex + neutron_public_interface_name: eth0 + # edpm_nodes_validation + edpm_nodes_validation_validate_controllers_icmp: false + edpm_nodes_validation_validate_gateway_icmp: false + ctlplane_dns_nameservers: + - 192.168.122.1 + dns_search_domains: [] + # edpm firewall, change the allowed CIDR if needed + edpm_sshd_allowed_ranges: ['192.168.122.0/24'] + edpm_frr_bgp_uplinks: ['nic2', 'nic3'] + edpm_frr_bgp_neighbor_password: f00barZ + edpm_frr_bgp_ipv4_src_network: bgpmainnet + edpm_frr_bgp_ipv6_src_network: bgpmainnet6 + edpm_ovn_bgp_agent_expose_tenant_networks: true + services: + - download-cache + - bootstrap + - configure-network + - validate-network + - frr + - install-os + - configure-os + - ssh-known-hosts + - run-os + - reboot-os + - install-certs + - ovn + - neutron-metadata + - ovn-bgp-agent + - libvirt + - nova + - telemetry diff --git a/config/samples/dataplane/bgp_ovn_cluster/kustomization.yaml b/config/samples/dataplane/bgp_ovn_cluster/kustomization.yaml new file mode 100644 index 000000000..249d23f65 --- /dev/null +++ b/config/samples/dataplane/bgp_ovn_cluster/kustomization.yaml @@ -0,0 +1,53 @@ +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component + +namespace: openstack + +components: +- ../preprovisioned # for baremetal nodes, replace with baremetal + +resources: + - values.yaml + +replacements: +# OpenStackDataPlaneNodeSet customizations +- source: + kind: DataPlaneConfig + name: edpm-bgp-ovn-cluster-values + fieldPath: data.nodeset.nodetemplate.ansible.vars + targets: + - select: + kind: OpenStackDataPlaneNodeSet + fieldPaths: + - spec.nodeTemplate.ansible.ansibleVars + options: + create: true +- source: + kind: DataPlaneConfig + name: edpm-bgp-ovn-cluster-values + fieldPath: data.nodeset.services + targets: + - select: + kind: OpenStackDataPlaneNodeSet + fieldPaths: + - spec.services + options: + create: true + +patches: +- target: + kind: OpenStackDataPlaneNodeSet + name: .* + patch: |- + - op: copy + from: /spec/nodes/edpm-compute-0 + path: /spec/nodes/edpm-compute-1 + - op: replace + path: /spec/nodes/edpm-compute-1/ansible/ansibleHost + value: 192.168.122.101 + - op: replace + path: /spec/nodes/edpm-compute-1/hostName + value: edpm-compute-1 + - op: replace + path: /spec/nodes/edpm-compute-1/networks/0/fixedIP + value: 192.168.122.101 diff --git a/config/samples/dataplane/bgp_ovn_cluster/values.yaml b/config/samples/dataplane/bgp_ovn_cluster/values.yaml new file mode 100644 index 000000000..a8c6b7eb3 --- /dev/null +++ b/config/samples/dataplane/bgp_ovn_cluster/values.yaml @@ -0,0 +1,128 @@ +# local-config: referenced, but not emitted by kustomize +apiVersion: v1 +kind: DataPlaneConfig +metadata: + name: edpm-bgp-ovn-cluster-values + annotations: + config.kubernetes.io/local-config: "true" +data: + nodeset: + nodetemplate: + ansible: + vars: + # CHANGEME -- see https://access.redhat.com/solutions/253273 + # edpm_bootstrap_command: | + # subscription-manager register --username {{ subscription_manager_username }} --password {{ subscription_manager_password }} + # podman login -u {{ registry_username }} -p {{ registry_password }} registry.redhat.io + timesync_ntp_servers: + - hostname: pool.ntp.org + # edpm_network_config + # Default nic config template for a EDPM compute node + # These vars are edpm_network_config role vars + edpm_network_config_hide_sensitive_logs: false + edpm_network_config_template: | + --- + {% set mtu_list = [ctlplane_mtu] %} + {% for network in nodeset_networks %} + {{ mtu_list.append(lookup('vars', networks_lower[network] ~ '_mtu')) }} + {%- endfor %} + {% set min_viable_mtu = mtu_list | max %} + network_config: + - type: interface + name: nic1 + mtu: {{ ctlplane_mtu }} + dns_servers: {{ ctlplane_dns_nameservers }} + domain: {{ dns_search_domains }} + use_dhcp: false + addresses: + - ip_netmask: {{ ctlplane_ip }}/{{ ctlplane_cidr }} + {% for network in nodeset_networks %} + {% if lookup('vars', networks_lower[network] ~ '_vlan_id', default='') %} + - type: vlan + device: nic1 + mtu: {{ lookup('vars', networks_lower[network] ~ '_mtu') }} + vlan_id: {{ lookup('vars', networks_lower[network] ~ '_vlan_id') }} + addresses: + - ip_netmask: + {{ lookup('vars', networks_lower[network] ~ '_ip') }}/{{ lookup('vars', networks_lower[network] ~ '_cidr') }} + routes: {{ lookup('vars', networks_lower[network] ~ '_host_routes') }} + {% endif %} + {%- endfor %} + - type: ovs_bridge + name: br-provider + use_dhcp: false + - type: ovs_bridge + name: {{ neutron_physical_bridge_name }} + mtu: {{ min_viable_mtu }} + use_dhcp: false + addresses: + - ip_netmask: {{ lookup('vars', 'bgpnet1_ip') }}/30 + members: + - type: interface + name: nic2 + mtu: {{ min_viable_mtu }} + # force the MAC address of the bridge to this interface + primary: true + - type: ovs_bridge + name: {{ neutron_physical_bridge_name }}-2 + mtu: {{ min_viable_mtu }} + use_dhcp: false + addresses: + - ip_netmask: {{ lookup('vars', 'bgpnet2_ip') }}/30 + members: + - type: interface + name: nic3 + mtu: {{ min_viable_mtu }} + # force the MAC address of the bridge to this interface + primary: true + - type: interface + name: lo + addresses: + - ip_netmask: {{ lookup('vars', 'bgpmainnet_ip') }}/32 + - ip_netmask: {{ lookup('vars', 'bgpmainnet6_ip') }}/128 + + # These vars are for the network config templates themselves and are + # considered EDPM network defaults. + neutron_physical_bridge_name: br-ex + neutron_public_interface_name: eth0 + # edpm_nodes_validation + edpm_nodes_validation_validate_controllers_icmp: false + edpm_nodes_validation_validate_gateway_icmp: false + ctlplane_dns_nameservers: + - 192.168.122.1 + dns_search_domains: [] + gather_facts: false + enable_debug: false + # edpm firewall, change the allowed CIDR if needed + edpm_sshd_configure_firewall: true + edpm_sshd_allowed_ranges: ['192.168.122.0/24'] + edpm_frr_bgp_uplinks: ['nic2', 'nic3'] + edpm_frr_bgp_neighbor_password: f00barZ + edpm_frr_bgp_ipv4_src_network: bgpmainnet + edpm_frr_bgp_ipv6_src_network: bgpmainnet6 + edpm_frr_bgp_peers: ['100.64.1.5', '100.65.1.5'] + edpm_ovn_bgp_agent_expose_tenant_networks: true + edpm_ovn_bgp_agent_local_ovn_routing: true + edpm_ovn_bridge_mappings: ['bgp:br-provider'] + edpm_ovn_bgp_agent_local_ovn_external_nics: ['eth1', 'eth2'] + edpm_ovn_bgp_agent_local_ovn_peer_ips: ['100.64.1.5', '100.65.1.5'] + edpm_ovn_bgp_agent_exposing_method: ovn + edpm_ovn_bgp_agent_provider_networks_pool_prefixes: '172.16.0.0/16' + services: + - download-cache + - bootstrap + - configure-network + - validate-network + - frr + - install-os + - configure-os + - ssh-known-hosts + - run-os + - reboot-os + - install-certs + - ovn + - neutron-metadata + - ovn-bgp-agent + - libvirt + - nova + - telemetry diff --git a/config/samples/dataplane/ceph/kustomization.yaml b/config/samples/dataplane/ceph/kustomization.yaml new file mode 100644 index 000000000..24d140900 --- /dev/null +++ b/config/samples/dataplane/ceph/kustomization.yaml @@ -0,0 +1,66 @@ +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component + +namespace: openstack +nameSuffix: -ceph + +components: +- ../preprovisioned # for baremetal nodes, replace with baremetal + +resources: + - values.yaml + +replacements: +# OpenStackDataPlaneNodeSet customizations +- source: + kind: DataPlaneConfig + name: edpm-ceph-values + fieldPath: data.nodeset.services + targets: + - select: + kind: OpenStackDataPlaneNodeSet + fieldPaths: + - spec.services + options: + create: true +- source: + kind: DataPlaneConfig + name: edpm-ceph-values + fieldPath: data.nodetemplate.extramounts + targets: + - select: + kind: OpenStackDataPlaneNodeSet + fieldPaths: + - spec.nodeTemplate.extraMounts + options: + create: true + +patches: +- target: + kind: OpenStackDataPlaneNodeSet + name: .* + patch: |- + - op: copy + from: /spec/nodes/edpm-compute-0 + path: /spec/nodes/edpm-compute-1 + - op: replace + path: /spec/nodes/edpm-compute-1/ansible/ansibleHost + value: 192.168.122.101 + - op: replace + path: /spec/nodes/edpm-compute-1/hostName + value: edpm-compute-1 + - op: replace + path: /spec/nodes/edpm-compute-1/networks/0/fixedIP + value: 192.168.122.101 + - op: copy + from: /spec/nodes/edpm-compute-0 + path: /spec/nodes/edpm-compute-2 + - op: replace + path: /spec/nodes/edpm-compute-2/ansible/ansibleHost + value: 192.168.122.102 + - op: replace + path: /spec/nodes/edpm-compute-2/hostName + value: edpm-compute-2 + - op: replace + path: /spec/nodes/edpm-compute-2/networks/0/fixedIP + value: 192.168.122.102 diff --git a/config/samples/dataplane/ceph/values.yaml b/config/samples/dataplane/ceph/values.yaml new file mode 100644 index 000000000..57553fc46 --- /dev/null +++ b/config/samples/dataplane/ceph/values.yaml @@ -0,0 +1,51 @@ +# local-config: referenced, but not emitted by kustomize +apiVersion: v1 +kind: DataPlaneConfig +metadata: + name: edpm-ceph-values + annotations: + config.kubernetes.io/local-config: "true" +data: + nodeset: + # Create a nova-custom-ceph service which uses a ConfigMap + # containing libvirt overrides for Ceph RBD. + services: + - bootstrap + - download-cache + - configure-network + - validate-network + - install-os + - configure-os + - ssh-known-hosts + - run-os + - reboot-os + - ceph-client + - install-certs + - ovn + - neutron-metadata + - libvirt + - nova-custom-ceph + nodetemplate: + # Create a secret called ceph-conf-files with the cephx key and + # ceph.conf file and mount it so the ceph-client service can copy + # those files to the EDPM nodes. + extramounts: + - extraVolType: Logs + mounts: + - mountPath: /runner/artifacts + name: ansible-logs + volumes: + - name: ansible-logs + persistentVolumeClaim: + claimName: ansible-ee-logs + - extraVolType: Ceph + mounts: + - mountPath: /etc/ceph + name: ceph + readOnly: true + volumes: + - name: ceph + projected: + sources: + - secret: + name: ceph-conf-files diff --git a/config/samples/dataplane/customnetworks/kustomization.yaml b/config/samples/dataplane/customnetworks/kustomization.yaml new file mode 100644 index 000000000..00ba307c5 --- /dev/null +++ b/config/samples/dataplane/customnetworks/kustomization.yaml @@ -0,0 +1,69 @@ +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component + +namespace: openstack +nameSuffix: -custom-network + +components: +- ../preprovisioned # for baremetal nodes, replace with baremetal + +resources: + - values.yaml + +replacements: +# OpenStackDataPlaneNodeSet customizations +- source: + kind: DataPlaneConfig + name: edpm-custom-networks-values + fieldPath: data.nodeset.nodetemplate.ansible.vars.neutron_public_interface_name + targets: + - select: + kind: OpenStackDataPlaneNodeSet + fieldPaths: + - spec.nodeTemplate.ansible.ansibleVars.neutron_public_interface_name + options: + create: true +- source: + kind: DataPlaneConfig + name: edpm-custom-networks-values + fieldPath: data.nodeset.nodetemplate.ansible.vars.ctlplane_dns_nameservers + targets: + - select: + kind: OpenStackDataPlaneNodeSet + fieldPaths: + - spec.nodeTemplate.ansible.ansibleVars.ctlplane_dns_nameservers + options: + create: true +- source: + kind: DataPlaneConfig + name: edpm-custom-networks-values + fieldPath: data.nodeset.nodetemplate.ansible.vars.edpm_ovn_dbs + targets: + - select: + kind: OpenStackDataPlaneNodeSet + fieldPaths: + - spec.nodeTemplate.ansible.ansibleVars.edpm_ovn_dbs + options: + create: true +- source: + kind: DataPlaneConfig + name: edpm-custom-networks-values + fieldPath: data.nodeset.nodetemplate.ansible.vars.edpm_sshd_allowed_ranges + targets: + - select: + kind: OpenStackDataPlaneNodeSet + fieldPaths: + - spec.nodeTemplate.ansible.ansibleVars.edpm_sshd_allowed_ranges + options: + create: true +- source: + kind: DataPlaneConfig + name: edpm-custom-networks-values + fieldPath: data.nodeset.nodes + targets: + - select: + kind: OpenStackDataPlaneNodeSet + fieldPaths: + - spec.nodes + options: + create: true diff --git a/config/samples/dataplane/customnetworks/values.yaml b/config/samples/dataplane/customnetworks/values.yaml new file mode 100644 index 000000000..81c95e7c3 --- /dev/null +++ b/config/samples/dataplane/customnetworks/values.yaml @@ -0,0 +1,39 @@ +# local-config: referenced, but not emitted by kustomize +apiVersion: v1 +kind: DataPlaneConfig +metadata: + name: edpm-custom-networks-values + annotations: + config.kubernetes.io/local-config: "true" +data: + nodeset: + nodetemplate: + ansible: + vars: + # CHANGEME -- see https://access.redhat.com/solutions/253273 + # edpm_bootstrap_command: | + # subscription-manager register --username {{ subscription_manager_username }} --password {{ subscription_manager_password }} + # podman login -u {{ registry_username }} -p {{ registry_password }} registry.redhat.io + + neutron_public_interface_name: enp7s0 + ctlplane_dns_nameservers: + - 192.168.1.254 + edpm_ovn_dbs: + - 192.168.24.1 + edpm_sshd_allowed_ranges: ['192.168.0.0/24', '172.20.0.0/16'] + nodes: + edpm-compute-0: + hostName: edpm-compute-0 + ansible: + ansibleHost: 192.168.1.5 + networks: + - name: ctlplane + subnetName: subnet1 + defaultRoute: true + fixedIP: 192.168.1.5 + - name: internalapi + subnetName: subnet1 + - name: storage + subnetName: subnet1 + - name: tenant + subnetName: subnet1 diff --git a/config/samples/dataplane/networker/kustomization.yaml b/config/samples/dataplane/networker/kustomization.yaml new file mode 100644 index 000000000..14d01ddba --- /dev/null +++ b/config/samples/dataplane/networker/kustomization.yaml @@ -0,0 +1,47 @@ +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component + +namespace: openstack +nameSuffix: -networker + +components: +- ../preprovisioned # for baremetal nodes, replace with baremetal + +resources: + - values.yaml + +replacements: +# OpenStackDataPlaneNodeSet customizations +- source: + kind: DataPlaneConfig + name: edpm-networker-values + fieldPath: data.nodeset.nodetemplate.ansible.vars.edpm_enable_chassis_gw + targets: + - select: + kind: OpenStackDataPlaneNodeSet + fieldPaths: + - spec.nodeTemplate.ansible.ansibleVars.edpm_enable_chassis_gw + options: + create: true +- source: + kind: DataPlaneConfig + name: edpm-networker-values + fieldPath: data.nodeset.services + targets: + - select: + kind: OpenStackDataPlaneNodeSet + fieldPaths: + - spec.services + options: + create: true +- source: + kind: DataPlaneConfig + name: edpm-networker-values + fieldPath: data.nodeset.nodes + targets: + - select: + kind: OpenStackDataPlaneNodeSet + fieldPaths: + - spec.nodes + options: + create: true diff --git a/config/samples/dataplane/networker/values.yaml b/config/samples/dataplane/networker/values.yaml new file mode 100644 index 000000000..73ee399c8 --- /dev/null +++ b/config/samples/dataplane/networker/values.yaml @@ -0,0 +1,60 @@ +# local-config: referenced, but not emitted by kustomize +apiVersion: v1 +kind: DataPlaneConfig +metadata: + name: edpm-networker-values + annotations: + config.kubernetes.io/local-config: "true" +data: + nodeset: + nodetemplate: + ansible: + vars: + # CHANGEME -- see https://access.redhat.com/solutions/253273 + # edpm_bootstrap_command: | + # subscription-manager register --username {{ subscription_manager_username }} --password {{ subscription_manager_password }} + # podman login -u {{ registry_username }} -p {{ registry_password }} registry.redhat.io + edpm_enable_chassis_gw: true + nodes: + edpm-networker-0: + hostName: edpm-networker-0 + ansible: + ansibleHost: 192.168.122.100 + networks: + - name: ctlplane + subnetName: subnet1 + defaultRoute: true + fixedIP: 192.168.122.100 + - name: internalapi + subnetName: subnet1 + - name: storage + subnetName: subnet1 + - name: tenant + subnetName: subnet1 + edpm-networker-1: + hostName: edpm-networker-1 + ansible: + ansibleHost: 192.168.122.101 + networks: + - name: ctlplane + subnetName: subnet1 + defaultRoute: true + fixedIP: 192.168.122.101 + - name: internalapi + subnetName: subnet1 + - name: storage + subnetName: subnet1 + - name: tenant + subnetName: subnet1 + services: + - download-cache + - bootstrap + - configure-network + - validate-network + - install-os + - configure-os + - ssh-known-hosts + - run-os + - reboot-os + - install-certs + - ovn diff --git a/config/samples/dataplane/nmstate/kustomization.yaml b/config/samples/dataplane/nmstate/kustomization.yaml new file mode 100644 index 000000000..adc002370 --- /dev/null +++ b/config/samples/dataplane/nmstate/kustomization.yaml @@ -0,0 +1,42 @@ +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component + +namespace: openstack + +components: +- ../preprovisioned # for baremetal nodes, replace with baremetal + +resources: + - values.yaml + +replacements: +# OpenStackDataPlaneNodeSet customizations +- source: + kind: DataPlaneConfig + name: edpm-nmstate-values + fieldPath: data.nodeset.nodetemplate.ansible.vars + targets: + - select: + kind: OpenStackDataPlaneNodeSet + fieldPaths: + - spec.nodeTemplate.ansible.ansibleVars + options: + create: true + +patches: +- target: + kind: OpenStackDataPlaneNodeSet + name: .* + patch: |- + - op: copy + from: /spec/nodes/edpm-compute-0 + path: /spec/nodes/edpm-compute-1 + - op: replace + path: /spec/nodes/edpm-compute-1/ansible/ansibleHost + value: 192.168.122.101 + - op: replace + path: /spec/nodes/edpm-compute-1/hostName + value: edpm-compute-1 + - op: replace + path: /spec/nodes/edpm-compute-1/networks/0/fixedIP + value: 192.168.122.101 diff --git a/config/samples/dataplane/nmstate/values.yaml b/config/samples/dataplane/nmstate/values.yaml new file mode 100644 index 000000000..a54703e70 --- /dev/null +++ b/config/samples/dataplane/nmstate/values.yaml @@ -0,0 +1,90 @@ +# local-config: referenced, but not emitted by kustomize +apiVersion: v1 +kind: DataPlaneConfig +metadata: + name: edpm-nmstate-values + annotations: + config.kubernetes.io/local-config: "true" +data: + nodeset: + nodetemplate: + ansible: + vars: + # CHANGEME -- see https://access.redhat.com/solutions/253273 + # edpm_bootstrap_command: | + # subscription-manager register --username {{ subscription_manager_username }} --password {{ subscription_manager_password }} + # podman login -u {{ registry_username }} -p {{ registry_password }} registry.redhat.io + edpm_network_config_hide_sensitive_logs: false + edpm_network_config_template: | + --- + {% set mtu_list = [ctlplane_mtu] %} + {% for network in nodeset_networks %} + {{ mtu_list.append(lookup('vars', networks_lower[network] ~ '_mtu')) }} + {%- endfor %} + {% set min_viable_mtu = mtu_list | max %} + dns-resolver: + config: + search: {{ dns_search_domains }} + server: {{ ctlplane_dns_nameservers }} + interfaces: + - name: {{ neutron_public_interface_name }} + type: ethernet + state: up + - name: {{ neutron_physical_bridge_name }} + type: ovs-interface + state: up + mtu: {{ min_viable_mtu }} + ipv4: + enabled: true + address: + - ip: {{ ctlplane_ip }} + prefix-length: {{ ctlplane_cidr }} + {% for network in nodeset_networks %} + - name: {{ "vlan" ~ lookup('vars', networks_lower[network] ~ '_vlan_id') }} + type: ovs-interface + state: up + mtu: {{ lookup('vars', networks_lower[network] ~ '_mtu') }} + ipv4: + enabled: true + address: + - ip: {{ lookup('vars', networks_lower[network] ~ '_ip') }} + prefix-length: {{ lookup('vars', networks_lower[network] ~ '_cidr') }} + {% endfor %} + - name: {{ neutron_physical_bridge_name }} + type: ovs-bridge + bridge: + options: + fail-mode: standalone + port: + - name: {{ neutron_public_interface_name }} + - name: {{ neutron_physical_bridge_name }} + {% for network in nodeset_networks %} + - name: {{ "vlan" ~ lookup('vars', networks_lower[network] ~ '_vlan_id') }} + vlan: + mode: access + tag: {{ lookup('vars', networks_lower[network] ~ '_vlan_id') }} + {% endfor %} + routes: + config: + - destination: {{ ctlplane_host_routes.ip_netmask }} + next-hop-address: {{ ctlplane_host_routes.next_hop }} + next-hop-interface: {{ neutron_physical_bridge_name }} + # edpm_network_config - nmstate + edpm_network_config_tool: 'nmstate' + # These vars are for the network config templates themselves and are + # considered EDPM network defaults. + neutron_physical_bridge_name: br-ex + neutron_public_interface_name: eth0 + # edpm_nodes_validation + edpm_nodes_validation_validate_controllers_icmp: false + edpm_nodes_validation_validate_gateway_icmp: false + ctlplane_dns_nameservers: + - 192.168.122.1 + dns_search_domains: [] + gather_facts: false + enable_debug: false + # edpm firewall, change the allowed CIDR if needed + edpm_sshd_configure_firewall: true + edpm_sshd_allowed_ranges: ['192.168.122.0/24'] + # SELinux module + edpm_selinux_mode: enforcing diff --git a/config/samples/dataplane/no_vars_from/kustomization.yaml b/config/samples/dataplane/no_vars_from/kustomization.yaml new file mode 100644 index 000000000..89f8450ee --- /dev/null +++ b/config/samples/dataplane/no_vars_from/kustomization.yaml @@ -0,0 +1,53 @@ +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component + +components: +- ../preprovisioned # for baremetal nodes, replace with baremetal + +replacements: +# OpenStackDataPlaneNodeSet customizations +- source: + kind: ConfigMap + name: neutron-edpm + fieldPath: data.physical_bridge_name + targets: + - select: + kind: OpenStackDataPlaneNodeSet + fieldPaths: + - spec.nodeTemplate.ansible.ansibleVars.neutron_physical_bridge_name + options: + create: true +- source: + kind: ConfigMap + name: neutron-edpm + fieldPath: data.public_interface_name + targets: + - select: + kind: OpenStackDataPlaneNodeSet + fieldPaths: + - spec.nodeTemplate.ansible.ansibleVars.neutron_public_interface_name + options: + create: true +- source: + kind: ConfigMap + name: network-config-template + fieldPath: data.network_config_template + targets: + - select: + kind: OpenStackDataPlaneNodeSet + fieldPaths: + - spec.nodeTemplate.ansible.ansibleVars.edpm_network_config_template + options: + create: true + + +patches: +- target: + kind: OpenStackDataPlaneNodeSet + name: openstack-edpm + patch: |- + - op: remove + path: /spec/nodeTemplate/ansible/ansibleVarsFrom + - op: add + path: /spec/nodeTemplate/ansible/ansibleUser + value: cloud-admin diff --git a/config/samples/dataplane/nova_cell_custom/kustomization.yaml b/config/samples/dataplane/nova_cell_custom/kustomization.yaml new file mode 100644 index 000000000..d0ed5968f --- /dev/null +++ b/config/samples/dataplane/nova_cell_custom/kustomization.yaml @@ -0,0 +1,33 @@ +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component + +namespace: openstack + +components: +- ../preprovisioned # for baremetal nodes, replace with baremetal + +resources: +- ../../../services/dataplane_v1beta1_openstackdataplaneservice_nova.yaml + +configMapGenerator: +- name: nova-extra-config + files: + - 25-nova-extra.conf=nova-extra.conf + options: + disableNameSuffixHash: true + +patches: +- target: + kind: OpenStackDataPlaneService + name: nova + patch: |- + - op: replace + path: /metadata/name + value: nova-cell-custom + - op: add + path: /spec/configMaps + value: + - nova-extra-config + +configurations: +- servicename.yaml diff --git a/config/samples/dataplane/nova_cell_custom/nova-extra.conf b/config/samples/dataplane/nova_cell_custom/nova-extra.conf new file mode 100644 index 000000000..e9de0ef51 --- /dev/null +++ b/config/samples/dataplane/nova_cell_custom/nova-extra.conf @@ -0,0 +1,3 @@ +[compute] +cpu_shared_set = 2,6 +cpu_dedicated_set = 1,3,5,7 diff --git a/config/samples/dataplane/nova_cell_custom/servicename.yaml b/config/samples/dataplane/nova_cell_custom/servicename.yaml new file mode 100644 index 000000000..7d43116bd --- /dev/null +++ b/config/samples/dataplane/nova_cell_custom/servicename.yaml @@ -0,0 +1,10 @@ +# This file is for teaching kustomize how to substitute OpenStackDataPlaneService name reference in OpenStackDataPlaneNodeSet +nameReference: +- kind: OpenStackDataPlaneService + version: v1beta1 + group: dataplane.openstack.org + fieldSpecs: + - kind: OpenStackDataPlaneNodeSet + version: v1beta1 + group: dataplane.openstack.org + path: spec/services diff --git a/config/samples/dataplane/ovs_dpdk/kustomization.yaml b/config/samples/dataplane/ovs_dpdk/kustomization.yaml new file mode 100644 index 000000000..b93691528 --- /dev/null +++ b/config/samples/dataplane/ovs_dpdk/kustomization.yaml @@ -0,0 +1,80 @@ +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component + +namespace: openstack +nameSuffix: -ovs-dpdk + +components: +- ../baremetal + +resources: + - values.yaml + +replacements: +# OpenStackDataPlaneNodeSet customizations +- source: + kind: DataPlaneConfig + name: edpm-ovs-dpdk-values + fieldPath: data.nodeset.nodetemplate.ansible.vars + targets: + - select: + kind: OpenStackDataPlaneNodeSet + fieldPaths: + - spec.nodeTemplate.ansible.ansibleVars + options: + create: true +- source: + kind: DataPlaneConfig + name: edpm-ovs-dpdk-values + fieldPath: data.nodeset.services + targets: + - select: + kind: OpenStackDataPlaneNodeSet + fieldPaths: + - spec.services + options: + create: true +- source: + kind: DataPlaneConfig + name: edpm-ovs-dpdk-values + fieldPath: data.nodeset.nodes + targets: + - select: + kind: OpenStackDataPlaneNodeSet + fieldPaths: + - spec.nodes + options: + create: true +- source: + kind: DataPlaneConfig + name: edpm-ovs-dpdk-values + fieldPath: data.nodeset.baremetalsettemplate + targets: + - select: + kind: OpenStackDataPlaneNodeSet + fieldPaths: + - spec.baremetalSetTemplate + options: + create: true +- source: + kind: DataPlaneConfig + name: edpm-ovs-dpdk-values + fieldPath: data.preProvisioned + targets: + - select: + kind: OpenStackDataPlaneNodeSet + fieldPaths: + - spec.preProvisioned + options: + create: true +- source: + kind: DataPlaneConfig + name: edpm-ovs-dpdk-values + fieldPath: data.nodeset.nodetemplate.networks + targets: + - select: + kind: OpenStackDataPlaneNodeSet + fieldPaths: + - spec.nodeTemplate.networks + options: + create: true diff --git a/config/samples/dataplane/ovs_dpdk/values.yaml b/config/samples/dataplane/ovs_dpdk/values.yaml new file mode 100644 index 000000000..6f7e868a8 --- /dev/null +++ b/config/samples/dataplane/ovs_dpdk/values.yaml @@ -0,0 +1,129 @@ +# local-config: referenced, but not emitted by kustomize +apiVersion: v1 +kind: DataPlaneConfig +metadata: + name: edpm-ovs-dpdk-values + annotations: + config.kubernetes.io/local-config: "true" +data: + preProvisioned: false + nodeset: + baremetalsettemplate: + bmhLabelSelector: + app: openstack + ctlplaneInterface: enp1s0 + cloudUserName: cloud-admin + nodetemplate: + ansible: + vars: + # CHANGEME -- see https://access.redhat.com/solutions/253273 + # edpm_bootstrap_command: | + # subscription-manager register --username {{ subscription_manager_username }} --password {{ subscription_manager_password }} + # podman login -u {{ registry_username }} -p {{ registry_password }} registry.redhat.io + edpm_network_config_hide_sensitive_logs: false + edpm_network_config_template: | + --- + {% set mtu_list = [ctlplane_mtu] %} + {% for network in nodeset_networks %} + {{ mtu_list.append(lookup('vars', networks_lower[network] ~ '_mtu')) }} + {%- endfor %} + {% set min_viable_mtu = mtu_list | max %} + network_config: + - type: ovs_bridge + name: {{ neutron_physical_bridge_name }} + mtu: {{ min_viable_mtu }} + use_dhcp: false + dns_servers: {{ ctlplane_dns_nameservers }} + domain: {{ dns_search_domains }} + addresses: + - ip_netmask: {{ ctlplane_ip }}/{{ ctlplane_cidr }} + routes: {{ ctlplane_host_routes }} + members: + - type: interface + name: nic1 + mtu: {{ min_viable_mtu }} + # force the MAC address of the bridge to this interface + primary: true + {% for network in nodeset_networks if network not in ["external", "tenant"] %} + - type: vlan + mtu: {{ lookup('vars', networks_lower[network] ~ '_mtu') }} + vlan_id: {{ lookup('vars', networks_lower[network] ~ '_vlan_id') }} + addresses: + - ip_netmask: + {{ lookup('vars', networks_lower[network] ~ '_ip') }}/{{ lookup('vars', networks_lower[network] ~ '_cidr') }} + routes: {{ lookup('vars', networks_lower[network] ~ '_host_routes') }} + {% endfor %} + - type: ovs_user_bridge + name: br-link1 + use_dhcp: false + ovs_extra: "set port br-link1 tag={{ lookup('vars', networks_lower['tenant'] ~ '_vlan_id') }}" + addresses: + - ip_netmask: {{ lookup('vars', networks_lower['tenant'] ~ '_ip') }}/{{ lookup('vars', networks_lower['tenant'] ~ '_cidr') }} + + mtu: {{ lookup('vars', networks_lower['tenant'] ~ '_mtu') }} + members: + - type: ovs_dpdk_port + name: dpdk1 + members: + - type: interface + name: nic3 + - type: ovs_user_bridge + name: br-link2 + use_dhcp: false + mtu: 9000 + members: + - type: ovs_dpdk_port + name: dpdk2 + members: + - type: interface + name: nic4 + + neutron_physical_bridge_name: br-ex + # edpm_nodes_validation + edpm_nodes_validation_validate_controllers_icmp: false + edpm_nodes_validation_validate_gateway_icmp: false + # edpm nfv ovs dpdk config + edpm_kernel_args: "default_hugepagesz=1GB hugepagesz=1G hugepages=64 iommu=pt intel_iommu=on tsx=off isolcpus=2-11,14-23" + edpm_tuned_profile: "cpu-partitioning" + edpm_nova_libvirt_qemu_group: "hugetlbfs" + edpm_tuned_isolated_cores: "2-11,14-23" + edpm_ovs_dpdk_pmd_core_list: "1,13,2,14,3,15" + edpm_ovs_dpdk_socket_memory: "4096" + edpm_ovs_dpdk_memory_channels: "4" + edpm_ovs_dpdk_vhost_postcopy_support: "true" + edpm_ovn_bridge_mappings: ['dpdk2:br-link2','dpdk1:br-link1'] + gather_facts: false + enable_debug: false + edpm_sshd_allowed_ranges: ['192.168.122.0/24'] + networks: + - defaultRoute: true + name: ctlplane + subnetName: subnet1 + - name: internalapi + subnetName: subnet1 + - name: storage + subnetName: subnet1 + - name: tenant + subnetName: subnet1 + nodes: + edpm-compute-0: + hostName: edpm-compute-0 + edpm-compute-1: + hostName: edpm-compute-1 + services: + - bootstrap + - download-cache + - reboot-os + - configure-ovs-dpdk + - configure-network + - validate-network + - install-os + - configure-os + - ssh-known-hosts + - run-os + - install-certs + - ovn + - neutron-metadata + - libvirt + - nova + - telemetry diff --git a/config/samples/dataplane/post_ceph_hci/kustomization.yaml b/config/samples/dataplane/post_ceph_hci/kustomization.yaml new file mode 100644 index 000000000..1ca420d47 --- /dev/null +++ b/config/samples/dataplane/post_ceph_hci/kustomization.yaml @@ -0,0 +1,60 @@ +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component + +namespace: openstack + +nameSuffix: -ceph + +components: +- ../preprovisioned # for baremetal nodes, replace with baremetal + +resources: + - values.yaml + +replacements: +# OpenStackDataPlaneNodeSet customizations +- source: + kind: DataPlaneConfig + name: edpm-ceph-values + fieldPath: data.nodeset.nodetemplate.ansible.vars + targets: + - select: + kind: OpenStackDataPlaneNodeSet + fieldPaths: + - spec.nodeTemplate.ansible.ansibleVars + options: + create: true +- source: + kind: DataPlaneConfig + name: edpm-ceph-values + fieldPath: data.nodeset.nodes + targets: + - select: + kind: OpenStackDataPlaneNodeSet + fieldPaths: + - spec.nodes + options: + create: true +- source: + kind: DataPlaneConfig + name: edpm-ceph-values + fieldPath: data.nodeset.nodetemplate.extramounts + targets: + - select: + kind: OpenStackDataPlaneNodeSet + fieldPaths: + - spec.nodeTemplate.extraMounts + options: + create: true +# OpenStackDataPlaneDeployment customizations +- source: + kind: DataPlaneConfig + name: edpm-ceph-values + fieldPath: data.deployment.services + targets: + - select: + kind: OpenStackDataPlaneDeployment + fieldPaths: + - spec.servicesOverride + options: + create: true diff --git a/config/samples/dataplane/post_ceph_hci/values.yaml b/config/samples/dataplane/post_ceph_hci/values.yaml new file mode 100644 index 000000000..03562d547 --- /dev/null +++ b/config/samples/dataplane/post_ceph_hci/values.yaml @@ -0,0 +1,163 @@ +# local-config: referenced, but not emitted by kustomize +apiVersion: v1 +kind: DataPlaneConfig +metadata: + name: edpm-ceph-values + annotations: + config.kubernetes.io/local-config: "true" +data: + nodeset: + nodetemplate: + ansible: + vars: + timesync_ntp_servers: + - hostname: clock.redhat.com + # CHANGEME -- see https://access.redhat.com/solutions/253273 + # edpm_bootstrap_command: | + # subscription-manager register --username {{ subscription_manager_username }} --password {{ subscription_manager_password }} + # podman login -u {{ registry_username }} -p {{ registry_password }} registry.redhat.io + edpm_iscsid_image: '{{ registry_url }}/openstack-iscsid:{{ image_tag }}' + edpm_logrotate_crond_image: '{{ registry_url }}/openstack-cron:{{ image_tag }}' + edpm_network_config_hide_sensitive_logs: false + edpm_network_config_os_net_config_mappings: + edpm-compute: + nic2: enp7s0 + edpm_network_config_template: | + --- + {% set mtu_list = [ctlplane_mtu] %} + {% for network in nodeset_networks %} + {{ mtu_list.append(lookup('vars', networks_lower[network] ~ '_mtu')) }} + {%- endfor %} + {% set min_viable_mtu = mtu_list | max %} + network_config: + - type: ovs_bridge + name: {{ neutron_physical_bridge_name }} + mtu: {{ min_viable_mtu }} + use_dhcp: false + dns_servers: {{ ctlplane_dns_nameservers }} + domain: {{ dns_search_domains }} + addresses: + - ip_netmask: {{ ctlplane_ip }}/{{ ctlplane_cidr }} + routes: {{ ctlplane_host_routes }} + members: + - type: interface + name: nic2 + mtu: {{ min_viable_mtu }} + # force the MAC address of the bridge to this interface + primary: true + {% for network in nodeset_networks %} + - type: vlan + mtu: {{ lookup('vars', networks_lower[network] ~ '_mtu') }} + vlan_id: {{ lookup('vars', networks_lower[network] ~ '_vlan_id') }} + addresses: + - ip_netmask: + {{ lookup('vars', networks_lower[network] ~ '_ip') }}/{{ lookup('vars', networks_lower[network] ~ '_cidr') }} + routes: {{ lookup('vars', networks_lower[network] ~ '_host_routes') }} + {% endfor %} + edpm_neutron_metadata_agent_image: '{{ registry_url }}/openstack-neutron-metadata-agent-ovn:{{ image_tag }}' + edpm_nodes_validation_validate_controllers_icmp: false + edpm_nodes_validation_validate_gateway_icmp: false + edpm_nova_compute_container_image: '{{ registry_url }}/openstack-nova-compute:{{ image_tag }}' + edpm_nova_libvirt_container_image: '{{ registry_url }}/openstack-nova-libvirt:{{ image_tag }}' + edpm_ovn_controller_agent_image: '{{ registry_url }}/openstack-ovn-controller:{{ image_tag }}' + edpm_selinux_mode: enforcing + edpm_sshd_allowed_ranges: + - 192.168.122.0/24 + edpm_sshd_configure_firewall: true + enable_debug: false + gather_facts: false + image_tag: current-podified + neutron_physical_bridge_name: br-ex + neutron_public_interface_name: eth0 + registry_url: quay.io/podified-antelope-centos9 + edpm_ceph_hci_pre_enabled_services: + - ceph_mon + - ceph_mgr + - ceph_osd + - ceph_rgw + - ceph_nfs + - ceph_rgw_frontend + - ceph_nfs_frontend + storage_mtu: 9000 + storage_mgmt_mtu: 9000 + storage_mgmt_vlan_id: 23 + storage_mgmt_cidr: "24" + storage_mgmt_host_routes: [] + extramounts: + - extraVolType: Logs + mounts: + - mountPath: /runner/artifacts + name: ansible-logs + volumes: + - name: ansible-logs + persistentVolumeClaim: + claimName: ansible-ee-logs + - extraVolType: Ceph + mounts: + - mountPath: /etc/ceph + name: ceph + readOnly: true + volumes: + - name: ceph + projected: + sources: + - secret: + name: ceph-conf-files + nodes: + edpm-compute-0: + ansible: + ansibleHost: 192.168.122.100 + hostName: edpm-compute-0 + networks: + - defaultRoute: true + fixedIP: 192.168.122.100 + name: ctlplane + subnetName: subnet1 + - name: internalapi + subnetName: subnet1 + - name: storage + subnetName: subnet1 + - name: storagemgmt + subnetName: subnet1 + - name: tenant + subnetName: subnet1 + edpm-compute-1: + ansible: + ansibleHost: 192.168.122.101 + hostName: edpm-compute-1 + networks: + - defaultRoute: true + fixedIP: 192.168.122.101 + name: ctlplane + subnetName: subnet1 + - name: internalapi + subnetName: subnet1 + - name: storage + subnetName: subnet1 + - name: storagemgmt + subnetName: subnet1 + - name: tenant + subnetName: subnet1 + edpm-compute-2: + ansible: + ansibleHost: 192.168.122.102 + hostName: edpm-compute-2 + networks: + - defaultRoute: true + fixedIP: 192.168.122.102 + name: ctlplane + subnetName: subnet1 + - name: internalapi + subnetName: subnet1 + - name: storage + subnetName: subnet1 + - name: storagemgmt + subnetName: subnet1 + - name: tenant + subnetName: subnet1 + deployment: + services: + - ceph-client + - ovn + - libvirt + - nova-custom-ceph diff --git a/config/samples/dataplane/pre_ceph_hci/kustomization.yaml b/config/samples/dataplane/pre_ceph_hci/kustomization.yaml new file mode 100644 index 000000000..446bf1191 --- /dev/null +++ b/config/samples/dataplane/pre_ceph_hci/kustomization.yaml @@ -0,0 +1,48 @@ +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component + +namespace: openstack + +nameSuffix: -ceph + +components: +- ../preprovisioned # for baremetal nodes, replace with baremetal + +resources: + - values.yaml + +replacements: +# OpenStackDataPlaneNodeSet customizations +- source: + kind: DataPlaneConfig + name: edpm-ceph-values + fieldPath: data.nodeset.nodetemplate.ansible.vars + targets: + - select: + kind: OpenStackDataPlaneNodeSet + fieldPaths: + - spec.nodeTemplate.ansible.ansibleVars + options: + create: true +- source: + kind: DataPlaneConfig + name: edpm-ceph-values + fieldPath: data.nodeset.nodes + targets: + - select: + kind: OpenStackDataPlaneNodeSet + fieldPaths: + - spec.nodes + options: + create: true +- source: + kind: DataPlaneConfig + name: edpm-ceph-values + fieldPath: data.deployment.services + targets: + - select: + kind: OpenStackDataPlaneDeployment + fieldPaths: + - spec.servicesOverride + options: + create: true diff --git a/config/samples/dataplane/pre_ceph_hci/values.yaml b/config/samples/dataplane/pre_ceph_hci/values.yaml new file mode 100644 index 000000000..109b22d10 --- /dev/null +++ b/config/samples/dataplane/pre_ceph_hci/values.yaml @@ -0,0 +1,147 @@ +# local-config: referenced, but not emitted by kustomize +apiVersion: v1 +kind: DataPlaneConfig +metadata: + name: edpm-ceph-values + annotations: + config.kubernetes.io/local-config: "true" +data: + nodeset: + nodetemplate: + ansible: + vars: + timesync_ntp_servers: + - hostname: clock.redhat.com + # CHANGEME -- see https://access.redhat.com/solutions/253273 + # edpm_bootstrap_command: | + # subscription-manager register --username {{ subscription_manager_username }} --password {{ subscription_manager_password }} + # podman login -u {{ registry_username }} -p {{ registry_password }} registry.redhat.io + edpm_iscsid_image: '{{ registry_url }}/openstack-iscsid:{{ image_tag }}' + edpm_logrotate_crond_image: '{{ registry_url }}/openstack-cron:{{ image_tag }}' + edpm_network_config_hide_sensitive_logs: false + edpm_network_config_os_net_config_mappings: + edpm-compute: + nic2: enp7s0 + edpm_network_config_template: | + --- + {% set mtu_list = [ctlplane_mtu] %} + {% for network in nodeset_networks %} + {{ mtu_list.append(lookup('vars', networks_lower[network] ~ '_mtu')) }} + {%- endfor %} + {% set min_viable_mtu = mtu_list | max %} + network_config: + - type: ovs_bridge + name: {{ neutron_physical_bridge_name }} + mtu: {{ min_viable_mtu }} + use_dhcp: false + dns_servers: {{ ctlplane_dns_nameservers }} + domain: {{ dns_search_domains }} + addresses: + - ip_netmask: {{ ctlplane_ip }}/{{ ctlplane_cidr }} + routes: {{ ctlplane_host_routes }} + members: + - type: interface + name: nic2 + mtu: {{ min_viable_mtu }} + # force the MAC address of the bridge to this interface + primary: true + {% for network in nodeset_networks %} + - type: vlan + mtu: {{ lookup('vars', networks_lower[network] ~ '_mtu') }} + vlan_id: {{ lookup('vars', networks_lower[network] ~ '_vlan_id') }} + addresses: + - ip_netmask: + {{ lookup('vars', networks_lower[network] ~ '_ip') }}/{{ lookup('vars', networks_lower[network] ~ '_cidr') }} + routes: {{ lookup('vars', networks_lower[network] ~ '_host_routes') }} + {% endfor %} + edpm_neutron_metadata_agent_image: '{{ registry_url }}/openstack-neutron-metadata-agent-ovn:{{ image_tag }}' + edpm_nodes_validation_validate_controllers_icmp: false + edpm_nodes_validation_validate_gateway_icmp: false + edpm_nova_compute_container_image: '{{ registry_url }}/openstack-nova-compute:{{ image_tag }}' + edpm_nova_libvirt_container_image: '{{ registry_url }}/openstack-nova-libvirt:{{ image_tag }}' + edpm_ovn_controller_agent_image: '{{ registry_url }}/openstack-ovn-controller:{{ image_tag }}' + edpm_selinux_mode: enforcing + edpm_sshd_allowed_ranges: + - 192.168.122.0/24 + edpm_sshd_configure_firewall: true + enable_debug: false + gather_facts: false + image_tag: current-podified + neutron_physical_bridge_name: br-ex + neutron_public_interface_name: eth0 + registry_url: quay.io/podified-antelope-centos9 + edpm_ceph_hci_pre_enabled_services: + - ceph_mon + - ceph_mgr + - ceph_osd + - ceph_rgw + - ceph_nfs + - ceph_rgw_frontend + - ceph_nfs_frontend + storage_mtu: 9000 + storage_mgmt_mtu: 9000 + storage_mgmt_vlan_id: 23 + storage_mgmt_cidr: "24" + storage_mgmt_host_routes: [] + nodes: + edpm-compute-0: + ansible: + ansibleHost: 192.168.122.100 + hostName: edpm-compute-0 + networks: + - defaultRoute: true + fixedIP: 192.168.122.100 + name: ctlplane + subnetName: subnet1 + - name: internalapi + subnetName: subnet1 + - name: storage + subnetName: subnet1 + - name: storagemgmt + subnetName: subnet1 + - name: tenant + subnetName: subnet1 + edpm-compute-1: + ansible: + ansibleHost: 192.168.122.101 + hostName: edpm-compute-1 + networks: + - defaultRoute: true + fixedIP: 192.168.122.101 + name: ctlplane + subnetName: subnet1 + - name: internalapi + subnetName: subnet1 + - name: storage + subnetName: subnet1 + - name: storagemgmt + subnetName: subnet1 + - name: tenant + subnetName: subnet1 + edpm-compute-2: + ansible: + ansibleHost: 192.168.122.102 + hostName: edpm-compute-2 + networks: + - defaultRoute: true + fixedIP: 192.168.122.102 + name: ctlplane + subnetName: subnet1 + - name: internalapi + subnetName: subnet1 + - name: storage + subnetName: subnet1 + - name: storagemgmt + subnetName: subnet1 + - name: tenant + subnetName: subnet1 + deployment: + services: + - bootstrap + - configure-network + - validate-network + - install-os + - ceph-hci-pre + - configure-os + - ssh-known-hosts + - run-os diff --git a/config/samples/dataplane/preprovisioned/kustomization.yaml b/config/samples/dataplane/preprovisioned/kustomization.yaml new file mode 100644 index 000000000..585eb787e --- /dev/null +++ b/config/samples/dataplane/preprovisioned/kustomization.yaml @@ -0,0 +1,8 @@ +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component + +namespace: openstack +nameSuffix: -ipam + +components: +- ../base diff --git a/config/samples/dataplane/sriov/kustomization.yaml b/config/samples/dataplane/sriov/kustomization.yaml new file mode 100644 index 000000000..02b1cec9b --- /dev/null +++ b/config/samples/dataplane/sriov/kustomization.yaml @@ -0,0 +1,43 @@ +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component + +namespace: openstack +nameSuffix: -sriov + +components: +- ../preprovisioned # for baremetal nodes, replace with baremetal + +resources: + - values.yaml + +replacements: +# OpenStackDataPlaneNodeSet customizations +- source: + kind: DataPlaneConfig + name: edpm-sriov-values + fieldPath: data.nodeset.services + targets: + - select: + kind: OpenStackDataPlaneNodeSet + fieldPaths: + - spec.services + options: + create: true + +patches: +- target: + kind: OpenStackDataPlaneNodeSet + name: .* + patch: |- + - op: copy + from: /spec/nodes/edpm-compute-0 + path: /spec/nodes/edpm-compute-1 + - op: replace + path: /spec/nodes/edpm-compute-1/ansible/ansibleHost + value: 192.168.122.101 + - op: replace + path: /spec/nodes/edpm-compute-1/hostName + value: edpm-compute-1 + - op: replace + path: /spec/nodes/edpm-compute-1/networks/0/fixedIP + value: 192.168.122.101 diff --git a/config/samples/dataplane/sriov/values.yaml b/config/samples/dataplane/sriov/values.yaml new file mode 100644 index 000000000..ed019a8b3 --- /dev/null +++ b/config/samples/dataplane/sriov/values.yaml @@ -0,0 +1,24 @@ +# local-config: referenced, but not emitted by kustomize +apiVersion: v1 +kind: DataPlaneConfig +metadata: + name: edpm-sriov-values + annotations: + config.kubernetes.io/local-config: "true" +data: + nodeset: + services: + - bootstrap + - download-cache + - configure-network + - validate-network + - install-os + - configure-os + - ssh-known-hosts + - run-os + - reboot-os + - install-certs + - neutron-sriov + - libvirt + - nova + - telemetry diff --git a/config/samples/dataplane/swift/kustomization.yaml b/config/samples/dataplane/swift/kustomization.yaml new file mode 100644 index 000000000..11e29d7de --- /dev/null +++ b/config/samples/dataplane/swift/kustomization.yaml @@ -0,0 +1,57 @@ +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component + +namespace: openstack + +components: +- ../preprovisioned # for baremetal nodes, replace with baremetal + +resources: + - values.yaml + +replacements: +# OpenStackDataPlaneNodeSet customizations +- source: + kind: DataPlaneConfig + name: edpm-swift-values + fieldPath: data.nodeset.nodetemplate.ansible.vars.edpm_swift_disks + targets: + - select: + kind: OpenStackDataPlaneNodeSet + fieldPaths: + - spec.nodeTemplate.ansible.ansibleVars.edpm_swift_disks + options: + create: true +- source: + kind: DataPlaneConfig + name: edpm-swift-values + fieldPath: data.nodeset.services + targets: + - select: + kind: OpenStackDataPlaneNodeSet + fieldPaths: + - spec.services + options: + create: true +- source: + kind: DataPlaneConfig + name: edpm-swift-values + fieldPath: data.nodeset.networkattachments + targets: + - select: + kind: OpenStackDataPlaneNodeSet + fieldPaths: + - spec.networkAttachments + options: + create: true +- source: + kind: DataPlaneConfig + name: edpm-swift-values + fieldPath: data.nodeset.nodes + targets: + - select: + kind: OpenStackDataPlaneNodeSet + fieldPaths: + - spec.nodes + options: + create: true diff --git a/config/samples/dataplane/swift/values.yaml b/config/samples/dataplane/swift/values.yaml new file mode 100644 index 000000000..960bf5ff7 --- /dev/null +++ b/config/samples/dataplane/swift/values.yaml @@ -0,0 +1,65 @@ +# local-config: referenced, but not emitted by kustomize +apiVersion: v1 +kind: DataPlaneConfig +metadata: + name: edpm-swift-values + annotations: + config.kubernetes.io/local-config: "true" +data: + nodeset: + networkattachments: + - ctlplane + - storage + nodetemplate: + ansible: + vars: + # Swift disks defined here apply to all nodes. Node-specific disks + # might be defined in the nodes: section below + # + # weight, region and zone are not used in the playbook, but + # in swift-operator itself to determine Swift ring values. weight + # should be usually set to the GiB of the disk; region and + # zone are optional and might be used to enforce distribution of + # replicas + edpm_swift_disks: + - device: /dev/vdb + path: /srv/node/vdb + weight: 4000 + region: 0 + zone: 0 + nodes: + edpm-swift-0: + ansible: + ansibleHost: 192.168.122.100 + ansibleVars: + # Same options as above for all nodes, this time for an individual + # node with a different disk. This node will use only vdc. It would + # use vdb from parent section if not defined + edpm_swift_disks: + - device: /dev/vdc + path: /srv/node/vdc + weight: 1000 + hostName: edpm-swift-0 + networks: + - defaultRoute: true + fixedIP: 192.168.122.100 + name: ctlplane + subnetName: subnet1 + - name: internalapi + subnetName: subnet1 + - name: storage + subnetName: subnet1 + - name: tenant + subnetName: subnet1 + services: + - bootstrap + - download-cache + - configure-network + - validate-network + - install-os + - configure-os + - ssh-known-hosts + - run-os + - reboot-os + - install-certs + - swift diff --git a/config/samples/dataplane_v1beta1_openstackdataplanedeployment.yaml b/config/samples/dataplane_v1beta1_openstackdataplanedeployment.yaml new file mode 100644 index 000000000..211f503db --- /dev/null +++ b/config/samples/dataplane_v1beta1_openstackdataplanedeployment.yaml @@ -0,0 +1,7 @@ +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneDeployment +metadata: + name: edpm-deployment +spec: + nodeSets: + - openstack-edpm diff --git a/config/samples/dataplane_v1beta1_openstackdataplanenodeset.yaml b/config/samples/dataplane_v1beta1_openstackdataplanenodeset.yaml new file mode 100644 index 000000000..ad35b6390 --- /dev/null +++ b/config/samples/dataplane_v1beta1_openstackdataplanenodeset.yaml @@ -0,0 +1,72 @@ +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneNodeSet +metadata: + name: openstack-edpm +spec: + tlsEnabled: true + env: + - name: ANSIBLE_FORCE_COLOR + value: "True" + services: + - bootstrap + - download-cache + - configure-network + - validate-network + - install-os + - configure-os + - ssh-known-hosts + - run-os + - reboot-os + - install-certs + - ovn + - neutron-metadata + - libvirt + - nova + - telemetry + preProvisioned: true + networkAttachments: + - ctlplane + nodes: + edpm-compute-0: + hostName: edpm-compute-0 + ansible: + ansibleHost: 192.168.122.100 + networks: + - name: ctlplane + subnetName: subnet1 + defaultRoute: true + fixedIP: 192.168.122.100 + - name: internalapi + subnetName: subnet1 + - name: storage + subnetName: subnet1 + - name: tenant + subnetName: subnet1 + nodeTemplate: + ansibleSSHPrivateKeySecret: dataplane-ansible-ssh-private-key-secret + ansible: + ansibleVarsFrom: + - prefix: edpm_ + configMapRef: + name: network-config-template + - prefix: neutron_ + configMapRef: + name: neutron-edpm + # CHANGEME -- see https://access.redhat.com/solutions/253273 + # - prefix: subscription_manager_ + # secretRef: + # name: subscription-manager + # - prefix: registry_ + # secretRef: + # name: redhat-registry + ansibleVars: + # CHANGEME -- see https://access.redhat.com/solutions/253273 + # edpm_bootstrap_command: | + # subscription-manager register --username {{ subscription_manager_username }} --password {{ subscription_manager_password }} + # podman login -u {{ registry_username }} -p {{ registry_password }} registry.redhat.io + edpm_nodes_validation_validate_controllers_icmp: false + edpm_nodes_validation_validate_gateway_icmp: false + gather_facts: false + enable_debug: false + # edpm firewall, change the allowed CIDR if needed + edpm_sshd_allowed_ranges: ['192.168.122.0/24'] diff --git a/config/samples/dataplane_v1beta1_openstackdataplaneservice.yaml b/config/samples/dataplane_v1beta1_openstackdataplaneservice.yaml new file mode 100644 index 000000000..13f953e4e --- /dev/null +++ b/config/samples/dataplane_v1beta1_openstackdataplaneservice.yaml @@ -0,0 +1,12 @@ +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + labels: + app.kubernetes.io/name: openstackdataplaneservice + app.kubernetes.io/instance: openstackdataplaneservice-sample + app.kubernetes.io/part-of: dataplane-operator + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: dataplane-operator + name: openstackdataplaneservice-sample +spec: + # TODO(user): Add fields here diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index 2b198bae2..75adeda52 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -5,4 +5,7 @@ resources: - core_v1beta1_openstackcontrolplane_network_isolation.yaml - client_v1beta1_openstackclient.yaml - core_v1beta1_openstackversion.yaml +- dataplane_v1beta1_openstackdataplanenodeset.yaml +- dataplane_v1beta1_openstackdataplaneservice.yaml +- dataplane_v1beta1_openstackdataplanedeployment.yaml #+kubebuilder:scaffold:manifestskustomizesamples diff --git a/config/services/dataplane_v1beta1_openstackdataplaneservice_bootstrap.yaml b/config/services/dataplane_v1beta1_openstackdataplaneservice_bootstrap.yaml new file mode 100644 index 000000000..bfd69dd31 --- /dev/null +++ b/config/services/dataplane_v1beta1_openstackdataplaneservice_bootstrap.yaml @@ -0,0 +1,12 @@ +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + labels: + app.kubernetes.io/name: openstackdataplaneservice + app.kubernetes.io/instance: openstackdataplaneservice-bootstrap + app.kubernetes.io/part-of: dataplane-operator + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: dataplane-operator + name: bootstrap +spec: + playbook: osp.edpm.bootstrap diff --git a/config/services/dataplane_v1beta1_openstackdataplaneservice_ceph_client.yaml b/config/services/dataplane_v1beta1_openstackdataplaneservice_ceph_client.yaml new file mode 100644 index 000000000..f79704a36 --- /dev/null +++ b/config/services/dataplane_v1beta1_openstackdataplaneservice_ceph_client.yaml @@ -0,0 +1,6 @@ +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + name: ceph-client +spec: + playbook: osp.edpm.ceph_client diff --git a/config/services/dataplane_v1beta1_openstackdataplaneservice_ceph_hci_pre.yaml b/config/services/dataplane_v1beta1_openstackdataplaneservice_ceph_hci_pre.yaml new file mode 100644 index 000000000..0d83e88e6 --- /dev/null +++ b/config/services/dataplane_v1beta1_openstackdataplaneservice_ceph_hci_pre.yaml @@ -0,0 +1,6 @@ +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + name: ceph-hci-pre +spec: + playbook: osp.edpm.ceph_hci_pre diff --git a/config/services/dataplane_v1beta1_openstackdataplaneservice_configure_network.yaml b/config/services/dataplane_v1beta1_openstackdataplaneservice_configure_network.yaml new file mode 100644 index 000000000..bb968d101 --- /dev/null +++ b/config/services/dataplane_v1beta1_openstackdataplaneservice_configure_network.yaml @@ -0,0 +1,12 @@ +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + labels: + app.kubernetes.io/name: openstackdataplaneservice + app.kubernetes.io/instance: openstackdataplaneservice-configure-network + app.kubernetes.io/part-of: dataplane-operator + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: dataplane-operator + name: configure-network +spec: + playbook: osp.edpm.configure_network diff --git a/config/services/dataplane_v1beta1_openstackdataplaneservice_configure_os.yaml b/config/services/dataplane_v1beta1_openstackdataplaneservice_configure_os.yaml new file mode 100644 index 000000000..2e475ddcc --- /dev/null +++ b/config/services/dataplane_v1beta1_openstackdataplaneservice_configure_os.yaml @@ -0,0 +1,6 @@ +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + name: configure-os +spec: + playbook: osp.edpm.configure_os diff --git a/config/services/dataplane_v1beta1_openstackdataplaneservice_configure_ovs_dpdk.yaml b/config/services/dataplane_v1beta1_openstackdataplaneservice_configure_ovs_dpdk.yaml new file mode 100644 index 000000000..77988fd8d --- /dev/null +++ b/config/services/dataplane_v1beta1_openstackdataplaneservice_configure_ovs_dpdk.yaml @@ -0,0 +1,12 @@ +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + labels: + app.kubernetes.io/name: openstackdataplaneservice + app.kubernetes.io/instance: openstackdataplaneservice-configure-ovs-dpdk + app.kubernetes.io/part-of: dataplane-operator + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: dataplane-operator + name: configure-ovs-dpdk +spec: + playbook: osp.edpm.configure_ovs_dpdk diff --git a/config/services/dataplane_v1beta1_openstackdataplaneservice_ddp_package_option.yaml b/config/services/dataplane_v1beta1_openstackdataplaneservice_ddp_package_option.yaml new file mode 100644 index 000000000..9ca47eb83 --- /dev/null +++ b/config/services/dataplane_v1beta1_openstackdataplaneservice_ddp_package_option.yaml @@ -0,0 +1,6 @@ +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + name: ddp-package-option +spec: + playbook: osp.edpm.select_kernel_ddp_package diff --git a/config/services/dataplane_v1beta1_openstackdataplaneservice_derive_pci_devicespec.yaml b/config/services/dataplane_v1beta1_openstackdataplaneservice_derive_pci_devicespec.yaml new file mode 100644 index 000000000..86e1b9ebd --- /dev/null +++ b/config/services/dataplane_v1beta1_openstackdataplaneservice_derive_pci_devicespec.yaml @@ -0,0 +1,12 @@ +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + labels: + app.kubernetes.io/name: openstackdataplaneservice + app.kubernetes.io/instance: openstackdataplaneservice-derive-pci-devicespec + app.kubernetes.io/part-of: dataplane-operator + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: dataplane-operator + name: derive-pci-devicespec +spec: + playbook: osp.edpm.sriov_derive_device_spec diff --git a/config/services/dataplane_v1beta1_openstackdataplaneservice_download_cache.yaml b/config/services/dataplane_v1beta1_openstackdataplaneservice_download_cache.yaml new file mode 100644 index 000000000..c480703b9 --- /dev/null +++ b/config/services/dataplane_v1beta1_openstackdataplaneservice_download_cache.yaml @@ -0,0 +1,12 @@ +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + labels: + app.kubernetes.io/name: openstackdataplaneservice + app.kubernetes.io/instance: openstackdataplaneservice-download-cache + app.kubernetes.io/part-of: dataplane-operator + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: dataplane-operator + name: download-cache +spec: + playbook: osp.edpm.download_cache diff --git a/config/services/dataplane_v1beta1_openstackdataplaneservice_fips_status.yaml b/config/services/dataplane_v1beta1_openstackdataplaneservice_fips_status.yaml new file mode 100644 index 000000000..b98890661 --- /dev/null +++ b/config/services/dataplane_v1beta1_openstackdataplaneservice_fips_status.yaml @@ -0,0 +1,7 @@ +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + name: fips-status +spec: + label: fips-status + playbook: osp.edpm.fips_status diff --git a/config/services/dataplane_v1beta1_openstackdataplaneservice_frr.yaml b/config/services/dataplane_v1beta1_openstackdataplaneservice_frr.yaml new file mode 100644 index 000000000..0561424fd --- /dev/null +++ b/config/services/dataplane_v1beta1_openstackdataplaneservice_frr.yaml @@ -0,0 +1,8 @@ +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + name: frr +spec: + playbook: osp.edpm.frr + containerImageFields: + - EdpmFrrImage diff --git a/config/services/dataplane_v1beta1_openstackdataplaneservice_install_certs.yaml b/config/services/dataplane_v1beta1_openstackdataplaneservice_install_certs.yaml new file mode 100644 index 000000000..45654d467 --- /dev/null +++ b/config/services/dataplane_v1beta1_openstackdataplaneservice_install_certs.yaml @@ -0,0 +1,13 @@ +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + labels: + app.kubernetes.io/name: openstackdataplaneservice + app.kubernetes.io/instance: openstackdataplaneservice-install-certs + app.kubernetes.io/part-of: dataplane-operator + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: dataplane-operator + name: install-certs +spec: + playbook: osp.edpm.install_certs + addCertMounts: True diff --git a/config/services/dataplane_v1beta1_openstackdataplaneservice_install_os.yaml b/config/services/dataplane_v1beta1_openstackdataplaneservice_install_os.yaml new file mode 100644 index 000000000..ff9bb9072 --- /dev/null +++ b/config/services/dataplane_v1beta1_openstackdataplaneservice_install_os.yaml @@ -0,0 +1,6 @@ +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + name: install-os +spec: + playbook: osp.edpm.install_os diff --git a/config/services/dataplane_v1beta1_openstackdataplaneservice_libvirt.yaml b/config/services/dataplane_v1beta1_openstackdataplaneservice_libvirt.yaml new file mode 100644 index 000000000..82b70cc50 --- /dev/null +++ b/config/services/dataplane_v1beta1_openstackdataplaneservice_libvirt.yaml @@ -0,0 +1,23 @@ +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + name: libvirt +spec: + playbook: osp.edpm.libvirt + secrets: + # NOTE: this Secret needs to be created before deploying the data plane. + # It should contain the libvirt sasl auth password using the key LibvirtPassword + - libvirt-secret + tlsCert: + contents: + - dnsnames + - ips + networks: + - ctlplane + keyUsages: + - digital signature + - key encipherment + - server auth + - client auth + issuer: osp-rootca-issuer-internal + caCerts: combined-ca-bundle diff --git a/config/services/dataplane_v1beta1_openstackdataplaneservice_logging.yaml b/config/services/dataplane_v1beta1_openstackdataplaneservice_logging.yaml new file mode 100644 index 000000000..2d5a5dac6 --- /dev/null +++ b/config/services/dataplane_v1beta1_openstackdataplaneservice_logging.yaml @@ -0,0 +1,8 @@ +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + name: logging +spec: + secrets: + - logging-compute-config-data + playbook: osp.edpm.telemetry_logging diff --git a/config/services/dataplane_v1beta1_openstackdataplaneservice_neutron_dhcp.yaml b/config/services/dataplane_v1beta1_openstackdataplaneservice_neutron_dhcp.yaml new file mode 100644 index 000000000..b0b342100 --- /dev/null +++ b/config/services/dataplane_v1beta1_openstackdataplaneservice_neutron_dhcp.yaml @@ -0,0 +1,8 @@ +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + name: neutron-dhcp +spec: + playbook: osp.edpm.neutron_dhcp + secrets: + - neutron-dhcp-agent-neutron-config diff --git a/config/services/dataplane_v1beta1_openstackdataplaneservice_neutron_metadata.yaml b/config/services/dataplane_v1beta1_openstackdataplaneservice_neutron_metadata.yaml new file mode 100644 index 000000000..662f03b35 --- /dev/null +++ b/config/services/dataplane_v1beta1_openstackdataplaneservice_neutron_metadata.yaml @@ -0,0 +1,23 @@ +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + name: neutron-metadata +spec: + playbook: osp.edpm.neutron_metadata + secrets: + - neutron-ovn-metadata-agent-neutron-config + - nova-metadata-neutron-config + tlsCert: + contents: + - dnsnames + - ips + networks: + - ctlplane + issuer: osp-rootca-issuer-ovn + keyUsages: + - digital signature + - key encipherment + - client auth + caCerts: combined-ca-bundle + containerImageFields: + - EdpmNeutronMetadataAgentImage diff --git a/config/services/dataplane_v1beta1_openstackdataplaneservice_neutron_ovn.yaml b/config/services/dataplane_v1beta1_openstackdataplaneservice_neutron_ovn.yaml new file mode 100644 index 000000000..5aa134b3d --- /dev/null +++ b/config/services/dataplane_v1beta1_openstackdataplaneservice_neutron_ovn.yaml @@ -0,0 +1,8 @@ +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + name: neutron-ovn +spec: + playbook: osp.edpm.neutron_ovn + secrets: + - neutron-ovn-agent-neutron-config diff --git a/config/services/dataplane_v1beta1_openstackdataplaneservice_neutron_sriov.yaml b/config/services/dataplane_v1beta1_openstackdataplaneservice_neutron_sriov.yaml new file mode 100644 index 000000000..56fc2c196 --- /dev/null +++ b/config/services/dataplane_v1beta1_openstackdataplaneservice_neutron_sriov.yaml @@ -0,0 +1,11 @@ +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + name: neutron-sriov +spec: + playbook: osp.edpm.neutron_sriov + secrets: + - neutron-sriov-agent-neutron-config + caCerts: combined-ca-bundle + containerImageFields: + - EdpmSriovAgentImage diff --git a/config/services/dataplane_v1beta1_openstackdataplaneservice_nova.yaml b/config/services/dataplane_v1beta1_openstackdataplaneservice_nova.yaml new file mode 100644 index 000000000..257fa5833 --- /dev/null +++ b/config/services/dataplane_v1beta1_openstackdataplaneservice_nova.yaml @@ -0,0 +1,16 @@ +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + name: nova +spec: + secrets: + - nova-cell1-compute-config + # NOTE: this Secret needs to be created before deploying the data plane. + # It should contain an ssh key-pair in the secret fields: ssh-privatekey + # and ssh-publickey + - nova-migration-ssh-key + playbook: osp.edpm.nova + caCerts: combined-ca-bundle + containerImageFields: + - NovaComputeImage + - EdpmIscsidImage diff --git a/config/services/dataplane_v1beta1_openstackdataplaneservice_os_reboot.yaml b/config/services/dataplane_v1beta1_openstackdataplaneservice_os_reboot.yaml new file mode 100644 index 000000000..fa64fe152 --- /dev/null +++ b/config/services/dataplane_v1beta1_openstackdataplaneservice_os_reboot.yaml @@ -0,0 +1,6 @@ +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + name: reboot-os +spec: + playbook: osp.edpm.reboot diff --git a/config/services/dataplane_v1beta1_openstackdataplaneservice_ovn.yaml b/config/services/dataplane_v1beta1_openstackdataplaneservice_ovn.yaml new file mode 100644 index 000000000..1816a3852 --- /dev/null +++ b/config/services/dataplane_v1beta1_openstackdataplaneservice_ovn.yaml @@ -0,0 +1,23 @@ +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + name: ovn +spec: + playbook: osp.edpm.ovn + configMaps: + - ovncontroller-config + tlsCert: + contents: + - dnsnames + - ips + networks: + - ctlplane + issuer: osp-rootca-issuer-ovn + keyUsages: + - digital signature + - key encipherment + - server auth + - client auth + caCerts: combined-ca-bundle + containerImageFields: + - OvnControllerImage diff --git a/config/services/dataplane_v1beta1_openstackdataplaneservice_ovn_bgp_agent.yaml b/config/services/dataplane_v1beta1_openstackdataplaneservice_ovn_bgp_agent.yaml new file mode 100644 index 000000000..e0da29eec --- /dev/null +++ b/config/services/dataplane_v1beta1_openstackdataplaneservice_ovn_bgp_agent.yaml @@ -0,0 +1,23 @@ +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + name: ovn-bgp-agent +spec: + playbook: osp.edpm.ovn_bgp_agent + secrets: + - neutron-ovn-agent-neutron-config + tlsCert: + contents: + - dnsnames + - ips + networks: + - ctlplane + issuer: osp-rootca-issuer-ovn + keyUsages: + - digital signature + - key encipherment + - server auth + - client auth + caCerts: combined-ca-bundle + containerImageFields: + - EdpmOvnBpgAgentImage diff --git a/config/services/dataplane_v1beta1_openstackdataplaneservice_run_os.yaml b/config/services/dataplane_v1beta1_openstackdataplaneservice_run_os.yaml new file mode 100644 index 000000000..1a8362fe3 --- /dev/null +++ b/config/services/dataplane_v1beta1_openstackdataplaneservice_run_os.yaml @@ -0,0 +1,9 @@ +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + name: run-os +spec: + playbook: osp.edpm.run_os + containerImageFields: + - EdpmLogrotateCrondImage + - EdpmIscsidImage diff --git a/config/services/dataplane_v1beta1_openstackdataplaneservice_ssh_known_hosts.yaml b/config/services/dataplane_v1beta1_openstackdataplaneservice_ssh_known_hosts.yaml new file mode 100644 index 000000000..7181a98ad --- /dev/null +++ b/config/services/dataplane_v1beta1_openstackdataplaneservice_ssh_known_hosts.yaml @@ -0,0 +1,7 @@ +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + name: ssh-known-hosts +spec: + playbook: osp.edpm.ssh_known_hosts + deployOnAllNodeSets: true diff --git a/config/services/dataplane_v1beta1_openstackdataplaneservice_swift.yaml b/config/services/dataplane_v1beta1_openstackdataplaneservice_swift.yaml new file mode 100644 index 000000000..c8ebf2448 --- /dev/null +++ b/config/services/dataplane_v1beta1_openstackdataplaneservice_swift.yaml @@ -0,0 +1,11 @@ +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + name: swift +spec: + playbook: osp.edpm.swift + secrets: + - swift-conf + configMaps: + - swift-storage-config-data + - swift-ring-files diff --git a/config/services/dataplane_v1beta1_openstackdataplaneservice_telemetry.yaml b/config/services/dataplane_v1beta1_openstackdataplaneservice_telemetry.yaml new file mode 100644 index 000000000..f43e4ec87 --- /dev/null +++ b/config/services/dataplane_v1beta1_openstackdataplaneservice_telemetry.yaml @@ -0,0 +1,16 @@ +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + name: telemetry +spec: + secrets: + - ceilometer-compute-config-data + playbook: osp.edpm.telemetry + tlsCert: + contents: + - ips + caCerts: combined-ca-bundle + containerImageFields: + - CeilometerComputeImage + - CeilometerIpmiImage + - EdpmNodeExporterImage diff --git a/config/services/dataplane_v1beta1_openstackdataplaneservice_validate_network.yaml b/config/services/dataplane_v1beta1_openstackdataplaneservice_validate_network.yaml new file mode 100644 index 000000000..723d59f55 --- /dev/null +++ b/config/services/dataplane_v1beta1_openstackdataplaneservice_validate_network.yaml @@ -0,0 +1,12 @@ +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + labels: + app.kubernetes.io/name: openstackdataplaneservice + app.kubernetes.io/instance: openstackdataplaneservice-validate-network + app.kubernetes.io/part-of: dataplane-operator + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: dataplane-operator + name: validate-network +spec: + playbook: osp.edpm.validate_network diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml index b5d33b665..98d40b57b 100644 --- a/config/webhook/manifests.yaml +++ b/config/webhook/manifests.yaml @@ -65,6 +65,66 @@ webhooks: resources: - openstackversions sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /mutate-dataplane-openstack-org-v1beta1-openstackdataplanedeployment + failurePolicy: Fail + name: mopenstackdataplanedeployment.kb.io + rules: + - apiGroups: + - dataplane.openstack.org + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - openstackdataplanedeployments + sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /mutate-dataplane-openstack-org-v1beta1-openstackdataplanenodeset + failurePolicy: Fail + name: mopenstackdataplanenodeset.kb.io + rules: + - apiGroups: + - dataplane.openstack.org + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - openstackdataplanenodesets + sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /mutate-dataplane-openstack-org-v1beta1-openstackdataplaneservice + failurePolicy: Fail + name: mopenstackdataplaneservice.kb.io + rules: + - apiGroups: + - dataplane.openstack.org + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - openstackdataplaneservices + sideEffects: None --- apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration @@ -132,3 +192,63 @@ webhooks: resources: - openstackversions sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /validate-dataplane-openstack-org-v1beta1-openstackdataplanedeployment + failurePolicy: Fail + name: vopenstackdataplanedeployment.kb.io + rules: + - apiGroups: + - dataplane.openstack.org + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - openstackdataplanedeployments + sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /validate-dataplane-openstack-org-v1beta1-openstackdataplanenodeset + failurePolicy: Fail + name: vopenstackdataplanenodeset.kb.io + rules: + - apiGroups: + - dataplane.openstack.org + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - openstackdataplanenodesets + sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /validate-dataplane-openstack-org-v1beta1-openstackdataplaneservice + failurePolicy: Fail + name: vopenstackdataplaneservice.kb.io + rules: + - apiGroups: + - dataplane.openstack.org + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - openstackdataplaneservices + sideEffects: None diff --git a/controllers/dataplane/openstackdataplanedeployment_controller.go b/controllers/dataplane/openstackdataplanedeployment_controller.go new file mode 100644 index 000000000..2e87baa19 --- /dev/null +++ b/controllers/dataplane/openstackdataplanedeployment_controller.go @@ -0,0 +1,435 @@ +/* +Copyright 2023. + +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 dataplane + +import ( + "context" + "fmt" + "time" + + "github.com/go-playground/validator/v10" + corev1 "k8s.io/api/core/v1" + k8s_errors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + "github.com/go-logr/logr" + condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + "github.com/openstack-k8s-operators/lib-common/modules/common/helper" + "github.com/openstack-k8s-operators/lib-common/modules/common/util" + ansibleeev1 "github.com/openstack-k8s-operators/openstack-ansibleee-operator/api/v1beta1" + dataplanev1 "github.com/openstack-k8s-operators/openstack-operator/apis/dataplane/v1beta1" + "github.com/openstack-k8s-operators/openstack-operator/pkg/deployment" + dataplaneutil "github.com/openstack-k8s-operators/openstack-operator/pkg/util" +) + +// OpenStackDataPlaneDeploymentReconciler reconciles a OpenStackDataPlaneDeployment object +type OpenStackDataPlaneDeploymentReconciler struct { + client.Client + Kclient kubernetes.Interface + Scheme *runtime.Scheme +} + +// GetLogger returns a logger object with a prefix of "controller.name" and additional controller context fields +func (r *OpenStackDataPlaneDeploymentReconciler) GetLogger(ctx context.Context) logr.Logger { + return log.FromContext(ctx).WithName("Controllers").WithName("OpenStackDataPlaneDeployment") +} + +//+kubebuilder:rbac:groups=dataplane.openstack.org,resources=openstackdataplanedeployments,verbs=get;list;watch;create;delete +//+kubebuilder:rbac:groups=dataplane.openstack.org,resources=openstackdataplanedeployments/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=dataplane.openstack.org,resources=openstackdataplanedeployments/finalizers,verbs=update +//+kubebuilder:rbac:groups=dataplane.openstack.org,resources=openstackdataplanenodesets,verbs=get;list;watch +//+kubebuilder:rbac:groups=dataplane.openstack.org,resources=openstackdataplaneservices,verbs=get;list;watch +//+kubebuilder:rbac:groups=ansibleee.openstack.org,resources=openstackansibleees,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=discovery.k8s.io,resources=endpointslices,verbs=get;list;watch;create;update;patch;delete; +//+kubebuilder:rbac:groups=cert-manager.io,resources=issuers,verbs=get;list;watch; +//+kubebuilder:rbac:groups=cert-manager.io,resources=certificates,verbs=get;list;watch;create;update;patch;delete; + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +func (r *OpenStackDataPlaneDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, _err error) { + + Log := r.GetLogger(ctx) + Log.Info("Reconciling Deployment") + + // Check if deployment name matches RFC1123 for use in labels + validate := validator.New() + if err := validate.Var(req.Name, "hostname_rfc1123"); err != nil { + Log.Error(err, "error validating OpenStackDataPlaneDeployment name, the name must follow RFC1123") + return ctrl.Result{}, err + } + // Fetch the OpenStackDataPlaneDeployment instance + instance := &dataplanev1.OpenStackDataPlaneDeployment{} + err := r.Client.Get(ctx, req.NamespacedName, instance) + if err != nil { + if k8s_errors.IsNotFound(err) { + // Request object not found, could have been deleted after reconcile request. + // Owned objects are automatically garbage collected. + // For additional cleanup logic use finalizers. Return and don't requeue. + return ctrl.Result{}, nil + } + // Error reading the object - requeue the request. + return ctrl.Result{}, err + } + + helper, _ := helper.NewHelper( + instance, + r.Client, + r.Kclient, + r.Scheme, + Log, + ) + + // If the deploy is already done, return immediately. + if instance.Status.Deployed { + Log.Info("Already deployed", "instance.Status.Deployed", instance.Status.Deployed) + return ctrl.Result{}, nil + } + + // initialize status if Conditions is nil, but do not reset if it already + // exists + isNewInstance := instance.Status.Conditions == nil + if isNewInstance { + instance.Status.Conditions = condition.Conditions{} + } + + // Save a copy of the conditions so that we can restore the LastTransitionTime + // when a condition's state doesn't change. + savedConditions := instance.Status.Conditions.DeepCopy() + + // Reset all conditions to Unknown as the state is not yet known for + // this reconcile loop. + instance.InitConditions() + // Set ObservedGeneration since we've reset conditions + instance.Status.ObservedGeneration = instance.Generation + + // Always patch the instance status when exiting this function so we can persist any changes. + defer func() { // update the Ready condition based on the sub conditions + condition.RestoreLastTransitionTimes( + &instance.Status.Conditions, savedConditions) + if instance.Status.Conditions.AllSubConditionIsTrue() { + instance.Status.Conditions.MarkTrue( + condition.ReadyCondition, condition.ReadyMessage) + } else if instance.Status.Conditions.IsUnknown(condition.ReadyCondition) { + // Recalculate ReadyCondition based on the state of the rest of the conditions + instance.Status.Conditions.Set( + instance.Status.Conditions.Mirror(condition.ReadyCondition)) + } + + err := helper.PatchInstance(ctx, instance) + if err != nil { + Log.Error(err, "Error updating instance status conditions") + _err = err + return + } + }() + + if instance.Status.ConfigMapHashes == nil { + instance.Status.ConfigMapHashes = make(map[string]string) + } + if instance.Status.SecretHashes == nil { + instance.Status.SecretHashes = make(map[string]string) + } + if instance.Status.NodeSetHashes == nil { + instance.Status.NodeSetHashes = make(map[string]string) + } + if instance.Status.ContainerImages == nil { + instance.Status.ContainerImages = make(map[string]string) + } + + // Ensure NodeSets + nodeSets := dataplanev1.OpenStackDataPlaneNodeSetList{} + for _, nodeSet := range instance.Spec.NodeSets { + + // Fetch the OpenStackDataPlaneNodeSet instance + nodeSetInstance := &dataplanev1.OpenStackDataPlaneNodeSet{} + err := r.Client.Get( + ctx, + types.NamespacedName{ + Namespace: instance.GetNamespace(), + Name: nodeSet, + }, + nodeSetInstance) + if err != nil { + // NodeSet not found, force a requeue + if k8s_errors.IsNotFound(err) { + Log.Info("NodeSet not found", "NodeSet", nodeSet) + return ctrl.Result{RequeueAfter: time.Second * time.Duration(instance.Spec.DeploymentRequeueTime)}, nil + } + instance.Status.Conditions.MarkFalse( + dataplanev1.SetupReadyCondition, + condition.ErrorReason, + condition.SeverityError, + dataplanev1.DataPlaneNodeSetErrorMessage, + err.Error()) + // Error reading the object - requeue the request. + return ctrl.Result{}, err + } + nodeSets.Items = append(nodeSets.Items, *nodeSetInstance) + } + + // Check that all nodeSets are SetupReady + for _, nodeSet := range nodeSets.Items { + if !nodeSet.Status.Conditions.IsTrue(dataplanev1.SetupReadyCondition) { + Log.Info("NodeSet SetupReadyCondition is not True", "NodeSet", nodeSet.Name) + return ctrl.Result{RequeueAfter: time.Second * time.Duration(instance.Spec.DeploymentRequeueTime)}, nil + } + } + + // get TLS certs + for _, nodeSet := range nodeSets.Items { + if nodeSet.Spec.TLSEnabled { + var services []string + if len(instance.Spec.ServicesOverride) != 0 { + services = instance.Spec.ServicesOverride + } else { + services = nodeSet.Spec.Services + } + + for _, serviceName := range services { + service, err := deployment.GetService(ctx, helper, serviceName) + if err != nil { + instance.Status.Conditions.MarkFalse( + condition.InputReadyCondition, + condition.ErrorReason, + condition.SeverityError, + dataplanev1.ServiceErrorMessage, + err.Error()) + return ctrl.Result{}, err + } + if service.Spec.TLSCert != nil { + result, err := deployment.EnsureTLSCerts(ctx, helper, &nodeSet, + nodeSet.Status.AllHostnames, nodeSet.Status.AllIPs, service) + if err != nil { + instance.Status.Conditions.MarkFalse( + condition.InputReadyCondition, + condition.ErrorReason, + condition.SeverityError, + condition.TLSInputErrorMessage, + err.Error()) + return ctrl.Result{}, err + } else if (*result != ctrl.Result{}) { + return *result, nil // requeue here + } + } + } + } + } + + // All nodeSets successfully fetched. + // Mark InputReadyCondition=True + instance.Status.Conditions.MarkTrue(condition.InputReadyCondition, condition.ReadyMessage) + shouldRequeue := false + haveError := false + deploymentErrMsg := "" + + globalInventorySecrets := map[string]string{} + globalSSHKeySecrets := map[string]string{} + + // Gathering individual inventory and ssh secrets for later use + for _, nodeSet := range nodeSets.Items { + // Add inventory secret to list of inventories for global services + globalInventorySecrets[nodeSet.Name] = fmt.Sprintf("dataplanenodeset-%s", nodeSet.Name) + globalSSHKeySecrets[nodeSet.Name] = nodeSet.Spec.NodeTemplate.AnsibleSSHPrivateKeySecret + } + + if instance.Spec.ServicesOverride == nil { + if err := deployment.CheckGlobalServiceExecutionConsistency(ctx, helper, nodeSets.Items); err != nil { + util.LogErrorForObject(helper, err, "OpenStackDeployment error for deployment", instance) + instance.Status.Conditions.MarkFalse( + condition.DeploymentReadyCondition, + condition.ErrorReason, + condition.SeverityError, + dataplanev1.ServiceErrorMessage, + err.Error()) + return ctrl.Result{}, err + } + + } + + version, err := dataplaneutil.GetVersion(ctx, helper, instance.Namespace) + if err != nil { + return ctrl.Result{}, err + } + + // Deploy each nodeSet + // The loop starts and checks NodeSet deployments sequentially. However, after they + // are started, they are running in parallel, since the loop does not wait + // for the first started NodeSet to finish before starting the next. + for _, nodeSet := range nodeSets.Items { + + Log.Info(fmt.Sprintf("Deploying NodeSet: %s", nodeSet.Name)) + Log.Info("Set Status.Deployed to false", "instance", instance) + instance.Status.Deployed = false + Log.Info("Set DeploymentReadyCondition false") + instance.Status.Conditions.MarkFalse( + condition.DeploymentReadyCondition, condition.RequestedReason, + condition.SeverityInfo, condition.DeploymentReadyRunningMessage) + ansibleEESpec := nodeSet.GetAnsibleEESpec() + ansibleEESpec.AnsibleTags = instance.Spec.AnsibleTags + ansibleEESpec.AnsibleSkipTags = instance.Spec.AnsibleSkipTags + ansibleEESpec.AnsibleLimit = instance.Spec.AnsibleLimit + ansibleEESpec.ExtraVars = instance.Spec.AnsibleExtraVars + + if nodeSet.Status.DNSClusterAddresses != nil && nodeSet.Status.CtlplaneSearchDomain != "" { + ansibleEESpec.DNSConfig = &corev1.PodDNSConfig{ + Nameservers: nodeSet.Status.DNSClusterAddresses, + Searches: []string{nodeSet.Status.CtlplaneSearchDomain}, + } + } + + deployer := deployment.Deployer{ + Ctx: ctx, + Helper: helper, + NodeSet: &nodeSet, + Deployment: instance, + Status: &instance.Status, + AeeSpec: &ansibleEESpec, + InventorySecrets: globalInventorySecrets, + AnsibleSSHPrivateKeySecrets: globalSSHKeySecrets, + Version: version, + } + + // When ServicesOverride is set on the OpenStackDataPlaneDeployment, + // deploy those services for each OpenStackDataPlaneNodeSet. Otherwise, + // deploy with the OpenStackDataPlaneNodeSet's Services. + var deployResult *ctrl.Result + if len(instance.Spec.ServicesOverride) != 0 { + deployResult, err = deployer.Deploy(instance.Spec.ServicesOverride) + } else { + deployResult, err = deployer.Deploy(nodeSet.Spec.Services) + } + + nsConditions := instance.Status.NodeSetConditions[nodeSet.Name] + + if err != nil { + util.LogErrorForObject(helper, err, fmt.Sprintf("OpenStackDeployment error for NodeSet %s", nodeSet.Name), instance) + Log.Info("Set NodeSetDeploymentReadyCondition false", "nodeSet", nodeSet.Name) + haveError = true + errMsg := fmt.Sprintf("nodeSet: %s error: %s", nodeSet.Name, err.Error()) + if len(deploymentErrMsg) == 0 { + deploymentErrMsg = errMsg + } else { + deploymentErrMsg = fmt.Sprintf("%s & %s", deploymentErrMsg, errMsg) + } + nsConditions.MarkFalse( + dataplanev1.NodeSetDeploymentReadyCondition, + condition.ErrorReason, + condition.SeverityError, + condition.DeploymentReadyErrorMessage, + err.Error()) + } + + if deployResult != nil { + shouldRequeue = true + } else { + Log.Info("OpenStackDeployment succeeded for NodeSet", "NodeSet", nodeSet.Name) + Log.Info("Set NodeSetDeploymentReadyCondition true", "nodeSet", nodeSet.Name) + nsConditions.MarkTrue( + dataplanev1.NodeSetDeploymentReadyCondition, + condition.DeploymentReadyMessage) + } + } + + if haveError { + instance.Status.Conditions.MarkFalse( + condition.DeploymentReadyCondition, + condition.ErrorReason, + condition.SeverityError, + condition.DeploymentReadyErrorMessage, + deploymentErrMsg) + return ctrl.Result{}, fmt.Errorf(deploymentErrMsg) + } + + if shouldRequeue { + Log.Info("Not all NodeSets done for OpenStackDeployment") + return ctrl.Result{}, nil + } + + Log.Info("Set DeploymentReadyCondition true") + instance.Status.Conditions.MarkTrue(condition.DeploymentReadyCondition, condition.DeploymentReadyMessage) + instance.Status.Deployed = true + if version != nil { + instance.Status.DeployedVersion = version.Spec.TargetVersion + } + err = r.setHashes(ctx, helper, instance, nodeSets) + if err != nil { + Log.Error(err, "Error setting service hashes") + } + Log.Info("Set status deploy true", "instance", instance) + return ctrl.Result{}, nil +} + +func (r *OpenStackDataPlaneDeploymentReconciler) setHashes( + ctx context.Context, + helper *helper.Helper, + instance *dataplanev1.OpenStackDataPlaneDeployment, + nodeSets dataplanev1.OpenStackDataPlaneNodeSetList, +) error { + + var err error + services := []string{} + + if len(instance.Spec.ServicesOverride) > 0 { + services = instance.Spec.ServicesOverride + } else { + // get the union of services across nodesets + type void struct{} + var member void + s := make(map[string]void) + for _, nodeSet := range nodeSets.Items { + for _, serviceName := range nodeSet.Spec.Services { + s[serviceName] = member + } + } + for service := range s { + services = append(services, service) + } + } + + for _, serviceName := range services { + err = deployment.GetDeploymentHashesForService( + ctx, + helper, + instance.Namespace, + serviceName, + instance.Status.ConfigMapHashes, + instance.Status.SecretHashes, + nodeSets) + if err != nil { + return err + } + } + + for _, nodeSet := range nodeSets.Items { + instance.Status.NodeSetHashes[nodeSet.Name] = nodeSet.Status.ConfigHash + } + + return nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *OpenStackDataPlaneDeploymentReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&dataplanev1.OpenStackDataPlaneDeployment{}). + Owns(&ansibleeev1.OpenStackAnsibleEE{}). + Complete(r) +} diff --git a/controllers/dataplane/openstackdataplanenodeset_controller.go b/controllers/dataplane/openstackdataplanenodeset_controller.go new file mode 100644 index 000000000..84b4ddcac --- /dev/null +++ b/controllers/dataplane/openstackdataplanenodeset_controller.go @@ -0,0 +1,682 @@ +/* +Copyright 2023. + +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 dataplane + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/go-playground/validator/v10" + "golang.org/x/exp/slices" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + k8s_errors "k8s.io/apimachinery/pkg/api/errors" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/go-logr/logr" + infranetworkv1 "github.com/openstack-k8s-operators/infra-operator/apis/network/v1beta1" + condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + "github.com/openstack-k8s-operators/lib-common/modules/common/helper" + "github.com/openstack-k8s-operators/lib-common/modules/common/rolebinding" + "github.com/openstack-k8s-operators/lib-common/modules/common/secret" + "github.com/openstack-k8s-operators/lib-common/modules/common/serviceaccount" + "github.com/openstack-k8s-operators/lib-common/modules/common/util" + ansibleeev1 "github.com/openstack-k8s-operators/openstack-ansibleee-operator/api/v1beta1" + baremetalv1 "github.com/openstack-k8s-operators/openstack-baremetal-operator/api/v1beta1" + openstackv1 "github.com/openstack-k8s-operators/openstack-operator/apis/core/v1beta1" + dataplanev1 "github.com/openstack-k8s-operators/openstack-operator/apis/dataplane/v1beta1" + "github.com/openstack-k8s-operators/openstack-operator/pkg/deployment" + dataplaneutil "github.com/openstack-k8s-operators/openstack-operator/pkg/util" +) + +const ( + // AnsibleSSHPrivateKey ssh private key + AnsibleSSHPrivateKey = "ssh-privatekey" + // AnsibleSSHAuthorizedKeys authorized keys + AnsibleSSHAuthorizedKeys = "authorized_keys" +) + +// OpenStackDataPlaneNodeSetReconciler reconciles a OpenStackDataPlaneNodeSet object +type OpenStackDataPlaneNodeSetReconciler struct { + client.Client + Kclient kubernetes.Interface + Scheme *runtime.Scheme +} + +// GetLogger returns a logger object with a prefix of "controller.name" and additional controller context fields +func (r *OpenStackDataPlaneNodeSetReconciler) GetLogger(ctx context.Context) logr.Logger { + return log.FromContext(ctx).WithName("Controllers").WithName("OpenStackDataPlaneNodeSet") +} + +//+kubebuilder:rbac:groups=dataplane.openstack.org,resources=openstackdataplanenodesets,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=dataplane.openstack.org,resources=openstackdataplanenodesets/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=dataplane.openstack.org,resources=openstackdataplanenodesets/finalizers,verbs=update +//+kubebuilder:rbac:groups=dataplane.openstack.org,resources=openstackdataplaneservices,verbs=get;list;watch;create;update;patch +//+kubebuilder:rbac:groups=dataplane.openstack.org,resources=openstackdataplaneservices/finalizers,verbs=update +//+kubebuilder:rbac:groups=baremetal.openstack.org,resources=openstackbaremetalsets,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=baremetal.openstack.org,resources=openstackbaremetalsets/status,verbs=get +//+kubebuilder:rbac:groups=baremetal.openstack.org,resources=openstackbaremetalsets/finalizers,verbs=update +//+kubebuilder:rbac:groups=core,resources=configmaps,verbs=get;list;watch;create;update;patch;delete; +//+kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;watch;create;update;patch;delete; +//+kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;watch;create;update;patch;delete; +//+kubebuilder:rbac:groups=k8s.cni.cncf.io,resources=network-attachment-definitions,verbs=get;list;watch +//+kubebuilder:rbac:groups=network.openstack.org,resources=ipsets,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=network.openstack.org,resources=ipsets/status,verbs=get +//+kubebuilder:rbac:groups=network.openstack.org,resources=ipsets/finalizers,verbs=update +//+kubebuilder:rbac:groups=network.openstack.org,resources=netconfigs,verbs=get;list;watch +//+kubebuilder:rbac:groups=network.openstack.org,resources=dnsmasqs,verbs=get;list;watch +//+kubebuilder:rbac:groups=network.openstack.org,resources=dnsmasqs/status,verbs=get +//+kubebuilder:rbac:groups=network.openstack.org,resources=dnsdata,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=network.openstack.org,resources=dnsdata/status,verbs=get +//+kubebuilder:rbac:groups=network.openstack.org,resources=dnsdata/finalizers,verbs=update +//+kubebuilder:rbac:groups=core,resources=services,verbs=get;list;watch;create;update;patch;delete; +//+kubebuilder:rbac:groups=core.openstack.org,resources=openstackversions,verbs=get;list;watch + +// RBAC for the ServiceAccount for the internal image registry +//+kubebuilder:rbac:groups="",resources=serviceaccounts,verbs=get;list;watch;create;update +//+kubebuilder:rbac:groups="rbac.authorization.k8s.io",resources=roles,verbs=get;list;watch;create;update +//+kubebuilder:rbac:groups="rbac.authorization.k8s.io",resources=rolebindings,verbs=get;list;watch;create;update;patch +//+kubebuilder:rbac:groups="security.openshift.io",resourceNames=anyuid,resources=securitycontextconstraints,verbs=use +//+kubebuilder:rbac:groups="",resources=pods,verbs=create;delete;get;list;patch;update;watch +//+kubebuilder:rbac:groups="",resources=projects,verbs=get +//+kubebuilder:rbac:groups="project.openshift.io",resources=projects,verbs=get +//+kubebuilder:rbac:groups="",resources=imagestreamimages,verbs=get;list;watch +//+kubebuilder:rbac:groups="",resources=imagestreammappings,verbs=get;list;watch +//+kubebuilder:rbac:groups="",resources=imagestreams,verbs=get;list;watch +//+kubebuilder:rbac:groups="",resources=imagestreams/layers,verbs=get;list;watch +//+kubebuilder:rbac:groups="",resources=imagestreamtags,verbs=get;list;watch +//+kubebuilder:rbac:groups="",resources=imagetags,verbs=get;list;watch +//+kubebuilder:rbac:groups="image.openshift.io",resources=imagestreamimages,verbs=get;list;watch +//+kubebuilder:rbac:groups="image.openshift.io",resources=imagestreammappings,verbs=get;list;watch +//+kubebuilder:rbac:groups="image.openshift.io",resources=imagestreams,verbs=get;list;watch +//+kubebuilder:rbac:groups="image.openshift.io",resources=imagestreams/layers,verbs=get +//+kubebuilder:rbac:groups="image.openshift.io",resources=imagetags,verbs=get;list;watch +//+kubebuilder:rbac:groups="image.openshift.io",resources=imagestreamtags,verbs=get;list;watch + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the OpenStackDataPlaneNodeSet object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.12.2/pkg/reconcile +func (r *OpenStackDataPlaneNodeSetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, _err error) { + Log := r.GetLogger(ctx) + Log.Info("Reconciling NodeSet") + + validate := validator.New() + + // Fetch the OpenStackDataPlaneNodeSet instance + instance := &dataplanev1.OpenStackDataPlaneNodeSet{} + err := r.Client.Get(ctx, req.NamespacedName, instance) + if err != nil { + if k8s_errors.IsNotFound(err) { + // Request object not found, could have been deleted after reconcile request. + // Owned objects are automatically garbage collected. + // For additional cleanup logic use finalizers. Return and don't requeue. + return ctrl.Result{}, nil + } + // Error reading the object - requeue the request. + return ctrl.Result{}, err + } + + helper, _ := helper.NewHelper( + instance, + r.Client, + r.Kclient, + r.Scheme, + Log, + ) + + // initialize status if Conditions is nil, but do not reset if it already + // exists + isNewInstance := instance.Status.Conditions == nil + if isNewInstance { + instance.Status.Conditions = condition.Conditions{} + } + + // Save a copy of the conditions so that we can restore the LastTransitionTime + // when a condition's state doesn't change. + savedConditions := instance.Status.Conditions.DeepCopy() + + // Reset all conditions to Unknown as the state is not yet known for + // this reconcile loop. + instance.InitConditions() + // Set ObservedGeneration since we've reset conditions + instance.Status.ObservedGeneration = instance.Generation + + // Always patch the instance status when exiting this function so we can persist any changes. + defer func() { // update the Ready condition based on the sub conditions + condition.RestoreLastTransitionTimes( + &instance.Status.Conditions, savedConditions) + if instance.Status.Conditions.AllSubConditionIsTrue() { + instance.Status.Conditions.MarkTrue( + condition.ReadyCondition, dataplanev1.NodeSetReadyMessage) + } else if instance.Status.Conditions.IsUnknown(condition.ReadyCondition) { + // Recalculate ReadyCondition based on the state of the rest of the conditions + instance.Status.Conditions.Set( + instance.Status.Conditions.Mirror(condition.ReadyCondition)) + } + + err := helper.PatchInstance(ctx, instance) + if err != nil { + Log.Error(err, "Error updating instance status conditions") + _err = err + return + } + }() + + if instance.Status.ConfigMapHashes == nil { + instance.Status.ConfigMapHashes = make(map[string]string) + } + if instance.Status.SecretHashes == nil { + instance.Status.SecretHashes = make(map[string]string) + } + if instance.Status.ContainerImages == nil { + instance.Status.ContainerImages = make(map[string]string) + } + + instance.Status.Conditions.MarkFalse(dataplanev1.SetupReadyCondition, condition.RequestedReason, condition.SeverityInfo, condition.ReadyInitMessage) + + // Detect config changes and set Status ConfigHash + configHash, err := r.GetSpecConfigHash(instance) + if err != nil { + return ctrl.Result{}, err + } + + if configHash != instance.Status.DeployedConfigHash { + instance.Status.ConfigHash = configHash + } + + // Ensure Services + err = deployment.EnsureServices(ctx, helper, instance, validate) + if err != nil { + instance.Status.Conditions.MarkFalse( + dataplanev1.SetupReadyCondition, + condition.ErrorReason, + condition.SeverityError, + dataplanev1.DataPlaneNodeSetErrorMessage, + err.Error()) + return ctrl.Result{}, err + } + + // Ensure IPSets Required for Nodes + allIPSets, isReady, err := deployment.EnsureIPSets(ctx, helper, instance) + if err != nil || !isReady { + return ctrl.Result{}, err + } + + // Ensure DNSData Required for Nodes + dnsData := deployment.DataplaneDNSData{} + err = dnsData.EnsureDNSData( + ctx, helper, + instance, allIPSets) + if err != nil || !isReady { + return ctrl.Result{}, err + } + + instance.Status.DNSClusterAddresses = dnsData.ClusterAddresses + instance.Status.CtlplaneSearchDomain = dnsData.CtlplaneSearchDomain + instance.Status.AllHostnames = dnsData.Hostnames + instance.Status.AllIPs = dnsData.AllIPs + + ansibleSSHPrivateKeySecret := instance.Spec.NodeTemplate.AnsibleSSHPrivateKeySecret + + secretKeys := []string{} + secretKeys = append(secretKeys, AnsibleSSHPrivateKey) + if !instance.Spec.PreProvisioned { + secretKeys = append(secretKeys, AnsibleSSHAuthorizedKeys) + } + _, result, err = secret.VerifySecret( + ctx, + types.NamespacedName{ + Namespace: instance.Namespace, + Name: ansibleSSHPrivateKeySecret, + }, + secretKeys, + helper.GetClient(), + time.Second*5, + ) + if err != nil { + if (result != ctrl.Result{}) { + instance.Status.Conditions.MarkFalse( + condition.InputReadyCondition, + condition.RequestedReason, + condition.SeverityInfo, + dataplanev1.InputReadyWaitingMessage, + "secret/"+ansibleSSHPrivateKeySecret) + } else { + instance.Status.Conditions.MarkFalse( + condition.InputReadyCondition, + condition.RequestedReason, + condition.SeverityError, + err.Error()) + } + return result, err + } + + // all our input checks out so report InputReady + instance.Status.Conditions.MarkTrue(condition.InputReadyCondition, condition.InputReadyMessage) + + // Reconcile ServiceAccount + nodeSetServiceAccount := serviceaccount.NewServiceAccount( + &corev1.ServiceAccount{ + ObjectMeta: v1.ObjectMeta{ + Namespace: instance.Namespace, + Name: instance.Name, + }, + }, + time.Duration(10), + ) + saResult, err := nodeSetServiceAccount.CreateOrPatch(ctx, helper) + if err != nil { + instance.Status.Conditions.MarkFalse( + condition.ServiceAccountReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.ServiceAccountReadyErrorMessage, + err.Error()) + return saResult, err + } else if (saResult != ctrl.Result{}) { + instance.Status.Conditions.MarkFalse( + condition.ServiceAccountReadyCondition, + condition.RequestedReason, + condition.SeverityInfo, + condition.ServiceAccountCreatingMessage) + return saResult, nil + } + + regViewerRoleBinding := rolebinding.NewRoleBinding( + &rbacv1.RoleBinding{ + ObjectMeta: v1.ObjectMeta{ + Namespace: instance.Namespace, + Name: instance.Name, + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: instance.Name, + Namespace: instance.Namespace, + }, + }, + RoleRef: rbacv1.RoleRef{ + Kind: "ClusterRole", + Name: "registry-viewer", + }, + }, + time.Duration(10), + ) + rbResult, err := regViewerRoleBinding.CreateOrPatch(ctx, helper) + if err != nil { + instance.Status.Conditions.MarkFalse( + condition.ServiceAccountReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.ServiceAccountReadyErrorMessage, + err.Error()) + return rbResult, err + } else if (rbResult != ctrl.Result{}) { + instance.Status.Conditions.MarkFalse( + condition.ServiceAccountReadyCondition, + condition.RequestedReason, + condition.SeverityInfo, + condition.ServiceAccountCreatingMessage) + return rbResult, nil + } + + instance.Status.Conditions.MarkTrue( + condition.ServiceAccountReadyCondition, + condition.ServiceAccountReadyMessage) + + // Reconcile BaremetalSet if required + if !instance.Spec.PreProvisioned { + // Reset the NodeSetBareMetalProvisionReadyCondition to unknown + instance.Status.Conditions.MarkUnknown(dataplanev1.NodeSetBareMetalProvisionReadyCondition, + condition.InitReason, condition.InitReason) + isReady, err := deployment.DeployBaremetalSet(ctx, helper, instance, + allIPSets, dnsData.ServerAddresses) + if err != nil || !isReady { + return ctrl.Result{}, err + } + } + + if instance.Status.Deployed && instance.DeletionTimestamp.IsZero() { + // The NodeSet is already deployed and not being deleted, so reconciliation + // is already complete. + Log.Info("NodeSet already deployed", "instance", instance) + return ctrl.Result{}, nil + } + + // Generate NodeSet Inventory + version, err := dataplaneutil.GetVersion(ctx, helper, instance.Namespace) + if err != nil { + return ctrl.Result{}, err + } + containerImages := dataplaneutil.GetContainerImages(version) + _, err = deployment.GenerateNodeSetInventory(ctx, helper, instance, + allIPSets, dnsData.ServerAddresses, containerImages) + if err != nil { + errorMsg := fmt.Sprintf("Unable to generate inventory for %s", instance.Name) + util.LogErrorForObject(helper, err, errorMsg, instance) + instance.Status.Conditions.MarkFalse( + dataplanev1.SetupReadyCondition, + condition.ErrorReason, + condition.SeverityError, + dataplanev1.DataPlaneNodeSetErrorMessage, + errorMsg) + return ctrl.Result{}, err + } + + // all setup tasks complete, mark SetupReadyCondition True + instance.Status.Conditions.MarkTrue(dataplanev1.SetupReadyCondition, condition.ReadyMessage) + + // Set DeploymentReadyCondition to False if it was unknown. + // Handles the case where the NodeSet is created, but not yet deployed. + if instance.Status.Conditions.IsUnknown(condition.DeploymentReadyCondition) { + Log.Info("Set DeploymentReadyCondition false") + instance.Status.Conditions.MarkFalse(condition.DeploymentReadyCondition, + condition.NotRequestedReason, condition.SeverityInfo, + condition.DeploymentReadyInitMessage) + } + + deploymentExists, isDeploymentReady, err := checkDeployment(helper, instance) + if err != nil { + instance.Status.Conditions.MarkFalse( + condition.DeploymentReadyCondition, + condition.ErrorReason, + condition.SeverityError, + condition.DeploymentReadyErrorMessage, + err.Error()) + Log.Error(err, "Unable to get deployed OpenStackDataPlaneDeployments.") + return ctrl.Result{}, err + } + if isDeploymentReady { + Log.Info("Set NodeSet DeploymentReadyCondition true") + instance.Status.Conditions.MarkTrue(condition.DeploymentReadyCondition, + condition.DeploymentReadyMessage) + } else if deploymentExists { + Log.Info("Set NodeSet DeploymentReadyCondition false") + instance.Status.Conditions.MarkFalse(condition.DeploymentReadyCondition, + condition.RequestedReason, condition.SeverityInfo, + condition.DeploymentReadyRunningMessage) + } else { + Log.Info("Set NodeSet DeploymentReadyCondition false") + instance.Status.Conditions.MarkFalse(condition.DeploymentReadyCondition, + condition.RequestedReason, condition.SeverityInfo, + condition.DeploymentReadyInitMessage) + } + return ctrl.Result{}, nil +} + +func checkDeployment(helper *helper.Helper, + instance *dataplanev1.OpenStackDataPlaneNodeSet, +) (bool, bool, error) { + // Get all completed deployments + deployments := &dataplanev1.OpenStackDataPlaneDeploymentList{} + opts := []client.ListOption{ + client.InNamespace(instance.Namespace), + } + err := helper.GetClient().List(context.Background(), deployments, opts...) + if err != nil { + helper.GetLogger().Error(err, "Unable to retrieve OpenStackDataPlaneDeployment CRs %v") + return false, false, err + } + + isDeploymentReady := false + deploymentExists := false + + // Sort deployments from oldest to newest by the LastTransitionTime of + // their DeploymentReadyCondition + slices.SortFunc(deployments.Items, func(a, b dataplanev1.OpenStackDataPlaneDeployment) int { + aReady := a.Status.Conditions.Get(condition.DeploymentReadyCondition) + bReady := b.Status.Conditions.Get(condition.DeploymentReadyCondition) + if aReady != nil && bReady != nil { + if aReady.LastTransitionTime.Before(&bReady.LastTransitionTime) { + return -1 + } + } + return 1 + }) + + for _, deployment := range deployments.Items { + if !deployment.DeletionTimestamp.IsZero() { + continue + } + if slices.Contains( + deployment.Spec.NodeSets, instance.Name) { + deploymentExists = true + isDeploymentReady = false + if deployment.Status.Deployed { + isDeploymentReady = true + for k, v := range deployment.Status.ConfigMapHashes { + instance.Status.ConfigMapHashes[k] = v + } + for k, v := range deployment.Status.SecretHashes { + instance.Status.SecretHashes[k] = v + } + for k, v := range deployment.Status.ContainerImages { + instance.Status.ContainerImages[k] = v + } + instance.Status.DeployedConfigHash = deployment.Status.NodeSetHashes[instance.Name] + instance.Status.DeployedVersion = deployment.Status.DeployedVersion + } + deploymentConditions := deployment.Status.NodeSetConditions[instance.Name] + if instance.Status.DeploymentStatuses == nil { + instance.Status.DeploymentStatuses = make(map[string]condition.Conditions) + } + instance.Status.DeploymentStatuses[deployment.Name] = deploymentConditions + if condition.IsError(deployment.Status.Conditions.Get(condition.ReadyCondition)) { + err = fmt.Errorf("check deploymentStatuses for more details") + } + } + } + + return deploymentExists, isDeploymentReady, err +} + +// SetupWithManager sets up the controller with the Manager. +func (r *OpenStackDataPlaneNodeSetReconciler) SetupWithManager(mgr ctrl.Manager) error { + // index for ConfigMaps listed on ansibleVarsFrom + if err := mgr.GetFieldIndexer().IndexField(context.Background(), + &dataplanev1.OpenStackDataPlaneNodeSet{}, "spec.ansibleVarsFrom.ansible.configMaps", + func(rawObj client.Object) []string { + nodeSet := rawObj.(*dataplanev1.OpenStackDataPlaneNodeSet) + configMaps := make([]string, 0) + + appendConfigMaps := func(varsFrom []dataplanev1.AnsibleVarsFromSource) { + for _, ref := range varsFrom { + if ref.ConfigMapRef != nil { + configMaps = append(configMaps, ref.ConfigMapRef.Name) + } + } + } + + appendConfigMaps(nodeSet.Spec.NodeTemplate.Ansible.AnsibleVarsFrom) + for _, node := range nodeSet.Spec.Nodes { + appendConfigMaps(node.Ansible.AnsibleVarsFrom) + } + return configMaps + }); err != nil { + return err + } + + // index for Secrets listed on ansibleVarsFrom + if err := mgr.GetFieldIndexer().IndexField(context.Background(), + &dataplanev1.OpenStackDataPlaneNodeSet{}, "spec.ansibleVarsFrom.ansible.secrets", + func(rawObj client.Object) []string { + nodeSet := rawObj.(*dataplanev1.OpenStackDataPlaneNodeSet) + secrets := make([]string, 0, len(nodeSet.Spec.Nodes)+1) + if nodeSet.Spec.NodeTemplate.AnsibleSSHPrivateKeySecret != "" { + secrets = append(secrets, nodeSet.Spec.NodeTemplate.AnsibleSSHPrivateKeySecret) + } + + appendSecrets := func(varsFrom []dataplanev1.AnsibleVarsFromSource) { + for _, ref := range varsFrom { + if ref.SecretRef != nil { + secrets = append(secrets, ref.SecretRef.Name) + } + } + } + + appendSecrets(nodeSet.Spec.NodeTemplate.Ansible.AnsibleVarsFrom) + for _, node := range nodeSet.Spec.Nodes { + appendSecrets(node.Ansible.AnsibleVarsFrom) + } + return secrets + }); err != nil { + return err + } + return ctrl.NewControllerManagedBy(mgr). + For(&dataplanev1.OpenStackDataPlaneNodeSet{}). + Owns(&ansibleeev1.OpenStackAnsibleEE{}). + Owns(&baremetalv1.OpenStackBaremetalSet{}). + Owns(&infranetworkv1.IPSet{}). + Owns(&infranetworkv1.DNSData{}). + Owns(&corev1.Secret{}). + Watches(&infranetworkv1.DNSMasq{}, + handler.EnqueueRequestsFromMapFunc(r.genericWatcherFn)). + Watches(&dataplanev1.OpenStackDataPlaneDeployment{}, + handler.EnqueueRequestsFromMapFunc(r.deploymentWatcherFn)). + Watches(&corev1.ConfigMap{}, + handler.EnqueueRequestsFromMapFunc(r.secretWatcherFn), + builder.WithPredicates(predicate.ResourceVersionChangedPredicate{})). + Watches(&corev1.Secret{}, + handler.EnqueueRequestsFromMapFunc(r.secretWatcherFn), + builder.WithPredicates(predicate.ResourceVersionChangedPredicate{})). + Watches(&openstackv1.OpenStackVersion{}, + handler.EnqueueRequestsFromMapFunc(r.genericWatcherFn)). + Complete(r) +} + +func (r *OpenStackDataPlaneNodeSetReconciler) secretWatcherFn( + ctx context.Context, obj client.Object) []reconcile.Request { + Log := r.GetLogger(ctx) + nodeSets := &dataplanev1.OpenStackDataPlaneNodeSetList{} + kind := strings.ToLower(obj.GetObjectKind().GroupVersionKind().Kind) + + selector := "spec.ansibleVarsFrom.ansible.configMaps" + if kind == "secret" { + selector = "spec.ansibleVarsFrom.ansible.secrets" + } + + listOpts := &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(selector, obj.GetName()), + Namespace: obj.GetNamespace(), + } + + if err := r.List(ctx, nodeSets, listOpts); err != nil { + Log.Error(err, "Unable to retrieve OpenStackDataPlaneNodeSetList") + return nil + } + + requests := make([]reconcile.Request, 0, len(nodeSets.Items)) + for _, nodeSet := range nodeSets.Items { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: obj.GetNamespace(), + Name: nodeSet.Name, + }, + }) + Log.Info(fmt.Sprintf("reconcile loop for openstackdataplanenodeset %s triggered by %s %s", + nodeSet.Name, kind, obj.GetName())) + } + return requests +} + +func (r *OpenStackDataPlaneNodeSetReconciler) genericWatcherFn( + ctx context.Context, obj client.Object) []reconcile.Request { + Log := r.GetLogger(ctx) + nodeSets := &dataplanev1.OpenStackDataPlaneNodeSetList{} + + listOpts := []client.ListOption{ + client.InNamespace(obj.GetNamespace()), + } + if err := r.Client.List(ctx, nodeSets, listOpts...); err != nil { + Log.Error(err, "Unable to retrieve OpenStackDataPlaneNodeSetList") + return nil + } + + requests := make([]reconcile.Request, 0, len(nodeSets.Items)) + for _, nodeSet := range nodeSets.Items { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: obj.GetNamespace(), + Name: nodeSet.Name, + }, + }) + Log.Info(fmt.Sprintf("Reconciling NodeSet %s due to watcher on %s/%s", nodeSet.Name, obj.GetObjectKind().GroupVersionKind().Kind, obj.GetName())) + } + return requests +} + +func (r *OpenStackDataPlaneNodeSetReconciler) deploymentWatcherFn( + ctx context.Context, obj client.Object) []reconcile.Request { + Log := r.GetLogger(ctx) + namespace := obj.GetNamespace() + deployment := obj.(*dataplanev1.OpenStackDataPlaneDeployment) + + requests := make([]reconcile.Request, 0, len(deployment.Spec.NodeSets)) + for _, nodeSet := range deployment.Spec.NodeSets { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: namespace, + Name: nodeSet, + }, + }) + } + + podsInterface := r.Kclient.CoreV1().Pods(namespace) + podsList, err := podsInterface.List(ctx, v1.ListOptions{ + LabelSelector: fmt.Sprintf("openstackdataplanedeployment=%s", deployment.Name), + FieldSelector: "status.phase=Failed", + }) + + if err != nil { + Log.Error(err, "unable to retrieve list of pods for dataplane diagnostic") + } else { + for _, pod := range podsList.Items { + Log.Info(fmt.Sprintf("openstackansibleee job %s failed due to %s with message: %s", pod.Name, pod.Status.Reason, pod.Status.Message)) + } + } + return requests +} + +// GetSpecConfigHash initialises a new struct with only the field we want to check for variances in. +// We then hash the contents of the new struct using md5 and return the hashed string. +func (r *OpenStackDataPlaneNodeSetReconciler) GetSpecConfigHash(instance *dataplanev1.OpenStackDataPlaneNodeSet) (string, error) { + configHash, err := util.ObjectHash(&instance.Spec) + if err != nil { + return "", err + } + + return configHash, nil +} diff --git a/custom-bundle.Dockerfile b/custom-bundle.Dockerfile index f0123f920..7137fd025 100644 --- a/custom-bundle.Dockerfile +++ b/custom-bundle.Dockerfile @@ -31,14 +31,12 @@ USER root # local operator manifests COPY bundle/manifests /manifests/ COPY bundle_extra_data /bundle_extra_data -RUN cp -a /bundle_extra_data/manifests/* /manifests/ # Merge things into our openstack-operator CSV: # -dataplane-operator CSV # -ENV vars from all operators (for webhooks) RUN /workspace/csv-merger \ --import-env-files=/bundle_extra_data/env-vars.yaml \ - --dataplane-csv=/bundle_extra_data/manifests/dataplane-operator.clusterserviceversion.yaml \ --base-csv=/manifests/openstack-operator.clusterserviceversion.yaml | tee /openstack-operator.clusterserviceversion.yaml.new # remove all individual operator CSV's diff --git a/dataplane_v1beta1_openstackdataplanedeployment.yaml b/dataplane_v1beta1_openstackdataplanedeployment.yaml new file mode 100644 index 000000000..211f503db --- /dev/null +++ b/dataplane_v1beta1_openstackdataplanedeployment.yaml @@ -0,0 +1,7 @@ +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneDeployment +metadata: + name: edpm-deployment +spec: + nodeSets: + - openstack-edpm diff --git a/go.mod b/go.mod index 7134a0bf3..8852e81e0 100644 --- a/go.mod +++ b/go.mod @@ -7,14 +7,15 @@ require ( github.com/cert-manager/cert-manager v1.13.5 github.com/ghodss/yaml v1.0.0 github.com/go-logr/logr v1.4.1 + github.com/go-playground/validator/v10 v10.19.0 github.com/google/uuid v1.6.0 + github.com/iancoleman/strcase v0.3.0 github.com/imdario/mergo v0.3.16 github.com/onsi/ginkgo/v2 v2.17.1 github.com/onsi/gomega v1.32.0 github.com/openshift/api v3.9.0+incompatible github.com/openstack-k8s-operators/barbican-operator/api v0.0.0-20240401125932-8d6162aed60d github.com/openstack-k8s-operators/cinder-operator/api v0.3.1-0.20240401190259-4d30fdbf5531 - github.com/openstack-k8s-operators/dataplane-operator/api v0.3.1-0.20240417195048-0d9ca04c1482 github.com/openstack-k8s-operators/designate-operator/api v0.0.0-20240403153039-29d27af23767 github.com/openstack-k8s-operators/glance-operator/api v0.3.1-0.20240417165217-4dca406ca0a1 github.com/openstack-k8s-operators/heat-operator/api v0.3.1-0.20240416110211-aaba96840297 @@ -22,8 +23,10 @@ require ( github.com/openstack-k8s-operators/infra-operator/apis v0.3.1-0.20240417142035-3e555cf09907 github.com/openstack-k8s-operators/ironic-operator/api v0.3.1-0.20240408054123-cb7b79a22b47 github.com/openstack-k8s-operators/keystone-operator/api v0.3.1-0.20240417151252-c5ede1e3eb6f + github.com/openstack-k8s-operators/lib-common/modules/ansible v0.3.1-0.20240417144545-d24e7a32d33b github.com/openstack-k8s-operators/lib-common/modules/certmanager v0.0.0-20240417144545-d24e7a32d33b github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20240417144545-d24e7a32d33b + github.com/openstack-k8s-operators/lib-common/modules/storage v0.3.1-0.20240417144545-d24e7a32d33b github.com/openstack-k8s-operators/lib-common/modules/test v0.3.1-0.20240417144545-d24e7a32d33b github.com/openstack-k8s-operators/manila-operator/api v0.3.1-0.20240417164648-969367d7f690 github.com/openstack-k8s-operators/mariadb-operator/api v0.3.1-0.20240411135034-a77c10351c47 @@ -41,6 +44,7 @@ require ( github.com/rabbitmq/cluster-operator/v2 v2.6.0 go.uber.org/zap v1.27.0 golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 + gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.28.9 k8s.io/apimachinery v0.28.9 k8s.io/client-go v0.28.9 @@ -62,7 +66,6 @@ require ( github.com/go-openapi/swag v0.22.9 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.19.0 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect @@ -83,7 +86,6 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/openstack-k8s-operators/lib-common/modules/openstack v0.3.1-0.20240417144545-d24e7a32d33b // indirect - github.com/openstack-k8s-operators/lib-common/modules/storage v0.3.1-0.20240417144545-d24e7a32d33b // indirect github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.18.0 // indirect github.com/prometheus/client_model v0.5.0 // indirect @@ -109,7 +111,6 @@ require ( google.golang.org/protobuf v1.33.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiextensions-apiserver v0.28.9 // indirect k8s.io/component-base v0.28.9 // indirect k8s.io/klog/v2 v2.120.1 // indirect diff --git a/go.sum b/go.sum index bc4fea779..812a27c20 100644 --- a/go.sum +++ b/go.sum @@ -63,6 +63,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gophercloud/gophercloud v1.11.0 h1:ls0O747DIq1D8SUHc7r2vI8BFbMLeLFuENaAIfEx7OM= github.com/gophercloud/gophercloud v1.11.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= +github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -100,8 +102,6 @@ github.com/openstack-k8s-operators/barbican-operator/api v0.0.0-20240401125932-8 github.com/openstack-k8s-operators/barbican-operator/api v0.0.0-20240401125932-8d6162aed60d/go.mod h1:d4lFj3oT9ZReHGT/ngbF8ViVnv3vnHs/nemKVubkBGA= github.com/openstack-k8s-operators/cinder-operator/api v0.3.1-0.20240401190259-4d30fdbf5531 h1:91Mwjt0eGtisBOiiB8fbu1i6Ow1bzd55/f1y89z1BcU= github.com/openstack-k8s-operators/cinder-operator/api v0.3.1-0.20240401190259-4d30fdbf5531/go.mod h1:ayvrE0oMzyA/AQDKpCqNT9uupRT0TqrSFXb1sjmvWqE= -github.com/openstack-k8s-operators/dataplane-operator/api v0.3.1-0.20240417195048-0d9ca04c1482 h1:lJpBvL/1PxrTDF4uDZqEaXXn2n5Ih7B9GbttAQLeBNY= -github.com/openstack-k8s-operators/dataplane-operator/api v0.3.1-0.20240417195048-0d9ca04c1482/go.mod h1:yW5/qhS1P1dTbL0SyDCHD9u3SSwAZjqnTad/2vzmPsM= github.com/openstack-k8s-operators/designate-operator/api v0.0.0-20240403153039-29d27af23767 h1:He5McazPpzOM00VkSpwK85oUq5JMHdjT8o26HxwQamc= github.com/openstack-k8s-operators/designate-operator/api v0.0.0-20240403153039-29d27af23767/go.mod h1:SHv9v0wscyVv0yT3VD2UuPvw+kwRAEX/x/8fbnfZVpo= github.com/openstack-k8s-operators/glance-operator/api v0.3.1-0.20240417165217-4dca406ca0a1 h1:8l+XpmKE7Mzxpz6i3HH2aSoqZrMHsrj+80gYqIuo4tI= @@ -116,6 +116,8 @@ github.com/openstack-k8s-operators/ironic-operator/api v0.3.1-0.20240408054123-c github.com/openstack-k8s-operators/ironic-operator/api v0.3.1-0.20240408054123-cb7b79a22b47/go.mod h1:qu/Kuk0zZNbdyAPCF1m+Amp9mU37ol2LyB+1Rvws948= github.com/openstack-k8s-operators/keystone-operator/api v0.3.1-0.20240417151252-c5ede1e3eb6f h1:LYCcVaMMVtNiVCqyVeYJJ3jW8CfN6KBoE0PqQQf7AMk= github.com/openstack-k8s-operators/keystone-operator/api v0.3.1-0.20240417151252-c5ede1e3eb6f/go.mod h1:WTnBrcUDNSVF/5wxaAAXYjMXKBp5DdQ0tkGnrvk78fY= +github.com/openstack-k8s-operators/lib-common/modules/ansible v0.3.1-0.20240417144545-d24e7a32d33b h1:UmbP+q05y4wEV8nCqIJI0BVt03JFxrjzkVHk/9P3HQU= +github.com/openstack-k8s-operators/lib-common/modules/ansible v0.3.1-0.20240417144545-d24e7a32d33b/go.mod h1:tP+nxk95PisCKJaXE/an2igG9lluxuOVhdmV9WtkR2s= github.com/openstack-k8s-operators/lib-common/modules/certmanager v0.0.0-20240417144545-d24e7a32d33b h1:1oUektOUcnAvy5kj8C1owY34HhDR4mlCQAVvmGgUK1w= github.com/openstack-k8s-operators/lib-common/modules/certmanager v0.0.0-20240417144545-d24e7a32d33b/go.mod h1:JiHsE8g5qYKDkjCyp5QQHYVWEwreY133DBoIIBVnTSA= github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20240417144545-d24e7a32d33b h1:nswxmyTDWr4S3bmv1Rx032lAaI0TOFQvr07ieJULEbE= diff --git a/hack/bundle-cache-data.sh b/hack/bundle-cache-data.sh index fc4495110..ec06440c9 100755 --- a/hack/bundle-cache-data.sh +++ b/hack/bundle-cache-data.sh @@ -5,14 +5,6 @@ # -dataplane-operator bundle is cached (in order to merge at build time) set -ex -function extract_bundle { - local IN_DIR=$1 - local OUT_DIR=$2 - for X in $(file ${IN_DIR}/* | grep gzip | cut -f 1 -d ':'); do - tar xvf $X -C ${OUT_DIR}/; - done -} - function extract_csv { local IN_DIR=$1 local OUT_DIR=$2 @@ -34,11 +26,7 @@ mkdir -p "$OUT_BUNDLE" for BUNDLE in $(hack/pin-bundle-images.sh | tr "," " "); do skopeo copy "docker://$BUNDLE" dir:${EXTRACT_DIR}/tmp; - if echo $BUNDLE | grep dataplane-operator &> /dev/null; then - extract_bundle "${EXTRACT_DIR}/tmp" "${OUT_BUNDLE}/" - else - extract_csv "${EXTRACT_DIR}/tmp" "${EXTRACT_DIR}/csvs" - fi + extract_csv "${EXTRACT_DIR}/tmp" "${EXTRACT_DIR}/csvs" done # Extract the ENV vars from all the CSVs diff --git a/hack/clean_local_webhook.sh b/hack/clean_local_webhook.sh index 90d1a69a4..c083a42a7 100755 --- a/hack/clean_local_webhook.sh +++ b/hack/clean_local_webhook.sh @@ -5,3 +5,9 @@ oc delete validatingwebhookconfiguration vopenstackcontrolplane.kb.io --ignore-n oc delete mutatingwebhookconfiguration mopenstackcontrolplane.kb.io --ignore-not-found oc delete validatingwebhookconfiguration/vopenstackclient.kb.io --ignore-not-found oc delete mutatingwebhookconfiguration/mopenstackclient.kb.io --ignore-not-found +oc delete validatingwebhookconfiguration/vopenstackdataplanenodeset.kb.io --ignore-not-found +oc delete validatingwebhookconfiguration/vopenstackdataplanedeployment.kb.io --ignore-not-found +oc delete validatingwebhookconfiguration/vopenstackdataplaneservice.kb.io --ignore-not-found +oc delete mutatingwebhookconfiguration/mopenstackdataplanenodeset.kb.io --ignore-not-found +oc delete mutatingwebhookconfiguration/mopenstackdataplaneservice.kb.io --ignore-not-found +oc delete mutatingwebhookconfiguration/mopenstackdataplanedeployment.kb.io --ignore-not-found diff --git a/hack/configure_local_webhook.sh b/hack/configure_local_webhook.sh index 1ec23d127..7006dd6b2 100755 --- a/hack/configure_local_webhook.sh +++ b/hack/configure_local_webhook.sh @@ -140,6 +140,62 @@ webhooks: scope: '*' sideEffects: None timeoutSeconds: 10 +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: vopenstackdataplanenodeset.kb.io +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + caBundle: ${CA_BUNDLE} + url: https://${CRC_IP}:9443/validate-dataplane-openstack-org-v1beta1-openstackdataplanenodeset + failurePolicy: Fail + matchPolicy: Equivalent + name: vopenstackdataplanenodeset.kb.io + objectSelector: {} + rules: + - apiGroups: + - dataplane.openstack.org + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - openstackdataplanenodesets + scope: '*' + sideEffects: None + timeoutSeconds: 10 +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: mopenstackdataplanenodeset.kb.io +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + caBundle: ${CA_BUNDLE} + url: https://${CRC_IP}:9443/mutate-dataplane-openstack-org-v1beta1-openstackdataplanenodeset + failurePolicy: Fail + matchPolicy: Equivalent + name: mopenstackdataplanenodeset.kb.io + objectSelector: {} + rules: + - apiGroups: + - dataplane.openstack.org + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - openstackdataplanenodesets + scope: '*' + sideEffects: None + timeoutSeconds: 10 EOF_CAT oc apply -n openstack -f ${TMPDIR}/patch_webhook_configurations.yaml diff --git a/kuttl-test.yaml b/kuttl-test.yaml index 771ee66a7..f4a278505 100644 --- a/kuttl-test.yaml +++ b/kuttl-test.yaml @@ -16,6 +16,9 @@ apiVersion: kuttl.dev/v1beta1 kind: TestSuite +commands: + - command: oc apply -n openstack-kuttl-tests -f https://raw.githubusercontent.com/openstack-k8s-operators/infra-operator/main/config/samples/network_v1beta1_netconfig.yaml + - command: oc create secret generic dataplane-ansible-ssh-private-key-secret --from-literal=authorized_keys=testing --from-literal=ssh-privatekey=test_private_key --from-literal=ssh-publickey=test_private_key -n openstack-kuttl-tests reportFormat: JSON reportName: kuttl-test-openstack namespace: openstack-kuttl-tests diff --git a/main.go b/main.go index a9c34b62b..6bf5de357 100644 --- a/main.go +++ b/main.go @@ -32,7 +32,6 @@ import ( certmgrv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" barbicanv1 "github.com/openstack-k8s-operators/barbican-operator/api/v1beta1" cinderv1 "github.com/openstack-k8s-operators/cinder-operator/api/v1beta1" - dataplanev1beta1 "github.com/openstack-k8s-operators/dataplane-operator/api/v1beta1" designatev1 "github.com/openstack-k8s-operators/designate-operator/api/v1beta1" glancev1 "github.com/openstack-k8s-operators/glance-operator/api/v1beta1" heatv1 "github.com/openstack-k8s-operators/heat-operator/api/v1beta1" @@ -71,9 +70,11 @@ import ( clientv1 "github.com/openstack-k8s-operators/openstack-operator/apis/client/v1beta1" corev1 "github.com/openstack-k8s-operators/openstack-operator/apis/core/v1beta1" + dataplanev1 "github.com/openstack-k8s-operators/openstack-operator/apis/dataplane/v1beta1" clientcontrollers "github.com/openstack-k8s-operators/openstack-operator/controllers/client" corecontrollers "github.com/openstack-k8s-operators/openstack-operator/controllers/core" + dataplanecontrollers "github.com/openstack-k8s-operators/openstack-operator/controllers/dataplane" "github.com/openstack-k8s-operators/openstack-operator/pkg/openstack" //+kubebuilder:scaffold:imports ) @@ -86,6 +87,7 @@ var ( func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(corev1.AddToScheme(scheme)) + utilruntime.Must(dataplanev1.AddToScheme(scheme)) utilruntime.Must(keystonev1.AddToScheme(scheme)) utilruntime.Must(mariadbv1.AddToScheme(scheme)) utilruntime.Must(memcachedv1.AddToScheme(scheme)) @@ -101,7 +103,6 @@ func init() { utilruntime.Must(neutronv1.AddToScheme(scheme)) utilruntime.Must(octaviav1.AddToScheme(scheme)) utilruntime.Must(designatev1.AddToScheme(scheme)) - utilruntime.Must(dataplanev1beta1.AddToScheme(scheme)) utilruntime.Must(ansibleeev1.AddToScheme(scheme)) utilruntime.Must(rabbitmqv1.AddToScheme(scheme)) utilruntime.Must(manilav1.AddToScheme(scheme)) @@ -214,6 +215,24 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "OpenStackVersion") os.Exit(1) } + + if err = (&dataplanecontrollers.OpenStackDataPlaneNodeSetReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Kclient: kclient, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "OpenStackDataPlaneNodeSet") + os.Exit(1) + } + + if err = (&dataplanecontrollers.OpenStackDataPlaneDeploymentReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Kclient: kclient, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "OpenStackDataPlaneDeployment") + os.Exit(1) + } corecontrollers.SetupVersionDefaults() // Defaults for service operators @@ -222,6 +241,9 @@ func main() { // Defaults for OpenStackClient clientv1.SetupDefaults() + // Defaults for Dataplane + dataplanev1.SetupDefaults() + // Defaults for anything else that was not covered by OpenStackClient nor service operator defaults corev1.SetupDefaults() corev1.SetupVersionDefaults() @@ -242,6 +264,18 @@ func main() { setupLog.Error(err, "unable to create webhook", "webhook", "OpenStackVersion") os.Exit(1) } + if err = (&dataplanev1.OpenStackDataPlaneNodeSet{}).SetupWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "OpenStackDataPlaneNodeSet") + os.Exit(1) + } + if err = (&dataplanev1.OpenStackDataPlaneDeployment{}).SetupWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "OpenStackDataPlaneDeployment") + os.Exit(1) + } + if err = (&dataplanev1.OpenStackDataPlaneService{}).SetupWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "OpenStackDataPlaneService") + os.Exit(1) + } checker = mgr.GetWebhookServer().StartedChecker() } diff --git a/pkg/deployment/baremetal.go b/pkg/deployment/baremetal.go new file mode 100644 index 000000000..87ce2a465 --- /dev/null +++ b/pkg/deployment/baremetal.go @@ -0,0 +1,109 @@ +/* +Copyright 2023. + +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 deployment + +import ( + "context" + "fmt" + "net" + "strings" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + infranetworkv1 "github.com/openstack-k8s-operators/infra-operator/apis/network/v1beta1" + condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + "github.com/openstack-k8s-operators/lib-common/modules/common/helper" + utils "github.com/openstack-k8s-operators/lib-common/modules/common/util" + baremetalv1 "github.com/openstack-k8s-operators/openstack-baremetal-operator/api/v1beta1" + dataplanev1 "github.com/openstack-k8s-operators/openstack-operator/apis/dataplane/v1beta1" +) + +// DeployBaremetalSet Deploy OpenStackBaremetalSet +func DeployBaremetalSet( + ctx context.Context, helper *helper.Helper, instance *dataplanev1.OpenStackDataPlaneNodeSet, + ipSets map[string]infranetworkv1.IPSet, + dnsAddresses []string, +) (bool, error) { + baremetalSet := &baremetalv1.OpenStackBaremetalSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: instance.Name, + Namespace: instance.Namespace, + }, + } + + if instance.Spec.BaremetalSetTemplate.BaremetalHosts == nil { + return false, fmt.Errorf("no baremetal hosts set in baremetalSetTemplate") + } + utils.LogForObject(helper, "Reconciling BaremetalSet", instance) + _, err := controllerutil.CreateOrPatch(ctx, helper.GetClient(), baremetalSet, func() error { + instance.Spec.BaremetalSetTemplate.DeepCopyInto(&baremetalSet.Spec) + for _, node := range instance.Spec.Nodes { + hostName := node.HostName + ipSet, ok := ipSets[hostName] + instanceSpec := baremetalSet.Spec.BaremetalHosts[hostName] + if !ok { + // TODO: Change this to raise an error instead. + // NOTE(hjensas): Hardcode /24 here, this used to rely on + // baremetalSet.Spec.CtlplaneNetmask's default value ("255.255.255.0"). + utils.LogForObject(helper, "IPAM Not configured for use, skipping", instance) + instanceSpec.CtlPlaneIP = fmt.Sprintf("%s/24", node.Ansible.AnsibleHost) + } else { + for _, res := range ipSet.Status.Reservation { + if strings.ToLower(string(res.Network)) == CtlPlaneNetwork { + _, ipNet, err := net.ParseCIDR(res.Cidr) + if err != nil { + return err + } + ipPrefix, _ := ipNet.Mask.Size() + instanceSpec.CtlPlaneIP = fmt.Sprintf("%s/%d", res.Address, ipPrefix) + baremetalSet.Spec.CtlplaneGateway = *res.Gateway + baremetalSet.Spec.BootstrapDNS = dnsAddresses + baremetalSet.Spec.DNSSearchDomains = []string{res.DNSDomain} + } + } + } + baremetalSet.Spec.BaremetalHosts[hostName] = instanceSpec + + } + err := controllerutil.SetControllerReference( + helper.GetBeforeObject(), baremetalSet, helper.GetScheme()) + return err + }) + + if err != nil { + instance.Status.Conditions.MarkFalse( + dataplanev1.NodeSetBareMetalProvisionReadyCondition, + condition.ErrorReason, condition.SeverityError, + dataplanev1.NodeSetBaremetalProvisionErrorMessage) + return false, err + } + + // Check if baremetalSet is ready + if !baremetalSet.IsReady() { + utils.LogForObject(helper, "BaremetalSet not ready, waiting...", instance) + instance.Status.Conditions.MarkFalse( + dataplanev1.NodeSetBareMetalProvisionReadyCondition, + condition.RequestedReason, condition.SeverityInfo, + dataplanev1.NodeSetBaremetalProvisionReadyWaitingMessage) + return false, nil + } + instance.Status.Conditions.MarkTrue( + dataplanev1.NodeSetBareMetalProvisionReadyCondition, + dataplanev1.NodeSetBaremetalProvisionReadyMessage) + return true, nil +} diff --git a/pkg/deployment/cert.go b/pkg/deployment/cert.go new file mode 100644 index 000000000..a313aaabd --- /dev/null +++ b/pkg/deployment/cert.go @@ -0,0 +1,270 @@ +/* +Copyright 2023. + +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 deployment + +import ( + "context" + "fmt" + "sort" + "strconv" + "strings" + "time" + + "golang.org/x/exp/slices" + + corev1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + certmgrv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + infranetworkv1 "github.com/openstack-k8s-operators/infra-operator/apis/network/v1beta1" + "github.com/openstack-k8s-operators/lib-common/modules/certmanager" + "github.com/openstack-k8s-operators/lib-common/modules/common/helper" + "github.com/openstack-k8s-operators/lib-common/modules/common/secret" + dataplanev1 "github.com/openstack-k8s-operators/openstack-operator/apis/dataplane/v1beta1" +) + +// Generates an organized data structure that is leveraged to create the secrets. +func createSecretsDataStructure(secretMaxSize int, + certsData map[string][]byte, +) []map[string][]byte { + ci := []map[string][]byte{} + + keys := []string{} + for k := range certsData { + keys = append(keys, k) + } + sort.Strings(keys) + + totalSize := secretMaxSize + var cur *map[string][]byte + // Going 3 by 3 to include CA, crt and key, in the same secret. + for k := 0; k < len(keys)-1; k += 3 { + szCa := len(certsData[keys[k]]) + len(keys[k]) + szCrt := len(certsData[keys[k+1]]) + len(keys[k+1]) + szKey := len(certsData[keys[k+2]]) + len(keys[k+2]) + sz := szCa + szCrt + szKey + if (totalSize + sz) > secretMaxSize { + i := len(ci) + ci = append(ci, make(map[string][]byte)) + cur = &ci[i] + totalSize = 0 + } + totalSize += sz + (*cur)[keys[k]] = certsData[keys[k]] + (*cur)[keys[k+1]] = certsData[keys[k+1]] + (*cur)[keys[k+2]] = certsData[keys[k+2]] + } + + return ci +} + +// EnsureTLSCerts generates secrets containing all the certificates for the relevant service +// These secrets will be mounted by the ansibleEE pod as an extra mount when the service is deployed. +func EnsureTLSCerts(ctx context.Context, helper *helper.Helper, + instance *dataplanev1.OpenStackDataPlaneNodeSet, + allHostnames map[string]map[infranetworkv1.NetNameStr]string, + allIPs map[string]map[infranetworkv1.NetNameStr]string, + service dataplanev1.OpenStackDataPlaneService, +) (*ctrl.Result, error) { + certsData := map[string][]byte{} + secretMaxSize := instance.Spec.SecretMaxSize + + // for each node in the nodeset, issue all the TLS certs needed based on the + // ips or DNS Names + for nodeName, node := range instance.Spec.Nodes { + var dnsNames map[infranetworkv1.NetNameStr]string + var ipsMap map[infranetworkv1.NetNameStr]string + var hosts []string + var ips []string + var issuer *certmgrv1.Issuer + var issuerLabelSelector map[string]string + var certName string + var certSecret *corev1.Secret + var err error + var result ctrl.Result + + // TODO(alee) decide if we want to use other labels + // For now we just add the hostname so we can select all the certs on one node + hostName := node.HostName + labels := map[string]string{ + HostnameLabel: hostName, + ServiceLabel: service.Name, + NodeSetLabel: instance.Name, + } + certName = service.Name + "-" + hostName + + dnsNames = allHostnames[hostName] + ipsMap = allIPs[hostName] + + dnsNamesInCert := slices.Contains(service.Spec.TLSCert.Contents, DNSNamesStr) + ipValuesInCert := slices.Contains(service.Spec.TLSCert.Contents, IPValuesStr) + + // Create the hosts and ips lists + if dnsNamesInCert { + if len(service.Spec.TLSCert.Networks) == 0 { + hosts = make([]string, 0, len(dnsNames)) + for _, host := range dnsNames { + hosts = append(hosts, host) + } + } else { + hosts = make([]string, 0, len(service.Spec.TLSCert.Networks)) + for _, network := range service.Spec.TLSCert.Networks { + certNetwork := strings.ToLower(string(network)) + hosts = append(hosts, dnsNames[infranetworkv1.NetNameStr(certNetwork)]) + } + } + } + if ipValuesInCert { + if len(service.Spec.TLSCert.Networks) == 0 { + ips = make([]string, 0, len(ipsMap)) + for _, ip := range ipsMap { + ips = append(ips, ip) + } + } else { + ips = make([]string, 0, len(service.Spec.TLSCert.Networks)) + for _, network := range service.Spec.TLSCert.Networks { + certNetwork := strings.ToLower(string(network)) + ips = append(ips, ipsMap[infranetworkv1.NetNameStr(certNetwork)]) + } + } + } + + if service.Spec.TLSCert.Issuer == "" { + // by default, use the internal root CA + issuerLabelSelector = map[string]string{certmanager.RootCAIssuerInternalLabel: ""} + } else { + issuerLabelSelector = map[string]string{service.Spec.TLSCert.Issuer: ""} + } + + issuer, err = certmanager.GetIssuerByLabels(ctx, helper, instance.Namespace, issuerLabelSelector) + if err != nil { + helper.GetLogger().Info("Error retrieving issuer by label", "issuerLabelSelector", issuerLabelSelector) + return &result, err + } + + // NOTE: we are assuming that there will always be a ctlplane network + // that means if you are not using network isolation with multiple networks + // you should still need to have a ctlplane network at a minimum to use tls-e + baseName, ok := dnsNames[CtlPlaneNetwork] + if !ok { + return &result, fmt.Errorf( + "control plane network not found for node %s , tls-e requires a control plane network to be present", + nodeName) + } + + certSecret, result, err = GetTLSNodeCert(ctx, helper, instance, certName, + issuer.Name, labels, baseName, hosts, ips, service.Spec.TLSCert.KeyUsages) + + // handle cert request errors + if (err != nil) || (result != ctrl.Result{}) { + return &result, err + } + + // TODO(alee) Add an owner reference to the secret so it can be monitored + // We'll do this once stuggi adds a function to do this in libcommon + + // To use this cert, add it to the relevant service data + certsData[baseName+"-tls.key"] = certSecret.Data["tls.key"] + certsData[baseName+"-tls.crt"] = certSecret.Data["tls.crt"] + certsData[baseName+"-ca.crt"] = certSecret.Data["ca.crt"] + } + + // Calculate number of secrets to create + ci := createSecretsDataStructure(secretMaxSize, certsData) + + labels := map[string]string{ + "numberOfSecrets": strconv.Itoa(len(ci)), + } + // create secrets to hold the certs for the services + for i := range ci { + labels["secretNumber"] = strconv.Itoa(i) + serviceCertsSecret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: GetServiceCertsSecretName(instance, service.Name, i), + Namespace: instance.Namespace, + Labels: labels, + }, + Data: ci[i], + } + _, result, err := secret.CreateOrPatchSecret(ctx, helper, instance, serviceCertsSecret) + if err != nil { + err = fmt.Errorf("error creating certs secret for %s - %w", service.Name, err) + return &ctrl.Result{}, err + } else if result != controllerutil.OperationResultNone { + return &ctrl.Result{RequeueAfter: time.Second * 5}, nil + } + } + + return &ctrl.Result{}, nil +} + +// GetTLSNodeCert creates or retrieves the cert for a node for a given service +func GetTLSNodeCert(ctx context.Context, helper *helper.Helper, + instance *dataplanev1.OpenStackDataPlaneNodeSet, + certName string, issuer string, + labels map[string]string, + commonName string, + hostnames []string, ips []string, usages []certmgrv1.KeyUsage, +) (*corev1.Secret, ctrl.Result, error) { + secretName := "cert-" + certName + certSecret, _, err := secret.GetSecret(ctx, helper, secretName, instance.Namespace) + var result ctrl.Result + if err != nil { + if !k8serrors.IsNotFound(err) { + err = fmt.Errorf("error retrieving secret %s - %w", secretName, err) + return nil, ctrl.Result{}, err + } + + duration := ptr.To(time.Hour * 24 * 365) + request := certmanager.CertificateRequest{ + CommonName: &commonName, + IssuerName: issuer, + CertName: certName, + Duration: duration, + Hostnames: hostnames, + Ips: ips, + Annotations: nil, + Labels: labels, + Usages: usages, + Subject: &certmgrv1.X509Subject{ + // NOTE(owalsh): For libvirt/QEMU this should match issuer CN + Organizations: []string{issuer}, + }, + } + + certSecret, result, err = certmanager.EnsureCert(ctx, helper, request, instance) + if err != nil { + return nil, ctrl.Result{}, err + } else if (result != ctrl.Result{}) { + return nil, result, nil + } + } + return certSecret, ctrl.Result{}, nil +} + +// GetServiceCertsSecretName - return name of secret to be mounted in ansibleEE which contains +// all the TLS certs that fit in a secret for the relevant service. The index variable is used +// to make the secret name unique. +// The convention we use here is "--certs-", so for example, +// openstack-epdm-nova-certs-0. +func GetServiceCertsSecretName(instance *dataplanev1.OpenStackDataPlaneNodeSet, serviceName string, index int) string { + return fmt.Sprintf("%s-%s-certs-%s", instance.Name, serviceName, strconv.Itoa(index)) +} diff --git a/pkg/deployment/const.go b/pkg/deployment/const.go new file mode 100644 index 000000000..903cbabdb --- /dev/null +++ b/pkg/deployment/const.go @@ -0,0 +1,71 @@ +/* +Copyright 2023. + +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 deployment + +const ( + + // CtlPlaneNetwork - default ctlplane Network Name in NetConfig + CtlPlaneNetwork = "ctlplane" + + // ValidateNetworkLabel for ValidateNetwork OpenStackAnsibleEE + ValidateNetworkLabel = "validate-network" + + // InstallOSLabel for InstallOS OpenStackAnsibleEE + InstallOSLabel = "install-os" + + // ConfigureOSLabel for ConfigureOS OpenStackAnsibleEE + ConfigureOSLabel = "configure-os" + + // RunOSLabel for RunOS OpenStackAnsibleEE + RunOSLabel = "run-os" + + // InstallOpenStackLabel for InstallOpenStack OpenStackAnsibleEE + InstallOpenStackLabel = "install-openstack" + + // ConfigureOpenStackLabel for ConfigureOpenStack OpenStackAnsibleEE + ConfigureOpenStackLabel = "configure-openstack" + + // RunOpenStackLabel for RunOpenStack OpenStackAnsibleEE + RunOpenStackLabel = "run-openstack" + + // NicConfigTemplateFile is the custom nic config file we use when user provided network config templates are provided. + NicConfigTemplateFile = "/runner/network/nic-config-template" + + // ConfigPaths base path for volume mounts in OpenStackAnsibleEE pod + ConfigPaths = "/var/lib/openstack/configs" + + // CertPaths base path for cert volume mount in OpenStackAnsibleEE pod + CertPaths = "/var/lib/openstack/certs" + + // CACertPaths base path for CA cert volume mount in OpenStackAnsibleEE pod + CACertPaths = "/var/lib/openstack/cacerts" + + // DNSNamesStr value for setting dns values in a cert + DNSNamesStr = "dnsnames" + + // IPValuesStr value for setting ip addresses in a cert + IPValuesStr = "ips" + + // NodeSetLabel label for marking secrets to be watched for changes + NodeSetLabel = "osdpns" + + //ServiceLabel label for marking secrets to be watched for changes + ServiceLabel = "osdp-service" + + //HostnameLabel label for marking secrets to be watched for changes + HostnameLabel = "hostname" +) diff --git a/pkg/deployment/deployment.go b/pkg/deployment/deployment.go new file mode 100644 index 000000000..0d6c22186 --- /dev/null +++ b/pkg/deployment/deployment.go @@ -0,0 +1,410 @@ +/* +Copyright 2023. + +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 deployment + +import ( + "context" + "fmt" + "path" + "reflect" + "sort" + "strconv" + + k8s_errors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + + "github.com/iancoleman/strcase" + condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + "github.com/openstack-k8s-operators/lib-common/modules/common/helper" + "github.com/openstack-k8s-operators/lib-common/modules/common/util" + "github.com/openstack-k8s-operators/lib-common/modules/storage" + ansibleeev1 "github.com/openstack-k8s-operators/openstack-ansibleee-operator/api/v1beta1" + openstackv1 "github.com/openstack-k8s-operators/openstack-operator/apis/core/v1beta1" + dataplanev1 "github.com/openstack-k8s-operators/openstack-operator/apis/dataplane/v1beta1" + dataplaneutil "github.com/openstack-k8s-operators/openstack-operator/pkg/util" + corev1 "k8s.io/api/core/v1" +) + +// Deployer defines a data structure with all of the relevant objects required for a full deployment. +type Deployer struct { + Ctx context.Context + Helper *helper.Helper + NodeSet *dataplanev1.OpenStackDataPlaneNodeSet + Deployment *dataplanev1.OpenStackDataPlaneDeployment + Status *dataplanev1.OpenStackDataPlaneDeploymentStatus + AeeSpec *dataplanev1.AnsibleEESpec + InventorySecrets map[string]string + AnsibleSSHPrivateKeySecrets map[string]string + Version *openstackv1.OpenStackVersion +} + +// Deploy function encapsulating primary deloyment handling +func (d *Deployer) Deploy(services []string) (*ctrl.Result, error) { + log := d.Helper.GetLogger() + + var readyCondition condition.Type + var readyMessage string + var readyWaitingMessage string + var readyErrorMessage string + var deployName string + + // Save a copy of the original ExtraMounts so it can be reset after each + // service deployment + aeeSpecMounts := make([]storage.VolMounts, len(d.AeeSpec.ExtraMounts)) + copy(aeeSpecMounts, d.AeeSpec.ExtraMounts) + // Deploy the composable services + for _, service := range services { + log.Info("Deploying service", "service", service) + foundService, err := GetService(d.Ctx, d.Helper, service) + if err != nil { + return &ctrl.Result{}, err + } + deployName = foundService.Name + readyCondition = condition.Type(fmt.Sprintf("Service%sDeploymentReady", strcase.ToCamel(service))) + readyWaitingMessage = fmt.Sprintf(dataplanev1.NodeSetServiceDeploymentReadyWaitingMessage, deployName) + readyMessage = fmt.Sprintf(dataplanev1.NodeSetServiceDeploymentReadyMessage, deployName) + readyErrorMessage = fmt.Sprintf(dataplanev1.NodeSetServiceDeploymentErrorMessage, deployName) + d.AeeSpec.OpenStackAnsibleEERunnerImage = foundService.Spec.OpenStackAnsibleEERunnerImage + + // Reset ExtraMounts to its original value, and then add in service + // specific mounts. + d.AeeSpec.ExtraMounts = make([]storage.VolMounts, len(aeeSpecMounts)) + copy(d.AeeSpec.ExtraMounts, aeeSpecMounts) + d.AeeSpec, err = d.addServiceExtraMounts(foundService) + if err != nil { + return &ctrl.Result{}, err + } + + // Add certMounts if TLS is enabled + if d.NodeSet.Spec.TLSEnabled { + if foundService.Spec.AddCertMounts { + d.AeeSpec, err = d.addCertMounts(services) + } + if err != nil { + return &ctrl.Result{}, err + } + } + + err = d.ConditionalDeploy( + readyCondition, + readyMessage, + readyWaitingMessage, + readyErrorMessage, + deployName, + foundService, + ) + + nsConditions := d.Status.NodeSetConditions[d.NodeSet.Name] + if err != nil || !nsConditions.IsTrue(readyCondition) { + log.Info(fmt.Sprintf("Condition %s not ready", readyCondition)) + return &ctrl.Result{}, err + } + + log.Info(fmt.Sprintf("Condition %s ready", readyCondition)) + + // (TODO) Only considers the container image values from the Version + // for the time being. Can be expanded later to look at the actual + // values used from the inventory, etc. + if d.Version != nil { + vContainerImages := reflect.ValueOf(d.Version.Status.ContainerImages) + for _, cif := range foundService.Spec.ContainerImageFields { + d.Deployment.Status.ContainerImages[cif] = reflect.Indirect(vContainerImages.FieldByName(cif)).String() + } + } + + } + + return nil, nil +} + +// ConditionalDeploy function encapsulating primary deloyment handling with +// conditions. +func (d *Deployer) ConditionalDeploy( + readyCondition condition.Type, + readyMessage string, + readyWaitingMessage string, + readyErrorMessage string, + deployName string, + foundService dataplanev1.OpenStackDataPlaneService, +) error { + var err error + log := d.Helper.GetLogger() + + nsConditions := d.Status.NodeSetConditions[d.NodeSet.Name] + if nsConditions.IsUnknown(readyCondition) { + log.Info(fmt.Sprintf("%s Unknown, starting %s", readyCondition, deployName)) + err = d.DeployService( + foundService) + if err != nil { + util.LogErrorForObject(d.Helper, err, fmt.Sprintf("Unable to %s for %s", deployName, d.NodeSet.Name), d.NodeSet) + return err + } + nsConditions.Set(condition.FalseCondition( + readyCondition, + condition.RequestedReason, + condition.SeverityInfo, + readyWaitingMessage)) + + } + + if nsConditions.IsFalse(readyCondition) { + var ansibleEE *ansibleeev1.OpenStackAnsibleEE + _, labelSelector := dataplaneutil.GetAnsibleExecutionNameAndLabels(&foundService, d.Deployment.Name, d.NodeSet.Name) + ansibleEE, err = dataplaneutil.GetAnsibleExecution(d.Ctx, d.Helper, d.Deployment, labelSelector) + if err != nil { + // Return nil if we don't have AnsibleEE available yet + if k8s_errors.IsNotFound(err) { + log.Info(fmt.Sprintf("%s OpenStackAnsibleEE not yet found", readyCondition)) + return nil + } + log.Error(err, fmt.Sprintf("Error getting ansibleEE job for %s", deployName)) + nsConditions.Set(condition.FalseCondition( + readyCondition, + condition.ErrorReason, + condition.SeverityError, + readyErrorMessage, + err.Error())) + } + + if ansibleEE.Status.JobStatus == ansibleeev1.JobStatusSucceeded { + log.Info(fmt.Sprintf("Condition %s ready", readyCondition)) + nsConditions.Set(condition.TrueCondition( + readyCondition, + readyMessage)) + } + + if ansibleEE.Status.JobStatus == ansibleeev1.JobStatusRunning || ansibleEE.Status.JobStatus == ansibleeev1.JobStatusPending { + log.Info(fmt.Sprintf("AnsibleEE job is not yet completed: Execution: %s, Status: %s", ansibleEE.Name, ansibleEE.Status.JobStatus)) + nsConditions.Set(condition.FalseCondition( + readyCondition, + condition.RequestedReason, + condition.SeverityInfo, + readyWaitingMessage)) + } + + if ansibleEE.Status.JobStatus == ansibleeev1.JobStatusFailed { + log.Info(fmt.Sprintf("Condition %s error", readyCondition)) + err = fmt.Errorf("execution.name %s Execution.namespace %s Execution.status.jobstatus: %s", ansibleEE.Name, ansibleEE.Namespace, ansibleEE.Status.JobStatus) + nsConditions.Set(condition.FalseCondition( + readyCondition, + condition.ErrorReason, + condition.SeverityError, + readyErrorMessage, + err.Error())) + } + } + d.Status.NodeSetConditions[d.NodeSet.Name] = nsConditions + + return err +} + +// addCertMounts adds the cert mounts to the aeeSpec for the install-certs service +func (d *Deployer) addCertMounts( + services []string, +) (*dataplanev1.AnsibleEESpec, error) { + log := d.Helper.GetLogger() + client := d.Helper.GetClient() + for _, svc := range services { + service, err := GetService(d.Ctx, d.Helper, svc) + if err != nil { + return nil, err + } + if service.Spec.TLSCert != nil { + log.Info("Mounting TLS cert for service", "service", svc) + volMounts := storage.VolMounts{} + + // add mount for certs and keys + secretName := GetServiceCertsSecretName(d.NodeSet, service.Name, 0) // Need to get the number of secrets + certSecret := &corev1.Secret{} + err := client.Get(d.Ctx, types.NamespacedName{Name: secretName, Namespace: service.Namespace}, certSecret) + if err != nil { + return d.AeeSpec, err + } + numberOfSecrets, _ := strconv.Atoi(certSecret.Labels["numberOfSecrets"]) + projectedVolumeSource := corev1.ProjectedVolumeSource{ + Sources: []corev1.VolumeProjection{}, + } + for i := 0; i < numberOfSecrets; i++ { + secretName := GetServiceCertsSecretName(d.NodeSet, service.Name, i) + certSecret := &corev1.Secret{} + err := client.Get(d.Ctx, types.NamespacedName{Name: secretName, Namespace: service.Namespace}, certSecret) + if err != nil { + return d.AeeSpec, err + } + volumeProjection := corev1.VolumeProjection{ + Secret: &corev1.SecretProjection{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: secretName, + }, + }, + } + projectedVolumeSource.Sources = append(projectedVolumeSource.Sources, volumeProjection) + } + certVolume := corev1.Volume{ + Name: GetServiceCertsSecretName(d.NodeSet, service.Name, 0), + VolumeSource: corev1.VolumeSource{ + Projected: &projectedVolumeSource, + }, + } + certVolumeMount := corev1.VolumeMount{ + Name: GetServiceCertsSecretName(d.NodeSet, service.Name, 0), + MountPath: path.Join(CertPaths, service.Name), + } + volMounts.Volumes = append(volMounts.Volumes, certVolume) + volMounts.Mounts = append(volMounts.Mounts, certVolumeMount) + d.AeeSpec.ExtraMounts = append(d.AeeSpec.ExtraMounts, volMounts) + + } + + // add mount for cacert bundle + if len(service.Spec.CACerts) > 0 { + log.Info("Mounting CA cert bundle for service", "service", svc) + volMounts := storage.VolMounts{} + cacertSecret := &corev1.Secret{} + err := client.Get(d.Ctx, types.NamespacedName{Name: service.Spec.CACerts, Namespace: service.Namespace}, cacertSecret) + if err != nil { + return d.AeeSpec, err + } + cacertVolume := corev1.Volume{ + Name: fmt.Sprintf("%s-%s", service.Name, service.Spec.CACerts), + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: service.Spec.CACerts, + }, + }, + } + + cacertVolumeMount := corev1.VolumeMount{ + Name: fmt.Sprintf("%s-%s", service.Name, service.Spec.CACerts), + MountPath: path.Join(CACertPaths, service.Name), + } + + volMounts.Volumes = append(volMounts.Volumes, cacertVolume) + volMounts.Mounts = append(volMounts.Mounts, cacertVolumeMount) + d.AeeSpec.ExtraMounts = append(d.AeeSpec.ExtraMounts, volMounts) + } + } + + return d.AeeSpec, nil +} + +// addServiceExtraMounts adds the service configs as ExtraMounts to aeeSpec +func (d *Deployer) addServiceExtraMounts( + service dataplanev1.OpenStackDataPlaneService, +) (*dataplanev1.AnsibleEESpec, error) { + client := d.Helper.GetClient() + baseMountPath := path.Join(ConfigPaths, service.Name) + + for _, cmName := range service.Spec.ConfigMaps { + + volMounts := storage.VolMounts{} + cm := &corev1.ConfigMap{} + err := client.Get(d.Ctx, types.NamespacedName{Name: cmName, Namespace: service.Namespace}, cm) + if err != nil { + return d.AeeSpec, err + } + + keys := []string{} + for key := range cm.Data { + keys = append(keys, key) + } + for key := range cm.BinaryData { + keys = append(keys, key) + } + sort.Strings(keys) + + for idx, key := range keys { + name := fmt.Sprintf("%s-%s", cmName, strconv.Itoa(idx)) + volume := corev1.Volume{ + Name: name, + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: cmName, + }, + Items: []corev1.KeyToPath{ + { + Key: key, + Path: key, + }, + }, + }, + }, + } + + volumeMount := corev1.VolumeMount{ + Name: name, + MountPath: path.Join(baseMountPath, key), + SubPath: key, + } + + volMounts.Volumes = append(volMounts.Volumes, volume) + volMounts.Mounts = append(volMounts.Mounts, volumeMount) + + } + + d.AeeSpec.ExtraMounts = append(d.AeeSpec.ExtraMounts, volMounts) + } + + for _, secretName := range service.Spec.Secrets { + + volMounts := storage.VolMounts{} + sec := &corev1.Secret{} + err := client.Get(d.Ctx, types.NamespacedName{Name: secretName, Namespace: service.Namespace}, sec) + if err != nil { + return d.AeeSpec, err + } + + keys := []string{} + for key := range sec.Data { + keys = append(keys, key) + } + sort.Strings(keys) + + for idx, key := range keys { + name := fmt.Sprintf("%s-%s", secretName, strconv.Itoa(idx)) + volume := corev1.Volume{ + Name: name, + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: secretName, + Items: []corev1.KeyToPath{ + { + Key: key, + Path: key, + }, + }, + }, + }, + } + + volumeMount := corev1.VolumeMount{ + Name: name, + MountPath: path.Join(baseMountPath, key), + SubPath: key, + } + + volMounts.Volumes = append(volMounts.Volumes, volume) + volMounts.Mounts = append(volMounts.Mounts, volumeMount) + + } + + d.AeeSpec.ExtraMounts = append(d.AeeSpec.ExtraMounts, volMounts) + } + return d.AeeSpec, nil +} diff --git a/pkg/deployment/hashes.go b/pkg/deployment/hashes.go new file mode 100644 index 000000000..ee2695b17 --- /dev/null +++ b/pkg/deployment/hashes.go @@ -0,0 +1,111 @@ +/* +Copyright 2023. + +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 deployment + +import ( + "context" + + "github.com/openstack-k8s-operators/lib-common/modules/common/configmap" + "github.com/openstack-k8s-operators/lib-common/modules/common/helper" + "github.com/openstack-k8s-operators/lib-common/modules/common/secret" + dataplanev1 "github.com/openstack-k8s-operators/openstack-operator/apis/dataplane/v1beta1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" +) + +// GetDeploymentHashesForService - Hash the ConfigMaps and Secrets for the provided service +func GetDeploymentHashesForService( + ctx context.Context, + helper *helper.Helper, + namespace string, + serviceName string, + configMapHashes map[string]string, + secretHashes map[string]string, + nodeSets dataplanev1.OpenStackDataPlaneNodeSetList, +) error { + + namespacedName := types.NamespacedName{ + Name: serviceName, + Namespace: namespace, + } + service := &dataplanev1.OpenStackDataPlaneService{} + err := helper.GetClient().Get(context.Background(), namespacedName, service) + if err != nil { + helper.GetLogger().Error(err, "Unable to retrieve OpenStackDataPlaneService %v") + return err + } + for _, cmName := range service.Spec.ConfigMaps { + namespacedName := types.NamespacedName{ + Name: cmName, + Namespace: namespace, + } + cm := &corev1.ConfigMap{} + err := helper.GetClient().Get(context.Background(), namespacedName, cm) + if err != nil { + helper.GetLogger().Error(err, "Unable to retrieve ConfigMap %v") + return err + } + configMapHashes[cmName], err = configmap.Hash(cm) + if err != nil { + helper.GetLogger().Error(err, "Unable to hash ConfigMap %v") + } + + } + for _, secretName := range service.Spec.Secrets { + namespacedName := types.NamespacedName{ + Name: secretName, + Namespace: namespace, + } + sec := &corev1.Secret{} + err := helper.GetClient().Get(ctx, namespacedName, sec) + if err != nil { + helper.GetLogger().Error(err, "Unable to retrieve Secret %v") + return err + } + secretHashes[secretName], err = secret.Hash(sec) + if err != nil { + helper.GetLogger().Error(err, "Unable to hash Secret %v") + } + } + + if service.Spec.TLSCert != nil { + var secrets *corev1.SecretList + for _, nodeSet := range nodeSets.Items { + labelSelectorMap := map[string]string{ + NodeSetLabel: nodeSet.Name, + ServiceLabel: serviceName, + } + secrets, err = secret.GetSecrets(ctx, helper, "", labelSelectorMap) + if err != nil { + helper.GetLogger().Error(err, "Unable to search for cert secrets %v") + return err + } + for _, sec := range secrets.Items { + // get secret? or is it already there + secretHashes[sec.Name], err = secret.Hash(&sec) + if err != nil { + helper.GetLogger().Error(err, "Unable to search for hash cert secrets %v") + return err + } + + } + + } + } + + return nil +} diff --git a/pkg/deployment/inventory.go b/pkg/deployment/inventory.go new file mode 100644 index 000000000..85503ffdf --- /dev/null +++ b/pkg/deployment/inventory.go @@ -0,0 +1,340 @@ +/* +Copyright 2023. + +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 deployment + +import ( + "context" + "encoding/json" + "fmt" + "net" + "strconv" + "strings" + + yaml "gopkg.in/yaml.v3" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + + infranetworkv1 "github.com/openstack-k8s-operators/infra-operator/apis/network/v1beta1" + "github.com/openstack-k8s-operators/lib-common/modules/ansible" + "github.com/openstack-k8s-operators/lib-common/modules/common/helper" + "github.com/openstack-k8s-operators/lib-common/modules/common/secret" + utils "github.com/openstack-k8s-operators/lib-common/modules/common/util" + openstackv1 "github.com/openstack-k8s-operators/openstack-operator/apis/core/v1beta1" + dataplanev1 "github.com/openstack-k8s-operators/openstack-operator/apis/dataplane/v1beta1" +) + +// getAnsibleVarsFrom gets ansible vars from ConfigMap/Secret +func getAnsibleVarsFrom(ctx context.Context, helper *helper.Helper, namespace string, ansible *dataplanev1.AnsibleOpts) (map[string]string, error) { + + var result = make(map[string]string) + + client := helper.GetClient() + + // AnsibleVars will override AnsibleVarsFrom variables. + // Process AnsibleVarsFrom first then allow AnsibleVars to replace existing values. + for _, varFrom := range ansible.AnsibleVarsFrom { + switch { + case varFrom.ConfigMapRef != nil: + cm := varFrom.ConfigMapRef + optional := cm.Optional != nil && *cm.Optional + configMap := &v1.ConfigMap{} + err := client.Get(ctx, types.NamespacedName{Name: cm.Name, Namespace: namespace}, configMap) + if err != nil { + if errors.IsNotFound(err) && optional { + // ignore error when marked optional + utils.LogErrorForObject(helper, err, "could not get ansible vars, the configMap: "+cm.Name+"is missing", configMap) + continue + } + return result, err + } + + for k, v := range configMap.Data { + if len(varFrom.Prefix) > 0 { + k = varFrom.Prefix + k + } + + result[k] = v + } + + case varFrom.SecretRef != nil: + s := varFrom.SecretRef + optional := s.Optional != nil && *s.Optional + secret := &v1.Secret{} + err := client.Get(ctx, types.NamespacedName{Name: s.Name, Namespace: namespace}, secret) + if err != nil { + if errors.IsNotFound(err) && optional { + // ignore error when marked optional + utils.LogErrorForObject(helper, err, "could not get ansible vars, the secret: "+s.Name+"is missing", secret) + continue + } + return result, err + } + + for k, v := range secret.Data { + if len(varFrom.Prefix) > 0 { + k = varFrom.Prefix + k + } + result[k] = string(v) + } + } + } + + return result, nil +} + +// GenerateNodeSetInventory yields a parsed Inventory for role +func GenerateNodeSetInventory(ctx context.Context, helper *helper.Helper, + instance *dataplanev1.OpenStackDataPlaneNodeSet, + allIPSets map[string]infranetworkv1.IPSet, dnsAddresses []string, + containerImages openstackv1.ContainerImages) (string, error) { + inventory := ansible.MakeInventory() + nodeSetGroup := inventory.AddGroup(instance.Name) + groupVars, err := getAnsibleVarsFrom(ctx, helper, instance.Namespace, &instance.Spec.NodeTemplate.Ansible) + if err != nil { + utils.LogErrorForObject(helper, err, "could not get ansible group vars from configMap/secret", instance) + return "", err + } + for k, v := range groupVars { + nodeSetGroup.Vars[k] = v + } + err = resolveGroupAnsibleVars(&instance.Spec.NodeTemplate, &nodeSetGroup, containerImages) + if err != nil { + utils.LogErrorForObject(helper, err, "Could not resolve ansible group vars", instance) + return "", err + } + + // add the NodeSet name variable + nodeSetGroup.Vars["edpm_nodeset_name"] = instance.Name + + // add TLS ansible variable + nodeSetGroup.Vars["edpm_tls_certs_enabled"] = instance.Spec.TLSEnabled + if instance.Spec.Tags != nil { + nodeSetGroup.Vars["nodeset_tags"] = instance.Spec.Tags + } + + nodeSetGroup.Vars["ansible_ssh_private_key_file"] = fmt.Sprintf("/runner/env/ssh_key/ssh_key_%s", instance.Name) + + for _, node := range instance.Spec.Nodes { + host := nodeSetGroup.AddHost(strings.Split(node.HostName, ".")[0]) + hostVars, err := getAnsibleVarsFrom(ctx, helper, instance.Namespace, &node.Ansible) + if err != nil { + utils.LogErrorForObject(helper, err, "could not get ansible host vars from configMap/secret", instance) + return "", err + } + for k, v := range hostVars { + host.Vars[k] = v + } + // Use ansible_host if provided else use hostname. Fall back to + // nodeName if all else fails. + if node.Ansible.AnsibleHost != "" { + host.Vars["ansible_host"] = node.Ansible.AnsibleHost + } else { + host.Vars["ansible_host"] = node.HostName + } + + err = resolveHostAnsibleVars(&node, &host) + if err != nil { + utils.LogErrorForObject(helper, err, "Could not resolve ansible host vars", instance) + return "", err + } + + ipSet, ok := allIPSets[node.HostName] + if ok { + populateInventoryFromIPAM(&ipSet, host, dnsAddresses, node.HostName) + } + + } + + invData, err := inventory.MarshalYAML() + if err != nil { + utils.LogErrorForObject(helper, err, "Could not parse NodeSet inventory", instance) + return "", err + } + secretData := map[string]string{ + "inventory": string(invData), + } + secretName := fmt.Sprintf("dataplanenodeset-%s", instance.Name) + template := []utils.Template{ + // Secret + { + Name: secretName, + Namespace: instance.Namespace, + Type: utils.TemplateTypeNone, + InstanceType: instance.Kind, + CustomData: secretData, + Labels: instance.ObjectMeta.Labels, + }, + } + err = secret.EnsureSecrets(ctx, helper, instance, template, nil) + return secretName, err +} + +// populateInventoryFromIPAM populates inventory from IPAM +func populateInventoryFromIPAM( + ipSet *infranetworkv1.IPSet, host ansible.Host, + dnsAddresses []string, hostName string) { + var dnsSearchDomains []string + for _, res := range ipSet.Status.Reservation { + // Build the vars for ips/routes etc + entry := strings.ToLower(string(res.Network)) + host.Vars[entry+"_ip"] = res.Address + _, ipnet, err := net.ParseCIDR(res.Cidr) + if err == nil { + netCidr, _ := ipnet.Mask.Size() + host.Vars[entry+"_cidr"] = netCidr + } + if res.Vlan != nil || entry != CtlPlaneNetwork { + host.Vars[entry+"_vlan_id"] = res.Vlan + } + host.Vars[entry+"_mtu"] = res.MTU + host.Vars[entry+"_gateway_ip"] = res.Gateway + host.Vars[entry+"_host_routes"] = res.Routes + + if entry == CtlPlaneNetwork { + host.Vars[entry+"_dns_nameservers"] = dnsAddresses + if !dataplanev1.NodeHostNameIsFQDN(hostName) { + host.Vars["canonical_hostname"] = strings.Join([]string{hostName, res.DNSDomain}, ".") + } else { + host.Vars["canonical_hostname"] = hostName + } + } + dnsSearchDomains = append(dnsSearchDomains, res.DNSDomain) + } + host.Vars["dns_search_domains"] = dnsSearchDomains +} + +// set group ansible vars from NodeTemplate +func resolveGroupAnsibleVars(template *dataplanev1.NodeTemplate, group *ansible.Group, + containerImages openstackv1.ContainerImages) error { + + if template.Ansible.AnsibleUser != "" { + group.Vars["ansible_user"] = template.Ansible.AnsibleUser + } + if template.Ansible.AnsiblePort > 0 { + group.Vars["ansible_port"] = strconv.Itoa(template.Ansible.AnsiblePort) + } + if template.ManagementNetwork != "" { + group.Vars["management_network"] = template.ManagementNetwork + } + + // Set the ansible variables for the container images if they are not + // provided by the user in the spec. + if template.Ansible.AnsibleVars["edpm_frr_image"] == nil { + group.Vars["edpm_frr_image"] = containerImages.EdpmFrrImage + } + if template.Ansible.AnsibleVars["edpm_iscsid_image"] == nil { + group.Vars["edpm_iscsid_image"] = containerImages.EdpmIscsidImage + } + if template.Ansible.AnsibleVars["edpm_logrotate_crond_image"] == nil { + group.Vars["edpm_logrotate_crond_image"] = containerImages.EdpmLogrotateCrondImage + } + if template.Ansible.AnsibleVars["edpm_multipathd_image"] == nil { + group.Vars["edpm_multipathd_image"] = containerImages.EdpmMultipathdImage + } + if template.Ansible.AnsibleVars["edpm_neutron_metadata_agent_image"] == nil { + group.Vars["edpm_neutron_metadata_agent_image"] = containerImages.EdpmNeutronMetadataAgentImage + } + if template.Ansible.AnsibleVars["edpm_neutron_sriov_agent_image"] == nil { + group.Vars["edpm_neutron_sriov_image"] = containerImages.EdpmNeutronSriovAgentImage + } + if template.Ansible.AnsibleVars["edpm_nova_compute_image"] == nil { + group.Vars["edpm_nova_compute_image"] = containerImages.NovaComputeImage + } + if template.Ansible.AnsibleVars["edpm_ovn_controller_agent_image"] == nil { + group.Vars["edpm_ovn_controller_agent_image"] = containerImages.OvnControllerImage + } + if template.Ansible.AnsibleVars["edpm_ovn_bgp_agent_image"] == nil { + group.Vars["edpm_ovn_bgp_agent_image"] = containerImages.EdpmOvnBgpAgentImage + } + if template.Ansible.AnsibleVars["edpm_telemetry_ceilometer_compute_image"] == nil { + group.Vars["edpm_telemetry_ceilometer_compute_image"] = containerImages.CeilometerComputeImage + } + if template.Ansible.AnsibleVars["edpm_telemetry_ceilometer_ipmi_image"] == nil { + group.Vars["edpm_telemetry_ceilometer_ipmi_image"] = containerImages.CeilometerIpmiImage + } + if template.Ansible.AnsibleVars["edpm_telemetry_node_exporter_image"] == nil { + group.Vars["edpm_telemetry_node_exporter_image"] = containerImages.EdpmNodeExporterImage + } + + err := unmarshalAnsibleVars(template.Ansible.AnsibleVars, group.Vars) + if err != nil { + return err + } + if len(template.Networks) != 0 { + nets, netsLower := buildNetworkVars(template.Networks) + group.Vars["nodeset_networks"] = nets + group.Vars["networks_lower"] = netsLower + } + + return nil +} + +// set host ansible vars from NodeSection +func resolveHostAnsibleVars(node *dataplanev1.NodeSection, host *ansible.Host) error { + + if node.Ansible.AnsibleUser != "" { + host.Vars["ansible_user"] = node.Ansible.AnsibleUser + } + if node.Ansible.AnsiblePort > 0 { + host.Vars["ansible_port"] = strconv.Itoa(node.Ansible.AnsiblePort) + } + if node.ManagementNetwork != "" { + host.Vars["management_network"] = node.ManagementNetwork + } + + err := unmarshalAnsibleVars(node.Ansible.AnsibleVars, host.Vars) + if err != nil { + return err + } + if len(node.Networks) != 0 { + nets, netsLower := buildNetworkVars(node.Networks) + host.Vars["nodeset_networks"] = nets + host.Vars["networks_lower"] = netsLower + } + return nil + +} + +// unmarshal raw strings into an ansible vars dictionary +func unmarshalAnsibleVars(ansibleVars map[string]json.RawMessage, + parsedVars map[string]interface{}) error { + + for key, val := range ansibleVars { + var v interface{} + err := yaml.Unmarshal(val, &v) + if err != nil { + return err + } + parsedVars[key] = v + } + return nil +} + +func buildNetworkVars(networks []infranetworkv1.IPSetNetwork) ([]string, map[string]string) { + netsLower := make(map[string]string) + var nets []string + for _, network := range networks { + netName := string(network.Name) + if strings.EqualFold(netName, CtlPlaneNetwork) { + continue + } + nets = append(nets, netName) + netsLower[netName] = strings.ToLower(netName) + } + return nets, netsLower +} diff --git a/pkg/deployment/ipam.go b/pkg/deployment/ipam.go new file mode 100644 index 000000000..fbdd449ac --- /dev/null +++ b/pkg/deployment/ipam.go @@ -0,0 +1,314 @@ +/* +Copyright 2023. + +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 deployment + +import ( + "context" + "strings" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + infranetworkv1 "github.com/openstack-k8s-operators/infra-operator/apis/network/v1beta1" + condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + "github.com/openstack-k8s-operators/lib-common/modules/common/helper" + "github.com/openstack-k8s-operators/lib-common/modules/common/util" + dataplanev1 "github.com/openstack-k8s-operators/openstack-operator/apis/dataplane/v1beta1" +) + +// EnsureIPSets Creates the IPSets +func EnsureIPSets(ctx context.Context, helper *helper.Helper, + instance *dataplanev1.OpenStackDataPlaneNodeSet, +) (map[string]infranetworkv1.IPSet, bool, error) { + allIPSets, err := reserveIPs(ctx, helper, instance) + if err != nil { + instance.Status.Conditions.MarkFalse( + dataplanev1.NodeSetIPReservationReadyCondition, + condition.ErrorReason, condition.SeverityError, + dataplanev1.NodeSetIPReservationReadyErrorMessage) + return nil, false, err + } + + if len(allIPSets) == 0 { + return nil, true, nil + } + + for _, s := range allIPSets { + if s.Status.Conditions.IsFalse(condition.ReadyCondition) { + instance.Status.Conditions.MarkFalse( + dataplanev1.NodeSetIPReservationReadyCondition, + condition.RequestedReason, condition.SeverityInfo, + dataplanev1.NodeSetIPReservationReadyWaitingMessage) + return nil, false, nil + } + } + instance.Status.Conditions.MarkTrue( + dataplanev1.NodeSetIPReservationReadyCondition, + dataplanev1.NodeSetIPReservationReadyMessage) + return allIPSets, true, nil +} + +// DataplaneDNSData holds information relevant to the OpenStack DNS configuration of the cluster. +type DataplaneDNSData struct { + // ServerAddresses holds a slice of DNS servers in the environment + ServerAddresses []string + // ClusterAddresses holds a slice of Kubernetes service ClusterIPs for the DNSMasq services + ClusterAddresses []string + // CtlplaneSearchDomain is the search domain provided by IPAM + CtlplaneSearchDomain string + // Ready indicates the ready status of the DNS service + Ready bool + // Hostnames is a map of hostnames provided by the NodeSet to the FQDNs + Hostnames map[string]map[infranetworkv1.NetNameStr]string + // AllIPs holds a map of all IP addresses per hostname. + AllIPs map[string]map[infranetworkv1.NetNameStr]string +} + +// createOrPatchDNSData builds the DNSData +func (dns *DataplaneDNSData) createOrPatchDNSData(ctx context.Context, helper *helper.Helper, + instance *dataplanev1.OpenStackDataPlaneNodeSet, + allIPSets map[string]infranetworkv1.IPSet, +) error { + var allDNSRecords []infranetworkv1.DNSHost + var ctlplaneSearchDomain string + dns.Hostnames = map[string]map[infranetworkv1.NetNameStr]string{} + dns.AllIPs = map[string]map[infranetworkv1.NetNameStr]string{} + + // Build DNSData CR + for _, node := range instance.Spec.Nodes { + var shortName string + nets := node.Networks + hostName := node.HostName + + dns.Hostnames[hostName] = map[infranetworkv1.NetNameStr]string{} + dns.AllIPs[hostName] = map[infranetworkv1.NetNameStr]string{} + + shortName = strings.Split(hostName, ".")[0] + if len(nets) == 0 { + nets = instance.Spec.NodeTemplate.Networks + } + if len(nets) > 0 { + // Get IPSet + ipSet, ok := allIPSets[hostName] + if ok { + for _, res := range ipSet.Status.Reservation { + var fqdnNames []string + dnsRecord := infranetworkv1.DNSHost{} + dnsRecord.IP = res.Address + netLower := strings.ToLower(string(res.Network)) + fqdnName := strings.Join([]string{shortName, res.DNSDomain}, ".") + if fqdnName != hostName { + fqdnNames = append(fqdnNames, fqdnName) + dns.Hostnames[hostName][infranetworkv1.NetNameStr(netLower)] = fqdnName + } + if dataplanev1.NodeHostNameIsFQDN(hostName) && netLower == CtlPlaneNetwork { + fqdnNames = append(fqdnNames, hostName) + dns.Hostnames[hostName][infranetworkv1.NetNameStr(netLower)] = hostName + } + dns.AllIPs[hostName][infranetworkv1.NetNameStr(netLower)] = res.Address + dnsRecord.Hostnames = fqdnNames + allDNSRecords = append(allDNSRecords, dnsRecord) + // Adding only ctlplane domain for ansibleee. + // TODO (rabi) This is not very efficient. + if netLower == CtlPlaneNetwork && ctlplaneSearchDomain == "" { + dns.CtlplaneSearchDomain = res.DNSDomain + } + } + } + } + } + util.LogForObject(helper, "Reconciling DNSData", instance) + dnsData := &infranetworkv1.DNSData{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: instance.Namespace, + Name: instance.Name, + }, + } + _, err := controllerutil.CreateOrPatch(ctx, helper.GetClient(), dnsData, func() error { + dnsData.Spec.Hosts = allDNSRecords + // TODO (rabi) DNSDataLabelSelectorValue can probably be + // used from dnsmasq(?) + dnsData.Spec.DNSDataLabelSelectorValue = "dnsdata" + // Set controller reference to the DataPlaneNode object + err := controllerutil.SetControllerReference( + helper.GetBeforeObject(), dnsData, helper.GetScheme()) + return err + }) + if err != nil { + return err + } + return nil +} + +// EnsureDNSData Ensures DNSData is created +func (dns *DataplaneDNSData) EnsureDNSData(ctx context.Context, helper *helper.Helper, + instance *dataplanev1.OpenStackDataPlaneNodeSet, + allIPSets map[string]infranetworkv1.IPSet, +) error { + // Verify dnsmasq CR exists + err := dns.CheckDNSService( + ctx, helper, instance) + + if err != nil || !dns.Ready || dns.ClusterAddresses == nil { + if err != nil { + instance.Status.Conditions.MarkFalse( + dataplanev1.NodeSetDNSDataReadyCondition, + condition.ErrorReason, condition.SeverityError, + dataplanev1.NodeSetDNSDataReadyErrorMessage) + } + if !dns.Ready { + instance.Status.Conditions.MarkFalse( + dataplanev1.NodeSetDNSDataReadyCondition, + condition.RequestedReason, condition.SeverityInfo, + dataplanev1.NodeSetDNSDataReadyWaitingMessage) + } + if dns.ClusterAddresses == nil { + instance.Status.Conditions.Remove(dataplanev1.NodeSetDNSDataReadyCondition) + } + return err + } + // Create or Patch DNSData + err = dns.createOrPatchDNSData( + ctx, helper, instance, allIPSets) + if err != nil { + instance.Status.Conditions.MarkFalse( + dataplanev1.NodeSetDNSDataReadyCondition, + condition.ErrorReason, condition.SeverityError, + dataplanev1.NodeSetDNSDataReadyErrorMessage) + return err + } + + dnsData := &infranetworkv1.DNSData{ + ObjectMeta: metav1.ObjectMeta{ + Name: instance.Name, + Namespace: instance.Namespace, + }, + } + key := client.ObjectKeyFromObject(dnsData) + err = helper.GetClient().Get(ctx, key, dnsData) + if err != nil { + instance.Status.Conditions.MarkFalse( + dataplanev1.NodeSetDNSDataReadyCondition, + condition.ErrorReason, condition.SeverityError, + dataplanev1.NodeSetDNSDataReadyErrorMessage) + return err + } + + if !dnsData.IsReady() { + util.LogForObject(helper, "DNSData not ready yet waiting", instance) + instance.Status.Conditions.MarkFalse( + dataplanev1.NodeSetDNSDataReadyCondition, + condition.RequestedReason, condition.SeverityInfo, + dataplanev1.NodeSetDNSDataReadyWaitingMessage) + dns.Ready = false + return nil + } + instance.Status.Conditions.MarkTrue( + dataplanev1.NodeSetDNSDataReadyCondition, + dataplanev1.NodeSetDNSDataReadyMessage) + dns.Ready = true + + return nil +} + +// reserveIPs Reserves IPs by creating IPSets +func reserveIPs(ctx context.Context, helper *helper.Helper, + instance *dataplanev1.OpenStackDataPlaneNodeSet, +) (map[string]infranetworkv1.IPSet, error) { + // Verify NetConfig CRs exist + netConfigList := &infranetworkv1.NetConfigList{} + listOpts := []client.ListOption{ + client.InNamespace(instance.GetNamespace()), + } + err := helper.GetClient().List(ctx, netConfigList, listOpts...) + if err != nil { + return nil, err + } + if len(netConfigList.Items) == 0 { + util.LogForObject(helper, "No NetConfig CR exists yet, IPAM won't be used", instance) + instance.Status.Conditions.Remove(dataplanev1.NodeSetIPReservationReadyCondition) + return nil, nil + } + + ipamUsed := false + allIPSets := make(map[string]infranetworkv1.IPSet) + // CreateOrPatch IPSets + for _, node := range instance.Spec.Nodes { + nets := node.Networks + hostName := node.HostName + if len(nets) == 0 { + nets = instance.Spec.NodeTemplate.Networks + } + + if len(nets) > 0 { + ipamUsed = true + util.LogForObject(helper, "Reconciling IPSet", instance) + ipSet := &infranetworkv1.IPSet{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: instance.Namespace, + Name: hostName, + }, + } + _, err := controllerutil.CreateOrPatch(ctx, helper.GetClient(), ipSet, func() error { + ipSet.Spec.Networks = nets + // Set controller reference to the DataPlaneNode object + err := controllerutil.SetControllerReference( + helper.GetBeforeObject(), ipSet, helper.GetScheme()) + return err + }) + if err != nil { + return nil, err + } + allIPSets[hostName] = *ipSet + } + } + if !ipamUsed { + util.LogForObject(helper, "No Networks defined for nodes, IPAM won't be used", instance) + instance.Status.Conditions.Remove(dataplanev1.NodeSetIPReservationReadyCondition) + } + + return allIPSets, nil +} + +// CheckDNSService checks if DNS is configured and ready +func (dns *DataplaneDNSData) CheckDNSService(ctx context.Context, helper *helper.Helper, + instance client.Object, +) error { + dnsmasqList := &infranetworkv1.DNSMasqList{} + listOpts := []client.ListOption{ + client.InNamespace(instance.GetNamespace()), + } + err := helper.GetClient().List(ctx, dnsmasqList, listOpts...) + if err != nil { + dns.Ready = false + return err + } + if len(dnsmasqList.Items) == 0 { + util.LogForObject(helper, "No DNSMasq CR exists yet, DNS Service won't be used", instance) + dns.Ready = true + return nil + } else if !dnsmasqList.Items[0].IsReady() { + util.LogForObject(helper, "DNSMasq service exists, but not ready yet ", instance) + dns.Ready = false + return nil + } + dns.ClusterAddresses = dnsmasqList.Items[0].Status.DNSClusterAddresses + dns.ServerAddresses = dnsmasqList.Items[0].Status.DNSAddresses + dns.Ready = true + return nil +} diff --git a/pkg/deployment/service.go b/pkg/deployment/service.go new file mode 100644 index 000000000..9e2c85b27 --- /dev/null +++ b/pkg/deployment/service.go @@ -0,0 +1,197 @@ +/* +Copyright 2023. + +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 deployment + +import ( + "context" + "fmt" + "os" + "path" + "strings" + + yaml "gopkg.in/yaml.v3" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + "github.com/go-playground/validator/v10" + "github.com/openstack-k8s-operators/lib-common/modules/common/helper" + "github.com/openstack-k8s-operators/lib-common/modules/common/util" + dataplanev1 "github.com/openstack-k8s-operators/openstack-operator/apis/dataplane/v1beta1" + dataplaneutil "github.com/openstack-k8s-operators/openstack-operator/pkg/util" +) + +// ServiceYAML struct for service YAML unmarshalling +type ServiceYAML struct { + Kind string + Metadata yaml.Node + Spec yaml.Node +} + +// DeployService service deployment +func (d *Deployer) DeployService(foundService dataplanev1.OpenStackDataPlaneService) error { + err := dataplaneutil.AnsibleExecution( + d.Ctx, + d.Helper, + d.Deployment, + &foundService, + d.AnsibleSSHPrivateKeySecrets, + d.InventorySecrets, + d.AeeSpec, + d.NodeSet) + + if err != nil { + d.Helper.GetLogger().Error(err, fmt.Sprintf("Unable to execute Ansible for %s", foundService.Name)) + return err + } + + return nil +} + +// GetService return service +func GetService(ctx context.Context, helper *helper.Helper, service string) (dataplanev1.OpenStackDataPlaneService, error) { + client := helper.GetClient() + beforeObj := helper.GetBeforeObject() + namespace := beforeObj.GetNamespace() + foundService := &dataplanev1.OpenStackDataPlaneService{} + err := client.Get(ctx, types.NamespacedName{Name: service, Namespace: namespace}, foundService) + return *foundService, err +} + +// EnsureServices - ensure the OpenStackDataPlaneServices exist +func EnsureServices(ctx context.Context, helper *helper.Helper, instance *dataplanev1.OpenStackDataPlaneNodeSet, validation *validator.Validate) error { + servicesPath, found := os.LookupEnv("OPERATOR_SERVICES") + if !found { + servicesPath = "config/services" + os.Setenv("OPERATOR_SERVICES", servicesPath) + util.LogForObject( + helper, "OPERATOR_SERVICES not set in env when reconciling ", instance, + "defaulting to ", servicesPath) + } + + helper.GetLogger().Info("Ensuring services", "servicesPath", servicesPath) + services, err := os.ReadDir(servicesPath) + if err != nil { + return err + } + + for _, service := range services { + + servicePath := path.Join(servicesPath, service.Name()) + + if !strings.HasSuffix(service.Name(), ".yaml") { + helper.GetLogger().Info("Skipping ensuring service from file without .yaml suffix", "file", service.Name()) + continue + } + + data, _ := os.ReadFile(servicePath) + var serviceObj ServiceYAML + err = yaml.Unmarshal(data, &serviceObj) + if err != nil { + helper.GetLogger().Info("Service YAML file Unmarshal error", "service YAML file", servicePath) + return err + } + + if serviceObj.Kind != "OpenStackDataPlaneService" { + helper.GetLogger().Info("Skipping ensuring service since kind is not OpenStackDataPlaneService", "file", servicePath, "Kind", serviceObj.Kind) + continue + } + + serviceObjMeta := &metav1.ObjectMeta{} + err = serviceObj.Metadata.Decode(serviceObjMeta) + if err != nil { + helper.GetLogger().Info("Service Metadata decode error") + return err + } + // Check if service name matches RFC1123 for use in labels + if err = validation.Var(serviceObjMeta.Name, "hostname_rfc1123"); err != nil { + helper.GetLogger().Info("service name must follow RFC1123") + return err + } + roleContainsService := false + for _, roleServiceName := range instance.Spec.Services { + if roleServiceName == serviceObjMeta.Name { + roleContainsService = true + break + } + } + if !roleContainsService { + helper.GetLogger().Info("Skipping ensure service since it is not a service on this role", "service", serviceObjMeta.Name) + continue + } + + serviceObjSpec := &dataplanev1.OpenStackDataPlaneServiceSpec{} + err = serviceObj.Spec.Decode(serviceObjSpec) + if err != nil { + helper.GetLogger().Info("Service Spec decode error") + return err + } + + ensureService := &dataplanev1.OpenStackDataPlaneService{ + ObjectMeta: metav1.ObjectMeta{ + Name: serviceObjMeta.Name, + Namespace: instance.Namespace, + }, + } + _, err = controllerutil.CreateOrPatch(ctx, helper.GetClient(), ensureService, func() error { + serviceObjSpec.DeepCopyInto(&ensureService.Spec) + return nil + }) + if err != nil { + return fmt.Errorf("error ensuring service: %w", err) + } + + } + + return nil +} + +// CheckGlobalServiceExecutionConsistency - Check that global services are defined only once in all nodesets, report and fail if there are duplicates +func CheckGlobalServiceExecutionConsistency(ctx context.Context, helper *helper.Helper, nodesets []dataplanev1.OpenStackDataPlaneNodeSet) error { + var globalServices []string + var allServices []string + + for _, nodeset := range nodesets { + allServices = append(allServices, nodeset.Spec.Services...) + } + for _, svc := range allServices { + service, err := GetService(ctx, helper, svc) + if err != nil { + helper.GetLogger().Error(err, fmt.Sprintf("error getting service %s for consistency check", svc)) + return err + } + + if service.Spec.DeployOnAllNodeSets { + if serviceInList(service.Name, globalServices) { + return fmt.Errorf("global service %s defined multiple times", service.Name) + } + globalServices = append(globalServices, service.Name) + } + } + + return nil +} + +// Check if service name is already in a list +func serviceInList(service string, services []string) bool { + for _, svc := range services { + if svc == service { + return true + } + } + return false +} diff --git a/pkg/util/ansible_execution.go b/pkg/util/ansible_execution.go new file mode 100644 index 000000000..10626f642 --- /dev/null +++ b/pkg/util/ansible_execution.go @@ -0,0 +1,291 @@ +/* +Copyright 2023. + +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 util + +import ( + "context" + "encoding/json" + "fmt" + "sort" + "strings" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + "github.com/openstack-k8s-operators/lib-common/modules/common/helper" + "github.com/openstack-k8s-operators/lib-common/modules/common/util" + "github.com/openstack-k8s-operators/lib-common/modules/storage" + ansibleeev1 "github.com/openstack-k8s-operators/openstack-ansibleee-operator/api/v1beta1" + dataplanev1 "github.com/openstack-k8s-operators/openstack-operator/apis/dataplane/v1beta1" +) + +// AnsibleExecution creates a OpenStackAnsiblEE CR +func AnsibleExecution( + ctx context.Context, + helper *helper.Helper, + deployment *dataplanev1.OpenStackDataPlaneDeployment, + service *dataplanev1.OpenStackDataPlaneService, + sshKeySecrets map[string]string, + inventorySecrets map[string]string, + aeeSpec *dataplanev1.AnsibleEESpec, + nodeSet client.Object, +) error { + var err error + var cmdLineArguments strings.Builder + var inventoryVolume corev1.Volume + var inventoryName string + var inventoryMountPath string + var sshKeyName string + var sshKeyMountPath string + var sshKeyMountSubPath string + + ansibleEEMounts := storage.VolMounts{} + + executionName, labels := GetAnsibleExecutionNameAndLabels(service, deployment.GetName(), nodeSet.GetName()) + ansibleEE, err := GetAnsibleExecution(ctx, helper, deployment, labels) + if err != nil && !k8serrors.IsNotFound(err) { + return err + } + if ansibleEE == nil { + ansibleEE = &ansibleeev1.OpenStackAnsibleEE{ + ObjectMeta: metav1.ObjectMeta{ + Name: executionName, + Namespace: deployment.GetNamespace(), + Labels: labels, + }, + } + } + + _, err = controllerutil.CreateOrPatch(ctx, helper.GetClient(), ansibleEE, func() error { + ansibleEE.Spec.NetworkAttachments = aeeSpec.NetworkAttachments + if aeeSpec.DNSConfig != nil { + ansibleEE.Spec.DNSConfig = aeeSpec.DNSConfig + } + if len(aeeSpec.OpenStackAnsibleEERunnerImage) > 0 { + ansibleEE.Spec.Image = aeeSpec.OpenStackAnsibleEERunnerImage + } + if len(aeeSpec.ExtraVars) > 0 { + ansibleEE.Spec.ExtraVars = aeeSpec.ExtraVars + } + if len(aeeSpec.AnsibleTags) > 0 { + fmt.Fprintf(&cmdLineArguments, "--tags %s ", aeeSpec.AnsibleTags) + } + if len(aeeSpec.AnsibleLimit) > 0 { + fmt.Fprintf(&cmdLineArguments, "--limit %s ", aeeSpec.AnsibleLimit) + } + if len(aeeSpec.AnsibleSkipTags) > 0 { + fmt.Fprintf(&cmdLineArguments, "--skip-tags %s ", aeeSpec.AnsibleSkipTags) + } + if len(aeeSpec.ServiceAccountName) > 0 { + ansibleEE.Spec.ServiceAccountName = aeeSpec.ServiceAccountName + } + if cmdLineArguments.Len() > 0 { + ansibleEE.Spec.CmdLine = strings.TrimSpace(cmdLineArguments.String()) + } + + if len(service.Spec.Play) > 0 { + ansibleEE.Spec.Play = service.Spec.Play + } + if len(service.Spec.Playbook) > 0 { + ansibleEE.Spec.Playbook = service.Spec.Playbook + } + + // If we have a service that ought to be deployed everywhere + // substitute the existing play target with 'all' + // Check if we have ExtraVars before accessing it + if ansibleEE.Spec.ExtraVars == nil { + ansibleEE.Spec.ExtraVars = make(map[string]json.RawMessage) + } + if service.Spec.DeployOnAllNodeSets { + ansibleEE.Spec.ExtraVars["edpm_override_hosts"] = json.RawMessage([]byte("\"all\"")) + util.LogForObject(helper, fmt.Sprintf("for service %s, substituting existing ansible play host with 'all'.", service.Name), ansibleEE) + } else { + ansibleEE.Spec.ExtraVars["edpm_override_hosts"] = json.RawMessage([]byte(fmt.Sprintf("\"%s\"", nodeSet.GetName()))) + util.LogForObject(helper, + fmt.Sprintf("for service %s, substituting existing ansible play host with '%s'.", service.Name, nodeSet.GetName()), ansibleEE) + } + ansibleEE.Spec.ExtraVars["edpm_service_name"] = json.RawMessage([]byte(fmt.Sprintf("\"%s\"", service.Name))) + + for sshKeyNodeName, sshKeySecret := range sshKeySecrets { + if service.Spec.DeployOnAllNodeSets { + sshKeyName = fmt.Sprintf("ssh-key-%s", sshKeyNodeName) + sshKeyMountSubPath = fmt.Sprintf("ssh_key_%s", sshKeyNodeName) + sshKeyMountPath = fmt.Sprintf("/runner/env/ssh_key/%s", sshKeyMountSubPath) + } else { + if sshKeyNodeName != nodeSet.GetName() { + continue + } + sshKeyName = "ssh-key" + sshKeyMountSubPath = "ssh_key" + sshKeyMountPath = "/runner/env/ssh_key" + } + sshKeyVolume := corev1.Volume{ + Name: sshKeyName, + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: sshKeySecret, + Items: []corev1.KeyToPath{ + { + Key: "ssh-privatekey", + Path: sshKeyMountSubPath, + }, + }, + }, + }, + } + sshKeyMount := corev1.VolumeMount{ + Name: sshKeyName, + MountPath: sshKeyMountPath, + SubPath: sshKeyMountSubPath, + } + // Mount ssh secrets + ansibleEEMounts.Mounts = append(ansibleEEMounts.Mounts, sshKeyMount) + ansibleEEMounts.Volumes = append(ansibleEEMounts.Volumes, sshKeyVolume) + } + + // order the inventory keys otherwise it could lead to changing order and mount order changing + invKeys := make([]string, 0) + for k := range inventorySecrets { + invKeys = append(invKeys, k) + } + sort.Strings(invKeys) + + // Mounting inventory and secrets + for inventoryIndex, nodeName := range invKeys { + if service.Spec.DeployOnAllNodeSets { + inventoryName = fmt.Sprintf("inventory-%d", inventoryIndex) + inventoryMountPath = fmt.Sprintf("/runner/inventory/%s", inventoryName) + } else { + if nodeName != nodeSet.GetName() { + continue + } + inventoryName = "inventory" + inventoryMountPath = "/runner/inventory/hosts" + } + + inventoryVolume = corev1.Volume{ + Name: inventoryName, + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: inventorySecrets[nodeName], + Items: []corev1.KeyToPath{ + { + Key: "inventory", + Path: inventoryName, + }, + }, + }, + }, + } + inventoryMount := corev1.VolumeMount{ + Name: inventoryName, + MountPath: inventoryMountPath, + SubPath: inventoryName, + } + // Inventory mount + ansibleEEMounts.Mounts = append(ansibleEEMounts.Mounts, inventoryMount) + ansibleEEMounts.Volumes = append(ansibleEEMounts.Volumes, inventoryVolume) + } + + ansibleEE.Spec.ExtraMounts = append(aeeSpec.ExtraMounts, []storage.VolMounts{ansibleEEMounts}...) + ansibleEE.Spec.Env = aeeSpec.Env + + err := controllerutil.SetControllerReference(deployment, ansibleEE, helper.GetScheme()) + if err != nil { + return err + } + + return nil + }) + + if err != nil { + util.LogErrorForObject(helper, err, fmt.Sprintf("Unable to create AnsibleEE %s", ansibleEE.Name), ansibleEE) + return err + } + + return nil +} + +// GetAnsibleExecution gets and returns an OpenStackAnsibleEE with the given +// labels where +// "openstackdataplaneservice": , +// "openstackdataplanedeployment": , +// "openstackdataplanenodeset": , +// If none or more than one is found, return nil and error +func GetAnsibleExecution(ctx context.Context, + helper *helper.Helper, obj client.Object, labelSelector map[string]string) (*ansibleeev1.OpenStackAnsibleEE, error) { + var err error + ansibleEEs := &ansibleeev1.OpenStackAnsibleEEList{} + + listOpts := []client.ListOption{ + client.InNamespace(obj.GetNamespace()), + } + if len(labelSelector) > 0 { + labels := client.MatchingLabels(labelSelector) + listOpts = append(listOpts, labels) + } + err = helper.GetClient().List(ctx, ansibleEEs, listOpts...) + if err != nil { + return nil, err + } + + var ansibleEE *ansibleeev1.OpenStackAnsibleEE + if len(ansibleEEs.Items) == 0 { + return nil, k8serrors.NewNotFound(appsv1.Resource("OpenStackAnsibleEE"), fmt.Sprintf("with label %s", labelSelector)) + } else if len(ansibleEEs.Items) == 1 { + ansibleEE = &ansibleEEs.Items[0] + } else { + return nil, fmt.Errorf("multiple OpenStackAnsibleEE's found with label %s", labelSelector) + } + + return ansibleEE, nil +} + +// getAnsibleExecutionNamePrefix compute the name of the AnsibleEE +func getAnsibleExecutionNamePrefix(serviceName string) string { + var executionNamePrefix string + if len(serviceName) > AnsibleExecutionServiceNameLen { + executionNamePrefix = serviceName[:AnsibleExecutionServiceNameLen] + } else { + executionNamePrefix = serviceName + } + return executionNamePrefix +} + +// GetAnsibleExecutionNameAndLabels Name and Labels of AnsibleEE +func GetAnsibleExecutionNameAndLabels(service *dataplanev1.OpenStackDataPlaneService, + deploymentName string, + nodeSetName string) (string, map[string]string) { + executionName := fmt.Sprintf("%s-%s", getAnsibleExecutionNamePrefix(service.Name), deploymentName) + if !service.Spec.DeployOnAllNodeSets { + executionName = fmt.Sprintf("%s-%s", executionName, nodeSetName) + } + if len(executionName) > AnsibleExcecutionNameLabelLen { + executionName = executionName[:AnsibleExcecutionNameLabelLen] + } + + labels := map[string]string{ + "openstackdataplaneservice": service.Name, + "openstackdataplanedeployment": deploymentName, + "openstackdataplanenodeset": nodeSetName, + } + return executionName, labels +} diff --git a/pkg/util/const.go b/pkg/util/const.go new file mode 100644 index 000000000..e16b44ce1 --- /dev/null +++ b/pkg/util/const.go @@ -0,0 +1,24 @@ +/* +Copyright 2023. + +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 util + +const ( + // AnsibleExecutionServiceNameLen max length for the ansibleEE service name prefix + AnsibleExecutionServiceNameLen = 53 + // AnsibleExcecutionNameLabelLen max length for the ansibleEE execution name + AnsibleExcecutionNameLabelLen = 63 +) diff --git a/pkg/util/version.go b/pkg/util/version.go new file mode 100644 index 000000000..c5fe9af45 --- /dev/null +++ b/pkg/util/version.go @@ -0,0 +1,92 @@ +/* +Copyright 2024. + +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 util + +import ( + "context" + "errors" + + "github.com/openstack-k8s-operators/lib-common/modules/common/helper" + openstackv1 "github.com/openstack-k8s-operators/openstack-operator/apis/core/v1beta1" + dataplanev1 "github.com/openstack-k8s-operators/openstack-operator/apis/dataplane/v1beta1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// GetVersion - Get OpenStackVersion and assume at most 1 should exist +func GetVersion(ctx context.Context, helper *helper.Helper, namespace string) (*openstackv1.OpenStackVersion, error) { + log := helper.GetLogger() + var version *openstackv1.OpenStackVersion + versions := &openstackv1.OpenStackVersionList{} + opts := []client.ListOption{ + client.InNamespace(namespace), + } + if err := helper.GetClient().List(ctx, versions, opts...); err != nil { + log.Error(err, "Unable to retrieve OpenStackVersions %w") + return nil, err + } + if len(versions.Items) > 1 { + errorMsg := "Found multiple OpenStackVersions when at most 1 should exist" + err := errors.New(errorMsg) + log.Error(err, errorMsg) + return nil, err + } else if len(versions.Items) == 1 { + version = &versions.Items[0] + } else { + version = nil + } + + return version, nil +} + +// GetContainerImages - get the container image values considering either the +// OpenStackVersion or the defaults +func GetContainerImages(version *openstackv1.OpenStackVersion) openstackv1.ContainerImages { + + var containerImages openstackv1.ContainerImages + + // Set the containerImages variable for the container images If there is an + // OpenStackVersion, use the value from there, else use the default value. + if version != nil { + containerImages.CeilometerComputeImage = version.Status.ContainerImages.CeilometerComputeImage + containerImages.CeilometerIpmiImage = version.Status.ContainerImages.CeilometerIpmiImage + containerImages.EdpmFrrImage = version.Status.ContainerImages.EdpmFrrImage + containerImages.EdpmIscsidImage = version.Status.ContainerImages.EdpmIscsidImage + containerImages.EdpmLogrotateCrondImage = version.Status.ContainerImages.EdpmLogrotateCrondImage + containerImages.EdpmMultipathdImage = version.Status.ContainerImages.EdpmMultipathdImage + containerImages.EdpmNeutronMetadataAgentImage = version.Status.ContainerImages.EdpmNeutronMetadataAgentImage + containerImages.EdpmNeutronSriovAgentImage = version.Status.ContainerImages.EdpmNeutronSriovAgentImage + containerImages.EdpmNodeExporterImage = version.Status.ContainerImages.EdpmNodeExporterImage + containerImages.EdpmOvnBgpAgentImage = version.Status.ContainerImages.EdpmOvnBgpAgentImage + containerImages.NovaComputeImage = version.Status.ContainerImages.NovaComputeImage + containerImages.OvnControllerImage = version.Status.ContainerImages.OvnControllerImage + } else { + containerImages.CeilometerComputeImage = dataplanev1.ContainerImages.CeilometerComputeImage + containerImages.CeilometerIpmiImage = dataplanev1.ContainerImages.CeilometerIpmiImage + containerImages.EdpmFrrImage = dataplanev1.ContainerImages.EdpmFrrImage + containerImages.EdpmIscsidImage = dataplanev1.ContainerImages.EdpmIscsidImage + containerImages.EdpmLogrotateCrondImage = dataplanev1.ContainerImages.EdpmLogrotateCrondImage + containerImages.EdpmMultipathdImage = dataplanev1.ContainerImages.EdpmMultipathdImage + containerImages.EdpmNeutronMetadataAgentImage = dataplanev1.ContainerImages.EdpmNeutronMetadataAgentImage + containerImages.EdpmNeutronSriovAgentImage = dataplanev1.ContainerImages.EdpmNeutronSriovAgentImage + containerImages.EdpmNodeExporterImage = dataplanev1.ContainerImages.EdpmNodeExporterImage + containerImages.EdpmOvnBgpAgentImage = dataplanev1.ContainerImages.EdpmOvnBgpAgentImage + containerImages.NovaComputeImage = dataplanev1.ContainerImages.NovaComputeImage + containerImages.OvnControllerImage = dataplanev1.ContainerImages.OvnControllerImage + } + + return containerImages +} diff --git a/tests/functional/base_test.go b/tests/functional/base_test.go index 6eb060015..89f90c0ea 100644 --- a/tests/functional/base_test.go +++ b/tests/functional/base_test.go @@ -18,18 +18,36 @@ package functional_test import ( "encoding/base64" + "fmt" - . "github.com/onsi/gomega" - + . "github.com/onsi/gomega" //revive:disable:dot-imports + "gopkg.in/yaml.v3" k8s_corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" + infrav1 "github.com/openstack-k8s-operators/infra-operator/apis/network/v1beta1" "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + "github.com/openstack-k8s-operators/openstack-ansibleee-operator/api/v1beta1" openstackclientv1 "github.com/openstack-k8s-operators/openstack-operator/apis/client/v1beta1" corev1 "github.com/openstack-k8s-operators/openstack-operator/apis/core/v1beta1" + dataplanev1 "github.com/openstack-k8s-operators/openstack-operator/apis/dataplane/v1beta1" ) +var DefaultEdpmServiceAnsibleVarList = []string{ + "edpm_frr_image", + "edpm_iscsid_image", + "edpm_logrotate_crond_image", + "edpm_neutron_metadata_agent_image", + "edpm_nova_compute_image", + "edpm_ovn_controller_agent_image", + "edpm_ovn_bgp_agent_image", +} + +var CustomEdpmServiceDomainTag = "test-image:latest" + type Names struct { Namespace string OpenStackControlplaneName types.NamespacedName @@ -401,3 +419,323 @@ func CreateOvnCACertSecret(name types.NamespacedName) *k8s_corev1.Secret { return s } + +// Create OpenstackDataPlaneNodeSet in k8s and test that no errors occur +func CreateDataplaneNodeSet(name types.NamespacedName, spec map[string]interface{}) *unstructured.Unstructured { + instance := DefaultDataplaneNodeSetTemplate(name, spec) + return th.CreateUnstructured(instance) +} + +// Create OpenStackDataPlaneDeployment in k8s and test that no errors occur +func CreateDataplaneDeployment(name types.NamespacedName, spec map[string]interface{}) *unstructured.Unstructured { + instance := DefaultDataplaneDeploymentTemplate(name, spec) + return th.CreateUnstructured(instance) +} + +// Create an OpenStackDataPlaneService with a given NamespacedName, assert on success +func CreateDataplaneService(name types.NamespacedName, globalService bool) *unstructured.Unstructured { + var raw map[string]interface{} + if globalService { + raw = DefaultDataplaneGlobalService(name) + } else { + raw = DefaultDataplaneService(name) + } + return th.CreateUnstructured(raw) +} + +// Build CustomServiceImageSpec struct with empty `Nodes` list +func CustomServiceImageSpec() map[string]interface{} { + + var ansibleServiceVars = make(map[string]interface{}) + for _, svcName := range DefaultEdpmServiceAnsibleVarList { + imageAddress := fmt.Sprintf(`"%s.%s"`, svcName, CustomEdpmServiceDomainTag) + ansibleServiceVars[svcName] = imageAddress + } + + return map[string]interface{}{ + "preProvisioned": true, + "nodeTemplate": map[string]interface{}{ + "ansibleSSHPrivateKeySecret": "dataplane-ansible-ssh-private-key-secret", + "ansible": map[string]interface{}{ + "ansibleVars": ansibleServiceVars, + }, + }, + "nodes": map[string]interface{}{}, + } +} + +func CreateNetConfig(name types.NamespacedName, spec map[string]interface{}) *unstructured.Unstructured { + raw := DefaultNetConfig(name, spec) + return th.CreateUnstructured(raw) +} + +// Create SSHSecret +func CreateSSHSecret(name types.NamespacedName) *k8s_corev1.Secret { + return th.CreateSecret( + types.NamespacedName{Namespace: name.Namespace, Name: name.Name}, + map[string][]byte{ + "ssh-privatekey": []byte("blah"), + "authorized_keys": []byte("blih"), + }, + ) +} + +// Struct initialization + +// Build OpenStackDataPlaneNodeSetSpec struct and fill it with preset values +func DefaultDataPlaneNodeSetSpec(nodeSetName string) map[string]interface{} { + + return map[string]interface{}{ + "services": []string{ + "foo-service", + "global-service", + }, + "nodeTemplate": map[string]interface{}{ + "ansibleSSHPrivateKeySecret": "dataplane-ansible-ssh-private-key-secret", + "ansible": map[string]interface{}{ + "ansibleUser": "cloud-user", + }, + }, + "nodes": map[string]interface{}{ + fmt.Sprintf("%s-node-1", nodeSetName): map[string]interface{}{ + "hostname": "edpm-bm-compute-1", + "networks": []map[string]interface{}{{ + "name": "CtlPlane", + "fixedIP": "172.20.12.76", + "subnetName": "ctlplane_subnet", + }, + }, + }, + }, + "baremetalSetTemplate": map[string]interface{}{ + "baremetalHosts": map[string]interface{}{ + "ctlPlaneIP": map[string]interface{}{}, + }, + "deploymentSSHSecret": "dataplane-ansible-ssh-private-key-secret", + "ctlplaneInterface": "172.20.12.1", + }, + "secretMaxSize": 1048576, + "tlsEnabled": true, + } +} + +// Build OpenStackDataPlaneNodeSetSpec struct with empty `Nodes` list +func DefaultDataPlaneNoNodeSetSpec(tlsEnabled bool) map[string]interface{} { + spec := map[string]interface{}{ + "preProvisioned": true, + "nodeTemplate": map[string]interface{}{ + "ansibleSSHPrivateKeySecret": "dataplane-ansible-ssh-private-key-secret", + }, + "nodes": map[string]interface{}{}, + "servicesOverride": []string{}, + } + if tlsEnabled { + spec["tlsEnabled"] = true + } + return spec +} + +// Build OpenStackDataPlnaeDeploymentSpec and fill it with preset values +func DefaultDataPlaneDeploymentSpec() map[string]interface{} { + + return map[string]interface{}{ + "nodeSets": []string{ + "edpm-compute-nodeset", + }, + "servicesOverride": []string{}, + } +} + +func DefaultNetConfigSpec() map[string]interface{} { + return map[string]interface{}{ + "networks": []map[string]interface{}{{ + "dnsDomain": "test-domain.test", + "mtu": 1500, + "name": "CtlPLane", + "subnets": []map[string]interface{}{{ + "allocationRanges": []map[string]interface{}{{ + "end": "172.20.12.120", + "start": "172.20.12.0", + }, + }, + "name": "ctlplane_subnet", + "cidr": "172.20.12.0/16", + "gateway": "172.20.12.1", + }, + }, + }, + }, + } +} + +// SimulateIPSetComplete - Simulates the result of the IPSet status +func SimulateIPSetComplete(name types.NamespacedName) { + Eventually(func(g Gomega) { + IPSet := &infrav1.IPSet{} + g.Expect(th.K8sClient.Get(th.Ctx, name, IPSet)).Should(Succeed()) + gateway := "172.20.12.1" + IPSet.Status.Reservation = []infrav1.IPSetReservation{ + { + Address: "172.20.12.76", + Cidr: "172.20.12.0/16", + MTU: 1500, + Network: "CtlPlane", + Subnet: "subnet1", + Gateway: &gateway, + }, + } + // This can return conflict so we have the gomega.Eventually block to retry + g.Expect(th.K8sClient.Status().Update(th.Ctx, IPSet)).To(Succeed()) + + }, th.Timeout, th.Interval).Should(Succeed()) + + th.Logger.Info("Simulated DB completed", "on", name) +} + +// Build OpenStackDataPlaneNodeSet struct and fill it with preset values +func DefaultDataplaneNodeSetTemplate(name types.NamespacedName, spec map[string]interface{}) map[string]interface{} { + return map[string]interface{}{ + + "apiVersion": "dataplane.openstack.org/v1beta1", + "kind": "OpenStackDataPlaneNodeSet", + "metadata": map[string]interface{}{ + "name": name.Name, + "namespace": name.Namespace, + }, + "spec": spec, + } +} + +// Build OpenStackDataPlaneDeployment struct and fill it with preset values +func DefaultDataplaneDeploymentTemplate(name types.NamespacedName, spec map[string]interface{}) map[string]interface{} { + return map[string]interface{}{ + + "apiVersion": "dataplane.openstack.org/v1beta1", + "kind": "OpenStackDataPlaneDeployment", + + "metadata": map[string]interface{}{ + "name": name.Name, + "namespace": name.Namespace, + }, + "spec": spec, + } +} + +func DefaultNetConfig(name types.NamespacedName, spec map[string]interface{}) map[string]interface{} { + return map[string]interface{}{ + "apiVersion": "network.openstack.org/v1beta1", + "kind": "NetConfig", + "metadata": map[string]interface{}{ + "name": name.Name, + "namespace": name.Namespace, + }, + "spec": spec, + } +} + +// Create an empty OpenStackDataPlaneService struct +// containing only given NamespacedName as metadata +func DefaultDataplaneService(name types.NamespacedName) map[string]interface{} { + + return map[string]interface{}{ + + "apiVersion": "dataplane.openstack.org/v1beta1", + "kind": "OpenStackDataPlaneService", + "metadata": map[string]interface{}{ + "name": name.Name, + "namespace": name.Namespace, + }} +} + +// Create an empty OpenStackDataPlaneService struct +// containing only given NamespacedName as metadata +func DefaultDataplaneGlobalService(name types.NamespacedName) map[string]interface{} { + + return map[string]interface{}{ + + "apiVersion": "dataplane.openstack.org/v1beta1", + "kind": "OpenStackDataPlaneService", + "metadata": map[string]interface{}{ + "name": name.Name, + "namespace": name.Namespace, + }, + "spec": map[string]interface{}{ + "deployOnAllNodeSets": true, + }, + } +} + +// Get resources + +// Retrieve OpenStackDataPlaneDeployment and check for errors +func GetDataplaneDeployment(name types.NamespacedName) *dataplanev1.OpenStackDataPlaneDeployment { + instance := &dataplanev1.OpenStackDataPlaneDeployment{} + Eventually(func(g Gomega) error { + g.Expect(k8sClient.Get(ctx, name, instance)).Should(Succeed()) + return nil + }, timeout, interval).Should(Succeed()) + return instance +} + +// Retrieve OpenStackDataPlaneDeployment and check for errors +func GetDataplaneNodeSet(name types.NamespacedName) *dataplanev1.OpenStackDataPlaneNodeSet { + instance := &dataplanev1.OpenStackDataPlaneNodeSet{} + Eventually(func(g Gomega) error { + g.Expect(k8sClient.Get(ctx, name, instance)).Should(Succeed()) + return nil + }, timeout, interval).Should(Succeed()) + return instance +} + +// Get service with given NamespacedName, assert on successful retrieval +func GetService(name types.NamespacedName) *dataplanev1.OpenStackDataPlaneService { + foundService := &dataplanev1.OpenStackDataPlaneService{} + Eventually(func(g Gomega) error { + g.Expect(k8sClient.Get(ctx, name, foundService)).Should(Succeed()) + return nil + }, timeout, interval).Should(Succeed()) + return foundService +} + +// Get OpenStackDataPlaneNodeSet conditions +func DataplaneConditionGetter(name types.NamespacedName) condition.Conditions { + instance := GetDataplaneNodeSet(name) + return instance.Status.Conditions +} + +// Get OpenStackDataPlaneDeployment conditions +func DataplaneDeploymentConditionGetter(name types.NamespacedName) condition.Conditions { + instance := GetDataplaneDeployment(name) + return instance.Status.Conditions +} + +func GetAnsibleee(name types.NamespacedName) *v1beta1.OpenStackAnsibleEE { + instance := &v1beta1.OpenStackAnsibleEE{} + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, name, instance)).Should(Succeed()) + }, timeout, interval).Should(Succeed()) + return instance +} + +// Delete resources + +// Delete namespace from k8s, check for errors +func DeleteNamespace(name string) { + ns := &k8s_corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + } + Expect(k8sClient.Delete(ctx, ns)).Should(Succeed()) +} + +func getCtlPlaneIP(secret *k8s_corev1.Secret) string { + secretData := secret.Data["inventory"] + + var inv AnsibleInventory + err := yaml.Unmarshal(secretData, &inv) + if err != nil { + fmt.Printf("Error unmarshalling secretData: %v", err) + } + return inv.EdpmComputeNodeset.Hosts.Node.CtlPlaneIP +} diff --git a/tests/functional/openstackdataplanedeployment_controller_test.go b/tests/functional/openstackdataplanedeployment_controller_test.go new file mode 100644 index 000000000..f84abb322 --- /dev/null +++ b/tests/functional/openstackdataplanedeployment_controller_test.go @@ -0,0 +1,667 @@ +package functional_test + +import ( + "fmt" + "os" + + . "github.com/onsi/ginkgo/v2" //revive:disable:dot-imports + . "github.com/onsi/gomega" //revive:disable:dot-imports + "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + dataplanev1 "github.com/openstack-k8s-operators/openstack-operator/apis/dataplane/v1beta1" + dataplaneutil "github.com/openstack-k8s-operators/openstack-operator/pkg/util" + + //revive:disable-next-line:dot-imports + . "github.com/openstack-k8s-operators/lib-common/modules/common/test/helpers" + ansibleeev1 "github.com/openstack-k8s-operators/openstack-ansibleee-operator/api/v1beta1" + baremetalv1 "github.com/openstack-k8s-operators/openstack-baremetal-operator/api/v1beta1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "k8s.io/apimachinery/pkg/types" +) + +var _ = Describe("Dataplane Deployment Test", func() { + var dataplaneDeploymentName types.NamespacedName + var dataplaneNodeSetName types.NamespacedName + var dataplaneSSHSecretName types.NamespacedName + var neutronOvnMetadataSecretName types.NamespacedName + var novaNeutronMetadataSecretName types.NamespacedName + var novaCellComputeConfigSecretName types.NamespacedName + var novaMigrationSSHKey types.NamespacedName + var ceilometerConfigSecretName types.NamespacedName + var dataplaneNetConfigName types.NamespacedName + var dataplaneMultiNodesetDeploymentName types.NamespacedName + var dataplaneServiceName types.NamespacedName + var dataplaneGlobalServiceName types.NamespacedName + + BeforeEach(func() { + dataplaneDeploymentName = types.NamespacedName{ + Name: "edpm-deployment", + Namespace: namespace, + } + dataplaneNodeSetName = types.NamespacedName{ + Name: "edpm-compute-nodeset", + Namespace: namespace, + } + dataplaneSSHSecretName = types.NamespacedName{ + Namespace: namespace, + Name: "dataplane-ansible-ssh-private-key-secret", + } + neutronOvnMetadataSecretName = types.NamespacedName{ + Namespace: namespace, + Name: "neutron-ovn-metadata-agent-neutron-config", + } + novaNeutronMetadataSecretName = types.NamespacedName{ + Namespace: namespace, + Name: "nova-metadata-neutron-config", + } + novaCellComputeConfigSecretName = types.NamespacedName{ + Namespace: namespace, + Name: "nova-cell1-compute-config", + } + novaMigrationSSHKey = types.NamespacedName{ + Namespace: namespace, + Name: "nova-migration-ssh-key", + } + ceilometerConfigSecretName = types.NamespacedName{ + Namespace: namespace, + Name: "ceilometer-compute-config-data", + } + dataplaneNetConfigName = types.NamespacedName{ + Namespace: namespace, + Name: "dataplane-netconfig", + } + dataplaneMultiNodesetDeploymentName = types.NamespacedName{ + Namespace: namespace, + Name: "edpm-compute-nodeset-global", + } + dataplaneServiceName = types.NamespacedName{ + Namespace: namespace, + Name: "foo-service", + } + dataplaneGlobalServiceName = types.NamespacedName{ + Name: "global-service", + Namespace: namespace, + } + err := os.Setenv("OPERATOR_SERVICES", "../../config/services") + Expect(err).NotTo(HaveOccurred()) + }) + + When("A dataplaneDeployment is created with matching NodeSet", func() { + BeforeEach(func() { + CreateSSHSecret(dataplaneSSHSecretName) + DeferCleanup(th.DeleteInstance, th.CreateSecret(neutronOvnMetadataSecretName, map[string][]byte{ + "fake_keys": []byte("blih"), + })) + DeferCleanup(th.DeleteInstance, th.CreateSecret(novaNeutronMetadataSecretName, map[string][]byte{ + "fake_keys": []byte("blih"), + })) + DeferCleanup(th.DeleteInstance, th.CreateSecret(novaCellComputeConfigSecretName, map[string][]byte{ + "fake_keys": []byte("blih"), + })) + DeferCleanup(th.DeleteInstance, th.CreateSecret(novaMigrationSSHKey, map[string][]byte{ + "ssh-privatekey": []byte("fake-ssh-private-key"), + "ssh-publickey": []byte("fake-ssh-public-key"), + })) + DeferCleanup(th.DeleteInstance, th.CreateSecret(ceilometerConfigSecretName, map[string][]byte{ + "fake_keys": []byte("blih"), + })) + // DefaultDataPlanenodeSetSpec comes with two mock services, one marked for deployment on all nodesets + CreateDataplaneService(dataplaneServiceName, false) + CreateDataplaneService(dataplaneGlobalServiceName, true) + + DeferCleanup(th.DeleteService, dataplaneServiceName) + DeferCleanup(th.DeleteService, dataplaneGlobalServiceName) + DeferCleanup(th.DeleteInstance, CreateNetConfig(dataplaneNetConfigName, DefaultNetConfigSpec())) + DeferCleanup(th.DeleteInstance, CreateDataplaneNodeSet(dataplaneNodeSetName, DefaultDataPlaneNodeSetSpec(dataplaneNodeSetName.Name))) + DeferCleanup(th.DeleteInstance, CreateDataplaneDeployment(dataplaneDeploymentName, DefaultDataPlaneDeploymentSpec())) + }) + + It("Should have Spec fields initialized", func() { + dataplaneDeploymentInstance := GetDataplaneDeployment(dataplaneDeploymentName) + expectedSpec := dataplanev1.OpenStackDataPlaneDeploymentSpec{ + NodeSets: []string{"edpm-compute-nodeset"}, + AnsibleTags: "", + AnsibleLimit: "", + AnsibleSkipTags: "", + DeploymentRequeueTime: 15, + ServicesOverride: nil, + } + Expect(dataplaneDeploymentInstance.Spec).Should(Equal(expectedSpec)) + }) + + It("should have conditions set", func() { + + nodeSet := dataplanev1.OpenStackDataPlaneNodeSet{} + baremetal := baremetalv1.OpenStackBaremetalSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: nodeSet.Name, + Namespace: nodeSet.Namespace, + }, + } + // Create config map for OVN service + ovnConfigMapName := types.NamespacedName{ + Namespace: namespace, + Name: "ovncontroller-config", + } + mapData := map[string]interface{}{ + "ovsdb-config": "test-ovn-config", + } + th.CreateConfigMap(ovnConfigMapName, mapData) + + nodeSet = *GetDataplaneNodeSet(dataplaneNodeSetName) + + // Set baremetal provisioning conditions to True + Eventually(func(g Gomega) { + // OpenStackBaremetalSet has the same name as OpenStackDataPlaneNodeSet + g.Expect(th.K8sClient.Get(th.Ctx, dataplaneNodeSetName, &baremetal)).To(Succeed()) + baremetal.Status.Conditions.MarkTrue( + condition.ReadyCondition, + condition.ReadyMessage) + g.Expect(th.K8sClient.Status().Update(th.Ctx, &baremetal)).To(Succeed()) + + }, th.Timeout, th.Interval).Should(Succeed()) + + // Create all services necessary for deployment + for _, serviceName := range nodeSet.Spec.Services { + dataplaneServiceName := types.NamespacedName{ + Name: serviceName, + Namespace: namespace, + } + service := GetService(dataplaneServiceName) + deployment := GetDataplaneDeployment(dataplaneDeploymentName) + //Retrieve service AnsibleEE and set JobStatus to Successful + aeeName, _ := dataplaneutil.GetAnsibleExecutionNameAndLabels( + service, deployment.GetName(), nodeSet.GetName()) + Eventually(func(g Gomega) { + // Make an AnsibleEE name for each service + ansibleeeName := types.NamespacedName{ + Name: aeeName, + Namespace: dataplaneDeploymentName.Namespace, + } + ansibleEE := &ansibleeev1.OpenStackAnsibleEE{ + ObjectMeta: metav1.ObjectMeta{ + Name: ansibleeeName.Name, + Namespace: ansibleeeName.Namespace, + }} + g.Expect(th.K8sClient.Get(th.Ctx, ansibleeeName, ansibleEE)).To(Succeed()) + ansibleEE.Status.JobStatus = ansibleeev1.JobStatusSucceeded + + g.Expect(th.K8sClient.Status().Update(th.Ctx, ansibleEE)).To(Succeed()) + g.Expect(string(ansibleEE.Spec.ExtraVars["edpm_service_name"])).To(Equal(fmt.Sprintf("\"%s\"", serviceName))) + g.Expect(ansibleEE.Spec.ExtraVars).To(HaveKey("edpm_override_hosts")) + if service.Spec.DeployOnAllNodeSets { + g.Expect(string(ansibleEE.Spec.ExtraVars["edpm_override_hosts"])).To(Equal("\"all\"")) + } else { + g.Expect(string(ansibleEE.Spec.ExtraVars["edpm_override_hosts"])).To(Equal(fmt.Sprintf("\"%s\"", dataplaneNodeSetName.Name))) + } + }, th.Timeout, th.Interval).Should(Succeed()) + } + + th.ExpectCondition( + dataplaneDeploymentName, + ConditionGetterFunc(DataplaneDeploymentConditionGetter), + condition.ReadyCondition, + corev1.ConditionTrue, + ) + th.ExpectCondition( + dataplaneDeploymentName, + ConditionGetterFunc(DataplaneDeploymentConditionGetter), + condition.InputReadyCondition, + corev1.ConditionTrue, + ) + }) + }) + + When("A dataplaneDeployment is created with two NodeSets", func() { + BeforeEach(func() { + CreateSSHSecret(dataplaneSSHSecretName) + DeferCleanup(th.DeleteInstance, th.CreateSecret(neutronOvnMetadataSecretName, map[string][]byte{ + "fake_keys": []byte("blih"), + })) + DeferCleanup(th.DeleteInstance, th.CreateSecret(novaNeutronMetadataSecretName, map[string][]byte{ + "fake_keys": []byte("blih"), + })) + DeferCleanup(th.DeleteInstance, th.CreateSecret(novaCellComputeConfigSecretName, map[string][]byte{ + "fake_keys": []byte("blih"), + })) + DeferCleanup(th.DeleteInstance, th.CreateSecret(novaMigrationSSHKey, map[string][]byte{ + "ssh-privatekey": []byte("fake-ssh-private-key"), + "ssh-publickey": []byte("fake-ssh-public-key"), + })) + DeferCleanup(th.DeleteInstance, th.CreateSecret(ceilometerConfigSecretName, map[string][]byte{ + "fake_keys": []byte("blih"), + })) + + alphaNodeSetName := types.NamespacedName{ + Name: "alpha-nodeset", + Namespace: namespace, + } + betaNodeSetName := types.NamespacedName{ + Name: "beta-nodeset", + Namespace: namespace, + } + + // Two services on both nodesets + CreateDataplaneService(dataplaneServiceName, false) + CreateDataplaneService(dataplaneGlobalServiceName, true) + + DeferCleanup(th.DeleteService, dataplaneServiceName) + DeferCleanup(th.DeleteService, dataplaneGlobalServiceName) + + DeferCleanup(th.DeleteInstance, CreateNetConfig(dataplaneNetConfigName, DefaultNetConfigSpec())) + + // Create both nodesets + + betaNodeSetSpec := map[string]interface{}{ + "preProvisioned": false, + "services": []string{ + "foo-service", + }, + "nodeTemplate": map[string]interface{}{ + "ansibleSSHPrivateKeySecret": "dataplane-ansible-ssh-private-key-secret", + "ansible": map[string]interface{}{ + "ansibleUser": "cloud-user", + }, + }, + "nodes": map[string]interface{}{ + fmt.Sprintf("%s-node-1", betaNodeSetName.Name): map[string]interface{}{ + "hostname": "edpm-bm-compute-2", + "networks": []map[string]interface{}{{ + "name": "CtlPlane", + "fixedIP": "172.20.12.77", + "subnetName": "ctlplane_subnet", + }, + }, + }, + }, + "baremetalSetTemplate": map[string]interface{}{ + "baremetalHosts": map[string]interface{}{ + "ctlPlaneIP": map[string]interface{}{}, + }, + "deploymentSSHSecret": "dataplane-ansible-ssh-private-key-secret", + "ctlplaneInterface": "172.20.12.1", + }, + "tlsEnabled": true, + } + DeferCleanup(th.DeleteInstance, CreateDataplaneNodeSet(alphaNodeSetName, DefaultDataPlaneNodeSetSpec(alphaNodeSetName.Name))) + DeferCleanup(th.DeleteInstance, CreateDataplaneNodeSet(betaNodeSetName, betaNodeSetSpec)) + + deploymentSpec := map[string]interface{}{ + "nodeSets": []string{ + "alpha-nodeset", + "beta-nodeset", + }, + } + DeferCleanup(th.DeleteInstance, CreateDataplaneDeployment(dataplaneMultiNodesetDeploymentName, deploymentSpec)) + }) + + It("Should have Spec fields initialized", func() { + dataplaneDeploymentInstance := GetDataplaneDeployment(dataplaneMultiNodesetDeploymentName) + nodeSetsNames := []string{ + "alpha-nodeset", + "beta-nodeset", + } + + expectedSpec := dataplanev1.OpenStackDataPlaneDeploymentSpec{ + NodeSets: nodeSetsNames, + AnsibleTags: "", + AnsibleLimit: "", + AnsibleSkipTags: "", + DeploymentRequeueTime: 15, + ServicesOverride: nil, + } + Expect(dataplaneDeploymentInstance.Spec).Should(Equal(expectedSpec)) + }) + + It("should have conditions set", func() { + alphaNodeSetName := types.NamespacedName{ + Name: "alpha-nodeset", + Namespace: namespace, + } + betaNodeSetName := types.NamespacedName{ + Name: "beta-nodeset", + Namespace: namespace, + } + + baremetalAlpha := baremetalv1.OpenStackBaremetalSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: alphaNodeSetName.Name, + Namespace: alphaNodeSetName.Namespace, + }, + } + + baremetalBeta := baremetalv1.OpenStackBaremetalSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: betaNodeSetName.Name, + Namespace: betaNodeSetName.Namespace, + }, + } + + // Create config map for OVN service + ovnConfigMapName := types.NamespacedName{ + Namespace: namespace, + Name: "ovncontroller-config", + } + mapData := map[string]interface{}{ + "ovsdb-config": "test-ovn-config", + } + th.CreateConfigMap(ovnConfigMapName, mapData) + + nodeSetAlpha := *GetDataplaneNodeSet(alphaNodeSetName) + nodeSetBeta := *GetDataplaneNodeSet(betaNodeSetName) + + // Set baremetal provisioning conditions to True + Eventually(func(g Gomega) { + // OpenStackBaremetalSet has the same name as OpenStackDataPlaneNodeSet + g.Expect(th.K8sClient.Get(th.Ctx, alphaNodeSetName, &baremetalAlpha)).To(Succeed()) + baremetalAlpha.Status.Conditions.MarkTrue( + condition.ReadyCondition, + condition.ReadyMessage) + g.Expect(th.K8sClient.Status().Update(th.Ctx, &baremetalAlpha)).To(Succeed()) + // OpenStackBaremetalSet has the same name as OpenStackDataPlaneNodeSet + g.Expect(th.K8sClient.Get(th.Ctx, betaNodeSetName, &baremetalBeta)).To(Succeed()) + baremetalBeta.Status.Conditions.MarkTrue( + condition.ReadyCondition, + condition.ReadyMessage) + g.Expect(th.K8sClient.Status().Update(th.Ctx, &baremetalBeta)).To(Succeed()) + + }, th.Timeout, th.Interval).Should(Succeed()) + + // Create all services necessary for deployment + for _, serviceName := range nodeSetAlpha.Spec.Services { + dataplaneServiceName := types.NamespacedName{ + Name: serviceName, + Namespace: namespace, + } + service := GetService(dataplaneServiceName) + deployment := GetDataplaneDeployment(dataplaneMultiNodesetDeploymentName) + aeeName, _ := dataplaneutil.GetAnsibleExecutionNameAndLabels( + service, deployment.GetName(), nodeSetAlpha.GetName()) + //Retrieve service AnsibleEE and set JobStatus to Successful + Eventually(func(g Gomega) { + // Make an AnsibleEE name for each service + ansibleeeName := types.NamespacedName{ + Name: aeeName, + Namespace: dataplaneMultiNodesetDeploymentName.Namespace, + } + ansibleEE := GetAnsibleee(ansibleeeName) + if service.Spec.DeployOnAllNodeSets { + g.Expect(ansibleEE.Spec.ExtraMounts[0].Volumes).Should(HaveLen(4)) + } else { + g.Expect(ansibleEE.Spec.ExtraMounts[0].Volumes).Should(HaveLen(2)) + } + ansibleEE.Status.JobStatus = ansibleeev1.JobStatusSucceeded + g.Expect(th.K8sClient.Status().Update(th.Ctx, ansibleEE)).To(Succeed()) + g.Expect(string(ansibleEE.Spec.ExtraVars["edpm_service_name"])).To(Equal(fmt.Sprintf("\"%s\"", serviceName))) + if service.Spec.DeployOnAllNodeSets { + g.Expect(string(ansibleEE.Spec.ExtraVars["edpm_override_hosts"])).To(Equal("\"all\"")) + } + }, th.Timeout, th.Interval).Should(Succeed()) + } + + // Create all services necessary for deployment + for _, serviceName := range nodeSetBeta.Spec.Services { + dataplaneServiceName := types.NamespacedName{ + Name: serviceName, + Namespace: namespace, + } + service := GetService(dataplaneServiceName) + deployment := GetDataplaneDeployment(dataplaneMultiNodesetDeploymentName) + aeeName, _ := dataplaneutil.GetAnsibleExecutionNameAndLabels( + service, deployment.GetName(), nodeSetBeta.GetName()) + + //Retrieve service AnsibleEE and set JobStatus to Successful + Eventually(func(g Gomega) { + // Make an AnsibleEE name for each service + ansibleeeName := types.NamespacedName{ + Name: aeeName, + Namespace: dataplaneMultiNodesetDeploymentName.Namespace, + } + ansibleEE := GetAnsibleee(ansibleeeName) + if service.Spec.DeployOnAllNodeSets { + g.Expect(ansibleEE.Spec.ExtraMounts[0].Volumes).Should(HaveLen(4)) + } else { + g.Expect(ansibleEE.Spec.ExtraMounts[0].Volumes).Should(HaveLen(2)) + } + ansibleEE.Status.JobStatus = ansibleeev1.JobStatusSucceeded + g.Expect(th.K8sClient.Status().Update(th.Ctx, ansibleEE)).To(Succeed()) + g.Expect(string(ansibleEE.Spec.ExtraVars["edpm_service_name"])).To(Equal(fmt.Sprintf("\"%s\"", serviceName))) + }, th.Timeout, th.Interval).Should(Succeed()) + } + + th.ExpectCondition( + dataplaneMultiNodesetDeploymentName, + ConditionGetterFunc(DataplaneDeploymentConditionGetter), + condition.ReadyCondition, + corev1.ConditionTrue, + ) + th.ExpectCondition( + dataplaneMultiNodesetDeploymentName, + ConditionGetterFunc(DataplaneDeploymentConditionGetter), + condition.InputReadyCondition, + corev1.ConditionTrue, + ) + }) + }) + + When("A dataplaneDeployment is created with a missing nodeset", func() { + BeforeEach(func() { + CreateSSHSecret(dataplaneSSHSecretName) + DeferCleanup(th.DeleteInstance, th.CreateSecret(neutronOvnMetadataSecretName, map[string][]byte{ + "fake_keys": []byte("blih"), + })) + DeferCleanup(th.DeleteInstance, th.CreateSecret(novaNeutronMetadataSecretName, map[string][]byte{ + "fake_keys": []byte("blih"), + })) + DeferCleanup(th.DeleteInstance, th.CreateSecret(novaCellComputeConfigSecretName, map[string][]byte{ + "fake_keys": []byte("blih"), + })) + DeferCleanup(th.DeleteInstance, th.CreateSecret(novaMigrationSSHKey, map[string][]byte{ + "ssh-privatekey": []byte("fake-ssh-private-key"), + "ssh-publickey": []byte("fake-ssh-public-key"), + })) + DeferCleanup(th.DeleteInstance, th.CreateSecret(ceilometerConfigSecretName, map[string][]byte{ + "fake_keys": []byte("blih"), + })) + + alphaNodeSetName := types.NamespacedName{ + Name: "alpha-nodeset", + Namespace: namespace, + } + + // Two services on both nodesets + CreateDataplaneService(dataplaneServiceName, false) + + DeferCleanup(th.DeleteService, dataplaneServiceName) + + DeferCleanup(th.DeleteInstance, CreateNetConfig(dataplaneNetConfigName, DefaultNetConfigSpec())) + + // Create only one nodeset + DeferCleanup(th.DeleteInstance, CreateDataplaneNodeSet(alphaNodeSetName, DefaultDataPlaneNodeSetSpec(alphaNodeSetName.Name))) + + deploymentSpec := map[string]interface{}{ + "nodeSets": []string{ + "alpha-nodeset", + "beta-nodeset", + }, + } + DeferCleanup(th.DeleteInstance, CreateDataplaneDeployment(dataplaneMultiNodesetDeploymentName, deploymentSpec)) + }) + + It("Should have Spec fields initialized", func() { + dataplaneDeploymentInstance := GetDataplaneDeployment(dataplaneMultiNodesetDeploymentName) + nodeSetsNames := []string{ + "alpha-nodeset", + "beta-nodeset", + } + + expectedSpec := dataplanev1.OpenStackDataPlaneDeploymentSpec{ + NodeSets: nodeSetsNames, + AnsibleTags: "", + AnsibleLimit: "", + AnsibleSkipTags: "", + DeploymentRequeueTime: 15, + ServicesOverride: nil, + } + Expect(dataplaneDeploymentInstance.Spec).Should(Equal(expectedSpec)) + }) + + It("should have conditions set to unknown", func() { + alphaNodeSetName := types.NamespacedName{ + Name: "alpha-nodeset", + Namespace: namespace, + } + betaNodeSetName := types.NamespacedName{ + Name: "beta-nodeset", + Namespace: namespace, + } + + baremetalAlpha := baremetalv1.OpenStackBaremetalSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: alphaNodeSetName.Name, + Namespace: alphaNodeSetName.Namespace, + }, + } + + baremetalBeta := baremetalv1.OpenStackBaremetalSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: betaNodeSetName.Name, + Namespace: betaNodeSetName.Namespace, + }, + } + + // Create config map for OVN service + ovnConfigMapName := types.NamespacedName{ + Namespace: namespace, + Name: "ovncontroller-config", + } + mapData := map[string]interface{}{ + "ovsdb-config": "test-ovn-config", + } + th.CreateConfigMap(ovnConfigMapName, mapData) + + // Set baremetal provisioning conditions to True + // This must succeed, as the "alpha-nodeset" exists + Eventually(func(g Gomega) { + // OpenStackBaremetalSet has the same name as OpenStackDataPlaneNodeSet + g.Expect(th.K8sClient.Get(th.Ctx, alphaNodeSetName, &baremetalAlpha)).To(Succeed()) + baremetalAlpha.Status.Conditions.MarkTrue( + condition.ReadyCondition, + condition.ReadyMessage) + g.Expect(th.K8sClient.Status().Update(th.Ctx, &baremetalAlpha)).To(Succeed()) + + }, th.Timeout, th.Interval).Should(Succeed()) + + // These must fail, as there is no "beta-nodeset" + Expect(th.K8sClient.Get(th.Ctx, betaNodeSetName, &baremetalBeta)).NotTo(Succeed()) + baremetalBeta.Status.Conditions.MarkTrue( + condition.ReadyCondition, + condition.ReadyMessage) + Expect(th.K8sClient.Status().Update(th.Ctx, &baremetalBeta)).NotTo(Succeed()) + + // These conditions must remain unknown + th.ExpectCondition( + dataplaneMultiNodesetDeploymentName, + ConditionGetterFunc(DataplaneDeploymentConditionGetter), + condition.ReadyCondition, + corev1.ConditionUnknown, + ) + th.ExpectCondition( + dataplaneMultiNodesetDeploymentName, + ConditionGetterFunc(DataplaneDeploymentConditionGetter), + condition.InputReadyCondition, + corev1.ConditionUnknown, + ) + }) + }) + + When("A dataplaneDeployment is created with non-existent service in nodeset", func() { + BeforeEach(func() { + CreateSSHSecret(dataplaneSSHSecretName) + DeferCleanup(th.DeleteInstance, th.CreateSecret(neutronOvnMetadataSecretName, map[string][]byte{ + "fake_keys": []byte("blih"), + })) + DeferCleanup(th.DeleteInstance, th.CreateSecret(novaNeutronMetadataSecretName, map[string][]byte{ + "fake_keys": []byte("blih"), + })) + DeferCleanup(th.DeleteInstance, th.CreateSecret(novaCellComputeConfigSecretName, map[string][]byte{ + "fake_keys": []byte("blih"), + })) + DeferCleanup(th.DeleteInstance, th.CreateSecret(novaMigrationSSHKey, map[string][]byte{ + "ssh-privatekey": []byte("fake-ssh-private-key"), + "ssh-publickey": []byte("fake-ssh-public-key"), + })) + DeferCleanup(th.DeleteInstance, th.CreateSecret(ceilometerConfigSecretName, map[string][]byte{ + "fake_keys": []byte("blih"), + })) + // DefaultDataPlanenodeSetSpec comes with two mock services, one marked for deployment on all nodesets + // But we will not create them to test this scenario + DeferCleanup(th.DeleteInstance, CreateNetConfig(dataplaneNetConfigName, DefaultNetConfigSpec())) + DeferCleanup(th.DeleteInstance, CreateDataplaneNodeSet(dataplaneNodeSetName, DefaultDataPlaneNodeSetSpec(dataplaneNodeSetName.Name))) + DeferCleanup(th.DeleteInstance, CreateDataplaneDeployment(dataplaneDeploymentName, DefaultDataPlaneDeploymentSpec())) + }) + + It("Should have Spec fields initialized", func() { + dataplaneDeploymentInstance := GetDataplaneDeployment(dataplaneDeploymentName) + expectedSpec := dataplanev1.OpenStackDataPlaneDeploymentSpec{ + NodeSets: []string{"edpm-compute-nodeset"}, + AnsibleTags: "", + AnsibleLimit: "", + AnsibleSkipTags: "", + DeploymentRequeueTime: 15, + ServicesOverride: nil, + } + Expect(dataplaneDeploymentInstance.Spec).Should(Equal(expectedSpec)) + }) + + It("should have conditions set to false", func() { + + nodeSet := dataplanev1.OpenStackDataPlaneNodeSet{} + baremetal := baremetalv1.OpenStackBaremetalSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: nodeSet.Name, + Namespace: nodeSet.Namespace, + }, + } + // Create config map for OVN service + ovnConfigMapName := types.NamespacedName{ + Namespace: namespace, + Name: "ovncontroller-config", + } + mapData := map[string]interface{}{ + "ovsdb-config": "test-ovn-config", + } + th.CreateConfigMap(ovnConfigMapName, mapData) + + nodeSet = *GetDataplaneNodeSet(dataplaneNodeSetName) + + // Set baremetal provisioning conditions to True + Eventually(func(g Gomega) { + // OpenStackBaremetalSet has the same name as OpenStackDataPlaneNodeSet + g.Expect(th.K8sClient.Get(th.Ctx, dataplaneNodeSetName, &baremetal)).To(Succeed()) + baremetal.Status.Conditions.MarkTrue( + condition.ReadyCondition, + condition.ReadyMessage) + g.Expect(th.K8sClient.Status().Update(th.Ctx, &baremetal)).To(Succeed()) + + }, th.Timeout, th.Interval).Should(Succeed()) + // Attempt to get the service ... fail + foundService := &dataplanev1.OpenStackDataPlaneService{} + Expect(k8sClient.Get(ctx, dataplaneServiceName, foundService)).ShouldNot(Succeed()) + + th.ExpectCondition( + dataplaneDeploymentName, + ConditionGetterFunc(DataplaneDeploymentConditionGetter), + condition.ReadyCondition, + corev1.ConditionFalse, + ) + th.ExpectCondition( + dataplaneDeploymentName, + ConditionGetterFunc(DataplaneDeploymentConditionGetter), + condition.InputReadyCondition, + corev1.ConditionFalse, + ) + }) + }) +}) diff --git a/tests/functional/openstackdataplanenodeset_controller_test.go b/tests/functional/openstackdataplanenodeset_controller_test.go new file mode 100644 index 000000000..a3e17f900 --- /dev/null +++ b/tests/functional/openstackdataplanenodeset_controller_test.go @@ -0,0 +1,1061 @@ +/* +Copyright 2023. + +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 functional_test + +import ( + "encoding/json" + "fmt" + "os" + + . "github.com/onsi/ginkgo/v2" //revive:disable:dot-imports + . "github.com/onsi/gomega" //revive:disable:dot-imports + "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + dataplanev1 "github.com/openstack-k8s-operators/openstack-operator/apis/dataplane/v1beta1" + + //revive:disable-next-line:dot-imports + . "github.com/openstack-k8s-operators/lib-common/modules/common/test/helpers" + baremetalv1 "github.com/openstack-k8s-operators/openstack-baremetal-operator/api/v1beta1" + "gopkg.in/yaml.v3" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" +) + +// Ansible Inventory Structs for testing specific values +type AnsibleInventory struct { + EdpmComputeNodeset struct { + Vars struct { + AnsibleUser string `yaml:"ansible_user"` + } `yaml:"vars"` + Hosts struct { + Node struct { + AnsibleHost string `yaml:"ansible_host"` + AnsiblePort string `yaml:"ansible_port"` + AnsibleUser string `yaml:"ansible_user"` + CtlPlaneIP string `yaml:"ctlplane_ip"` + DNSSearchDomains []interface{} `yaml:"dns_search_domains"` + ManagementNetwork string `yaml:"management_network"` + Networks []interface{} `yaml:"networks"` + } `yaml:"edpm-compute-node-1"` + } `yaml:"hosts"` + } `yaml:"edpm-compute-nodeset"` +} + +var _ = Describe("Dataplane NodeSet Test", func() { + var dataplaneNodeSetName types.NamespacedName + var dataplaneSecretName types.NamespacedName + var dataplaneSSHSecretName types.NamespacedName + var dataplaneNetConfigName types.NamespacedName + var dataplaneIPSetName types.NamespacedName + var dataplaneDeploymentName types.NamespacedName + var dataplaneConfigHash string + var dataplaneGlobalServiceName types.NamespacedName + + defaultEdpmServiceList := []string{ + "edpm_frr_image", + "edpm_iscsid_image", + "edpm_logrotate_crond_image", + "edpm_neutron_metadata_agent_image", + "edpm_nova_compute_image", + "edpm_ovn_controller_agent_image", + "edpm_ovn_bgp_agent_image", + } + + BeforeEach(func() { + dataplaneNodeSetName = types.NamespacedName{ + Name: "edpm-compute-nodeset", + Namespace: namespace, + } + dataplaneSecretName = types.NamespacedName{ + Namespace: namespace, + Name: "dataplanenodeset-edpm-compute-nodeset", + } + dataplaneSSHSecretName = types.NamespacedName{ + Namespace: namespace, + Name: "dataplane-ansible-ssh-private-key-secret", + } + dataplaneNetConfigName = types.NamespacedName{ + Namespace: namespace, + Name: "dataplane-netconfig", + } + dataplaneIPSetName = types.NamespacedName{ + Namespace: namespace, + Name: "edpm-compute-node-1", + } + dataplaneDeploymentName = types.NamespacedName{ + Name: "edpm-deployment", + Namespace: namespace, + } + dataplaneGlobalServiceName = types.NamespacedName{ + Name: "global-service", + Namespace: namespace, + } + err := os.Setenv("OPERATOR_SERVICES", "../../config/services") + Expect(err).NotTo(HaveOccurred()) + }) + + When("TLS is enabled", func() { + tlsEnabled := true + When("A Dataplane resorce is created with PreProvisioned nodes, no deployment", func() { + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreateDataplaneNodeSet(dataplaneNodeSetName, DefaultDataPlaneNoNodeSetSpec(tlsEnabled))) + }) + It("should have the Spec fields initialized", func() { + dataplaneNodeSetInstance := GetDataplaneNodeSet(dataplaneNodeSetName) + emptyNodeSpec := dataplanev1.OpenStackDataPlaneNodeSetSpec{ + BaremetalSetTemplate: baremetalv1.OpenStackBaremetalSetSpec{ + BaremetalHosts: nil, + OSImage: "", + UserData: nil, + NetworkData: nil, + AutomatedCleaningMode: "metadata", + ProvisionServerName: "", + ProvisioningInterface: "", + CtlplaneInterface: "", + CtlplaneGateway: "", + CtlplaneNetmask: "255.255.255.0", + BmhNamespace: "openshift-machine-api", + HardwareReqs: baremetalv1.HardwareReqs{ + CPUReqs: baremetalv1.CPUReqs{ + Arch: "", + CountReq: baremetalv1.CPUCountReq{Count: 0, ExactMatch: false}, + MhzReq: baremetalv1.CPUMhzReq{Mhz: 0, ExactMatch: false}, + }, + MemReqs: baremetalv1.MemReqs{ + GbReq: baremetalv1.MemGbReq{Gb: 0, ExactMatch: false}, + }, + DiskReqs: baremetalv1.DiskReqs{ + GbReq: baremetalv1.DiskGbReq{Gb: 0, ExactMatch: false}, + SSDReq: baremetalv1.DiskSSDReq{SSD: false, ExactMatch: false}, + }, + }, + PasswordSecret: nil, + CloudUserName: "", + DomainName: "", + BootstrapDNS: nil, + DNSSearchDomains: nil, + }, + NodeTemplate: dataplanev1.NodeTemplate{ + AnsibleSSHPrivateKeySecret: "dataplane-ansible-ssh-private-key-secret", + Networks: nil, + ManagementNetwork: "ctlplane", + Ansible: dataplanev1.AnsibleOpts{ + AnsibleUser: "cloud-admin", + AnsibleHost: "", + AnsiblePort: 0, + AnsibleVars: nil, + }, + ExtraMounts: nil, + UserData: nil, + NetworkData: nil, + }, + Env: nil, + PreProvisioned: true, + NetworkAttachments: nil, + SecretMaxSize: 1048576, + TLSEnabled: tlsEnabled, + Nodes: map[string]dataplanev1.NodeSection{}, + Services: []string{ + "download-cache", + "bootstrap", + "configure-network", + "validate-network", + "install-os", + "configure-os", + "ssh-known-hosts", + "run-os", + "reboot-os", + "install-certs", + "ovn", + "neutron-metadata", + "libvirt", + "nova", + "telemetry"}, + } + Expect(dataplaneNodeSetInstance.Spec).Should(Equal(emptyNodeSpec)) + }) + + It("should have the Status fields initialized", func() { + dataplaneNodeSetInstance := GetDataplaneNodeSet(dataplaneNodeSetName) + Expect(dataplaneNodeSetInstance.Status.Deployed).Should(BeFalse()) + }) + + It("should have input not ready and unknown Conditions initialized", func() { + th.ExpectCondition( + dataplaneNodeSetName, + ConditionGetterFunc(DataplaneConditionGetter), + condition.ReadyCondition, + corev1.ConditionFalse, + ) + th.ExpectCondition( + dataplaneNodeSetName, + ConditionGetterFunc(DataplaneConditionGetter), + condition.InputReadyCondition, + corev1.ConditionFalse, + ) + th.ExpectCondition( + dataplaneNodeSetName, + ConditionGetterFunc(DataplaneConditionGetter), + dataplanev1.SetupReadyCondition, + corev1.ConditionFalse, + ) + }) + + It("Should not have created a Secret", func() { + th.AssertSecretDoesNotExist(dataplaneSecretName) + }) + }) + + When("A Dataplane resource is created with PreProvisioned nodes, no deployment and global service", func() { + BeforeEach(func() { + nodeSetSpec := DefaultDataPlaneNoNodeSetSpec(tlsEnabled) + nodeSetSpec["services"] = []string{ + "download-cache", + "bootstrap", + "configure-network", + "validate-network", + "install-os", + "configure-os", + "run-os", + "reboot-os", + "install-certs", + "ovn", + "neutron-metadata", + "libvirt", + "nova", + "telemetry", + "global-service"} + + CreateDataplaneService(dataplaneGlobalServiceName, true) + DeferCleanup(th.DeleteService, dataplaneGlobalServiceName) + DeferCleanup(th.DeleteInstance, CreateDataplaneNodeSet(dataplaneNodeSetName, nodeSetSpec)) + }) + It("should have the Spec fields initialized", func() { + dataplaneNodeSetInstance := GetDataplaneNodeSet(dataplaneNodeSetName) + emptyNodeSpec := dataplanev1.OpenStackDataPlaneNodeSetSpec{ + BaremetalSetTemplate: baremetalv1.OpenStackBaremetalSetSpec{ + BaremetalHosts: nil, + OSImage: "", + UserData: nil, + NetworkData: nil, + AutomatedCleaningMode: "metadata", + ProvisionServerName: "", + ProvisioningInterface: "", + CtlplaneInterface: "", + CtlplaneGateway: "", + CtlplaneNetmask: "255.255.255.0", + BmhNamespace: "openshift-machine-api", + HardwareReqs: baremetalv1.HardwareReqs{ + CPUReqs: baremetalv1.CPUReqs{ + Arch: "", + CountReq: baremetalv1.CPUCountReq{Count: 0, ExactMatch: false}, + MhzReq: baremetalv1.CPUMhzReq{Mhz: 0, ExactMatch: false}, + }, + MemReqs: baremetalv1.MemReqs{ + GbReq: baremetalv1.MemGbReq{Gb: 0, ExactMatch: false}, + }, + DiskReqs: baremetalv1.DiskReqs{ + GbReq: baremetalv1.DiskGbReq{Gb: 0, ExactMatch: false}, + SSDReq: baremetalv1.DiskSSDReq{SSD: false, ExactMatch: false}, + }, + }, + PasswordSecret: nil, + CloudUserName: "", + DomainName: "", + BootstrapDNS: nil, + DNSSearchDomains: nil, + }, + NodeTemplate: dataplanev1.NodeTemplate{ + AnsibleSSHPrivateKeySecret: "dataplane-ansible-ssh-private-key-secret", + Networks: nil, + ManagementNetwork: "ctlplane", + Ansible: dataplanev1.AnsibleOpts{ + AnsibleUser: "cloud-admin", + AnsibleHost: "", + AnsiblePort: 0, + AnsibleVars: nil, + }, + ExtraMounts: nil, + UserData: nil, + NetworkData: nil, + }, + Env: nil, + PreProvisioned: true, + NetworkAttachments: nil, + SecretMaxSize: 1048576, + TLSEnabled: tlsEnabled, + Nodes: map[string]dataplanev1.NodeSection{}, + Services: []string{ + "download-cache", + "bootstrap", + "configure-network", + "validate-network", + "install-os", + "configure-os", + "run-os", + "reboot-os", + "install-certs", + "ovn", + "neutron-metadata", + "libvirt", + "nova", + "telemetry", + "global-service"}, + } + Expect(dataplaneNodeSetInstance.Spec).Should(Equal(emptyNodeSpec)) + }) + + It("should have the Status fields initialized", func() { + dataplaneNodeSetInstance := GetDataplaneNodeSet(dataplaneNodeSetName) + Expect(dataplaneNodeSetInstance.Status.Deployed).Should(BeFalse()) + }) + + It("should have input not ready and unknown Conditions initialized", func() { + th.ExpectCondition( + dataplaneNodeSetName, + ConditionGetterFunc(DataplaneConditionGetter), + condition.ReadyCondition, + corev1.ConditionFalse, + ) + th.ExpectCondition( + dataplaneNodeSetName, + ConditionGetterFunc(DataplaneConditionGetter), + condition.InputReadyCondition, + corev1.ConditionFalse, + ) + th.ExpectCondition( + dataplaneNodeSetName, + ConditionGetterFunc(DataplaneConditionGetter), + dataplanev1.SetupReadyCondition, + corev1.ConditionFalse, + ) + }) + + It("Should not have created a Secret", func() { + th.AssertSecretDoesNotExist(dataplaneSecretName) + }) + + It("Should have service called 'global-service'", func() { + service := GetService(dataplaneGlobalServiceName) + Expect(service.Spec.DeployOnAllNodeSets).Should(BeTrue()) + }) + }) + + When("A Dataplane resorce is created without PreProvisioned nodes and ordered deployment", func() { + BeforeEach(func() { + spec := DefaultDataPlaneNoNodeSetSpec(tlsEnabled) + spec["metadata"] = map[string]interface{}{"ansiblesshprivatekeysecret": ""} + spec["preProvisioned"] = false + DeferCleanup(th.DeleteInstance, CreateDataplaneNodeSet(dataplaneNodeSetName, spec)) + }) + It("should have the Spec fields initialized", func() { + dataplaneNodeSetInstance := GetDataplaneNodeSet(dataplaneNodeSetName) + Expect(dataplaneNodeSetInstance.Spec.PreProvisioned).Should(BeFalse()) + }) + + It("should have the Status fields initialized", func() { + dataplaneNodeSetInstance := GetDataplaneNodeSet(dataplaneNodeSetName) + Expect(dataplaneNodeSetInstance.Status.Deployed).Should(BeFalse()) + }) + + It("should have ReadyCondition, InputReadyCondition and SetupReadyCondition set to false, and DeploymentReadyCondition set to Unknown", func() { + th.ExpectCondition( + dataplaneNodeSetName, + ConditionGetterFunc(DataplaneConditionGetter), + condition.ReadyCondition, + corev1.ConditionFalse, + ) + th.ExpectCondition( + dataplaneNodeSetName, + ConditionGetterFunc(DataplaneConditionGetter), + condition.InputReadyCondition, + corev1.ConditionFalse, + ) + th.ExpectCondition( + dataplaneNodeSetName, + ConditionGetterFunc(DataplaneConditionGetter), + dataplanev1.SetupReadyCondition, + corev1.ConditionFalse, + ) + th.ExpectCondition( + dataplaneNodeSetName, + ConditionGetterFunc(DataplaneConditionGetter), + condition.DeploymentReadyCondition, + corev1.ConditionUnknown, + ) + }) + + It("Should not have created a Secret", func() { + th.AssertSecretDoesNotExist(dataplaneSecretName) + }) + }) + + When("A Dataplane resorce is created without PreProvisioned nodes but is marked as PreProvisioned, with ordered deployment", func() { + BeforeEach(func() { + spec := DefaultDataPlaneNoNodeSetSpec(tlsEnabled) + spec["metadata"] = map[string]interface{}{"ansiblesshprivatekeysecret": ""} + DeferCleanup(th.DeleteInstance, CreateDataplaneNodeSet(dataplaneNodeSetName, spec)) + }) + It("should have the Spec fields initialized", func() { + dataplaneNodeSetInstance := GetDataplaneNodeSet(dataplaneNodeSetName) + Expect(dataplaneNodeSetInstance.Spec.PreProvisioned).Should(BeTrue()) + }) + + It("should have the Status fields initialized", func() { + dataplaneNodeSetInstance := GetDataplaneNodeSet(dataplaneNodeSetName) + Expect(dataplaneNodeSetInstance.Status.Deployed).Should(BeFalse()) + }) + + It("should have ReadyCondition, InputReadCondition and SetupReadyCondition set to false, and DeploymentReadyCondition set to unknown", func() { + th.ExpectCondition( + dataplaneNodeSetName, + ConditionGetterFunc(DataplaneConditionGetter), + condition.ReadyCondition, + corev1.ConditionFalse, + ) + th.ExpectCondition( + dataplaneNodeSetName, + ConditionGetterFunc(DataplaneConditionGetter), + condition.InputReadyCondition, + corev1.ConditionFalse, + ) + th.ExpectCondition( + dataplaneNodeSetName, + ConditionGetterFunc(DataplaneConditionGetter), + dataplanev1.SetupReadyCondition, + corev1.ConditionFalse, + ) + th.ExpectCondition( + dataplaneNodeSetName, + ConditionGetterFunc(DataplaneConditionGetter), + condition.DeploymentReadyCondition, + corev1.ConditionUnknown, + ) + }) + + It("Should not have created a Secret", func() { + th.AssertSecretDoesNotExist(dataplaneSecretName) + }) + }) + + When("A ssh secret is created", func() { + + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreateDataplaneNodeSet(dataplaneNodeSetName, DefaultDataPlaneNoNodeSetSpec(tlsEnabled))) + CreateSSHSecret(dataplaneSSHSecretName) + }) + It("Should have created a Secret", func() { + secret := th.GetSecret(dataplaneSecretName) + Expect(secret.Data["inventory"]).Should( + ContainSubstring("edpm-compute-nodeset")) + }) + It("Should set Input and Setup ready", func() { + + th.ExpectCondition( + dataplaneNodeSetName, + ConditionGetterFunc(DataplaneConditionGetter), + condition.InputReadyCondition, + corev1.ConditionTrue, + ) + }) + }) + + When("No default service image is provided", func() { + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreateDataplaneNodeSet(dataplaneNodeSetName, DefaultDataPlaneNoNodeSetSpec(tlsEnabled))) + CreateSSHSecret(dataplaneSSHSecretName) + }) + It("Should have default service values provided", func() { + secret := th.GetSecret(dataplaneSecretName) + for _, svcImage := range defaultEdpmServiceList { + Expect(secret.Data["inventory"]).Should( + ContainSubstring(svcImage)) + } + }) + }) + + When("A user provides a custom service image", func() { + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreateDataplaneNodeSet(dataplaneNodeSetName, CustomServiceImageSpec())) + CreateSSHSecret(dataplaneSSHSecretName) + }) + It("Should have the user defined image in the inventory", func() { + secret := th.GetSecret(dataplaneSecretName) + for _, svcAnsibleVar := range DefaultEdpmServiceAnsibleVarList { + Expect(secret.Data["inventory"]).Should( + ContainSubstring(fmt.Sprintf("%s.%s", svcAnsibleVar, CustomEdpmServiceDomainTag))) + } + }) + }) + + When("No default service image is provided", func() { + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreateDataplaneNodeSet(dataplaneNodeSetName, DefaultDataPlaneNoNodeSetSpec(tlsEnabled))) + CreateSSHSecret(dataplaneSSHSecretName) + }) + It("Should have default service values provided", func() { + secret := th.GetSecret(dataplaneSecretName) + for _, svcAnsibleVar := range DefaultEdpmServiceAnsibleVarList { + Expect(secret.Data["inventory"]).Should( + ContainSubstring(svcAnsibleVar)) + } + }) + }) + + When("A user provides a custom service image", func() { + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreateDataplaneNodeSet(dataplaneNodeSetName, CustomServiceImageSpec())) + CreateSSHSecret(dataplaneSSHSecretName) + }) + It("Should have the user defined image in the inventory", func() { + secret := th.GetSecret(dataplaneSecretName) + for _, svcAnsibleVar := range DefaultEdpmServiceAnsibleVarList { + Expect(secret.Data["inventory"]).Should( + ContainSubstring(fmt.Sprintf("%s.%s", svcAnsibleVar, CustomEdpmServiceDomainTag))) + } + }) + }) + + When("The nodeTemplate contains a ansibleUser but the individual node does not", func() { + BeforeEach(func() { + nodeSetSpec := DefaultDataPlaneNodeSetSpec("edpm-compute") + nodeSetSpec["preProvisioned"] = true + DeferCleanup(th.DeleteInstance, CreateNetConfig(dataplaneNetConfigName, DefaultNetConfigSpec())) + DeferCleanup(th.DeleteInstance, CreateDataplaneNodeSet(dataplaneNodeSetName, nodeSetSpec)) + CreateSSHSecret(dataplaneSSHSecretName) + SimulateIPSetComplete(dataplaneIPSetName) + }) + It("Should not have set the node specific ansible_user variable", func() { + secret := th.GetSecret(dataplaneSecretName) + secretData := secret.Data["inventory"] + + var inv AnsibleInventory + err := yaml.Unmarshal(secretData, &inv) + if err != nil { + fmt.Printf("Error: %v", err) + } + Expect(inv.EdpmComputeNodeset.Vars.AnsibleUser).Should(Equal("cloud-user")) + Expect(inv.EdpmComputeNodeset.Hosts.Node.AnsibleUser).Should(BeEmpty()) + }) + }) + + When("The individual node has a AnsibleUser override", func() { + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreateNetConfig(dataplaneNetConfigName, DefaultNetConfigSpec())) + nodeOverrideSpec := map[string]interface{}{ + "hostname": "edpm-bm-compute-1", + "networks": []map[string]interface{}{{ + "name": "CtlPlane", + "fixedIP": "172.20.12.76", + "subnetName": "ctlplane_subnet", + }, + }, + "ansible": map[string]interface{}{ + "ansibleUser": "test-user", + }, + } + + nodeTemplateOverrideSpec := map[string]interface{}{ + "ansibleSSHPrivateKeySecret": "dataplane-ansible-ssh-private-key-secret", + "ansible": map[string]interface{}{ + "ansibleUser": "cloud-user", + }, + } + + nodeSetSpec := DefaultDataPlaneNoNodeSetSpec(tlsEnabled) + nodeSetSpec["nodes"].(map[string]interface{})["edpm-compute-node-1"] = nodeOverrideSpec + nodeSetSpec["nodeTemplate"] = nodeTemplateOverrideSpec + + DeferCleanup(th.DeleteInstance, CreateDataplaneNodeSet(dataplaneNodeSetName, nodeSetSpec)) + CreateSSHSecret(dataplaneSSHSecretName) + SimulateIPSetComplete(dataplaneIPSetName) + }) + It("Should have a node specific override that is different to the group", func() { + secret := th.GetSecret(dataplaneSecretName) + secretData := secret.Data["inventory"] + + var inv AnsibleInventory + err := yaml.Unmarshal(secretData, &inv) + if err != nil { + fmt.Printf("Error: %v", err) + } + Expect(inv.EdpmComputeNodeset.Hosts.Node.AnsibleUser).Should(Equal("test-user")) + Expect(inv.EdpmComputeNodeset.Vars.AnsibleUser).Should(Equal("cloud-user")) + }) + }) + + When("A nodeSet is created with IPAM", func() { + BeforeEach(func() { + nodeSetSpec := DefaultDataPlaneNodeSetSpec("edpm-compute") + nodeSetSpec["preProvisioned"] = true + DeferCleanup(th.DeleteInstance, CreateNetConfig(dataplaneNetConfigName, DefaultNetConfigSpec())) + DeferCleanup(th.DeleteInstance, CreateDataplaneNodeSet(dataplaneNodeSetName, nodeSetSpec)) + CreateSSHSecret(dataplaneSSHSecretName) + SimulateIPSetComplete(dataplaneIPSetName) + }) + It("Should set the ctlplane_ip variable in the Ansible inventory secret", func() { + Eventually(func() string { + secret := th.GetSecret(dataplaneSecretName) + return getCtlPlaneIP(&secret) + }).Should(Equal("172.20.12.76")) + }) + }) + + When("A DataPlaneNodeSet is created with NoNodes and a OpenStackDataPlaneDeployment is created", func() { + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreateDataplaneNodeSet(dataplaneNodeSetName, DefaultDataPlaneNoNodeSetSpec(tlsEnabled))) + DeferCleanup(th.DeleteInstance, CreateDataplaneDeployment(dataplaneDeploymentName, DefaultDataPlaneDeploymentSpec())) + CreateSSHSecret(dataplaneSSHSecretName) + }) + It("Should reach Input and Setup Ready completion", func() { + var conditionList = []condition.Type{ + condition.InputReadyCondition, + dataplanev1.SetupReadyCondition, + } + for _, cond := range conditionList { + th.ExpectCondition( + dataplaneNodeSetName, + ConditionGetterFunc(DataplaneConditionGetter), + cond, + corev1.ConditionTrue, + ) + } + }) + }) + }) + When("TLS is not enabled explicitly its enabled by default", func() { + tlsEnabled := true + When("A Dataplane resorce is created with PreProvisioned nodes, no deployment", func() { + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreateDataplaneNodeSet(dataplaneNodeSetName, DefaultDataPlaneNoNodeSetSpec(tlsEnabled))) + }) + It("should have the Spec fields initialized", func() { + dataplaneNodeSetInstance := GetDataplaneNodeSet(dataplaneNodeSetName) + emptyNodeSpec := dataplanev1.OpenStackDataPlaneNodeSetSpec{ + BaremetalSetTemplate: baremetalv1.OpenStackBaremetalSetSpec{ + BaremetalHosts: nil, + OSImage: "", + UserData: nil, + NetworkData: nil, + AutomatedCleaningMode: "metadata", + ProvisionServerName: "", + ProvisioningInterface: "", + CtlplaneInterface: "", + CtlplaneGateway: "", + CtlplaneNetmask: "255.255.255.0", + BmhNamespace: "openshift-machine-api", + HardwareReqs: baremetalv1.HardwareReqs{ + CPUReqs: baremetalv1.CPUReqs{ + Arch: "", + CountReq: baremetalv1.CPUCountReq{Count: 0, ExactMatch: false}, + MhzReq: baremetalv1.CPUMhzReq{Mhz: 0, ExactMatch: false}, + }, + MemReqs: baremetalv1.MemReqs{ + GbReq: baremetalv1.MemGbReq{Gb: 0, ExactMatch: false}, + }, + DiskReqs: baremetalv1.DiskReqs{ + GbReq: baremetalv1.DiskGbReq{Gb: 0, ExactMatch: false}, + SSDReq: baremetalv1.DiskSSDReq{SSD: false, ExactMatch: false}, + }, + }, + PasswordSecret: nil, + CloudUserName: "", + DomainName: "", + BootstrapDNS: nil, + DNSSearchDomains: nil, + }, + NodeTemplate: dataplanev1.NodeTemplate{ + AnsibleSSHPrivateKeySecret: "dataplane-ansible-ssh-private-key-secret", + Networks: nil, + ManagementNetwork: "ctlplane", + Ansible: dataplanev1.AnsibleOpts{ + AnsibleUser: "cloud-admin", + AnsibleHost: "", + AnsiblePort: 0, + AnsibleVars: nil, + }, + ExtraMounts: nil, + UserData: nil, + NetworkData: nil, + }, + Env: nil, + PreProvisioned: true, + NetworkAttachments: nil, + SecretMaxSize: 1048576, + TLSEnabled: tlsEnabled, + Nodes: map[string]dataplanev1.NodeSection{}, + Services: []string{ + "download-cache", + "bootstrap", + "configure-network", + "validate-network", + "install-os", + "configure-os", + "ssh-known-hosts", + "run-os", + "reboot-os", + "install-certs", + "ovn", + "neutron-metadata", + "libvirt", + "nova", + "telemetry"}, + } + Expect(dataplaneNodeSetInstance.Spec).Should(Equal(emptyNodeSpec)) + }) + + It("should have the Status fields initialized", func() { + dataplaneNodeSetInstance := GetDataplaneNodeSet(dataplaneNodeSetName) + Expect(dataplaneNodeSetInstance.Status.Deployed).Should(BeFalse()) + }) + + It("should have input not ready and unknown Conditions initialized", func() { + th.ExpectCondition( + dataplaneNodeSetName, + ConditionGetterFunc(DataplaneConditionGetter), + condition.ReadyCondition, + corev1.ConditionFalse, + ) + th.ExpectCondition( + dataplaneNodeSetName, + ConditionGetterFunc(DataplaneConditionGetter), + condition.InputReadyCondition, + corev1.ConditionFalse, + ) + th.ExpectCondition( + dataplaneNodeSetName, + ConditionGetterFunc(DataplaneConditionGetter), + dataplanev1.SetupReadyCondition, + corev1.ConditionFalse, + ) + }) + + It("Should not have created a Secret", func() { + th.AssertSecretDoesNotExist(dataplaneSecretName) + }) + }) + + When("A Dataplane resorce is created without PreProvisioned nodes and ordered deployment", func() { + BeforeEach(func() { + spec := DefaultDataPlaneNoNodeSetSpec(tlsEnabled) + spec["metadata"] = map[string]interface{}{"ansiblesshprivatekeysecret": ""} + spec["preProvisioned"] = false + DeferCleanup(th.DeleteInstance, CreateDataplaneNodeSet(dataplaneNodeSetName, spec)) + }) + It("should have the Spec fields initialized", func() { + dataplaneNodeSetInstance := GetDataplaneNodeSet(dataplaneNodeSetName) + Expect(dataplaneNodeSetInstance.Spec.PreProvisioned).Should(BeFalse()) + }) + + It("should have the Status fields initialized", func() { + dataplaneNodeSetInstance := GetDataplaneNodeSet(dataplaneNodeSetName) + Expect(dataplaneNodeSetInstance.Status.Deployed).Should(BeFalse()) + }) + + It("should have ReadyCondition, InputReadyCondition and SetupReadyCondition set to false, and DeploymentReadyCondition set to Unknown", func() { + th.ExpectCondition( + dataplaneNodeSetName, + ConditionGetterFunc(DataplaneConditionGetter), + condition.ReadyCondition, + corev1.ConditionFalse, + ) + th.ExpectCondition( + dataplaneNodeSetName, + ConditionGetterFunc(DataplaneConditionGetter), + condition.InputReadyCondition, + corev1.ConditionFalse, + ) + th.ExpectCondition( + dataplaneNodeSetName, + ConditionGetterFunc(DataplaneConditionGetter), + dataplanev1.SetupReadyCondition, + corev1.ConditionFalse, + ) + th.ExpectCondition( + dataplaneNodeSetName, + ConditionGetterFunc(DataplaneConditionGetter), + condition.DeploymentReadyCondition, + corev1.ConditionUnknown, + ) + }) + + It("Should not have created a Secret", func() { + th.AssertSecretDoesNotExist(dataplaneSecretName) + }) + }) + + When("A Dataplane resorce is created without PreProvisioned nodes but is marked as PreProvisioned, with ordered deployment", func() { + BeforeEach(func() { + spec := DefaultDataPlaneNoNodeSetSpec(tlsEnabled) + spec["metadata"] = map[string]interface{}{"ansiblesshprivatekeysecret": ""} + DeferCleanup(th.DeleteInstance, CreateDataplaneNodeSet(dataplaneNodeSetName, spec)) + }) + It("should have the Spec fields initialized", func() { + dataplaneNodeSetInstance := GetDataplaneNodeSet(dataplaneNodeSetName) + Expect(dataplaneNodeSetInstance.Spec.PreProvisioned).Should(BeTrue()) + }) + + It("should have the Status fields initialized", func() { + dataplaneNodeSetInstance := GetDataplaneNodeSet(dataplaneNodeSetName) + Expect(dataplaneNodeSetInstance.Status.Deployed).Should(BeFalse()) + }) + + It("should have ReadyCondition, InputReadCondition and SetupReadyCondition set to false, and DeploymentReadyCondition set to unknown", func() { + th.ExpectCondition( + dataplaneNodeSetName, + ConditionGetterFunc(DataplaneConditionGetter), + condition.ReadyCondition, + corev1.ConditionFalse, + ) + th.ExpectCondition( + dataplaneNodeSetName, + ConditionGetterFunc(DataplaneConditionGetter), + condition.InputReadyCondition, + corev1.ConditionFalse, + ) + th.ExpectCondition( + dataplaneNodeSetName, + ConditionGetterFunc(DataplaneConditionGetter), + dataplanev1.SetupReadyCondition, + corev1.ConditionFalse, + ) + th.ExpectCondition( + dataplaneNodeSetName, + ConditionGetterFunc(DataplaneConditionGetter), + condition.DeploymentReadyCondition, + corev1.ConditionUnknown, + ) + }) + + It("Should not have created a Secret", func() { + th.AssertSecretDoesNotExist(dataplaneSecretName) + }) + }) + + When("A ssh secret is created", func() { + + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreateDataplaneNodeSet(dataplaneNodeSetName, DefaultDataPlaneNoNodeSetSpec(tlsEnabled))) + CreateSSHSecret(dataplaneSSHSecretName) + }) + It("Should have created a Secret", func() { + secret := th.GetSecret(dataplaneSecretName) + Expect(secret.Data["inventory"]).Should( + ContainSubstring("edpm-compute-nodeset")) + }) + It("Should set Input and Setup ready", func() { + + th.ExpectCondition( + dataplaneNodeSetName, + ConditionGetterFunc(DataplaneConditionGetter), + condition.InputReadyCondition, + corev1.ConditionTrue, + ) + }) + }) + + When("No default service image is provided", func() { + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreateDataplaneNodeSet(dataplaneNodeSetName, DefaultDataPlaneNoNodeSetSpec(tlsEnabled))) + CreateSSHSecret(dataplaneSSHSecretName) + }) + It("Should have default service values provided", func() { + secret := th.GetSecret(dataplaneSecretName) + for _, svcImage := range defaultEdpmServiceList { + Expect(secret.Data["inventory"]).Should( + ContainSubstring(svcImage)) + } + }) + }) + + When("A user provides a custom service image", func() { + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreateDataplaneNodeSet(dataplaneNodeSetName, CustomServiceImageSpec())) + CreateSSHSecret(dataplaneSSHSecretName) + }) + It("Should have the user defined image in the inventory", func() { + secret := th.GetSecret(dataplaneSecretName) + for _, svcAnsibleVar := range DefaultEdpmServiceAnsibleVarList { + Expect(secret.Data["inventory"]).Should( + ContainSubstring(fmt.Sprintf("%s.%s", svcAnsibleVar, CustomEdpmServiceDomainTag))) + } + }) + }) + + When("No default service image is provided", func() { + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreateDataplaneNodeSet(dataplaneNodeSetName, DefaultDataPlaneNoNodeSetSpec(tlsEnabled))) + CreateSSHSecret(dataplaneSSHSecretName) + }) + It("Should have default service values provided", func() { + secret := th.GetSecret(dataplaneSecretName) + for _, svcAnsibleVar := range DefaultEdpmServiceAnsibleVarList { + Expect(secret.Data["inventory"]).Should( + ContainSubstring(svcAnsibleVar)) + } + }) + }) + + When("A user provides a custom service image", func() { + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreateDataplaneNodeSet(dataplaneNodeSetName, CustomServiceImageSpec())) + CreateSSHSecret(dataplaneSSHSecretName) + }) + It("Should have the user defined image in the inventory", func() { + secret := th.GetSecret(dataplaneSecretName) + for _, svcAnsibleVar := range DefaultEdpmServiceAnsibleVarList { + Expect(secret.Data["inventory"]).Should( + ContainSubstring(fmt.Sprintf("%s.%s", svcAnsibleVar, CustomEdpmServiceDomainTag))) + } + }) + }) + + When("The nodeTemplate contains a ansibleUser but the individual node does not", func() { + BeforeEach(func() { + nodeSetSpec := DefaultDataPlaneNodeSetSpec("edpm-compute") + nodeSetSpec["preProvisioned"] = true + DeferCleanup(th.DeleteInstance, CreateNetConfig(dataplaneNetConfigName, DefaultNetConfigSpec())) + DeferCleanup(th.DeleteInstance, CreateDataplaneNodeSet(dataplaneNodeSetName, nodeSetSpec)) + CreateSSHSecret(dataplaneSSHSecretName) + SimulateIPSetComplete(dataplaneIPSetName) + }) + It("Should not have set the node specific ansible_user variable", func() { + secret := th.GetSecret(dataplaneSecretName) + secretData := secret.Data["inventory"] + + var inv AnsibleInventory + err := yaml.Unmarshal(secretData, &inv) + if err != nil { + fmt.Printf("Error: %v", err) + } + Expect(inv.EdpmComputeNodeset.Vars.AnsibleUser).Should(Equal("cloud-user")) + Expect(inv.EdpmComputeNodeset.Hosts.Node.AnsibleUser).Should(BeEmpty()) + }) + }) + + When("The individual node has a AnsibleUser override", func() { + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreateNetConfig(dataplaneNetConfigName, DefaultNetConfigSpec())) + nodeOverrideSpec := map[string]interface{}{ + "hostname": "edpm-bm-compute-1", + "networks": []map[string]interface{}{{ + "name": "CtlPlane", + "fixedIP": "172.20.12.76", + "subnetName": "ctlplane_subnet", + }, + }, + "ansible": map[string]interface{}{ + "ansibleUser": "test-user", + }, + } + + nodeTemplateOverrideSpec := map[string]interface{}{ + "ansibleSSHPrivateKeySecret": "dataplane-ansible-ssh-private-key-secret", + "ansible": map[string]interface{}{ + "ansibleUser": "cloud-user", + }, + } + + nodeSetSpec := DefaultDataPlaneNoNodeSetSpec(tlsEnabled) + nodeSetSpec["nodes"].(map[string]interface{})["edpm-compute-node-1"] = nodeOverrideSpec + nodeSetSpec["nodeTemplate"] = nodeTemplateOverrideSpec + + DeferCleanup(th.DeleteInstance, CreateDataplaneNodeSet(dataplaneNodeSetName, nodeSetSpec)) + CreateSSHSecret(dataplaneSSHSecretName) + SimulateIPSetComplete(dataplaneIPSetName) + }) + It("Should have a node specific override that is different to the group", func() { + secret := th.GetSecret(dataplaneSecretName) + secretData := secret.Data["inventory"] + + var inv AnsibleInventory + err := yaml.Unmarshal(secretData, &inv) + if err != nil { + fmt.Printf("Error: %v", err) + } + Expect(inv.EdpmComputeNodeset.Hosts.Node.AnsibleUser).Should(Equal("test-user")) + Expect(inv.EdpmComputeNodeset.Vars.AnsibleUser).Should(Equal("cloud-user")) + }) + }) + + When("A nodeSet is created with IPAM", func() { + BeforeEach(func() { + nodeSetSpec := DefaultDataPlaneNodeSetSpec("edpm-compute") + nodeSetSpec["preProvisioned"] = true + DeferCleanup(th.DeleteInstance, CreateNetConfig(dataplaneNetConfigName, DefaultNetConfigSpec())) + DeferCleanup(th.DeleteInstance, CreateDataplaneNodeSet(dataplaneNodeSetName, nodeSetSpec)) + CreateSSHSecret(dataplaneSSHSecretName) + SimulateIPSetComplete(dataplaneIPSetName) + }) + It("Should set the ctlplane_ip variable in the Ansible inventory secret", func() { + Eventually(func() string { + secret := th.GetSecret(dataplaneSecretName) + return getCtlPlaneIP(&secret) + }).Should(Equal("172.20.12.76")) + }) + }) + + When("A DataPlaneNodeSet is created with NoNodes and a OpenStackDataPlaneDeployment is created", func() { + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreateDataplaneNodeSet(dataplaneNodeSetName, DefaultDataPlaneNoNodeSetSpec(tlsEnabled))) + DeferCleanup(th.DeleteInstance, CreateDataplaneDeployment(dataplaneDeploymentName, DefaultDataPlaneDeploymentSpec())) + CreateSSHSecret(dataplaneSSHSecretName) + }) + It("Should reach Input and Setup Ready completion", func() { + var conditionList = []condition.Type{ + condition.InputReadyCondition, + dataplanev1.SetupReadyCondition, + } + for _, cond := range conditionList { + th.ExpectCondition( + dataplaneNodeSetName, + ConditionGetterFunc(DataplaneConditionGetter), + cond, + corev1.ConditionTrue, + ) + } + }) + }) + }) + + When("A user changes spec field that would require a new Ansible execution", func() { + BeforeEach(func() { + nodeSetSpec := DefaultDataPlaneNodeSetSpec("edpm-compute") + nodeSetSpec["nodeTemplate"] = dataplanev1.NodeTemplate{ + Ansible: dataplanev1.AnsibleOpts{ + AnsibleVars: map[string]json.RawMessage{ + "edpm_network_config_hide_sensitive_logs": json.RawMessage([]byte(`"false"`)), + }, + }, + } + DeferCleanup(th.DeleteInstance, CreateDataplaneNodeSet(dataplaneNodeSetName, nodeSetSpec)) + }) + + It("Should change the ConfigHash", func() { + Eventually(func(_ Gomega) error { + instance := GetDataplaneNodeSet(dataplaneNodeSetName) + dataplaneConfigHash = instance.Status.ConfigHash + instance.Spec.NodeTemplate.Ansible.AnsibleVars = map[string]json.RawMessage{ + "edpm_network_config_hide_sensitive_logs": json.RawMessage([]byte(`"true"`)), + } + return th.K8sClient.Update(th.Ctx, instance) + }).Should(Succeed()) + Eventually(func(_ Gomega) bool { + updatedInstance := GetDataplaneNodeSet(dataplaneNodeSetName) + return dataplaneConfigHash != updatedInstance.Status.ConfigHash + }).Should(BeTrue()) + }) + }) +}) diff --git a/tests/functional/openstackdataplanenodeset_webhook_test.go b/tests/functional/openstackdataplanenodeset_webhook_test.go new file mode 100644 index 000000000..adf978550 --- /dev/null +++ b/tests/functional/openstackdataplanenodeset_webhook_test.go @@ -0,0 +1,184 @@ +package functional_test + +import ( + "fmt" + "os" + + . "github.com/onsi/ginkgo/v2" //revive:disable:dot-imports + . "github.com/onsi/gomega" //revive:disable:dot-imports + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/types" + + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + baremetalv1 "github.com/openstack-k8s-operators/openstack-baremetal-operator/api/v1beta1" +) + +var _ = Describe("DataplaneNodeSet Webhook", func() { + + var dataplaneNodeSetName types.NamespacedName + + BeforeEach(func() { + dataplaneNodeSetName = types.NamespacedName{ + Name: "edpm-compute-nodeset", + Namespace: namespace, + } + err := os.Setenv("OPERATOR_SERVICES", "../../config/services") + Expect(err).NotTo(HaveOccurred()) + }) + + When("User tries to change forbidden items in the baremetalSetTemplate", func() { + BeforeEach(func() { + nodeSetSpec := DefaultDataPlaneNoNodeSetSpec(false) + nodeSetSpec["preProvisioned"] = false + nodeSetSpec["nodes"] = map[string]interface{}{ + "compute-0": map[string]interface{}{ + "hostName": "compute-0"}, + } + nodeSetSpec["baremetalSetTemplate"] = baremetalv1.OpenStackBaremetalSetSpec{ + CloudUserName: "test-user", + BmhLabelSelector: map[string]string{ + "app": "test-openstack", + }, + } + DeferCleanup(th.DeleteInstance, CreateDataplaneNodeSet(dataplaneNodeSetName, nodeSetSpec)) + }) + + It("Should block changes to the BmhLabelSelector object in baremetalSetTemplate spec", func() { + Eventually(func(_ Gomega) string { + instance := GetDataplaneNodeSet(dataplaneNodeSetName) + instance.Spec.BaremetalSetTemplate = baremetalv1.OpenStackBaremetalSetSpec{ + CloudUserName: "new-user", + BmhLabelSelector: map[string]string{ + "app": "openstack1", + }, + } + err := th.K8sClient.Update(th.Ctx, instance) + return fmt.Sprintf("%s", err) + }).Should(ContainSubstring("Forbidden: cannot change")) + }) + }) + + When("A user changes an allowed field in the baremetalSetTemplate", func() { + BeforeEach(func() { + nodeSetSpec := DefaultDataPlaneNoNodeSetSpec(false) + nodeSetSpec["preProvisioned"] = false + nodeSetSpec["baremetalSetTemplate"] = baremetalv1.OpenStackBaremetalSetSpec{ + CloudUserName: "test-user", + BmhLabelSelector: map[string]string{ + "app": "test-openstack", + }, + BaremetalHosts: map[string]baremetalv1.InstanceSpec{ + "compute-0": { + CtlPlaneIP: "192.168.1.12/24", + }, + }, + } + DeferCleanup(th.DeleteInstance, CreateDataplaneNodeSet(dataplaneNodeSetName, nodeSetSpec)) + }) + It("Should allow changes to the CloudUserName", func() { + Eventually(func(_ Gomega) error { + instance := GetDataplaneNodeSet(dataplaneNodeSetName) + instance.Spec.BaremetalSetTemplate = baremetalv1.OpenStackBaremetalSetSpec{ + CloudUserName: "new-user", + BmhLabelSelector: map[string]string{ + "app": "test-openstack", + }, + BaremetalHosts: map[string]baremetalv1.InstanceSpec{ + "compute-0": { + CtlPlaneIP: "192.168.1.12/24", + }, + }, + } + return th.K8sClient.Update(th.Ctx, instance) + }).Should(Succeed()) + }) + }) + + When("domainName in baremetalSetTemplate", func() { + BeforeEach(func() { + nodeSetSpec := DefaultDataPlaneNoNodeSetSpec(false) + nodeSetSpec["preProvisioned"] = false + nodeSetSpec["nodes"] = map[string]interface{}{ + "compute-0": map[string]interface{}{ + "hostName": "compute-0"}, + } + nodeSetSpec["baremetalSetTemplate"] = baremetalv1.OpenStackBaremetalSetSpec{ + DomainName: "example.com", + BmhLabelSelector: map[string]string{ + "app": "test-openstack", + }, + BaremetalHosts: map[string]baremetalv1.InstanceSpec{ + "compute-0": { + CtlPlaneIP: "192.168.1.12/24", + }, + }, + } + DeferCleanup(th.DeleteInstance, CreateDataplaneNodeSet(dataplaneNodeSetName, nodeSetSpec)) + }) + + It("hostName should be fqdn", func() { + instance := GetDataplaneNodeSet(dataplaneNodeSetName) + Expect(instance.Spec.Nodes["compute-0"].HostName).Should(Equal( + "compute-0.example.com")) + }) + + }) + + When("A user tries to redeclare an existing node in a new NodeSet", func() { + BeforeEach(func() { + nodeSetSpec := DefaultDataPlaneNoNodeSetSpec(false) + nodeSetSpec["preProvisioned"] = true + nodeSetSpec["nodes"] = map[string]interface{}{ + "compute-0": map[string]interface{}{ + "hostName": "compute-0"}, + } + DeferCleanup(th.DeleteInstance, CreateDataplaneNodeSet(dataplaneNodeSetName, nodeSetSpec)) + }) + + It("Should block duplicate node declaration", func() { + Eventually(func(_ Gomega) string { + newNodeSetSpec := DefaultDataPlaneNoNodeSetSpec(false) + newNodeSetSpec["preProvisioned"] = true + newNodeSetSpec["nodes"] = map[string]interface{}{ + "compute-0": map[string]interface{}{ + "hostName": "compute-0"}, + } + newInstance := DefaultDataplaneNodeSetTemplate(types.NamespacedName{Name: "test-duplicate-node", Namespace: namespace}, newNodeSetSpec) + unstructuredObj := &unstructured.Unstructured{Object: newInstance} + _, err := controllerutil.CreateOrPatch( + th.Ctx, th.K8sClient, unstructuredObj, func() error { return nil }) + return fmt.Sprintf("%s", err) + }).Should(ContainSubstring("already exists in another cluster")) + }) + + It("Should block NodeSets if they contain a duplicate ansibleHost", func() { + Eventually(func(_ Gomega) string { + newNodeSetSpec := DefaultDataPlaneNoNodeSetSpec(false) + newNodeSetSpec["preProvisioned"] = true + newNodeSetSpec["nodes"] = map[string]interface{}{ + "compute-3": map[string]interface{}{ + "hostName": "compute-3", + "ansible": map[string]interface{}{ + "ansibleHost": "compute-3", + }, + }, + "compute-2": map[string]interface{}{ + "hostName": "compute-2"}, + "compute-8": map[string]interface{}{ + "hostName": "compute-8"}, + "compute-0": map[string]interface{}{ + "ansible": map[string]interface{}{ + "ansibleHost": "compute-0", + }, + }, + } + newInstance := DefaultDataplaneNodeSetTemplate(types.NamespacedName{Name: "test-nodeset-with-duplicate-node", Namespace: namespace}, newNodeSetSpec) + unstructuredObj := &unstructured.Unstructured{Object: newInstance} + _, err := controllerutil.CreateOrPatch( + th.Ctx, th.K8sClient, unstructuredObj, func() error { return nil }) + return fmt.Sprintf("%s", err) + }).Should(ContainSubstring("already exists in another cluster")) + }) + }) +}) diff --git a/tests/functional/service_test.go b/tests/functional/service_test.go new file mode 100644 index 000000000..9bfeec763 --- /dev/null +++ b/tests/functional/service_test.go @@ -0,0 +1,66 @@ +/* +Copyright 2023. + +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 functional_test + +import ( + "os" + + . "github.com/onsi/ginkgo/v2" //revive:disable:dot-imports + . "github.com/onsi/gomega" //revive:disable:dot-imports + "k8s.io/apimachinery/pkg/types" +) + +var _ = Describe("OpenstackDataplaneService Test", func() { + var dataplaneServiceName types.NamespacedName + BeforeEach(func() { + dataplaneServiceName = types.NamespacedName{ + Namespace: namespace, + Name: "configure-network", + } + }) + + When("A defined service resource is created", func() { + BeforeEach(func() { + os.Unsetenv("OPERATOR_SERVICES") + CreateDataplaneService(dataplaneServiceName, false) + DeferCleanup(th.DeleteService, dataplaneServiceName) + }) + + It("spec fields are set up", func() { + service := GetService(dataplaneServiceName) + Expect(service.Spec.Secrets).To(BeEmpty()) + Expect(service.Spec.Playbook).To(BeEmpty()) + Expect(service.Spec.ConfigMaps).To(BeEmpty()) + Expect(service.Spec.DeployOnAllNodeSets).To(BeFalse()) + }) + }) + + When("A defined service resource for all nodes is created", func() { + BeforeEach(func() { + os.Unsetenv("OPERATOR_SERVICES") + CreateDataplaneService(dataplaneServiceName, true) + DeferCleanup(th.DeleteService, dataplaneServiceName) + }) + + It("spec fields are set up", func() { + service := GetService(dataplaneServiceName) + Expect(service.Spec.Secrets).To(BeEmpty()) + Expect(service.Spec.Playbook).To(BeEmpty()) + Expect(service.Spec.ConfigMaps).To(BeEmpty()) + Expect(service.Spec.DeployOnAllNodeSets).To(BeTrue()) + }) + }) +}) diff --git a/tests/functional/suite_test.go b/tests/functional/suite_test.go index caafa9f98..2c0255481 100644 --- a/tests/functional/suite_test.go +++ b/tests/functional/suite_test.go @@ -31,6 +31,9 @@ import ( certmgrv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" routev1 "github.com/openshift/api/route/v1" rabbitmqv2 "github.com/rabbitmq/cluster-operator/v2/api/v1beta1" + appsv1 "k8s.io/api/apps/v1" + batchv1 "k8s.io/api/batch/v1" + k8s_corev1 "k8s.io/api/core/v1" barbicanv1 "github.com/openstack-k8s-operators/barbican-operator/api/v1beta1" cinderv1 "github.com/openstack-k8s-operators/cinder-operator/api/v1beta1" @@ -47,8 +50,12 @@ import ( neutronv1 "github.com/openstack-k8s-operators/neutron-operator/api/v1beta1" novav1 "github.com/openstack-k8s-operators/nova-operator/api/v1beta1" octaviav1 "github.com/openstack-k8s-operators/octavia-operator/api/v1beta1" + aeev1 "github.com/openstack-k8s-operators/openstack-ansibleee-operator/api/v1beta1" + baremetalv1 "github.com/openstack-k8s-operators/openstack-baremetal-operator/api/v1beta1" openstackclientv1 "github.com/openstack-k8s-operators/openstack-operator/apis/client/v1beta1" corev1 "github.com/openstack-k8s-operators/openstack-operator/apis/core/v1beta1" + dataplanev1 "github.com/openstack-k8s-operators/openstack-operator/apis/dataplane/v1beta1" + dataplanecontrollers "github.com/openstack-k8s-operators/openstack-operator/controllers/dataplane" "github.com/openstack-k8s-operators/openstack-operator/pkg/openstack" ovnv1 "github.com/openstack-k8s-operators/ovn-operator/api/v1beta1" placementv1 "github.com/openstack-k8s-operators/placement-operator/api/v1beta1" @@ -89,11 +96,10 @@ var ( ) const ( - timeout = time.Second * 5 - SecretName = "test-osp-secret" - - interval = time.Millisecond * 200 + timeout = 40 * time.Second + // have maximum 100 retries before the timeout hits + interval = timeout / 100 ) func TestAPIs(t *testing.T) { @@ -112,6 +118,12 @@ var _ = BeforeSuite(func() { routev1CRDs, err := test.GetOpenShiftCRDDir("route/v1", "../../go.mod") Expect(err).ShouldNot(HaveOccurred()) + aeeCRDs, err := test.GetCRDDirFromModule( + "github.com/openstack-k8s-operators/openstack-ansibleee-operator/api", "../../go.mod", "bases") + Expect(err).ShouldNot(HaveOccurred()) + baremetalCRDs, err := test.GetCRDDirFromModule( + "github.com/openstack-k8s-operators/openstack-baremetal-operator/api", "../../go.mod", "bases") + Expect(err).ShouldNot(HaveOccurred()) mariaDBCRDs, err := test.GetCRDDirFromModule( "github.com/openstack-k8s-operators/mariadb-operator/api", "../../go.mod", "bases") Expect(err).ShouldNot(HaveOccurred()) @@ -177,6 +189,8 @@ var _ = BeforeSuite(func() { CRDDirectoryPaths: []string{ filepath.Join("..", "..", "config", "crd", "bases"), routev1CRDs, + aeeCRDs, + baremetalCRDs, mariaDBCRDs, infraCRDs, cinderv1CRDs, @@ -217,6 +231,8 @@ var _ = BeforeSuite(func() { Expect(err).NotTo(HaveOccurred()) err = corev1.AddToScheme(scheme.Scheme) Expect(err).NotTo(HaveOccurred()) + err = dataplanev1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) err = routev1.AddToScheme(scheme.Scheme) Expect(err).NotTo(HaveOccurred()) err = cinderv1.AddToScheme(scheme.Scheme) @@ -261,6 +277,16 @@ var _ = BeforeSuite(func() { Expect(err).NotTo(HaveOccurred()) err = networkv1.AddToScheme(scheme.Scheme) Expect(err).NotTo(HaveOccurred()) + err = aeev1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = batchv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = k8s_corev1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = appsv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = baremetalv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) //+kubebuilder:scaffold:scheme @@ -285,6 +311,7 @@ var _ = BeforeSuite(func() { // Start the controller-manager if goroutine webhookInstallOptions := &testEnv.WebhookInstallOptions k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{ + Scheme: scheme.Scheme, Metrics: metricsserver.Options{ BindAddress: "0", }, @@ -307,6 +334,12 @@ var _ = BeforeSuite(func() { Expect(err).NotTo(HaveOccurred()) err = (&corev1.OpenStackControlPlane{}).SetupWebhookWithManager(k8sManager) Expect(err).NotTo(HaveOccurred()) + err = (&dataplanev1.OpenStackDataPlaneNodeSet{}).SetupWebhookWithManager(k8sManager) + Expect(err).NotTo(HaveOccurred()) + err = (&dataplanev1.OpenStackDataPlaneDeployment{}).SetupWebhookWithManager(k8sManager) + Expect(err).NotTo(HaveOccurred()) + err = (&dataplanev1.OpenStackDataPlaneService{}).SetupWebhookWithManager(k8sManager) + Expect(err).NotTo(HaveOccurred()) core_ctrl.SetupVersionDefaults() openstack.SetupServiceOperatorDefaults() @@ -335,6 +368,20 @@ var _ = BeforeSuite(func() { }).SetupWithManager(k8sManager) Expect(err).ToNot(HaveOccurred()) + err = (&dataplanecontrollers.OpenStackDataPlaneNodeSetReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + Kclient: kclient, + }).SetupWithManager(k8sManager) + Expect(err).ToNot(HaveOccurred()) + + err = (&dataplanecontrollers.OpenStackDataPlaneDeploymentReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + Kclient: kclient, + }).SetupWithManager(k8sManager) + Expect(err).ToNot(HaveOccurred()) + go func() { defer GinkgoRecover() err = k8sManager.Start(ctx) diff --git a/tests/kuttl/tests/basic-deployment/01-assert-deploy-openstack.yaml b/tests/kuttl/tests/ctlplane-basic-deployment/01-assert-deploy-openstack.yaml similarity index 100% rename from tests/kuttl/tests/basic-deployment/01-assert-deploy-openstack.yaml rename to tests/kuttl/tests/ctlplane-basic-deployment/01-assert-deploy-openstack.yaml diff --git a/tests/kuttl/tests/basic-deployment/01-deploy-openstack.yaml b/tests/kuttl/tests/ctlplane-basic-deployment/01-deploy-openstack.yaml similarity index 100% rename from tests/kuttl/tests/basic-deployment/01-deploy-openstack.yaml rename to tests/kuttl/tests/ctlplane-basic-deployment/01-deploy-openstack.yaml diff --git a/tests/kuttl/tests/basic-deployment/02-cleanup.yaml b/tests/kuttl/tests/ctlplane-basic-deployment/02-cleanup.yaml similarity index 100% rename from tests/kuttl/tests/basic-deployment/02-cleanup.yaml rename to tests/kuttl/tests/ctlplane-basic-deployment/02-cleanup.yaml diff --git a/tests/kuttl/tests/basic-deployment/02-errors-cleanup.yaml b/tests/kuttl/tests/ctlplane-basic-deployment/02-errors-cleanup.yaml similarity index 100% rename from tests/kuttl/tests/basic-deployment/02-errors-cleanup.yaml rename to tests/kuttl/tests/ctlplane-basic-deployment/02-errors-cleanup.yaml diff --git a/tests/kuttl/tests/collapsed/01-assert-collapsed-cell.yaml b/tests/kuttl/tests/ctlplane-collapsed/01-assert-collapsed-cell.yaml similarity index 100% rename from tests/kuttl/tests/collapsed/01-assert-collapsed-cell.yaml rename to tests/kuttl/tests/ctlplane-collapsed/01-assert-collapsed-cell.yaml diff --git a/tests/kuttl/tests/collapsed/01-deploy-openstack-collapsed-cell.yaml b/tests/kuttl/tests/ctlplane-collapsed/01-deploy-openstack-collapsed-cell.yaml similarity index 100% rename from tests/kuttl/tests/collapsed/01-deploy-openstack-collapsed-cell.yaml rename to tests/kuttl/tests/ctlplane-collapsed/01-deploy-openstack-collapsed-cell.yaml diff --git a/tests/kuttl/tests/collapsed/02-cleanup.yaml b/tests/kuttl/tests/ctlplane-collapsed/02-cleanup.yaml similarity index 100% rename from tests/kuttl/tests/collapsed/02-cleanup.yaml rename to tests/kuttl/tests/ctlplane-collapsed/02-cleanup.yaml diff --git a/tests/kuttl/tests/collapsed/02-errors-cleanup.yaml b/tests/kuttl/tests/ctlplane-collapsed/02-errors-cleanup.yaml similarity index 100% rename from tests/kuttl/tests/collapsed/02-errors-cleanup.yaml rename to tests/kuttl/tests/ctlplane-collapsed/02-errors-cleanup.yaml diff --git a/tests/kuttl/tests/galera-3replicas/01-assert-galera-3replicas.yaml b/tests/kuttl/tests/ctlplane-galera-3replicas/01-assert-galera-3replicas.yaml similarity index 100% rename from tests/kuttl/tests/galera-3replicas/01-assert-galera-3replicas.yaml rename to tests/kuttl/tests/ctlplane-galera-3replicas/01-assert-galera-3replicas.yaml diff --git a/tests/kuttl/tests/galera-3replicas/01-deploy-galera-3replicas.yaml b/tests/kuttl/tests/ctlplane-galera-3replicas/01-deploy-galera-3replicas.yaml similarity index 100% rename from tests/kuttl/tests/galera-3replicas/01-deploy-galera-3replicas.yaml rename to tests/kuttl/tests/ctlplane-galera-3replicas/01-deploy-galera-3replicas.yaml diff --git a/tests/kuttl/tests/galera-3replicas/02-cleanup.yaml b/tests/kuttl/tests/ctlplane-galera-3replicas/02-cleanup.yaml similarity index 100% rename from tests/kuttl/tests/galera-3replicas/02-cleanup.yaml rename to tests/kuttl/tests/ctlplane-galera-3replicas/02-cleanup.yaml diff --git a/tests/kuttl/tests/galera-3replicas/02-errors-cleanup.yaml b/tests/kuttl/tests/ctlplane-galera-3replicas/02-errors-cleanup.yaml similarity index 100% rename from tests/kuttl/tests/galera-3replicas/02-errors-cleanup.yaml rename to tests/kuttl/tests/ctlplane-galera-3replicas/02-errors-cleanup.yaml diff --git a/tests/kuttl/tests/galera-basic/01-assert-galera.yaml b/tests/kuttl/tests/ctlplane-galera-basic/01-assert-galera.yaml similarity index 100% rename from tests/kuttl/tests/galera-basic/01-assert-galera.yaml rename to tests/kuttl/tests/ctlplane-galera-basic/01-assert-galera.yaml diff --git a/tests/kuttl/tests/galera-basic/01-deploy-galera.yaml b/tests/kuttl/tests/ctlplane-galera-basic/01-deploy-galera.yaml similarity index 100% rename from tests/kuttl/tests/galera-basic/01-deploy-galera.yaml rename to tests/kuttl/tests/ctlplane-galera-basic/01-deploy-galera.yaml diff --git a/tests/kuttl/tests/galera-basic/02-cleanup.yaml b/tests/kuttl/tests/ctlplane-galera-basic/02-cleanup.yaml similarity index 100% rename from tests/kuttl/tests/galera-basic/02-cleanup.yaml rename to tests/kuttl/tests/ctlplane-galera-basic/02-cleanup.yaml diff --git a/tests/kuttl/tests/galera-basic/02-errors-cleanup.yaml b/tests/kuttl/tests/ctlplane-galera-basic/02-errors-cleanup.yaml similarity index 100% rename from tests/kuttl/tests/galera-basic/02-errors-cleanup.yaml rename to tests/kuttl/tests/ctlplane-galera-basic/02-errors-cleanup.yaml diff --git a/tests/kuttl/tests/dataplane-create-test/00-assert.yaml b/tests/kuttl/tests/dataplane-create-test/00-assert.yaml new file mode 100644 index 000000000..92aaa44bf --- /dev/null +++ b/tests/kuttl/tests/dataplane-create-test/00-assert.yaml @@ -0,0 +1,125 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 600 +collectors: +- type: command + command: oc logs -n openstack-operators -l openstack.org/operator-name=dataplane + name: dataplane-operator-logs +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneNodeSet +metadata: + name: openstack-edpm +spec: + env: + - name: ANSIBLE_FORCE_COLOR + value: "True" + nodes: + edpm-compute-0: + ansible: + ansibleHost: 192.168.122.100 + ansibleUser: cloud-admin + ansibleVars: + ctlplane_ip: 192.168.122.100 + fqdn_internalapi: edpm-compute-0.example.com + internalapi_ip: 172.17.0.100 + storage_ip: 172.18.0.100 + tenant_ip: 172.19.0.100 + hostName: edpm-compute-0 + nodeTemplate: + ansible: + ansiblePort: 22 + ansibleUser: cloud-admin + ansibleVars: + ctlplane_dns_nameservers: + - 192.168.122.1 + ctlplane_gateway_ip: 192.168.122.1 + ctlplane_host_routes: + - ip_netmask: 0.0.0.0/0 + next_hop: 192.168.122.1 + ctlplane_mtu: 1500 + ctlplane_cidr: 24 + dns_search_domains: [] + timesync_ntp_servers: + - hostname: clock.redhat.com + edpm_network_config_hide_sensitive_logs: false + edpm_nodes_validation_validate_controllers_icmp: false + edpm_nodes_validation_validate_gateway_icmp: false + edpm_selinux_mode: enforcing + edpm_sshd_allowed_ranges: + - 192.168.122.0/24 + edpm_sshd_configure_firewall: true + enable_debug: false + external_cidr: "24" + external_host_routes: [] + external_mtu: 1500 + external_vlan_id: 44 + gather_facts: false + internalapi_cidr: "24" + internalapi_host_routes: [] + internalapi_mtu: 1500 + internalapi_vlan_id: 20 + networks_lower: + External: external + InternalApi: internalapi + Storage: storage + Tenant: tenant + neutron_physical_bridge_name: br-ex + neutron_public_interface_name: eth0 + nodeset_networks: + - InternalApi + - Storage + - Tenant + storage_cidr: "24" + storage_host_routes: [] + storage_mtu: 1500 + storage_vlan_id: 21 + tenant_cidr: "24" + tenant_host_routes: [] + tenant_mtu: 1500 + tenant_vlan_id: 22 + ansibleSSHPrivateKeySecret: dataplane-ansible-ssh-private-key-secret + managementNetwork: ctlplane + preProvisioned: true + tlsEnabled: true + services: + - download-cache + - bootstrap + - configure-network + - validate-network + - install-os + - configure-os + - run-os + - install-certs + - ovn + - neutron-metadata + - libvirt + - nova +status: + observedGeneration: 1 + conditions: + - message: Deployment not started + reason: Requested + status: "False" + type: Ready + - message: Deployment not started + reason: Requested + status: "False" + type: DeploymentReady + - message: Input data complete + reason: Ready + status: "True" + type: InputReady + - message: ServiceAccount created + reason: Ready + status: "True" + type: ServiceAccountReady + - message: Setup complete + reason: Ready + status: "True" + type: SetupReady +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: openstack-edpm diff --git a/tests/kuttl/tests/dataplane-create-test/00-dataplane-create.yaml b/tests/kuttl/tests/dataplane-create-test/00-dataplane-create.yaml new file mode 100644 index 000000000..977e977f1 --- /dev/null +++ b/tests/kuttl/tests/dataplane-create-test/00-dataplane-create.yaml @@ -0,0 +1,137 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: network-config-template +data: + network_config_template: | + --- + {% set mtu_list = [ctlplane_mtu] %} + {% for network in nodeset_networks %} + {{ mtu_list.append(lookup('vars', networks_lower[network] ~ '_mtu')) }} + {%- endfor %} + {% set min_viable_mtu = mtu_list | max %} + network_config: + - type: ovs_bridge + name: {{ neutron_physical_bridge_name }} + mtu: {{ min_viable_mtu }} + use_dhcp: false + dns_servers: {{ ctlplane_dns_nameservers }} + domain: {{ dns_search_domains }} + addresses: + - ip_netmask: {{ ctlplane_ip }}/{{ ctlplane_cidr }} + routes: {{ ctlplane_host_routes }} + members: + - type: interface + name: nic1 + mtu: {{ min_viable_mtu }} + # force the MAC address of the bridge to this interface + primary: true + {% for network in nodeset_networks %} + - type: vlan + mtu: {{ lookup('vars', networks_lower[network] ~ '_mtu') }} + vlan_id: {{ lookup('vars', networks_lower[network] ~ '_vlan_id') }} + addresses: + - ip_netmask: + {{ lookup('vars', networks_lower[network] ~ '_ip') }}/{{ lookup('vars', networks_lower[network] ~ '_cidr') }} + routes: {{ lookup('vars', networks_lower[network] ~ '_host_routes') }} + {% endfor %} +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneNodeSet +metadata: + name: openstack-edpm +spec: + env: + - name: ANSIBLE_FORCE_COLOR + value: "True" + services: + - download-cache + - bootstrap + - configure-network + - validate-network + - install-os + - configure-os + - run-os + - install-certs + - ovn + - neutron-metadata + - libvirt + - nova + preProvisioned: true + nodes: + edpm-compute-0: + hostName: edpm-compute-0 + ansible: + ansibleHost: 192.168.122.100 + ansibleUser: cloud-admin + ansibleVars: + ctlplane_ip: 192.168.122.100 + internalapi_ip: 172.17.0.100 + storage_ip: 172.18.0.100 + tenant_ip: 172.19.0.100 + fqdn_internalapi: edpm-compute-0.example.com + nodeTemplate: + ansibleSSHPrivateKeySecret: dataplane-ansible-ssh-private-key-secret + managementNetwork: ctlplane + ansible: + ansibleUser: cloud-admin + ansiblePort: 22 + ansibleVarsFrom: + - prefix: edpm_ + configMapRef: + name: network-config-template + ansibleVars: + timesync_ntp_servers: + - hostname: clock.redhat.com + # edpm_network_config + # Default nic config template for a EDPM compute node + # These vars are edpm_network_config role vars + edpm_network_config_hide_sensitive_logs: false + # These vars are for the network config templates themselves and are + # considered EDPM network defaults. + neutron_physical_bridge_name: br-ex + neutron_public_interface_name: eth0 + ctlplane_mtu: 1500 + ctlplane_cidr: 24 + ctlplane_gateway_ip: 192.168.122.1 + ctlplane_host_routes: + - ip_netmask: 0.0.0.0/0 + next_hop: 192.168.122.1 + external_mtu: 1500 + external_vlan_id: 44 + external_cidr: '24' + external_host_routes: [] + internalapi_mtu: 1500 + internalapi_vlan_id: 20 + internalapi_cidr: '24' + internalapi_host_routes: [] + storage_mtu: 1500 + storage_vlan_id: 21 + storage_cidr: '24' + storage_host_routes: [] + tenant_mtu: 1500 + tenant_vlan_id: 22 + tenant_cidr: '24' + tenant_host_routes: [] + nodeset_networks: + - InternalApi + - Storage + - Tenant + networks_lower: + External: external + InternalApi: internalapi + Storage: storage + Tenant: tenant + # edpm_nodes_validation + edpm_nodes_validation_validate_controllers_icmp: false + edpm_nodes_validation_validate_gateway_icmp: false + ctlplane_dns_nameservers: + - 192.168.122.1 + dns_search_domains: [] + gather_facts: false + enable_debug: false + # edpm firewall, change the allowed CIDR if needed + edpm_sshd_configure_firewall: true + edpm_sshd_allowed_ranges: ['192.168.122.0/24'] + # SELinux module + edpm_selinux_mode: enforcing diff --git a/tests/kuttl/tests/dataplane-deploy-global-service-test/00-assert.yaml b/tests/kuttl/tests/dataplane-deploy-global-service-test/00-assert.yaml new file mode 100644 index 000000000..577afb6db --- /dev/null +++ b/tests/kuttl/tests/dataplane-deploy-global-service-test/00-assert.yaml @@ -0,0 +1,130 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 600 +collectors: +- type: command + command: oc get OpenStackDataPlaneDeployment -n openstack edpm-compute-global -o yaml + name: edpm-compute-global-deployment +- type: command + command: oc logs -n openstack-operators -l openstack.org/operator-name=dataplane + name: dataplane-operator-logs +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneNodeSet +metadata: + name: edpm-compute-global + namespace: openstack-kuttl-tests +spec: + preProvisioned: true + services: + - download-cache + - bootstrap + - configure-network + - validate-network + - install-os + - configure-os + - run-os + - install-certs + - ovn + - neutron-metadata + - neutron-ovn + - neutron-sriov + - neutron-dhcp + - libvirt + - nova + - custom-global-service + tlsEnabled: false + env: + - name: ANSIBLE_FORCE_COLOR + value: "True" + nodes: {} + nodeTemplate: + ansibleSSHPrivateKeySecret: dataplane-ansible-ssh-private-key-secret + managementNetwork: ctlplane + ansible: + ansibleUser: cloud-admin + ansiblePort: 22 + ansibleVarsFrom: + - prefix: edpm_ + configMapRef: + name: network-config-template + ansibleVars: + timesync_ntp_servers: + - hostname: clock.redhat.com + # edpm_network_config + # Default nic config template for a EDPM compute node + # These vars are edpm_network_config role vars + edpm_network_config_hide_sensitive_logs: false + # These vars are for the network config templates themselves and are + # considered EDPM network defaults. + neutron_physical_bridge_name: br-ex + neutron_public_interface_name: eth0 + ctlplane_mtu: 1500 + ctlplane_cidr: 24 + ctlplane_gateway_ip: 192.168.122.1 + ctlplane_host_routes: + - ip_netmask: 0.0.0.0/0 + next_hop: 192.168.122.1 + external_mtu: 1500 + external_vlan_id: 44 + external_cidr: '24' + external_host_routes: [] + internalapi_mtu: 1500 + internalapi_vlan_id: 20 + internalapi_cidr: '24' + internalapi_host_routes: [] + storage_mtu: 1500 + storage_vlan_id: 21 + storage_cidr: '24' + storage_host_routes: [] + tenant_mtu: 1500 + tenant_vlan_id: 22 + tenant_cidr: '24' + tenant_host_routes: [] + nodeset_networks: + - InternalApi + - Storage + - Tenant + networks_lower: + External: external + InternalApi: internalapi + Storage: storage + Tenant: tenant + # edpm_nodes_validation + edpm_nodes_validation_validate_controllers_icmp: false + edpm_nodes_validation_validate_gateway_icmp: false + ctlplane_dns_nameservers: + - 192.168.122.1 + dns_search_domains: [] + gather_facts: false + enable_debug: false + # edpm firewall, change the allowed CIDR if needed + edpm_sshd_configure_firewall: true + edpm_sshd_allowed_ranges: ['192.168.122.0/24'] + # SELinux module + edpm_selinux_mode: enforcing +status: + observedGeneration: 1 + conditions: + - message: Deployment not started + reason: Requested + severity: Info + status: "False" + type: Ready + - message: Deployment not started + reason: Requested + severity: Info + status: "False" + type: DeploymentReady + - message: Input data complete + reason: Ready + status: "True" + type: InputReady + - message: ServiceAccount created + reason: Ready + status: "True" + type: ServiceAccountReady + - message: Setup complete + reason: Ready + status: "True" + type: SetupReady diff --git a/tests/kuttl/tests/dataplane-deploy-global-service-test/00-dataplane-create.yaml b/tests/kuttl/tests/dataplane-deploy-global-service-test/00-dataplane-create.yaml new file mode 100644 index 000000000..9aff2f749 --- /dev/null +++ b/tests/kuttl/tests/dataplane-deploy-global-service-test/00-dataplane-create.yaml @@ -0,0 +1,230 @@ +apiVersion: v1 +kind: Secret +metadata: + name: nova-cell1-compute-config +data: + nova-blank.conf: Zm9vCg== + 01-nova.conf: Zm9vCg== +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: ovncontroller-config +data: + ovsdb-config: test-ovn-config +--- +apiVersion: v1 +kind: Secret +metadata: + name: neutron-ovn-metadata-agent-neutron-config +data: + 10-neutron-metadata.conf: dGVzdC1uZXV0cm9uLW92bi1tZXRhZGF0YS1hZ2VudC1jb25maWc= +--- +apiVersion: v1 +kind: Secret +metadata: + name: nova-metadata-neutron-config +data: + 05-nova-metadata.conf: dGVzdC1ub3ZhLW1ldGFkYXRhLWNvbXB1dGUtY29uZmln + httpd.conf: dGVzdC1ub3ZhLW1ldGFkYXRhLWNvbXB1dGUtY29uZmln + nova-metadata-config.json: dGVzdC1ub3ZhLW1ldGFkYXRhLWNvbXB1dGUtY29uZmln +--- +apiVersion: v1 +kind: Secret +metadata: + name: neutron-ovn-agent-neutron-config +data: + 10-neutron-ovn.conf: dGVzdC1uZXV0cm9uLW92bi1hZ2VudC1jb25maWc= +--- +apiVersion: v1 +kind: Secret +metadata: + name: neutron-sriov-agent-neutron-config +data: + 10-neutron-sriov.conf: dGVzdC1uZXV0cm9uLXNyaW92LWFnZW50LXNlY3JldC1jb25maWcK +--- +apiVersion: v1 +kind: Secret +metadata: + name: neutron-dhcp-agent-neutron-config +data: + 10-neutron-dhcp.conf: dGVzdC1uZXV0cm9uLWRoY3AtYWdlbnQtc2VjcmV0LWNvbmZpZwo= +--- +apiVersion: v1 +kind: Secret +metadata: + name: nova-migration-ssh-key +data: + ssh-privatekey: ZmFrZQo= + ssh-publickey: ZmFrZQo= +--- +apiVersion: v1 +kind: Secret +metadata: + name: libvirt-secret +data: + LibvirtPassword: ZmFrZQo= +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + name: custom-global-service +spec: + label: custom-global-service + play: | + - hosts: localhost + gather_facts: no + name: global kuttl play + tasks: + - name: Sleep + command: sleep 1 + delegate_to: localhost + deployOnAllNodeSets: true +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: network-config-template +data: + network_config_template: | + --- + {% set mtu_list = [ctlplane_mtu] %} + {% for network in nodeset_networks %} + {{ mtu_list.append(lookup('vars', networks_lower[network] ~ '_mtu')) }} + {%- endfor %} + {% set min_viable_mtu = mtu_list | max %} + network_config: + - type: ovs_bridge + name: {{ neutron_physical_bridge_name }} + mtu: {{ min_viable_mtu }} + use_dhcp: false + dns_servers: {{ ctlplane_dns_nameservers }} + domain: {{ dns_search_domains }} + addresses: + - ip_netmask: {{ ctlplane_ip }}/{{ ctlplane_cidr }} + routes: {{ ctlplane_host_routes }} + members: + - type: interface + name: nic1 + mtu: {{ min_viable_mtu }} + # force the MAC address of the bridge to this interface + primary: true + {% for network in nodeset_networks %} + - type: vlan + mtu: {{ lookup('vars', networks_lower[network] ~ '_mtu') }} + vlan_id: {{ lookup('vars', networks_lower[network] ~ '_vlan_id') }} + addresses: + - ip_netmask: + {{ lookup('vars', networks_lower[network] ~ '_ip') }}/{{ lookup('vars', networks_lower[network] ~ '_cidr') }} + routes: {{ lookup('vars', networks_lower[network] ~ '_host_routes') }} + {% endfor %} +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneNodeSet +metadata: + name: edpm-compute-global +spec: + baremetalSetTemplate: + automatedCleaningMode: metadata + bmhNamespace: openshift-machine-api + cloudUserName: "" + ctlplaneInterface: "" + ctlplaneNetmask: 255.255.255.0 + deploymentSSHSecret: "" + hardwareReqs: + cpuReqs: + countReq: {} + mhzReq: {} + diskReqs: + gbReq: {} + ssdReq: {} + memReqs: + gbReq: {} + preProvisioned: true + tlsEnabled: false + services: + - download-cache + - bootstrap + - configure-network + - validate-network + - install-os + - configure-os + - run-os + - install-certs + - ovn + - neutron-metadata + - neutron-ovn + - neutron-sriov + - neutron-dhcp + - libvirt + - nova + - custom-global-service + env: + - name: ANSIBLE_FORCE_COLOR + value: "True" + nodes: {} + nodeTemplate: + ansibleSSHPrivateKeySecret: dataplane-ansible-ssh-private-key-secret + managementNetwork: ctlplane + ansible: + ansibleUser: cloud-admin + ansiblePort: 22 + ansibleVarsFrom: + - prefix: edpm_ + configMapRef: + name: network-config-template + ansibleVars: + timesync_ntp_servers: + - hostname: clock.redhat.com + # edpm_network_config + # Default nic config template for a EDPM compute node + # These vars are edpm_network_config role vars + edpm_network_config_hide_sensitive_logs: false + # These vars are for the network config templates themselves and are + # considered EDPM network defaults. + neutron_physical_bridge_name: br-ex + neutron_public_interface_name: eth0 + ctlplane_mtu: 1500 + ctlplane_cidr: 24 + ctlplane_gateway_ip: 192.168.122.1 + ctlplane_host_routes: + - ip_netmask: 0.0.0.0/0 + next_hop: 192.168.122.1 + external_mtu: 1500 + external_vlan_id: 44 + external_cidr: '24' + external_host_routes: [] + internalapi_mtu: 1500 + internalapi_vlan_id: 20 + internalapi_cidr: '24' + internalapi_host_routes: [] + storage_mtu: 1500 + storage_vlan_id: 21 + storage_cidr: '24' + storage_host_routes: [] + tenant_mtu: 1500 + tenant_vlan_id: 22 + tenant_cidr: '24' + tenant_host_routes: [] + nodeset_networks: + - InternalApi + - Storage + - Tenant + networks_lower: + External: external + InternalApi: internalapi + Storage: storage + Tenant: tenant + # edpm_nodes_validation + edpm_nodes_validation_validate_controllers_icmp: false + edpm_nodes_validation_validate_gateway_icmp: false + ctlplane_dns_nameservers: + - 192.168.122.1 + dns_search_domains: [] + gather_facts: false + enable_debug: false + # edpm firewall, change the allowed CIDR if needed + edpm_sshd_configure_firewall: true + edpm_sshd_allowed_ranges: ['192.168.122.0/24'] + # SELinux module + edpm_selinux_mode: enforcing diff --git a/tests/kuttl/tests/dataplane-deploy-global-service-test/01-assert.yaml b/tests/kuttl/tests/dataplane-deploy-global-service-test/01-assert.yaml new file mode 100644 index 000000000..3f1c9f6ac --- /dev/null +++ b/tests/kuttl/tests/dataplane-deploy-global-service-test/01-assert.yaml @@ -0,0 +1,1018 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 600 +collectors: +- type: command + command: oc get OpenStackDataPlaneDeployment -n openstack edpm-compute-global -o yaml + name: edpm-compute-global-deployment +- type: command + command: oc logs -n openstack-operators -l openstack.org/operator-name=dataplane + name: dataplane-operator-logs +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneNodeSet +metadata: + name: edpm-compute-global + namespace: openstack-kuttl-tests +spec: + services: + - download-cache + - bootstrap + - configure-network + - validate-network + - install-os + - configure-os + - run-os + - install-certs + - ovn + - neutron-metadata + - neutron-ovn + - neutron-sriov + - neutron-dhcp + - libvirt + - nova + - custom-global-service + env: + - name: ANSIBLE_FORCE_COLOR + value: "True" + nodes: {} + nodeTemplate: + ansibleSSHPrivateKeySecret: dataplane-ansible-ssh-private-key-secret +status: + observedGeneration: 1 + conditions: + - message: NodeSet Ready + reason: Ready + status: "True" + type: Ready + - message: Deployment completed + reason: Ready + status: "True" + type: DeploymentReady + - message: Input data complete + reason: Ready + status: "True" + type: InputReady + - message: ServiceAccount created + reason: Ready + status: "True" + type: ServiceAccountReady + - message: Setup complete + reason: Ready + status: "True" + type: SetupReady +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + generation: 1 + name: custom-global-service-edpm-compute-global + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + blockOwnerDeletion: true + controller: true + kind: OpenStackDataPlaneDeployment + name: edpm-compute-global +spec: + backoffLimit: 6 + env: + - name: ANSIBLE_FORCE_COLOR + value: "True" + envConfigMapName: openstack-aee-default-env + extraMounts: + - mounts: + - mountPath: /runner/env/ssh_key/ssh_key_edpm-compute-global + name: ssh-key-edpm-compute-global + subPath: ssh_key_edpm-compute-global + - mountPath: /runner/inventory/inventory-0 + name: inventory-0 + subPath: inventory-0 + volumes: + - name: ssh-key-edpm-compute-global + secret: + items: + - key: ssh-privatekey + path: ssh_key_edpm-compute-global + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory-0 + secret: + items: + - key: inventory + path: inventory-0 + secretName: dataplanenodeset-edpm-compute-global + extraVars: + edpm_override_hosts: all + name: openstackansibleee + play: | + - hosts: localhost + gather_facts: no + name: global kuttl play + tasks: + - name: Sleep + command: sleep 1 + delegate_to: localhost + preserveJobs: true + restartPolicy: Never + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + name: download-cache-edpm-compute-global-edpm-compute-global + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + blockOwnerDeletion: true + controller: true + kind: OpenStackDataPlaneDeployment + name: edpm-compute-global +spec: + backoffLimit: 6 + extraMounts: + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-edpm-compute-global + name: openstackansibleee + restartPolicy: Never + playbook: osp.edpm.download_cache + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + name: bootstrap-edpm-compute-global-edpm-compute-global + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + blockOwnerDeletion: true + controller: true + kind: OpenStackDataPlaneDeployment + name: edpm-compute-global +spec: + backoffLimit: 6 + extraMounts: + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-edpm-compute-global + name: openstackansibleee + restartPolicy: Never + playbook: osp.edpm.bootstrap + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady + +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + name: configure-network-edpm-compute-global-edpm-compute-global + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + blockOwnerDeletion: true + controller: true + kind: OpenStackDataPlaneDeployment + name: edpm-compute-global +spec: + backoffLimit: 6 + extraMounts: + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-edpm-compute-global + name: openstackansibleee + restartPolicy: Never + playbook: osp.edpm.configure_network + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + name: validate-network-edpm-compute-global-edpm-compute-global + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + blockOwnerDeletion: true + controller: true + kind: OpenStackDataPlaneDeployment + name: edpm-compute-global +spec: + backoffLimit: 6 + extraMounts: + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-edpm-compute-global + name: openstackansibleee + restartPolicy: Never + playbook: osp.edpm.validate_network + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + name: install-os-edpm-compute-global-edpm-compute-global + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + blockOwnerDeletion: true + controller: true + kind: OpenStackDataPlaneDeployment + name: edpm-compute-global +spec: + backoffLimit: 6 + extraMounts: + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-edpm-compute-global + name: openstackansibleee + restartPolicy: Never + playbook: osp.edpm.install_os + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + generation: 1 + name: configure-os-edpm-compute-global-edpm-compute-global + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + blockOwnerDeletion: true + controller: true + kind: OpenStackDataPlaneDeployment + name: edpm-compute-global +spec: + backoffLimit: 6 + extraMounts: + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-edpm-compute-global + name: openstackansibleee + restartPolicy: Never + playbook: osp.edpm.configure_os + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + name: run-os-edpm-compute-global-edpm-compute-global + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + blockOwnerDeletion: true + controller: true + kind: OpenStackDataPlaneDeployment + name: edpm-compute-global +spec: + backoffLimit: 6 + extraMounts: + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-edpm-compute-global + name: openstackansibleee + restartPolicy: Never + playbook: osp.edpm.run_os + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + name: install-certs-edpm-compute-global-edpm-compute-global + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + blockOwnerDeletion: true + controller: true + kind: OpenStackDataPlaneDeployment + name: edpm-compute-global +spec: + backoffLimit: 6 + extraMounts: + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-edpm-compute-global + name: openstackansibleee + restartPolicy: Never + playbook: osp.edpm.install_certs + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + generation: 1 + name: ovn-edpm-compute-global-edpm-compute-global + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + blockOwnerDeletion: true + controller: true + kind: OpenStackDataPlaneDeployment + name: edpm-compute-global +spec: + backoffLimit: 6 + extraMounts: + - mounts: + - mountPath: /var/lib/openstack/configs/ovn/ovsdb-config + name: ovncontroller-config-0 + subPath: ovsdb-config + volumes: + - configMap: + items: + - key: ovsdb-config + path: ovsdb-config + name: ovncontroller-config + name: ovncontroller-config-0 + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-edpm-compute-global + name: openstackansibleee + restartPolicy: Never + playbook: osp.edpm.ovn + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + generation: 1 + name: neutron-metadata-edpm-compute-global-edpm-compute-global + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + blockOwnerDeletion: true + controller: true + kind: OpenStackDataPlaneDeployment + name: edpm-compute-global +spec: + backoffLimit: 6 + extraMounts: + - mounts: + - mountPath: /var/lib/openstack/configs/neutron-metadata/10-neutron-metadata.conf + name: neutron-ovn-metadata-agent-neutron-config-0 + subPath: 10-neutron-metadata.conf + volumes: + - secret: + items: + - key: 10-neutron-metadata.conf + path: 10-neutron-metadata.conf + secretName: neutron-ovn-metadata-agent-neutron-config + name: neutron-ovn-metadata-agent-neutron-config-0 + - mounts: + - mountPath: /var/lib/openstack/configs/neutron-metadata/05-nova-metadata.conf + name: nova-metadata-neutron-config-0 + subPath: 05-nova-metadata.conf + - mountPath: /var/lib/openstack/configs/neutron-metadata/httpd.conf + name: nova-metadata-neutron-config-1 + subPath: httpd.conf + - mountPath: /var/lib/openstack/configs/neutron-metadata/nova-metadata-config.json + name: nova-metadata-neutron-config-2 + subPath: nova-metadata-config.json + volumes: + - secret: + items: + - key: 05-nova-metadata.conf + path: 05-nova-metadata.conf + secretName: nova-metadata-neutron-config + name: nova-metadata-neutron-config-0 + - name: nova-metadata-neutron-config-1 + secret: + items: + - key: httpd.conf + path: httpd.conf + secretName: nova-metadata-neutron-config + - name: nova-metadata-neutron-config-2 + secret: + items: + - key: nova-metadata-config.json + path: nova-metadata-config.json + secretName: nova-metadata-neutron-config + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-edpm-compute-global + name: openstackansibleee + restartPolicy: Never + playbook: osp.edpm.neutron_metadata + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + generation: 1 + name: neutron-ovn-edpm-compute-global-edpm-compute-global + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + blockOwnerDeletion: true + controller: true + kind: OpenStackDataPlaneDeployment + name: edpm-compute-global +spec: + backoffLimit: 6 + extraMounts: + - mounts: + - mountPath: /var/lib/openstack/configs/neutron-ovn/10-neutron-ovn.conf + name: neutron-ovn-agent-neutron-config-0 + subPath: 10-neutron-ovn.conf + volumes: + - secret: + items: + - key: 10-neutron-ovn.conf + path: 10-neutron-ovn.conf + secretName: neutron-ovn-agent-neutron-config + name: neutron-ovn-agent-neutron-config-0 + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-edpm-compute-global + name: openstackansibleee + restartPolicy: Never + playbook: osp.edpm.neutron_ovn + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + generation: 1 + name: neutron-sriov-edpm-compute-global-edpm-compute-global + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + blockOwnerDeletion: true + controller: true + kind: OpenStackDataPlaneDeployment + name: edpm-compute-global +spec: + backoffLimit: 6 + extraMounts: + - mounts: + - mountPath: /var/lib/openstack/configs/neutron-sriov/10-neutron-sriov.conf + name: neutron-sriov-agent-neutron-config-0 + subPath: 10-neutron-sriov.conf + volumes: + - secret: + items: + - key: 10-neutron-sriov.conf + path: 10-neutron-sriov.conf + secretName: neutron-sriov-agent-neutron-config + name: neutron-sriov-agent-neutron-config-0 + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-edpm-compute-global + name: openstackansibleee + restartPolicy: Never + playbook: osp.edpm.neutron_sriov + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + generation: 1 + name: neutron-dhcp-edpm-compute-global-edpm-compute-global + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + blockOwnerDeletion: true + controller: true + kind: OpenStackDataPlaneDeployment + name: edpm-compute-global +spec: + backoffLimit: 6 + extraMounts: + - mounts: + - mountPath: /var/lib/openstack/configs/neutron-dhcp/10-neutron-dhcp.conf + name: neutron-dhcp-agent-neutron-config-0 + subPath: 10-neutron-dhcp.conf + volumes: + - secret: + items: + - key: 10-neutron-dhcp.conf + path: 10-neutron-dhcp.conf + secretName: neutron-dhcp-agent-neutron-config + name: neutron-dhcp-agent-neutron-config-0 + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-edpm-compute-global + name: openstackansibleee + restartPolicy: Never + playbook: osp.edpm.neutron_dhcp + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + name: libvirt-edpm-compute-global-edpm-compute-global + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + blockOwnerDeletion: true + controller: true + kind: OpenStackDataPlaneDeployment + name: edpm-compute-global +spec: + backoffLimit: 6 + envConfigMapName: openstack-aee-default-env + extraMounts: + - mounts: + - mountPath: /var/lib/openstack/configs/libvirt/LibvirtPassword + name: libvirt-secret-0 + subPath: LibvirtPassword + volumes: + - name: libvirt-secret-0 + secret: + items: + - key: LibvirtPassword + path: LibvirtPassword + secretName: libvirt-secret + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-edpm-compute-global + name: openstackansibleee + preserveJobs: true + restartPolicy: Never + playbook: osp.edpm.libvirt + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + name: nova-edpm-compute-global-edpm-compute-global + namespace: openstack-kuttl-tests +spec: + backoffLimit: 6 + envConfigMapName: openstack-aee-default-env + extraMounts: + - mounts: + - mountPath: /var/lib/openstack/configs/nova/01-nova.conf + name: nova-cell1-compute-config-0 + subPath: 01-nova.conf + - mountPath: /var/lib/openstack/configs/nova/nova-blank.conf + name: nova-cell1-compute-config-1 + subPath: nova-blank.conf + volumes: + - name: nova-cell1-compute-config-0 + secret: + items: + - key: 01-nova.conf + path: 01-nova.conf + secretName: nova-cell1-compute-config + - name: nova-cell1-compute-config-1 + secret: + items: + - key: nova-blank.conf + path: nova-blank.conf + secretName: nova-cell1-compute-config + - mounts: + - mountPath: /var/lib/openstack/configs/nova/ssh-privatekey + name: nova-migration-ssh-key-0 + subPath: ssh-privatekey + - mountPath: /var/lib/openstack/configs/nova/ssh-publickey + name: nova-migration-ssh-key-1 + subPath: ssh-publickey + volumes: + - name: nova-migration-ssh-key-0 + secret: + items: + - key: ssh-privatekey + path: ssh-privatekey + secretName: nova-migration-ssh-key + - name: nova-migration-ssh-key-1 + secret: + items: + - key: ssh-publickey + path: ssh-publickey + secretName: nova-migration-ssh-key + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-edpm-compute-global + name: openstackansibleee + preserveJobs: true + restartPolicy: Never + playbook: osp.edpm.nova + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady diff --git a/tests/kuttl/tests/dataplane-deploy-global-service-test/01-dataplane-deploy.yaml b/tests/kuttl/tests/dataplane-deploy-global-service-test/01-dataplane-deploy.yaml new file mode 100644 index 000000000..62f2006c3 --- /dev/null +++ b/tests/kuttl/tests/dataplane-deploy-global-service-test/01-dataplane-deploy.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneDeployment +metadata: + name: edpm-compute-global +spec: + nodeSets: + - edpm-compute-global diff --git a/tests/kuttl/tests/dataplane-deploy-global-service-test/02-add-nodeset.yaml b/tests/kuttl/tests/dataplane-deploy-global-service-test/02-add-nodeset.yaml new file mode 100644 index 000000000..d9ae4555e --- /dev/null +++ b/tests/kuttl/tests/dataplane-deploy-global-service-test/02-add-nodeset.yaml @@ -0,0 +1,42 @@ +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneNodeSet +metadata: + name: edpm-compute-beta-nodeset +spec: + baremetalSetTemplate: + automatedCleaningMode: metadata + bmhNamespace: openshift-machine-api + cloudUserName: "" + ctlplaneInterface: "" + ctlplaneNetmask: 255.255.255.0 + deploymentSSHSecret: "" + hardwareReqs: + cpuReqs: + countReq: {} + mhzReq: {} + diskReqs: + gbReq: {} + ssdReq: {} + memReqs: + gbReq: {} + preProvisioned: true + services: + - download-cache + - bootstrap + env: + - name: ANSIBLE_FORCE_COLOR + value: "True" + nodes: {} + nodeTemplate: + ansibleSSHPrivateKeySecret: dataplane-ansible-ssh-private-key-secret + ansible: + ansibleUser: cloud-admin +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneDeployment +metadata: + name: edpm-multinodeset +spec: + nodeSets: + - edpm-compute-global + - edpm-compute-beta-nodeset diff --git a/tests/kuttl/tests/dataplane-deploy-global-service-test/02-assert.yaml b/tests/kuttl/tests/dataplane-deploy-global-service-test/02-assert.yaml new file mode 100644 index 000000000..dd063a11e --- /dev/null +++ b/tests/kuttl/tests/dataplane-deploy-global-service-test/02-assert.yaml @@ -0,0 +1,168 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 600 +collectors: +- type: command + command: oc logs -n openstack-operators -l openstack.org/operator-name=dataplane + name: dataplane-operator-logs +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneNodeSet +metadata: + name: edpm-compute-beta-nodeset + namespace: openstack-kuttl-tests +spec: + services: + - download-cache + - bootstrap + env: + - name: ANSIBLE_FORCE_COLOR + value: "True" + nodes: {} + nodeTemplate: + ansibleSSHPrivateKeySecret: dataplane-ansible-ssh-private-key-secret +status: + conditions: + - message: NodeSet Ready + reason: Ready + status: "True" + type: Ready + - message: Deployment completed + reason: Ready + status: "True" + type: DeploymentReady + - message: Input data complete + reason: Ready + status: "True" + type: InputReady + - message: ServiceAccount created + reason: Ready + status: "True" + type: ServiceAccountReady + - message: Setup complete + reason: Ready + status: "True" + type: SetupReady + configHash: ncbh88h5c5h59bh8bh664hffh564hf8h697h5cbh585h6fh55dh5f6h66ch57dh5cdh585h678hb9h65ch547h595h596h8bh65bh5h585h68chfbh5dcq + configMapHashes: + ovncontroller-config: n647h6fh674h55fh56ch5bh68bh5fdh8dh59ch58dhdch59ch646h568h675h99h66bh59bhcch5b4h589h674h568hbch84h554h95h6dhc4hbh699q + deployedConfigHash: ncbh88h5c5h59bh8bh664hffh564hf8h697h5cbh585h6fh55dh5f6h66ch57dh5cdh585h678hb9h65ch547h595h596h8bh65bh5h585h68chfbh5dcq + deploymentStatuses: {} + secretHashes: + neutron-dhcp-agent-neutron-config: n68h676h98h689hd4h575h5dbh694h6fh688h57h665h5c5h56dh5ddh65bh5d7h5cdh644hb8h8fh5d9h5b9h555h9ch56dh5fh6chd4h5c5h5c5h68q + neutron-ovn-agent-neutron-config: n5f4h89hb8h645h55bh657h9fh5d9h5c6h595h9dh667h5f4hfhffh7fh685h56ch57fh679h5ddh5ddh95h696hbch5c7h669h84h54dh685hfh85q + neutron-ovn-metadata-agent-neutron-config: n68dh585h666h5c4h568hf7h65fh695h649hb9h657h5f6h548h679h77h5b4h664h8h5b8h654h5hf5h674h664h545h74h58ch57ch8ch56h54fh5ddq + neutron-sriov-agent-neutron-config: n685h567h697h5bch8ch5cfh87h698h658h684h8h99h5dch5c5h699h79hb5h87h66dh664h546h586h7bh56fh5d6h5d4h566h56bh87h678h696h56cq + nova-cell1-compute-config: n89hd6h5h545h644h58h556hd9h5c5h598hd4h7bh5f9h5bdh649hb5h99h686h677h8ch575h665h574h587h5b6h5ddh8fh687h9bh657h675h97q + nova-metadata-neutron-config: n7fh696h674h5b9h68dh77h677h5c5hd9h5dbh89h646h696h65ch64bh86hd8h56h78h558h5h5c7h87h86h5bh5bch78h6ch5cbh54fh56fhfdq + nova-migration-ssh-key: n64dh97h54dhffh65fh577h59bh664hbch54dhcbh547hdbhdch655hd9h675h5d4h67dh5ch67bh64h5fdh5c8h5cdh66bh5f5h58dhcbh9bh66bhd4q +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneDeployment +metadata: + name: edpm-multinodeset + namespace: openstack-kuttl-tests +spec: + nodeSets: + - edpm-compute-global + - edpm-compute-beta-nodeset +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + name: download-cache-edpm-multinodeset-edpm-compute-beta-nodeset + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + blockOwnerDeletion: true + controller: true + kind: OpenStackDataPlaneDeployment + name: edpm-multinodeset +spec: + backoffLimit: 6 + extraMounts: + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-edpm-compute-beta-nodeset + name: openstackansibleee + restartPolicy: Never + playbook: osp.edpm.download_cache + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + name: bootstrap-edpm-multinodeset-edpm-compute-beta-nodeset + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + blockOwnerDeletion: true + controller: true + kind: OpenStackDataPlaneDeployment + name: edpm-multinodeset +spec: + backoffLimit: 6 + extraMounts: + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-edpm-compute-beta-nodeset + name: openstackansibleee + restartPolicy: Never + playbook: osp.edpm.bootstrap + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady diff --git a/tests/kuttl/tests/dataplane-deploy-multiple-secrets/00-assert.yaml b/tests/kuttl/tests/dataplane-deploy-multiple-secrets/00-assert.yaml new file mode 100644 index 000000000..0114915be --- /dev/null +++ b/tests/kuttl/tests/dataplane-deploy-multiple-secrets/00-assert.yaml @@ -0,0 +1,358 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 600 +collectors: +- type: command + command: oc logs -n openstack-operators -l openstack.org/operator-name=dataplane + name: dataplane-operator-logs +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + name: generic-service1 +spec: + caCerts: combined-ca-bundle + tlsCert: + contents: + - dnsnames + play: | + - hosts: localhost + gather_facts: no + name: kuttl play + tasks: + - name: Sleep + command: sleep 1 + delegate_to: localhost +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + name: install-certs-ovr +spec: + addCertMounts: True + play: | + - hosts: localhost + gather_facts: no + name: kuttl play + tasks: + - name: Sleep + command: sleep 1 + delegate_to: localhost +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneNodeSet +metadata: + name: openstack-edpm-tls +spec: + env: + - name: ANSIBLE_FORCE_COLOR + value: "True" + nodes: + edpm-compute-0: + hostName: edpm-compute-0 + networks: + - name: ctlplane + subnetName: subnet1 + defaultRoute: true + fixedIP: 192.168.122.100 + - name: internalapi + subnetName: subnet1 + fixedIP: 172.17.0.100 + - name: storage + subnetName: subnet1 + fixedIP: 172.18.0.100 + - name: tenant + subnetName: subnet1 + fixedIP: 172.19.0.100 + edpm-compute-1: + hostName: edpm-compute-1 + networks: + - name: ctlplane + subnetName: subnet1 + defaultRoute: true + fixedIP: 192.168.122.101 + - name: internalapi + subnetName: subnet1 + fixedIP: 172.17.0.101 + - name: storage + subnetName: subnet1 + fixedIP: 172.18.0.101 + - name: tenant + subnetName: subnet1 + fixedIP: 172.19.0.101 + edpm-compute-2: + hostName: edpm-compute-2 + networks: + - name: ctlplane + subnetName: subnet1 + defaultRoute: true + fixedIP: 192.168.122.102 + - name: internalapi + subnetName: subnet1 + fixedIP: 172.17.0.102 + - name: storage + subnetName: subnet1 + fixedIP: 172.18.0.102 + - name: tenant + subnetName: subnet1 + fixedIP: 172.19.0.102 + nodeTemplate: + ansible: + ansiblePort: 22 + ansibleUser: cloud-admin + ansibleVars: + timesync_ntp_servers: + - hostname: clock.redhat.com + edpm_network_config_hide_sensitive_logs: false + edpm_network_config_template: templates/single_nic_vlans/single_nic_vlans.j2 + edpm_nodes_validation_validate_controllers_icmp: false + edpm_nodes_validation_validate_gateway_icmp: false + edpm_selinux_mode: enforcing + edpm_sshd_allowed_ranges: + - 192.168.122.0/24 + edpm_sshd_configure_firewall: true + enable_debug: false + ansibleSSHPrivateKeySecret: dataplane-ansible-ssh-private-key-secret + preProvisioned: true + tlsEnabled: true + secretMaxSize: 2880 + services: + - install-certs-ovr + - generic-service1 +status: + observedGeneration: 1 + conditions: + - message: Deployment not started + reason: Requested + status: "False" + type: Ready + - message: Deployment not started + reason: Requested + status: "False" + type: DeploymentReady + - message: Input data complete + reason: Ready + status: "True" + type: InputReady + - message: NodeSetDNSDataReady ready + reason: Ready + status: "True" + type: NodeSetDNSDataReady + - message: NodeSetIPReservationReady ready + reason: Ready + status: "True" + type: NodeSetIPReservationReady + - message: ServiceAccount created + reason: Ready + status: "True" + type: ServiceAccountReady + - message: Setup complete + reason: Ready + status: "True" + type: SetupReady +--- +apiVersion: network.openstack.org/v1beta1 +kind: IPSet +metadata: + name: edpm-compute-0 +spec: + immutable: false + networks: + - defaultRoute: true + name: ctlplane + subnetName: subnet1 + - name: internalapi + subnetName: subnet1 + - name: storage + subnetName: subnet1 + - name: tenant + subnetName: subnet1 +status: + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: Input data complete + reason: Ready + status: "True" + type: InputReady + - message: Reservation successful + reason: Ready + status: "True" + type: ReservationReady + reservations: + - address: 192.168.122.100 + cidr: 192.168.122.0/24 + dnsDomain: ctlplane.example.com + gateway: 192.168.122.1 + mtu: 1500 + network: ctlplane + routes: + - destination: 0.0.0.0/0 + nexthop: 192.168.122.1 + subnet: subnet1 + - address: 172.17.0.100 + cidr: 172.17.0.0/24 + dnsDomain: internalapi.example.com + mtu: 1500 + network: internalapi + subnet: subnet1 + vlan: 20 + - address: 172.18.0.100 + cidr: 172.18.0.0/24 + dnsDomain: storage.example.com + mtu: 1500 + network: storage + subnet: subnet1 + vlan: 21 + - address: 172.19.0.100 + cidr: 172.19.0.0/24 + dnsDomain: tenant.example.com + mtu: 1500 + network: tenant + subnet: subnet1 + vlan: 22 +--- +apiVersion: network.openstack.org/v1beta1 +kind: IPSet +metadata: + name: edpm-compute-1 +spec: + immutable: false + networks: + - defaultRoute: true + name: ctlplane + subnetName: subnet1 + - name: internalapi + subnetName: subnet1 + - name: storage + subnetName: subnet1 + - name: tenant + subnetName: subnet1 +status: + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: Input data complete + reason: Ready + status: "True" + type: InputReady + - message: Reservation successful + reason: Ready + status: "True" + type: ReservationReady + reservations: + - address: 192.168.122.101 + cidr: 192.168.122.0/24 + dnsDomain: ctlplane.example.com + gateway: 192.168.122.1 + mtu: 1500 + network: ctlplane + routes: + - destination: 0.0.0.0/0 + nexthop: 192.168.122.1 + subnet: subnet1 + - address: 172.17.0.101 + cidr: 172.17.0.0/24 + dnsDomain: internalapi.example.com + mtu: 1500 + network: internalapi + subnet: subnet1 + vlan: 20 + - address: 172.18.0.101 + cidr: 172.18.0.0/24 + dnsDomain: storage.example.com + mtu: 1500 + network: storage + subnet: subnet1 + vlan: 21 + - address: 172.19.0.101 + cidr: 172.19.0.0/24 + dnsDomain: tenant.example.com + mtu: 1500 + network: tenant + subnet: subnet1 + vlan: 22 +--- +apiVersion: network.openstack.org/v1beta1 +kind: IPSet +metadata: + name: edpm-compute-2 +spec: + immutable: false + networks: + - defaultRoute: true + name: ctlplane + subnetName: subnet1 + - name: internalapi + subnetName: subnet1 + - name: storage + subnetName: subnet1 + - name: tenant + subnetName: subnet1 +status: + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: Input data complete + reason: Ready + status: "True" + type: InputReady + - message: Reservation successful + reason: Ready + status: "True" + type: ReservationReady + reservations: + - address: 192.168.122.102 + cidr: 192.168.122.0/24 + dnsDomain: ctlplane.example.com + gateway: 192.168.122.1 + mtu: 1500 + network: ctlplane + routes: + - destination: 0.0.0.0/0 + nexthop: 192.168.122.1 + subnet: subnet1 + - address: 172.17.0.102 + cidr: 172.17.0.0/24 + dnsDomain: internalapi.example.com + mtu: 1500 + network: internalapi + subnet: subnet1 + vlan: 20 + - address: 172.18.0.102 + cidr: 172.18.0.0/24 + dnsDomain: storage.example.com + mtu: 1500 + network: storage + subnet: subnet1 + vlan: 21 + - address: 172.19.0.102 + cidr: 172.19.0.0/24 + dnsDomain: tenant.example.com + mtu: 1500 + network: tenant + subnet: subnet1 + vlan: 22 +--- +apiVersion: network.openstack.org/v1beta1 +kind: DNSData +metadata: + name: openstack-edpm-tls +spec: + dnsDataLabelSelectorValue: dnsdata +status: + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: Input data complete + reason: Ready + status: "True" + type: ServiceConfigReady diff --git a/tests/kuttl/tests/dataplane-deploy-multiple-secrets/00-dataplane-create.yaml b/tests/kuttl/tests/dataplane-deploy-multiple-secrets/00-dataplane-create.yaml new file mode 100644 index 000000000..6ed62d139 --- /dev/null +++ b/tests/kuttl/tests/dataplane-deploy-multiple-secrets/00-dataplane-create.yaml @@ -0,0 +1,131 @@ +apiVersion: network.openstack.org/v1beta1 +kind: DNSMasq +metadata: + name: dnsmasq +spec: + replicas: 1 + options: + - key: server + values: + - 192.168.122.1 + debug: + service: false +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + name: generic-service1 +spec: + caCerts: combined-ca-bundle + tlsCert: + contents: + - dnsnames + play: | + - hosts: localhost + gather_facts: no + name: kuttl play + tasks: + - name: Sleep + command: sleep 1 + delegate_to: localhost +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + name: install-certs-ovr +spec: + addCertMounts: True + play: | + - hosts: localhost + gather_facts: no + name: kuttl play + tasks: + - name: Sleep + command: sleep 1 + delegate_to: localhost +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneNodeSet +metadata: + name: openstack-edpm-tls +spec: + env: + - name: ANSIBLE_FORCE_COLOR + value: "True" + services: + - install-certs-ovr + - generic-service1 + preProvisioned: true + tlsEnabled: true + secretMaxSize: 2880 + nodes: + edpm-compute-0: + hostName: edpm-compute-0 + networks: + - name: ctlplane + subnetName: subnet1 + defaultRoute: true + fixedIP: 192.168.122.100 + - name: internalapi + subnetName: subnet1 + fixedIP: 172.17.0.100 + - name: storage + subnetName: subnet1 + fixedIP: 172.18.0.100 + - name: tenant + subnetName: subnet1 + fixedIP: 172.19.0.100 + edpm-compute-1: + hostName: edpm-compute-1 + networks: + - name: ctlplane + subnetName: subnet1 + defaultRoute: true + fixedIP: 192.168.122.101 + - name: internalapi + subnetName: subnet1 + fixedIP: 172.17.0.101 + - name: storage + subnetName: subnet1 + fixedIP: 172.18.0.101 + - name: tenant + subnetName: subnet1 + fixedIP: 172.19.0.101 + edpm-compute-2: + hostName: edpm-compute-2 + networks: + - name: ctlplane + subnetName: subnet1 + defaultRoute: true + fixedIP: 192.168.122.102 + - name: internalapi + subnetName: subnet1 + fixedIP: 172.17.0.102 + - name: storage + subnetName: subnet1 + fixedIP: 172.18.0.102 + - name: tenant + subnetName: subnet1 + fixedIP: 172.19.0.102 + nodeTemplate: + ansibleSSHPrivateKeySecret: dataplane-ansible-ssh-private-key-secret + ansible: + ansibleUser: cloud-admin + ansiblePort: 22 + ansibleVars: + timesync_ntp_servers: + - hostname: clock.redhat.com + # edpm_network_config + # Default nic config template for a EDPM compute node + # These vars are edpm_network_config role vars + edpm_network_config_hide_sensitive_logs: false + edpm_network_config_template: templates/single_nic_vlans/single_nic_vlans.j2 + edpm_nodes_validation_validate_controllers_icmp: false + edpm_nodes_validation_validate_gateway_icmp: false + gather_facts: false + enable_debug: false + # edpm firewall, change the allowed CIDR if needed + edpm_sshd_configure_firewall: true + edpm_sshd_allowed_ranges: ['192.168.122.0/24'] + # SELinux module + edpm_selinux_mode: enforcing diff --git a/tests/kuttl/tests/dataplane-deploy-multiple-secrets/01-create-cert-issuers.yaml b/tests/kuttl/tests/dataplane-deploy-multiple-secrets/01-create-cert-issuers.yaml new file mode 100644 index 000000000..772ba643f --- /dev/null +++ b/tests/kuttl/tests/dataplane-deploy-multiple-secrets/01-create-cert-issuers.yaml @@ -0,0 +1,22 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - script: | + function wait_for() { + timeout=$1 + shift 1 + until [ $timeout -le 0 ] || ("$@" &> /dev/null); do + echo waiting for "$@" + sleep 1 + timeout=$(( timeout - 1 )) + done + if [ $timeout -le 0 ]; then + return 1 + fi + } + + if oc get secret combined-ca-bundle -n openstack; then oc delete secret combined-ca-bundle -n openstack; fi + oc apply -f ./certs.yaml + wait_for 100 oc get secret osp-rootca-secret -n openstack + CA_CRT=$(oc get secret osp-rootca-secret -n openstack -o json|jq -r '.data."ca.crt"') + oc create secret generic combined-ca-bundle -n openstack --from-literal=TLSCABundleFile=$CA_CRT diff --git a/tests/kuttl/tests/dataplane-deploy-multiple-secrets/02-assert.yaml b/tests/kuttl/tests/dataplane-deploy-multiple-secrets/02-assert.yaml new file mode 100644 index 000000000..0413e0737 --- /dev/null +++ b/tests/kuttl/tests/dataplane-deploy-multiple-secrets/02-assert.yaml @@ -0,0 +1,218 @@ +apiVersion: v1 +kind: Secret +metadata: + name: cert-generic-service1-edpm-compute-0 + annotations: + cert-manager.io/certificate-name: generic-service1-edpm-compute-0 + cert-manager.io/issuer-group: cert-manager.io + cert-manager.io/issuer-kind: Issuer + cert-manager.io/issuer-name: rootca-internal + labels: + hostname: edpm-compute-0 + osdp-service: generic-service1 + osdpns: openstack-edpm-tls +type: kubernetes.io/tls +--- +apiVersion: v1 +kind: Secret +metadata: + name: cert-generic-service1-edpm-compute-1 + annotations: + cert-manager.io/certificate-name: generic-service1-edpm-compute-1 + cert-manager.io/issuer-group: cert-manager.io + cert-manager.io/issuer-kind: Issuer + cert-manager.io/issuer-name: rootca-internal + labels: + hostname: edpm-compute-1 + osdp-service: generic-service1 + osdpns: openstack-edpm-tls +type: kubernetes.io/tls +--- +apiVersion: v1 +kind: Secret +metadata: + name: cert-generic-service1-edpm-compute-2 + annotations: + cert-manager.io/certificate-name: generic-service1-edpm-compute-2 + cert-manager.io/issuer-group: cert-manager.io + cert-manager.io/issuer-kind: Issuer + cert-manager.io/issuer-name: rootca-internal + labels: + hostname: edpm-compute-2 + osdp-service: generic-service1 + osdpns: openstack-edpm-tls +type: kubernetes.io/tls +--- +# validate the alt-names - which is a list with elements that can be in any order +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 600 +commands: + - script: | + template='{{index .metadata.annotations "cert-manager.io/alt-names" }}' + names=$(oc get secret cert-generic-service1-edpm-compute-0 -n openstack -o go-template="$template") + echo $names > test123.data + regex="(?=.*(edpm-compute-0\.internalapi\.example\.com))(?=.*(edpm-compute-0\.storage\.example\.com))(?=.*(edpm-compute-0\.tenant\.example\.com))(?=.*(edpm-compute-0\.ctlplane\.example\.com))" + matches=$(grep -P "$regex" test123.data) + rm test123.data + if [ -z "$matches" ]; then + echo "bad match: $names" + exit 1 + else + exit 0 + fi +--- +apiVersion: v1 +kind: Secret +metadata: + name: openstack-edpm-tls-generic-service1-certs-0 + labels: + numberOfSecrets: "3" + secretNumber: "0" + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + kind: OpenStackDataPlaneNodeSet + name: openstack-edpm-tls +type: Opaque +--- +apiVersion: v1 +kind: Secret +metadata: + name: openstack-edpm-tls-generic-service1-certs-1 + labels: + numberOfSecrets: "3" + secretNumber: "1" + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + kind: OpenStackDataPlaneNodeSet + name: openstack-edpm-tls +type: Opaque +--- +apiVersion: v1 +kind: Secret +metadata: + name: openstack-edpm-tls-generic-service1-certs-2 + labels: + numberOfSecrets: "3" + secretNumber: "2" + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + kind: OpenStackDataPlaneNodeSet + name: openstack-edpm-tls +type: Opaque +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + name: install-certs-ovr-openstack-edpm-tls-openstack-edpm-tls + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + blockOwnerDeletion: true + controller: true + kind: OpenStackDataPlaneDeployment + name: openstack-edpm-tls +spec: + backoffLimit: 6 + extraMounts: + - mounts: + - mountPath: /var/lib/openstack/certs/generic-service1 + name: openstack-edpm-tls-generic-service1-certs-0 + volumes: + - name: openstack-edpm-tls-generic-service1-certs-0 + projected: + sources: + - secret: + name: openstack-edpm-tls-generic-service1-certs-0 + - secret: + name: openstack-edpm-tls-generic-service1-certs-1 + - secret: + name: openstack-edpm-tls-generic-service1-certs-2 + - mounts: + - mountPath: /var/lib/openstack/cacerts/generic-service1 + name: generic-service1-combined-ca-bundle + volumes: + - name: generic-service1-combined-ca-bundle + secret: + secretName: combined-ca-bundle + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-openstack-edpm-tls + name: openstackansibleee + restartPolicy: Never + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + name: generic-service1-openstack-edpm-tls-openstack-edpm-tls + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + kind: OpenStackDataPlaneDeployment + name: openstack-edpm-tls +spec: + backoffLimit: 6 + extraMounts: + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-openstack-edpm-tls + name: openstackansibleee + restartPolicy: Never + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady diff --git a/tests/kuttl/tests/dataplane-deploy-multiple-secrets/02-dataplane-deploy.yaml b/tests/kuttl/tests/dataplane-deploy-multiple-secrets/02-dataplane-deploy.yaml new file mode 100644 index 000000000..61aa719ef --- /dev/null +++ b/tests/kuttl/tests/dataplane-deploy-multiple-secrets/02-dataplane-deploy.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneDeployment +metadata: + name: openstack-edpm-tls +spec: + nodeSets: + - openstack-edpm-tls + services: + - generic-service1 diff --git a/tests/kuttl/tests/dataplane-deploy-multiple-secrets/certs.yaml b/tests/kuttl/tests/dataplane-deploy-multiple-secrets/certs.yaml new file mode 100644 index 000000000..7cffc290c --- /dev/null +++ b/tests/kuttl/tests/dataplane-deploy-multiple-secrets/certs.yaml @@ -0,0 +1,38 @@ +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: selfsigned-issuer + namespace: openstack-kuttl-tests +spec: + selfSigned: {} +--- +# RootCA Certificate used to sign certificates +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: osp-rootca + namespace: openstack-kuttl-tests +spec: + isCA: true + commonName: osp-rootca + secretName: osp-rootca-secret + privateKey: + algorithm: ECDSA + size: 256 + issuerRef: + name: selfsigned-issuer + kind: Issuer + group: cert-manager.io +--- +# Issuer that uses the generated CA certificate to issue certs +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: rootca-internal + namespace: openstack-kuttl-tests + labels: + osp-rootca-issuer-internal: "" +spec: + ca: + secretName: osp-rootca-secret +--- diff --git a/tests/kuttl/tests/dataplane-deploy-no-nodes-test/00-assert.yaml b/tests/kuttl/tests/dataplane-deploy-no-nodes-test/00-assert.yaml new file mode 100644 index 000000000..74b2e2b0a --- /dev/null +++ b/tests/kuttl/tests/dataplane-deploy-no-nodes-test/00-assert.yaml @@ -0,0 +1,60 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 600 +collectors: +- type: command + command: oc logs -n openstack-operators -l openstack.org/operator-name=dataplane + name: dataplane-operator-logs +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneNodeSet +metadata: + name: edpm-compute-no-nodes + namespace: openstack-kuttl-tests +spec: + preProvisioned: true + services: + - download-cache + - bootstrap + - configure-network + - validate-network + - install-os + - configure-os + - run-os + - install-certs + - ovn + - neutron-metadata + - neutron-ovn + - neutron-sriov + - neutron-dhcp + - libvirt + - nova + env: + - name: ANSIBLE_FORCE_COLOR + value: "True" + nodes: {} + nodeTemplate: + ansibleSSHPrivateKeySecret: dataplane-ansible-ssh-private-key-secret +status: + observedGeneration: 1 + conditions: + - message: Deployment not started + reason: Requested + status: "False" + type: Ready + - message: Deployment not started + reason: Requested + status: "False" + type: DeploymentReady + - message: Input data complete + reason: Ready + status: "True" + type: InputReady + - message: ServiceAccount created + reason: Ready + status: "True" + type: ServiceAccountReady + - message: Setup complete + reason: Ready + status: "True" + type: SetupReady diff --git a/tests/kuttl/tests/dataplane-deploy-no-nodes-test/00-dataplane-create.yaml b/tests/kuttl/tests/dataplane-deploy-no-nodes-test/00-dataplane-create.yaml new file mode 100644 index 000000000..23e5d5cf1 --- /dev/null +++ b/tests/kuttl/tests/dataplane-deploy-no-nodes-test/00-dataplane-create.yaml @@ -0,0 +1,97 @@ +apiVersion: v1 +kind: Secret +metadata: + name: nova-cell1-compute-config +data: + nova-blank.conf: Zm9vCg== + 01-nova.conf: Zm9vCg== +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: ovncontroller-config +data: + ovsdb-config: test-ovn-config +--- +apiVersion: v1 +kind: Secret +metadata: + name: neutron-ovn-metadata-agent-neutron-config +data: + 10-neutron-metadata.conf: dGVzdC1uZXV0cm9uLW92bi1tZXRhZGF0YS1hZ2VudC1jb25maWc= +--- +apiVersion: v1 +kind: Secret +metadata: + name: nova-metadata-neutron-config +data: + 05-nova-metadata.conf: dGVzdC1ub3ZhLW1ldGFkYXRhLWNvbXB1dGUtY29uZmln + httpd.conf: dGVzdC1ub3ZhLW1ldGFkYXRhLWNvbXB1dGUtY29uZmln + nova-metadata-config.json: dGVzdC1ub3ZhLW1ldGFkYXRhLWNvbXB1dGUtY29uZmln +--- +apiVersion: v1 +kind: Secret +metadata: + name: neutron-ovn-agent-neutron-config +data: + 10-neutron-ovn.conf: dGVzdC1uZXV0cm9uLW92bi1hZ2VudC1jb25maWc= +--- +--- +apiVersion: v1 +kind: Secret +metadata: + name: neutron-sriov-agent-neutron-config +data: + 10-neutron-sriov.conf: dGVzdC1uZXV0cm9uLXNyaW92LWFnZW50LXNlY3JldC1jb25maWcK +--- +apiVersion: v1 +kind: Secret +metadata: + name: neutron-dhcp-agent-neutron-config +data: + 10-neutron-dhcp.conf: dGVzdC1uZXV0cm9uLWRoY3AtYWdlbnQtc2VjcmV0LWNvbmZpZwo= +--- +apiVersion: v1 +kind: Secret +metadata: + name: nova-migration-ssh-key +data: + ssh-privatekey: ZmFrZQo= + ssh-publickey: ZmFrZQo= +--- +apiVersion: v1 +kind: Secret +metadata: + name: libvirt-secret +data: + LibvirtPassword: ZmFrZQo= +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneNodeSet +metadata: + name: edpm-compute-no-nodes +spec: + preProvisioned: true + tlsEnabled: false + services: + - download-cache + - bootstrap + - configure-network + - validate-network + - install-os + - configure-os + - run-os + - install-certs + - ovn + - neutron-metadata + - neutron-ovn + - neutron-sriov + - neutron-dhcp + - libvirt + - nova + env: + - name: ANSIBLE_FORCE_COLOR + value: "True" + nodes: {} + nodeTemplate: + ansibleSSHPrivateKeySecret: dataplane-ansible-ssh-private-key-secret diff --git a/tests/kuttl/tests/dataplane-deploy-no-nodes-test/01-assert.yaml b/tests/kuttl/tests/dataplane-deploy-no-nodes-test/01-assert.yaml new file mode 100644 index 000000000..1cb70923b --- /dev/null +++ b/tests/kuttl/tests/dataplane-deploy-no-nodes-test/01-assert.yaml @@ -0,0 +1,951 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 600 +collectors: +- type: command + command: oc logs -n openstack-operators -l openstack.org/operator-name=dataplane + name: dataplane-operator-logs +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneNodeSet +metadata: + name: edpm-compute-no-nodes + namespace: openstack-kuttl-tests +spec: + services: + - download-cache + - bootstrap + - configure-network + - validate-network + - install-os + - configure-os + - run-os + - install-certs + - ovn + - neutron-metadata + - neutron-ovn + - neutron-sriov + - neutron-dhcp + - libvirt + - nova + env: + - name: ANSIBLE_FORCE_COLOR + value: "True" + nodes: {} + nodeTemplate: + ansibleSSHPrivateKeySecret: dataplane-ansible-ssh-private-key-secret +status: + observedGeneration: 1 + conditions: + - message: NodeSet Ready + reason: Ready + status: "True" + type: Ready + - message: Deployment completed + reason: Ready + status: "True" + type: DeploymentReady + - message: Input data complete + reason: Ready + status: "True" + type: InputReady + - message: ServiceAccount created + reason: Ready + status: "True" + type: ServiceAccountReady + - message: Setup complete + reason: Ready + status: "True" + type: SetupReady +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + name: download-cache-edpm-compute-no-nodes-edpm-compute-no-nodes + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + blockOwnerDeletion: true + controller: true + kind: OpenStackDataPlaneDeployment + name: edpm-compute-no-nodes +spec: + backoffLimit: 6 + extraMounts: + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-edpm-compute-no-nodes + name: openstackansibleee + restartPolicy: Never + playbook: osp.edpm.download_cache + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + name: bootstrap-edpm-compute-no-nodes-edpm-compute-no-nodes + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + blockOwnerDeletion: true + controller: true + kind: OpenStackDataPlaneDeployment + name: edpm-compute-no-nodes +spec: + backoffLimit: 6 + extraVars: + foo: bar + extraMounts: + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-edpm-compute-no-nodes + name: openstackansibleee + restartPolicy: Never + playbook: osp.edpm.bootstrap + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady + +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + name: configure-network-edpm-compute-no-nodes-edpm-compute-no-nodes + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + blockOwnerDeletion: true + controller: true + kind: OpenStackDataPlaneDeployment + name: edpm-compute-no-nodes +spec: + backoffLimit: 6 + extraMounts: + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-edpm-compute-no-nodes + name: openstackansibleee + restartPolicy: Never + playbook: osp.edpm.configure_network + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + name: validate-network-edpm-compute-no-nodes-edpm-compute-no-nodes + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + blockOwnerDeletion: true + controller: true + kind: OpenStackDataPlaneDeployment + name: edpm-compute-no-nodes +spec: + backoffLimit: 6 + extraMounts: + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-edpm-compute-no-nodes + name: openstackansibleee + restartPolicy: Never + playbook: osp.edpm.validate_network + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + name: install-os-edpm-compute-no-nodes-edpm-compute-no-nodes + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + blockOwnerDeletion: true + controller: true + kind: OpenStackDataPlaneDeployment + name: edpm-compute-no-nodes +spec: + backoffLimit: 6 + extraMounts: + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-edpm-compute-no-nodes + name: openstackansibleee + restartPolicy: Never + playbook: osp.edpm.install_os + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + generation: 1 + name: configure-os-edpm-compute-no-nodes-edpm-compute-no-nodes + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + blockOwnerDeletion: true + controller: true + kind: OpenStackDataPlaneDeployment + name: edpm-compute-no-nodes +spec: + backoffLimit: 6 + extraMounts: + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-edpm-compute-no-nodes + name: openstackansibleee + restartPolicy: Never + playbook: osp.edpm.configure_os + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + name: run-os-edpm-compute-no-nodes-edpm-compute-no-nodes + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + blockOwnerDeletion: true + controller: true + kind: OpenStackDataPlaneDeployment + name: edpm-compute-no-nodes +spec: + backoffLimit: 6 + extraMounts: + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-edpm-compute-no-nodes + name: openstackansibleee + restartPolicy: Never + playbook: osp.edpm.run_os + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + name: install-certs-edpm-compute-no-nodes-edpm-compute-no-nodes + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + blockOwnerDeletion: true + controller: true + kind: OpenStackDataPlaneDeployment + name: edpm-compute-no-nodes +spec: + backoffLimit: 6 + extraMounts: + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-edpm-compute-no-nodes + name: openstackansibleee + restartPolicy: Never + playbook: osp.edpm.install_certs + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + generation: 1 + name: ovn-edpm-compute-no-nodes-edpm-compute-no-nodes + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + blockOwnerDeletion: true + controller: true + kind: OpenStackDataPlaneDeployment + name: edpm-compute-no-nodes +spec: + backoffLimit: 6 + extraMounts: + - mounts: + - mountPath: /var/lib/openstack/configs/ovn/ovsdb-config + name: ovncontroller-config-0 + subPath: ovsdb-config + volumes: + - configMap: + items: + - key: ovsdb-config + path: ovsdb-config + name: ovncontroller-config + name: ovncontroller-config-0 + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-edpm-compute-no-nodes + name: openstackansibleee + restartPolicy: Never + playbook: osp.edpm.ovn + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + generation: 1 + name: neutron-metadata-edpm-compute-no-nodes-edpm-compute-no-nodes + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + blockOwnerDeletion: true + controller: true + kind: OpenStackDataPlaneDeployment + name: edpm-compute-no-nodes +spec: + backoffLimit: 6 + extraMounts: + - mounts: + - mountPath: /var/lib/openstack/configs/neutron-metadata/10-neutron-metadata.conf + name: neutron-ovn-metadata-agent-neutron-config-0 + subPath: 10-neutron-metadata.conf + volumes: + - secret: + items: + - key: 10-neutron-metadata.conf + path: 10-neutron-metadata.conf + secretName: neutron-ovn-metadata-agent-neutron-config + name: neutron-ovn-metadata-agent-neutron-config-0 + - mounts: + - mountPath: /var/lib/openstack/configs/neutron-metadata/05-nova-metadata.conf + name: nova-metadata-neutron-config-0 + subPath: 05-nova-metadata.conf + - mountPath: /var/lib/openstack/configs/neutron-metadata/httpd.conf + name: nova-metadata-neutron-config-1 + subPath: httpd.conf + - mountPath: /var/lib/openstack/configs/neutron-metadata/nova-metadata-config.json + name: nova-metadata-neutron-config-2 + subPath: nova-metadata-config.json + volumes: + - secret: + items: + - key: 05-nova-metadata.conf + path: 05-nova-metadata.conf + secretName: nova-metadata-neutron-config + name: nova-metadata-neutron-config-0 + - name: nova-metadata-neutron-config-1 + secret: + items: + - key: httpd.conf + path: httpd.conf + secretName: nova-metadata-neutron-config + - name: nova-metadata-neutron-config-2 + secret: + items: + - key: nova-metadata-config.json + path: nova-metadata-config.json + secretName: nova-metadata-neutron-config + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-edpm-compute-no-nodes + name: openstackansibleee + restartPolicy: Never + playbook: osp.edpm.neutron_metadata + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + generation: 1 + name: neutron-ovn-edpm-compute-no-nodes-edpm-compute-no-nodes + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + blockOwnerDeletion: true + controller: true + kind: OpenStackDataPlaneDeployment + name: edpm-compute-no-nodes +spec: + backoffLimit: 6 + extraMounts: + - mounts: + - mountPath: /var/lib/openstack/configs/neutron-ovn/10-neutron-ovn.conf + name: neutron-ovn-agent-neutron-config-0 + subPath: 10-neutron-ovn.conf + volumes: + - secret: + items: + - key: 10-neutron-ovn.conf + path: 10-neutron-ovn.conf + secretName: neutron-ovn-agent-neutron-config + name: neutron-ovn-agent-neutron-config-0 + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-edpm-compute-no-nodes + name: openstackansibleee + restartPolicy: Never + playbook: osp.edpm.neutron_ovn + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + generation: 1 + name: neutron-sriov-edpm-compute-no-nodes-edpm-compute-no-nodes + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + blockOwnerDeletion: true + controller: true + kind: OpenStackDataPlaneDeployment + name: edpm-compute-no-nodes +spec: + backoffLimit: 6 + extraMounts: + - mounts: + - mountPath: /var/lib/openstack/configs/neutron-sriov/10-neutron-sriov.conf + name: neutron-sriov-agent-neutron-config-0 + subPath: 10-neutron-sriov.conf + volumes: + - secret: + items: + - key: 10-neutron-sriov.conf + path: 10-neutron-sriov.conf + secretName: neutron-sriov-agent-neutron-config + name: neutron-sriov-agent-neutron-config-0 + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-edpm-compute-no-nodes + name: openstackansibleee + restartPolicy: Never + playbook: osp.edpm.neutron_sriov + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + generation: 1 + name: neutron-dhcp-edpm-compute-no-nodes-edpm-compute-no-nodes + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + blockOwnerDeletion: true + controller: true + kind: OpenStackDataPlaneDeployment + name: edpm-compute-no-nodes +spec: + backoffLimit: 6 + extraMounts: + - mounts: + - mountPath: /var/lib/openstack/configs/neutron-dhcp/10-neutron-dhcp.conf + name: neutron-dhcp-agent-neutron-config-0 + subPath: 10-neutron-dhcp.conf + volumes: + - secret: + items: + - key: 10-neutron-dhcp.conf + path: 10-neutron-dhcp.conf + secretName: neutron-dhcp-agent-neutron-config + name: neutron-dhcp-agent-neutron-config-0 + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-edpm-compute-no-nodes + name: openstackansibleee + restartPolicy: Never + playbook: osp.edpm.neutron_dhcp + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + name: libvirt-edpm-compute-no-nodes-edpm-compute-no-nodes + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + blockOwnerDeletion: true + controller: true + kind: OpenStackDataPlaneDeployment + name: edpm-compute-no-nodes +spec: + backoffLimit: 6 + envConfigMapName: openstack-aee-default-env + extraMounts: + - mounts: + - mountPath: /var/lib/openstack/configs/libvirt/LibvirtPassword + name: libvirt-secret-0 + subPath: LibvirtPassword + volumes: + - name: libvirt-secret-0 + secret: + items: + - key: LibvirtPassword + path: LibvirtPassword + secretName: libvirt-secret + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-edpm-compute-no-nodes + name: openstackansibleee + preserveJobs: true + restartPolicy: Never + playbook: osp.edpm.libvirt + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + name: nova-edpm-compute-no-nodes-edpm-compute-no-nodes + namespace: openstack-kuttl-tests +spec: + backoffLimit: 6 + envConfigMapName: openstack-aee-default-env + extraMounts: + - mounts: + - mountPath: /var/lib/openstack/configs/nova/01-nova.conf + name: nova-cell1-compute-config-0 + subPath: 01-nova.conf + - mountPath: /var/lib/openstack/configs/nova/nova-blank.conf + name: nova-cell1-compute-config-1 + subPath: nova-blank.conf + volumes: + - name: nova-cell1-compute-config-0 + secret: + items: + - key: 01-nova.conf + path: 01-nova.conf + secretName: nova-cell1-compute-config + - name: nova-cell1-compute-config-1 + secret: + items: + - key: nova-blank.conf + path: nova-blank.conf + secretName: nova-cell1-compute-config + - mounts: + - mountPath: /var/lib/openstack/configs/nova/ssh-privatekey + name: nova-migration-ssh-key-0 + subPath: ssh-privatekey + - mountPath: /var/lib/openstack/configs/nova/ssh-publickey + name: nova-migration-ssh-key-1 + subPath: ssh-publickey + volumes: + - name: nova-migration-ssh-key-0 + secret: + items: + - key: ssh-privatekey + path: ssh-privatekey + secretName: nova-migration-ssh-key + - name: nova-migration-ssh-key-1 + secret: + items: + - key: ssh-publickey + path: ssh-publickey + secretName: nova-migration-ssh-key + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-edpm-compute-no-nodes + name: openstackansibleee + preserveJobs: true + restartPolicy: Never + playbook: osp.edpm.nova + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady diff --git a/tests/kuttl/tests/dataplane-deploy-no-nodes-test/01-dataplane-deploy.yaml b/tests/kuttl/tests/dataplane-deploy-no-nodes-test/01-dataplane-deploy.yaml new file mode 100644 index 000000000..2f68ffbfe --- /dev/null +++ b/tests/kuttl/tests/dataplane-deploy-no-nodes-test/01-dataplane-deploy.yaml @@ -0,0 +1,9 @@ +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneDeployment +metadata: + name: edpm-compute-no-nodes +spec: + nodeSets: + - edpm-compute-no-nodes + ansibleExtraVars: + foo: bar diff --git a/tests/kuttl/tests/dataplane-deploy-no-nodes-test/02-assert.yaml b/tests/kuttl/tests/dataplane-deploy-no-nodes-test/02-assert.yaml new file mode 100644 index 000000000..8ca5091d9 --- /dev/null +++ b/tests/kuttl/tests/dataplane-deploy-no-nodes-test/02-assert.yaml @@ -0,0 +1,70 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 600 +collectors: +- type: command + command: oc logs -n openstack-operators -l openstack.org/operator-name=dataplane + name: dataplane-operator-logs +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + generation: 1 + name: custom-svc-edpm-compute-no-nodes-ovrd-edpm-compute-no-nodes + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + blockOwnerDeletion: true + controller: true + kind: OpenStackDataPlaneDeployment + name: edpm-compute-no-nodes-ovrd +spec: + backoffLimit: 6 + env: + - name: ANSIBLE_FORCE_COLOR + value: "True" + envConfigMapName: openstack-aee-default-env + extraMounts: + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-edpm-compute-no-nodes + name: openstackansibleee + play: | + - hosts: localhost + gather_facts: no + name: kuttl play + tasks: + - name: Sleep + command: sleep 1 + delegate_to: localhost + preserveJobs: true + restartPolicy: Never + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady diff --git a/tests/kuttl/tests/dataplane-deploy-no-nodes-test/02-dataplane-deploy-services-override.yaml b/tests/kuttl/tests/dataplane-deploy-no-nodes-test/02-dataplane-deploy-services-override.yaml new file mode 100644 index 000000000..dc383709d --- /dev/null +++ b/tests/kuttl/tests/dataplane-deploy-no-nodes-test/02-dataplane-deploy-services-override.yaml @@ -0,0 +1,25 @@ +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + name: custom-svc +spec: + label: custom-svc + play: | + - hosts: localhost + gather_facts: no + name: kuttl play + tasks: + - name: Sleep + command: sleep 1 + delegate_to: localhost +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneDeployment +metadata: + name: edpm-compute-no-nodes-ovrd +spec: + nodeSets: + - edpm-compute-no-nodes + servicesOverride: + - custom-svc diff --git a/tests/kuttl/tests/dataplane-deploy-no-nodes-test/03-assert.yaml b/tests/kuttl/tests/dataplane-deploy-no-nodes-test/03-assert.yaml new file mode 100644 index 000000000..4708ae000 --- /dev/null +++ b/tests/kuttl/tests/dataplane-deploy-no-nodes-test/03-assert.yaml @@ -0,0 +1,59 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 600 +collectors: +- type: command + command: oc logs -n openstack-operators -l openstack.org/operator-name=dataplane + name: dataplane-operator-logs +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneNodeSet +metadata: + name: edpm-compute-no-nodes + namespace: openstack-kuttl-tests +spec: + services: + - download-cache + - bootstrap + - configure-network + - validate-network + - install-os + - configure-os + - run-os + - install-certs + - ovn + - neutron-metadata + - neutron-ovn + - neutron-sriov + - neutron-dhcp + - libvirt + - nova + env: + - name: ANSIBLE_FORCE_COLOR + value: "True" + nodes: {} + nodeTemplate: + ansibleSSHPrivateKeySecret: dataplane-ansible-ssh-private-key-secret +status: + observedGeneration: 1 + conditions: + - message: NodeSet Ready + reason: Ready + status: "True" + type: Ready + - message: Deployment completed + reason: Ready + status: "True" + type: DeploymentReady + - message: Input data complete + reason: Ready + status: "True" + type: InputReady + - message: ServiceAccount created + reason: Ready + status: "True" + type: ServiceAccountReady + - message: Setup complete + reason: Ready + status: "True" + type: SetupReady diff --git a/tests/kuttl/tests/dataplane-deploy-no-nodes-test/03-update-ovn-cm.yaml b/tests/kuttl/tests/dataplane-deploy-no-nodes-test/03-update-ovn-cm.yaml new file mode 100644 index 000000000..98df55230 --- /dev/null +++ b/tests/kuttl/tests/dataplane-deploy-no-nodes-test/03-update-ovn-cm.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: ovncontroller-config +data: + ovsdb-config: test-ovn-config-updated +--- +# Sleep for 30s, b/c this test is meant to assert that even though we've +# changed the above CM, the hash on the NodeSet does not change. +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - script: sleep 30 diff --git a/tests/kuttl/tests/dataplane-deploy-no-nodes-test/04-assert.yaml b/tests/kuttl/tests/dataplane-deploy-no-nodes-test/04-assert.yaml new file mode 100644 index 000000000..4f2062436 --- /dev/null +++ b/tests/kuttl/tests/dataplane-deploy-no-nodes-test/04-assert.yaml @@ -0,0 +1,121 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 600 +collectors: +- type: command + command: oc logs -n openstack-operators -l openstack.org/operator-name=dataplane + name: dataplane-operator-logs +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneNodeSet +metadata: + name: edpm-compute-no-nodes + namespace: openstack-kuttl-tests +spec: + services: + - download-cache + - bootstrap + - configure-network + - validate-network + - install-os + - configure-os + - run-os + - install-certs + - ovn + - neutron-metadata + - neutron-ovn + - neutron-sriov + - neutron-dhcp + - libvirt + - nova + env: + - name: ANSIBLE_FORCE_COLOR + value: "True" + nodes: {} + nodeTemplate: + ansibleSSHPrivateKeySecret: dataplane-ansible-ssh-private-key-secret +status: + observedGeneration: 1 + conditions: + - message: NodeSet Ready + reason: Ready + status: "True" + type: Ready + - message: Deployment completed + reason: Ready + status: "True" + type: DeploymentReady + - message: Input data complete + reason: Ready + status: "True" + type: InputReady + - message: ServiceAccount created + reason: Ready + status: "True" + type: ServiceAccountReady + - message: Setup complete + reason: Ready + status: "True" + type: SetupReady +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + generation: 1 + name: ovn-edpm-compute-no-nodes-updated-ovn-cm-edpm-compute-no-nodes + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + blockOwnerDeletion: true + controller: true + kind: OpenStackDataPlaneDeployment + name: edpm-compute-no-nodes-updated-ovn-cm +spec: + backoffLimit: 6 + extraMounts: + - mounts: + - mountPath: /var/lib/openstack/configs/ovn/ovsdb-config + name: ovncontroller-config-0 + subPath: ovsdb-config + volumes: + - configMap: + items: + - key: ovsdb-config + path: ovsdb-config + name: ovncontroller-config + name: ovncontroller-config-0 + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-edpm-compute-no-nodes + name: openstackansibleee + restartPolicy: Never + playbook: osp.edpm.ovn + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady diff --git a/tests/kuttl/tests/dataplane-deploy-no-nodes-test/04-dataplane-deploy.yaml b/tests/kuttl/tests/dataplane-deploy-no-nodes-test/04-dataplane-deploy.yaml new file mode 100644 index 000000000..5295f1181 --- /dev/null +++ b/tests/kuttl/tests/dataplane-deploy-no-nodes-test/04-dataplane-deploy.yaml @@ -0,0 +1,9 @@ +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneDeployment +metadata: + name: edpm-compute-no-nodes-updated-ovn-cm +spec: + nodeSets: + - edpm-compute-no-nodes + servicesOverride: + - ovn diff --git a/tests/kuttl/tests/dataplane-deploy-no-nodes-test/05-assert.yaml b/tests/kuttl/tests/dataplane-deploy-no-nodes-test/05-assert.yaml new file mode 100644 index 000000000..6a4fe5e45 --- /dev/null +++ b/tests/kuttl/tests/dataplane-deploy-no-nodes-test/05-assert.yaml @@ -0,0 +1,190 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 600 +collectors: +- type: command + command: oc logs -n openstack-operators -l openstack.org/operator-name=dataplane + name: dataplane-operator-logs +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneNodeSet +metadata: + name: edpm-compute-no-nodes + namespace: openstack-kuttl-tests +spec: + services: + - download-cache + - bootstrap + - configure-network + - validate-network + - install-os + - configure-os + - run-os + - install-certs + - ovn + - neutron-metadata + - neutron-ovn + - neutron-sriov + - neutron-dhcp + - libvirt + - nova + env: + - name: ANSIBLE_FORCE_COLOR + value: "True" + nodes: {} + nodeTemplate: + ansibleSSHPrivateKeySecret: dataplane-ansible-ssh-private-key-secret +status: + observedGeneration: 1 + conditions: + - message: Deployment error occurred check deploymentStatuses for more details + reason: Error + severity: Error + status: "False" + type: Ready + - message: Deployment error occurred check deploymentStatuses for more details + reason: Error + severity: Error + status: "False" + type: DeploymentReady + - message: Input data complete + reason: Ready + status: "True" + type: InputReady + - message: ServiceAccount created + reason: Ready + status: "True" + type: ServiceAccountReady + - message: Setup complete + reason: Ready + status: "True" + type: SetupReady + deploymentStatuses: + edpm-compute-no-nodes: + - message: Deployment completed + reason: Ready + status: "True" + type: NodeSetDeploymentReady + - message: bootstrap Deployment ready + reason: Ready + status: "True" + type: ServiceBootstrapDeploymentReady + - message: configure-network Deployment ready + reason: Ready + status: "True" + type: ServiceConfigureNetworkDeploymentReady + - message: configure-os Deployment ready + reason: Ready + status: "True" + type: ServiceConfigureOsDeploymentReady + - message: download-cache Deployment ready + reason: Ready + status: "True" + type: ServiceDownloadCacheDeploymentReady + - message: install-certs Deployment ready + reason: Ready + status: "True" + type: ServiceInstallCertsDeploymentReady + - message: install-os Deployment ready + reason: Ready + status: "True" + type: ServiceInstallOsDeploymentReady + - message: libvirt Deployment ready + reason: Ready + status: "True" + type: ServiceLibvirtDeploymentReady + - message: neutron-dhcp Deployment ready + reason: Ready + status: "True" + type: ServiceNeutronDhcpDeploymentReady + - message: neutron-metadata Deployment ready + reason: Ready + status: "True" + type: ServiceNeutronMetadataDeploymentReady + - message: neutron-ovn Deployment ready + reason: Ready + status: "True" + type: ServiceNeutronOvnDeploymentReady + - message: neutron-sriov Deployment ready + reason: Ready + status: "True" + type: ServiceNeutronSriovDeploymentReady + - message: nova Deployment ready + reason: Ready + status: "True" + type: ServiceNovaDeploymentReady + - message: ovn Deployment ready + reason: Ready + status: "True" + type: ServiceOvnDeploymentReady + - message: run-os Deployment ready + reason: Ready + status: "True" + type: ServiceRunOsDeploymentReady + - message: validate-network Deployment ready + reason: Ready + status: "True" + type: ServiceValidateNetworkDeploymentReady + edpm-compute-no-nodes-non-existent-service: + - message: Deployment error occurred OpenStackDataPlaneService.dataplane.openstack.org + "this-service-does-not-exist" not found + reason: Error + severity: Error + status: "False" + type: NodeSetDeploymentReady + edpm-compute-no-nodes-ovrd: + - message: Deployment completed + reason: Ready + status: "True" + type: NodeSetDeploymentReady + - message: custom-svc Deployment ready + reason: Ready + status: "True" + type: ServiceCustomSvcDeploymentReady + edpm-compute-no-nodes-updated-ovn-cm: + - message: Deployment completed + reason: Ready + status: "True" + type: NodeSetDeploymentReady + - message: ovn Deployment ready + reason: Ready + status: "True" + type: ServiceOvnDeploymentReady +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneDeployment +metadata: + name: edpm-compute-no-nodes-non-existent-service + namespace: openstack-kuttl-tests +spec: + nodeSets: + - edpm-compute-no-nodes + servicesOverride: + - this-service-does-not-exist +status: + observedGeneration: 1 + conditions: + - message: 'Deployment error occurred nodeSet: edpm-compute-no-nodes error: OpenStackDataPlaneService.dataplane.openstack.org + "this-service-does-not-exist" not found' + reason: Error + severity: Error + status: "False" + type: Ready + - message: 'Deployment error occurred nodeSet: edpm-compute-no-nodes error: OpenStackDataPlaneService.dataplane.openstack.org + "this-service-does-not-exist" not found' + reason: Error + severity: Error + status: "False" + type: DeploymentReady + - message: Setup complete + reason: Ready + status: "True" + type: InputReady + nodeSetConditions: + edpm-compute-no-nodes: + - message: Deployment error occurred OpenStackDataPlaneService.dataplane.openstack.org + "this-service-does-not-exist" not found + reason: Error + severity: Error + status: "False" + type: NodeSetDeploymentReady diff --git a/tests/kuttl/tests/dataplane-deploy-no-nodes-test/05-dataplane-deploy-service-not-found.yaml b/tests/kuttl/tests/dataplane-deploy-no-nodes-test/05-dataplane-deploy-service-not-found.yaml new file mode 100644 index 000000000..76b2f46eb --- /dev/null +++ b/tests/kuttl/tests/dataplane-deploy-no-nodes-test/05-dataplane-deploy-service-not-found.yaml @@ -0,0 +1,9 @@ +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneDeployment +metadata: + name: edpm-compute-no-nodes-non-existent-service +spec: + nodeSets: + - edpm-compute-no-nodes + servicesOverride: + - this-service-does-not-exist diff --git a/tests/kuttl/tests/dataplane-deploy-no-nodes-test/06-add-nodeset.yaml b/tests/kuttl/tests/dataplane-deploy-no-nodes-test/06-add-nodeset.yaml new file mode 100644 index 000000000..e24b42809 --- /dev/null +++ b/tests/kuttl/tests/dataplane-deploy-no-nodes-test/06-add-nodeset.yaml @@ -0,0 +1,44 @@ +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneNodeSet +metadata: + name: edpm-compute-beta-nodeset +spec: + baremetalSetTemplate: + automatedCleaningMode: metadata + bmhNamespace: openshift-machine-api + cloudUserName: "" + ctlplaneInterface: "" + ctlplaneNetmask: 255.255.255.0 + deploymentSSHSecret: "" + hardwareReqs: + cpuReqs: + countReq: {} + mhzReq: {} + diskReqs: + gbReq: {} + ssdReq: {} + memReqs: + gbReq: {} + preProvisioned: true + services: + - download-cache + - bootstrap + env: + - name: ANSIBLE_FORCE_COLOR + value: "True" + nodes: {} + nodeTemplate: + ansibleSSHPrivateKeySecret: dataplane-ansible-ssh-private-key-secret + ansible: + ansibleUser: cloud-admin +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneDeployment +metadata: + name: edpm-multinodeset +spec: + nodeSets: + - edpm-compute-no-nodes + - edpm-compute-beta-nodeset + ansibleExtraVars: + foo: bar diff --git a/tests/kuttl/tests/dataplane-deploy-no-nodes-test/06-assert.yaml b/tests/kuttl/tests/dataplane-deploy-no-nodes-test/06-assert.yaml new file mode 100644 index 000000000..feb11f622 --- /dev/null +++ b/tests/kuttl/tests/dataplane-deploy-no-nodes-test/06-assert.yaml @@ -0,0 +1,168 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 600 +collectors: +- type: command + command: oc logs -n openstack-operators -l openstack.org/operator-name=dataplane + name: dataplane-operator-logs +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneNodeSet +metadata: + name: edpm-compute-beta-nodeset + namespace: openstack-kuttl-tests +spec: + services: + - download-cache + - bootstrap + env: + - name: ANSIBLE_FORCE_COLOR + value: "True" + nodes: {} + nodeTemplate: + ansibleSSHPrivateKeySecret: dataplane-ansible-ssh-private-key-secret +status: + conditions: + - message: NodeSet Ready + reason: Ready + status: "True" + type: Ready + - message: Deployment completed + reason: Ready + status: "True" + type: DeploymentReady + - message: Input data complete + reason: Ready + status: "True" + type: InputReady + - message: ServiceAccount created + reason: Ready + status: "True" + type: ServiceAccountReady + - message: Setup complete + reason: Ready + status: "True" + type: SetupReady + configHash: ncbh88h5c5h59bh8bh664hffh564hf8h697h5cbh585h6fh55dh5f6h66ch57dh5cdh585h678hb9h65ch547h595h596h8bh65bh5h585h68chfbh5dcq + configMapHashes: + ovncontroller-config: n56h54bh9bhcbh65ch9fhdh66dh95h5dch569h678h7fh599h7ch84h597h59h54dh58dhf6h66bh565h4hc4h587h645hd7hcch5d8h5f4h55cq + deployedConfigHash: ncbh88h5c5h59bh8bh664hffh564hf8h697h5cbh585h6fh55dh5f6h66ch57dh5cdh585h678hb9h65ch547h595h596h8bh65bh5h585h68chfbh5dcq + deploymentStatuses: {} + secretHashes: + neutron-dhcp-agent-neutron-config: n68h676h98h689hd4h575h5dbh694h6fh688h57h665h5c5h56dh5ddh65bh5d7h5cdh644hb8h8fh5d9h5b9h555h9ch56dh5fh6chd4h5c5h5c5h68q + neutron-ovn-agent-neutron-config: n5f4h89hb8h645h55bh657h9fh5d9h5c6h595h9dh667h5f4hfhffh7fh685h56ch57fh679h5ddh5ddh95h696hbch5c7h669h84h54dh685hfh85q + neutron-ovn-metadata-agent-neutron-config: n68dh585h666h5c4h568hf7h65fh695h649hb9h657h5f6h548h679h77h5b4h664h8h5b8h654h5hf5h674h664h545h74h58ch57ch8ch56h54fh5ddq + neutron-sriov-agent-neutron-config: n685h567h697h5bch8ch5cfh87h698h658h684h8h99h5dch5c5h699h79hb5h87h66dh664h546h586h7bh56fh5d6h5d4h566h56bh87h678h696h56cq + nova-cell1-compute-config: n89hd6h5h545h644h58h556hd9h5c5h598hd4h7bh5f9h5bdh649hb5h99h686h677h8ch575h665h574h587h5b6h5ddh8fh687h9bh657h675h97q + nova-metadata-neutron-config: n7fh696h674h5b9h68dh77h677h5c5hd9h5dbh89h646h696h65ch64bh86hd8h56h78h558h5h5c7h87h86h5bh5bch78h6ch5cbh54fh56fhfdq + nova-migration-ssh-key: n64dh97h54dhffh65fh577h59bh664hbch54dhcbh547hdbhdch655hd9h675h5d4h67dh5ch67bh64h5fdh5c8h5cdh66bh5f5h58dhcbh9bh66bhd4q +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneDeployment +metadata: + name: edpm-multinodeset + namespace: openstack-kuttl-tests +spec: + nodeSets: + - edpm-compute-no-nodes + - edpm-compute-beta-nodeset +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + name: download-cache-edpm-multinodeset-edpm-compute-beta-nodeset + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + blockOwnerDeletion: true + controller: true + kind: OpenStackDataPlaneDeployment + name: edpm-multinodeset +spec: + backoffLimit: 6 + extraMounts: + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-edpm-compute-beta-nodeset + name: openstackansibleee + restartPolicy: Never + playbook: osp.edpm.download_cache + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + name: bootstrap-edpm-multinodeset-edpm-compute-beta-nodeset + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + blockOwnerDeletion: true + controller: true + kind: OpenStackDataPlaneDeployment + name: edpm-multinodeset +spec: + backoffLimit: 6 + extraMounts: + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-edpm-compute-beta-nodeset + name: openstackansibleee + restartPolicy: Never + playbook: osp.edpm.bootstrap + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady diff --git a/tests/kuttl/tests/dataplane-deploy-nodehashes-test/00-assert.yaml b/tests/kuttl/tests/dataplane-deploy-nodehashes-test/00-assert.yaml new file mode 100644 index 000000000..84ee32427 --- /dev/null +++ b/tests/kuttl/tests/dataplane-deploy-nodehashes-test/00-assert.yaml @@ -0,0 +1,33 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 600 +collectors: +- type: command + command: oc logs -n openstack-operators -l openstack.org/operator-name=dataplane + name: dataplane-operator-logs +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneDeployment +metadata: + name: edpm-compute-no-nodes +spec: + nodeSets: + - edpm-compute-no-nodes +status: + deployed: true + nodeSetHashes: + edpm-compute-no-nodes: n595h74h98h5d8h579h5c7h84h5dh599h544hf8h65bh686h5f7h99h669h676h97h678h646h9fh5cch649h66fhcch5fh688h95h7bhbh675hd9q +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneNodeSet +metadata: + name: edpm-compute-no-nodes +spec: + nodeTemplate: + ansibleSSHPrivateKeySecret: dataplane-ansible-ssh-private-key-secret + nodes: {} + services: + - download-cache +status: + configHash: n595h74h98h5d8h579h5c7h84h5dh599h544hf8h65bh686h5f7h99h669h676h97h678h646h9fh5cch649h66fhcch5fh688h95h7bhbh675hd9q + deployedConfigHash: n595h74h98h5d8h579h5c7h84h5dh599h544hf8h65bh686h5f7h99h669h676h97h678h646h9fh5cch649h66fhcch5fh688h95h7bhbh675hd9q diff --git a/tests/kuttl/tests/dataplane-deploy-nodehashes-test/00-dataplane-create.yaml b/tests/kuttl/tests/dataplane-deploy-nodehashes-test/00-dataplane-create.yaml new file mode 100644 index 000000000..ed61d0db2 --- /dev/null +++ b/tests/kuttl/tests/dataplane-deploy-nodehashes-test/00-dataplane-create.yaml @@ -0,0 +1,38 @@ +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneNodeSet +metadata: + name: edpm-compute-no-nodes +spec: + baremetalSetTemplate: + automatedCleaningMode: metadata + bmhNamespace: openshift-machine-api + cloudUserName: "" + ctlplaneInterface: "" + ctlplaneNetmask: 255.255.255.0 + deploymentSSHSecret: "" + hardwareReqs: + cpuReqs: + countReq: {} + mhzReq: {} + diskReqs: + gbReq: {} + ssdReq: {} + memReqs: + gbReq: {} + preProvisioned: true + services: + - download-cache + nodes: {} + nodeTemplate: + ansibleSSHPrivateKeySecret: dataplane-ansible-ssh-private-key-secret + ansible: + ansibleUser: cloud-admin +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneDeployment +metadata: + name: edpm-compute-no-nodes +spec: + nodeSets: + - edpm-compute-no-nodes diff --git a/tests/kuttl/tests/dataplane-deploy-nodehashes-test/01-assert.yaml b/tests/kuttl/tests/dataplane-deploy-nodehashes-test/01-assert.yaml new file mode 100644 index 000000000..03f005ab6 --- /dev/null +++ b/tests/kuttl/tests/dataplane-deploy-nodehashes-test/01-assert.yaml @@ -0,0 +1,23 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 600 +collectors: +- type: command + command: oc logs -n openstack-operators -l openstack.org/operator-name=dataplane + name: dataplane-operator-logs +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneNodeSet +metadata: + name: edpm-compute-no-nodes +spec: + nodeTemplate: + ansible: + ansibleVars: + newVar: newValue + nodes: {} + services: + - download-cache +status: + configHash: n55dhb7h5dh576h5c4h5fch55dh55h566hb8h67dh58dhcbh598h8ch95h85h557h66bh687h686h5dfh5c6h5bfh9dhdch656h566h95hcdh595h5fbq + deployedConfigHash: n595h74h98h5d8h579h5c7h84h5dh599h544hf8h65bh686h5f7h99h669h676h97h678h646h9fh5cch649h66fhcch5fh688h95h7bhbh675hd9q diff --git a/tests/kuttl/tests/dataplane-deploy-nodehashes-test/01-dataplane-edit.yaml b/tests/kuttl/tests/dataplane-deploy-nodehashes-test/01-dataplane-edit.yaml new file mode 100644 index 000000000..c7d57f631 --- /dev/null +++ b/tests/kuttl/tests/dataplane-deploy-nodehashes-test/01-dataplane-edit.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneNodeSet +metadata: + name: edpm-compute-no-nodes +spec: + preProvisioned: true + services: + - download-cache + nodes: {} + nodeTemplate: + ansibleSSHPrivateKeySecret: dataplane-ansible-ssh-private-key-secret + ansible: + ansibleVars: + newVar: newValue diff --git a/tests/kuttl/tests/dataplane-deploy-tls-test/00-assert.yaml b/tests/kuttl/tests/dataplane-deploy-tls-test/00-assert.yaml new file mode 100644 index 000000000..2ac67b48b --- /dev/null +++ b/tests/kuttl/tests/dataplane-deploy-tls-test/00-assert.yaml @@ -0,0 +1,213 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 600 +collectors: +- type: command + command: oc logs -n openstack-operators -l openstack.org/operator-name=dataplane + name: dataplane-operator-logs +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + name: tls-dnsnames +spec: + caCerts: combined-ca-bundle + tlsCert: + contents: + - dnsnames + play: | + - hosts: localhost + gather_facts: no + name: kuttl play + tasks: + - name: Sleep + command: sleep 1 + delegate_to: localhost +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + name: install-certs-ovrd +spec: + addCertMounts: True + play: | + - hosts: localhost + gather_facts: no + name: kuttl play + tasks: + - name: Sleep + command: sleep 1 + delegate_to: localhost +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneNodeSet +metadata: + name: openstack-edpm-tls +spec: + env: + - name: ANSIBLE_FORCE_COLOR + value: "True" + nodes: + edpm-compute-0: + hostName: edpm-compute-0 + networks: + - name: ctlplane + subnetName: subnet1 + defaultRoute: true + fixedIP: 192.168.122.100 + - name: internalapi + subnetName: subnet1 + - name: storage + subnetName: subnet1 + - name: tenant + subnetName: subnet1 + + nodeTemplate: + ansible: + ansiblePort: 22 + ansibleUser: cloud-admin + ansibleVarsFrom: + - prefix: edpm_ + configMapRef: + name: network-config-template + ansibleVars: + timesync_ntp_servers: + - hostname: clock.redhat.com + edpm_network_config_hide_sensitive_logs: false + edpm_nodes_validation_validate_controllers_icmp: false + edpm_nodes_validation_validate_gateway_icmp: false + edpm_selinux_mode: enforcing + edpm_sshd_allowed_ranges: + - 192.168.122.0/24 + edpm_sshd_configure_firewall: true + enable_debug: false + ansibleSSHPrivateKeySecret: dataplane-ansible-ssh-private-key-secret + preProvisioned: true + tlsEnabled: true + services: + - install-certs-ovrd + - tls-dnsnames +status: + observedGeneration: 1 + conditions: + - message: Deployment not started + reason: Requested + status: "False" + type: Ready + - message: Deployment not started + reason: Requested + status: "False" + type: DeploymentReady + - message: Input data complete + reason: Ready + status: "True" + type: InputReady + - message: NodeSetDNSDataReady ready + reason: Ready + status: "True" + type: NodeSetDNSDataReady + - message: NodeSetIPReservationReady ready + reason: Ready + status: "True" + type: NodeSetIPReservationReady + - message: ServiceAccount created + reason: Ready + status: "True" + type: ServiceAccountReady + - message: Setup complete + reason: Ready + status: "True" + type: SetupReady +--- +apiVersion: network.openstack.org/v1beta1 +kind: IPSet +metadata: + name: edpm-compute-0 +spec: + immutable: false + networks: + - defaultRoute: true + name: ctlplane + subnetName: subnet1 + - name: internalapi + subnetName: subnet1 + - name: storage + subnetName: subnet1 + - name: tenant + subnetName: subnet1 +status: + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: Input data complete + reason: Ready + status: "True" + type: InputReady + - message: Reservation successful + reason: Ready + status: "True" + type: ReservationReady + reservations: + - address: 192.168.122.100 + cidr: 192.168.122.0/24 + dnsDomain: ctlplane.example.com + gateway: 192.168.122.1 + mtu: 1500 + network: ctlplane + routes: + - destination: 0.0.0.0/0 + nexthop: 192.168.122.1 + subnet: subnet1 + - address: 172.17.0.100 + cidr: 172.17.0.0/24 + dnsDomain: internalapi.example.com + mtu: 1500 + network: internalapi + subnet: subnet1 + vlan: 20 + - address: 172.18.0.100 + cidr: 172.18.0.0/24 + dnsDomain: storage.example.com + mtu: 1500 + network: storage + subnet: subnet1 + vlan: 21 + - address: 172.19.0.100 + cidr: 172.19.0.0/24 + dnsDomain: tenant.example.com + mtu: 1500 + network: tenant + subnet: subnet1 + vlan: 22 +--- +apiVersion: network.openstack.org/v1beta1 +kind: DNSData +metadata: + name: openstack-edpm-tls +spec: + dnsDataLabelSelectorValue: dnsdata + hosts: + - hostnames: + - edpm-compute-0.ctlplane.example.com + ip: 192.168.122.100 + - hostnames: + - edpm-compute-0.internalapi.example.com + ip: 172.17.0.100 + - hostnames: + - edpm-compute-0.storage.example.com + ip: 172.18.0.100 + - hostnames: + - edpm-compute-0.tenant.example.com + ip: 172.19.0.100 +status: + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: Input data complete + reason: Ready + status: "True" + type: ServiceConfigReady diff --git a/tests/kuttl/tests/dataplane-deploy-tls-test/00-dataplane-create.yaml b/tests/kuttl/tests/dataplane-deploy-tls-test/00-dataplane-create.yaml new file mode 100644 index 000000000..fa215fed3 --- /dev/null +++ b/tests/kuttl/tests/dataplane-deploy-tls-test/00-dataplane-create.yaml @@ -0,0 +1,136 @@ +apiVersion: network.openstack.org/v1beta1 +kind: DNSMasq +metadata: + name: dnsmasq +spec: + replicas: 1 + options: + - key: server + values: + - 192.168.122.1 + debug: + service: false +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + name: tls-dnsnames +spec: + caCerts: combined-ca-bundle + tlsCert: + contents: + - dnsnames + play: | + - hosts: localhost + gather_facts: no + name: kuttl play + tasks: + - name: Sleep + command: sleep 1 + delegate_to: localhost +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + name: install-certs-ovrd +spec: + addCertMounts: True + play: | + - hosts: localhost + gather_facts: no + name: kuttl play + tasks: + - name: Sleep + command: sleep 1 + delegate_to: localhost +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: network-config-template +data: + network_config_template: | + --- + {% set mtu_list = [ctlplane_mtu] %} + {% for network in nodeset_networks %} + {{ mtu_list.append(lookup('vars', networks_lower[network] ~ '_mtu')) }} + {%- endfor %} + {% set min_viable_mtu = mtu_list | max %} + network_config: + - type: ovs_bridge + name: {{ neutron_physical_bridge_name }} + mtu: {{ min_viable_mtu }} + use_dhcp: false + dns_servers: {{ ctlplane_dns_nameservers }} + domain: {{ dns_search_domains }} + addresses: + - ip_netmask: {{ ctlplane_ip }}/{{ ctlplane_cidr }} + routes: {{ ctlplane_host_routes }} + members: + - type: interface + name: nic1 + mtu: {{ min_viable_mtu }} + # force the MAC address of the bridge to this interface + primary: true + {% for network in nodeset_networks %} + - type: vlan + mtu: {{ lookup('vars', networks_lower[network] ~ '_mtu') }} + vlan_id: {{ lookup('vars', networks_lower[network] ~ '_vlan_id') }} + addresses: + - ip_netmask: + {{ lookup('vars', networks_lower[network] ~ '_ip') }}/{{ lookup('vars', networks_lower[network] ~ '_cidr') }} + routes: {{ lookup('vars', networks_lower[network] ~ '_host_routes') }} + {% endfor %} +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneNodeSet +metadata: + name: openstack-edpm-tls +spec: + env: + - name: ANSIBLE_FORCE_COLOR + value: "True" + services: + - install-certs-ovrd + - tls-dnsnames + preProvisioned: true + tlsEnabled: true + nodes: + edpm-compute-0: + hostName: edpm-compute-0 + networks: + - name: ctlplane + subnetName: subnet1 + defaultRoute: true + fixedIP: 192.168.122.100 + - name: internalapi + subnetName: subnet1 + - name: storage + subnetName: subnet1 + - name: tenant + subnetName: subnet1 + nodeTemplate: + ansibleSSHPrivateKeySecret: dataplane-ansible-ssh-private-key-secret + ansible: + ansibleUser: cloud-admin + ansiblePort: 22 + ansibleVarsFrom: + - prefix: edpm_ + configMapRef: + name: network-config-template + ansibleVars: + timesync_ntp_servers: + - hostname: clock.redhat.com + # edpm_network_config + # Default nic config template for a EDPM compute node + # These vars are edpm_network_config role vars + edpm_network_config_hide_sensitive_logs: false + edpm_nodes_validation_validate_controllers_icmp: false + edpm_nodes_validation_validate_gateway_icmp: false + gather_facts: false + enable_debug: false + # edpm firewall, change the allowed CIDR if needed + edpm_sshd_configure_firewall: true + edpm_sshd_allowed_ranges: ['192.168.122.0/24'] + # SELinux module + edpm_selinux_mode: enforcing diff --git a/tests/kuttl/tests/dataplane-deploy-tls-test/01-create-cert-issuers.yaml b/tests/kuttl/tests/dataplane-deploy-tls-test/01-create-cert-issuers.yaml new file mode 100644 index 000000000..d8048fe30 --- /dev/null +++ b/tests/kuttl/tests/dataplane-deploy-tls-test/01-create-cert-issuers.yaml @@ -0,0 +1,22 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - script: | + function wait_for() { + timeout=$1 + shift 1 + until [ $timeout -le 0 ] || ("$@" &> /dev/null); do + echo waiting for "$@" + sleep 1 + timeout=$(( timeout - 1 )) + done + if [ $timeout -le 0 ]; then + return 1 + fi + } + + if oc get secret combined-ca-bundle -n openstack; then oc delete secret combined-ca-bundle -n openstack; fi + oc apply -f ./certs.yaml + wait_for 100 oc get secret osp-rootca-secret -n openstack + CA_CRT=$(oc get secret osp-rootca-secret -n openstack -o json|jq -r '.data."ca.crt"') + oc create secret generic combined-ca-bundle -n openstack --from-literal=tls-ca-bundle.pem=$CA_CRT diff --git a/tests/kuttl/tests/dataplane-deploy-tls-test/02-assert.yaml b/tests/kuttl/tests/dataplane-deploy-tls-test/02-assert.yaml new file mode 100644 index 000000000..9d72be7f9 --- /dev/null +++ b/tests/kuttl/tests/dataplane-deploy-tls-test/02-assert.yaml @@ -0,0 +1,183 @@ +apiVersion: v1 +kind: Secret +metadata: + name: cert-tls-dnsnames-edpm-compute-0 + annotations: + cert-manager.io/certificate-name: tls-dnsnames-edpm-compute-0 + cert-manager.io/issuer-group: cert-manager.io + cert-manager.io/issuer-kind: Issuer + cert-manager.io/issuer-name: rootca-internal + labels: + hostname: edpm-compute-0 + osdp-service: tls-dnsnames + osdpns: openstack-edpm-tls +type: kubernetes.io/tls +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + labels: + hostname: edpm-compute-0 + osdp-service: tls-dnsnames + osdpns: openstack-edpm-tls + name: tls-dnsnames-edpm-compute-0 + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + kind: OpenStackDataPlaneNodeSet + name: openstack-edpm-tls +spec: + issuerRef: + group: cert-manager.io + kind: Issuer + name: rootca-internal + secretName: cert-tls-dnsnames-edpm-compute-0 + secretTemplate: + labels: + hostname: edpm-compute-0 + osdp-service: tls-dnsnames + osdpns: openstack-edpm-tls +--- +# validate the alt-names and usages - which is a list with elements that can be in any order +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 600 +commands: + - script: | + template='{{index .spec.dnsNames }}' + names=$(oc get certificate tls-dnsnames-edpm-compute-0 -n openstack -o go-template="$template") + echo $names > test123.data + regex="(?=.*(edpm-compute-0\.internalapi\.example\.com))(?=.*(edpm-compute-0\.storage\.example\.com))(?=.*(edpm-compute-0\.tenant\.example\.com))(?=.*(edpm-compute-0\.ctlplane\.example\.com))" + matches=$(grep -P "$regex" test123.data) + rm test123.data + if [ -z "$matches" ]; then + echo "bad dnsnames match: $names" + exit 1 + else + exit 0 + fi + - script: | + template='{{index .spec.usages }}' + usages=$(oc get certificate tls-dnsnames-edpm-compute-0 -n openstack -o go-template="$template") + echo $usages > test123.data + regex="(?=.*(key encipherment))(?=.*(digital signature))(?=.*(server auth))" + matches=$(grep -P "$regex" test123.data) + rm test123.data + if [ -z "$matches" ]; then + echo "bad usages match: $usages" + exit 1 + else + exit 0 + fi +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + name: install-certs-ovrd-openstack-edpm-tls-openstack-edpm-tls + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + blockOwnerDeletion: true + controller: true + kind: OpenStackDataPlaneDeployment + name: openstack-edpm-tls +spec: + backoffLimit: 6 + extraMounts: + - mounts: + - mountPath: /var/lib/openstack/certs/tls-dnsnames + name: openstack-edpm-tls-tls-dnsnames-certs-0 + volumes: + - name: openstack-edpm-tls-tls-dnsnames-certs-0 + projected: + sources: + - secret: + name: openstack-edpm-tls-tls-dnsnames-certs-0 + - mounts: + - mountPath: /var/lib/openstack/cacerts/tls-dnsnames + name: tls-dnsnames-combined-ca-bundle + volumes: + - name: tls-dnsnames-combined-ca-bundle + secret: + secretName: combined-ca-bundle + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-openstack-edpm-tls + name: openstackansibleee + restartPolicy: Never + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + name: tls-dnsnames-openstack-edpm-tls-openstack-edpm-tls + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + kind: OpenStackDataPlaneDeployment + name: openstack-edpm-tls +spec: + backoffLimit: 6 + extraMounts: + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-openstack-edpm-tls + name: openstackansibleee + restartPolicy: Never + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady diff --git a/tests/kuttl/tests/dataplane-deploy-tls-test/02-dataplane-deploy.yaml b/tests/kuttl/tests/dataplane-deploy-tls-test/02-dataplane-deploy.yaml new file mode 100644 index 000000000..17c50468f --- /dev/null +++ b/tests/kuttl/tests/dataplane-deploy-tls-test/02-dataplane-deploy.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneDeployment +metadata: + name: openstack-edpm-tls +spec: + nodeSets: + - openstack-edpm-tls diff --git a/tests/kuttl/tests/dataplane-deploy-tls-test/03-assert.yaml b/tests/kuttl/tests/dataplane-deploy-tls-test/03-assert.yaml new file mode 100644 index 000000000..8f82d76c7 --- /dev/null +++ b/tests/kuttl/tests/dataplane-deploy-tls-test/03-assert.yaml @@ -0,0 +1,310 @@ +apiVersion: v1 +kind: Secret +metadata: + name: cert-tls-dns-ips-edpm-compute-0 + annotations: + cert-manager.io/alt-names: edpm-compute-0.ctlplane.example.com + cert-manager.io/certificate-name: tls-dns-ips-edpm-compute-0 + cert-manager.io/ip-sans: 192.168.122.100 + cert-manager.io/issuer-group: cert-manager.io + cert-manager.io/issuer-kind: Issuer + cert-manager.io/issuer-name: rootca-internal + labels: + hostname: edpm-compute-0 + osdp-service: tls-dns-ips + osdpns: openstack-edpm-tls +type: kubernetes.io/tls +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + labels: + hostname: edpm-compute-0 + osdp-service: tls-dns-ips + osdpns: openstack-edpm-tls + name: tls-dns-ips-edpm-compute-0 + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + kind: OpenStackDataPlaneNodeSet + name: openstack-edpm-tls +spec: + dnsNames: + - edpm-compute-0.ctlplane.example.com + issuerRef: + group: cert-manager.io + kind: Issuer + name: rootca-internal + secretName: cert-tls-dns-ips-edpm-compute-0 + secretTemplate: + labels: + hostname: edpm-compute-0 + osdp-service: tls-dns-ips + osdpns: openstack-edpm-tls +--- +apiVersion: v1 +kind: Secret +metadata: + name: cert-custom-tls-dns-edpm-compute-0 + annotations: + cert-manager.io/certificate-name: custom-tls-dns-edpm-compute-0 + cert-manager.io/issuer-group: cert-manager.io + cert-manager.io/issuer-kind: Issuer + cert-manager.io/issuer-name: rootca-internal + labels: + hostname: edpm-compute-0 + osdp-service: custom-tls-dns + osdpns: openstack-edpm-tls +type: kubernetes.io/tls +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + labels: + hostname: edpm-compute-0 + osdp-service: custom-tls-dns + osdpns: openstack-edpm-tls + name: custom-tls-dns-edpm-compute-0 + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + kind: OpenStackDataPlaneNodeSet + name: openstack-edpm-tls +spec: + issuerRef: + group: cert-manager.io + kind: Issuer + name: rootca-internal + secretName: cert-custom-tls-dns-edpm-compute-0 + secretTemplate: + labels: + hostname: edpm-compute-0 + osdp-service: custom-tls-dns + osdpns: openstack-edpm-tls +--- +# validate the alt-names and usages - which is a list with elements that can be in any order +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 600 +commands: + - script: | + template='{{index .spec.dnsNames }}' + names=$(oc get certificate custom-tls-dns-edpm-compute-0 -n openstack -o go-template="$template") + echo $names > test123.data + regex="(?=.*(edpm-compute-0\.internalapi\.example\.com))(?=.*(edpm-compute-0\.storage\.example\.com))(?=.*(edpm-compute-0\.tenant\.example\.com))(?=.*(edpm-compute-0\.ctlplane\.example\.com))" + matches=$(grep -P "$regex" test123.data) + rm test123.data + if [ -z "$matches" ]; then + echo "bad dnsnames match: $names" + exit 1 + else + exit 0 + fi + - script: | + template='{{index .spec.usages }}' + usages=$(oc get certificate custom-tls-dns-edpm-compute-0 -n openstack -o go-template="$template") + echo $usages > test123.data + regex="(?=.*(key encipherment))(?=.*(digital signature))(?=.*(server auth))(?=.*(client auth))" + matches=$(grep -P "$regex" test123.data) + rm test123.data + if [ -z "$matches" ]; then + echo "bad usages match: $usages" + exit 1 + else + exit 0 + fi +--- +apiVersion: v1 +kind: Secret +metadata: + name: openstack-edpm-tls-tls-dns-ips-certs-0 + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + kind: OpenStackDataPlaneNodeSet + name: openstack-edpm-tls +type: Opaque +--- +apiVersion: v1 +kind: Secret +metadata: + name: openstack-edpm-tls-custom-tls-dns-certs-0 + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + kind: OpenStackDataPlaneNodeSet + name: openstack-edpm-tls +type: Opaque +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + name: install-certs-ovrd-openstack-edpm-tls-ovrd-openstack-edpm-tls + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + blockOwnerDeletion: true + controller: true + kind: OpenStackDataPlaneDeployment + name: openstack-edpm-tls-ovrd +spec: + backoffLimit: 6 + extraMounts: + - mounts: + - mountPath: /var/lib/openstack/certs/tls-dns-ips + name: openstack-edpm-tls-tls-dns-ips-certs-0 + volumes: + - name: openstack-edpm-tls-tls-dns-ips-certs-0 + projected: + sources: + - secret: + name: openstack-edpm-tls-tls-dns-ips-certs-0 + - mounts: + - mountPath: /var/lib/openstack/cacerts/tls-dns-ips + name: tls-dns-ips-combined-ca-bundle + volumes: + - name: tls-dns-ips-combined-ca-bundle + secret: + secretName: combined-ca-bundle + - mounts: + - mountPath: /var/lib/openstack/certs/custom-tls-dns + name: openstack-edpm-tls-custom-tls-dns-certs-0 + volumes: + - name: openstack-edpm-tls-custom-tls-dns-certs-0 + projected: + sources: + - secret: + name: openstack-edpm-tls-custom-tls-dns-certs-0 + - mounts: + - mountPath: /var/lib/openstack/cacerts/custom-tls-dns + name: custom-tls-dns-combined-ca-bundle + volumes: + - name: custom-tls-dns-combined-ca-bundle + secret: + secretName: combined-ca-bundle + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-openstack-edpm-tls + name: openstackansibleee + restartPolicy: Never + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + name: tls-dns-ips-openstack-edpm-tls-ovrd-openstack-edpm-tls + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + kind: OpenStackDataPlaneDeployment + name: openstack-edpm-tls-ovrd +spec: + backoffLimit: 6 + extraMounts: + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-openstack-edpm-tls + name: openstackansibleee + restartPolicy: Never + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + name: custom-tls-dns-openstack-edpm-tls-ovrd-openstack-edpm-tls + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + kind: OpenStackDataPlaneDeployment + name: openstack-edpm-tls-ovrd +spec: + backoffLimit: 6 + extraMounts: + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-openstack-edpm-tls + name: openstackansibleee + restartPolicy: Never + uid: 1001 +status: + JobStatus: Succeeded + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: AnsibleExecutionJob complete + reason: Ready + status: "True" + type: AnsibleExecutionJobReady diff --git a/tests/kuttl/tests/dataplane-deploy-tls-test/03-dataplane-deploy-services-override.yaml b/tests/kuttl/tests/dataplane-deploy-tls-test/03-dataplane-deploy-services-override.yaml new file mode 100644 index 000000000..e61b86017 --- /dev/null +++ b/tests/kuttl/tests/dataplane-deploy-tls-test/03-dataplane-deploy-services-override.yaml @@ -0,0 +1,72 @@ +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + name: tls-dns-ips +spec: + caCerts: combined-ca-bundle + tlsCert: + contents: + - dnsnames + - ips + issuer: osp-rootca-issuer-internal + networks: + - ctlplane + play: | + - hosts: localhost + gather_facts: no + name: kuttl play + tasks: + - name: Sleep + command: sleep 1 + delegate_to: localhost +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + name: custom-tls-dns +spec: + caCerts: combined-ca-bundle + tlsCert: + contents: + - dnsnames + keyUsages: + - key encipherment + - digital signature + - server auth + - client auth + play: | + - hosts: localhost + gather_facts: no + name: kuttl play + tasks: + - name: Sleep + command: sleep 1 + delegate_to: localhost +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + name: install-certs-ovrd +spec: + addCertMounts: True + play: | + - hosts: localhost + gather_facts: no + name: kuttl play + tasks: + - name: Sleep + command: sleep 1 + delegate_to: localhost +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneDeployment +metadata: + name: openstack-edpm-tls-ovrd +spec: + nodeSets: + - openstack-edpm-tls + servicesOverride: + - install-certs-ovrd + - tls-dns-ips + - custom-tls-dns diff --git a/tests/kuttl/tests/dataplane-deploy-tls-test/certs.yaml b/tests/kuttl/tests/dataplane-deploy-tls-test/certs.yaml new file mode 100644 index 000000000..7cffc290c --- /dev/null +++ b/tests/kuttl/tests/dataplane-deploy-tls-test/certs.yaml @@ -0,0 +1,38 @@ +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: selfsigned-issuer + namespace: openstack-kuttl-tests +spec: + selfSigned: {} +--- +# RootCA Certificate used to sign certificates +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: osp-rootca + namespace: openstack-kuttl-tests +spec: + isCA: true + commonName: osp-rootca + secretName: osp-rootca-secret + privateKey: + algorithm: ECDSA + size: 256 + issuerRef: + name: selfsigned-issuer + kind: Issuer + group: cert-manager.io +--- +# Issuer that uses the generated CA certificate to issue certs +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: rootca-internal + namespace: openstack-kuttl-tests + labels: + osp-rootca-issuer-internal: "" +spec: + ca: + secretName: osp-rootca-secret +--- diff --git a/tests/kuttl/tests/dataplane-extramounts/00-assert.yaml b/tests/kuttl/tests/dataplane-extramounts/00-assert.yaml new file mode 100644 index 000000000..4c42c3614 --- /dev/null +++ b/tests/kuttl/tests/dataplane-extramounts/00-assert.yaml @@ -0,0 +1,71 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 600 +collectors: +- type: command + command: oc logs -n openstack-operators -l openstack.org/operator-name=dataplane + name: dataplane-operator-logs +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneNodeSet +metadata: + name: edpm-extramounts +spec: + preProvisioned: true + services: + - test-service + nodes: {} + nodeTemplate: + extraMounts: + - extraVolType: edpm-ansible + mounts: + - mountPath: /usr/share/ansible/collections/ansible_collections/osp/edpm + name: edpm-ansible + volumes: + - name: edpm-ansible + persistentVolumeClaim: + claimName: edpm-ansible + readOnly: true +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + name: test-service-edpm-extramounts-edpm-extramounts + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + blockOwnerDeletion: true + controller: true + kind: OpenStackDataPlaneDeployment + name: edpm-extramounts +spec: + extraMounts: + - extraVolType: edpm-ansible + mounts: + - mountPath: /usr/share/ansible/collections/ansible_collections/osp/edpm + name: edpm-ansible + volumes: + - name: edpm-ansible + persistentVolumeClaim: + claimName: edpm-ansible + readOnly: true + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-edpm-extramounts diff --git a/tests/kuttl/tests/dataplane-extramounts/00-dataplane-create.yaml b/tests/kuttl/tests/dataplane-extramounts/00-dataplane-create.yaml new file mode 100644 index 000000000..9069dea31 --- /dev/null +++ b/tests/kuttl/tests/dataplane-extramounts/00-dataplane-create.yaml @@ -0,0 +1,37 @@ +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + name: test-service +spec: + label: test-service + playbook: test.yml +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneNodeSet +metadata: + name: edpm-extramounts +spec: + preProvisioned: true + services: + - test-service + nodes: {} + nodeTemplate: + ansibleSSHPrivateKeySecret: dataplane-ansible-ssh-private-key-secret + extraMounts: + - extraVolType: edpm-ansible + mounts: + - mountPath: /usr/share/ansible/collections/ansible_collections/osp/edpm + name: edpm-ansible + volumes: + - name: edpm-ansible + persistentVolumeClaim: + claimName: edpm-ansible + readOnly: true +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneDeployment +metadata: + name: edpm-extramounts +spec: + nodeSets: + - edpm-extramounts diff --git a/tests/kuttl/tests/dataplane-service-config/00-assert.yaml b/tests/kuttl/tests/dataplane-service-config/00-assert.yaml new file mode 100644 index 000000000..48ae18794 --- /dev/null +++ b/tests/kuttl/tests/dataplane-service-config/00-assert.yaml @@ -0,0 +1,126 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 600 +collectors: +- type: command + command: oc logs -n openstack-operators -l openstack.org/operator-name=dataplane + name: dataplane-operator-logs +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + name: kuttl-service-edpm-compute-no-nodes-edpm-compute-no-nodes + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + blockOwnerDeletion: true + controller: true + kind: OpenStackDataPlaneDeployment + name: edpm-compute-no-nodes +spec: + env: + - name: ANSIBLE_FORCE_COLOR + value: "True" + backoffLimit: 6 + envConfigMapName: openstack-aee-default-env + extraMounts: + - mounts: + - mountPath: /var/lib/openstack/configs/kuttl-service/00-ansibleVars + subPath: 00-ansibleVars + - mountPath: /var/lib/openstack/configs/kuttl-service/00-kuttl-service.conf + subPath: 00-kuttl-service.conf + - mountPath: /var/lib/openstack/configs/kuttl-service/01-kuttl-service.conf + subPath: 01-kuttl-service.conf + volumes: + - configMap: + items: + - key: 00-ansibleVars + path: 00-ansibleVars + name: kuttl-service-cm-0 + - configMap: + items: + - key: 00-kuttl-service.conf + path: 00-kuttl-service.conf + name: kuttl-service-cm-0 + - configMap: + items: + - key: 01-kuttl-service.conf + path: 01-kuttl-service.conf + name: kuttl-service-cm-0 + - mounts: + - mountPath: /var/lib/openstack/configs/kuttl-service/01-ansibleVars + subPath: 01-ansibleVars + - mountPath: /var/lib/openstack/configs/kuttl-service/10-kuttl-service.conf + subPath: 10-kuttl-service.conf + - mountPath: /var/lib/openstack/configs/kuttl-service/20-kuttl-service.conf + subPath: 20-kuttl-service.conf + volumes: + - configMap: + items: + - key: 01-ansibleVars + path: 01-ansibleVars + name: kuttl-service-cm-1 + - configMap: + items: + - key: 10-kuttl-service.conf + path: 10-kuttl-service.conf + name: kuttl-service-cm-1 + - configMap: + items: + - key: 20-kuttl-service.conf + path: 20-kuttl-service.conf + name: kuttl-service-cm-1 + - mounts: + - mountPath: /var/lib/openstack/configs/kuttl-service/30-kuttl-service.conf + subPath: 30-kuttl-service.conf + volumes: + - configMap: + items: + - key: 30-kuttl-service.conf + path: 30-kuttl-service.conf + name: kuttl-service-cm-2 + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-edpm-compute-no-nodes + name: openstackansibleee + play: | + - hosts: localhost + gather_facts: no + name: kuttl play + tasks: + - name: Sleep + command: sleep infinity + delegate_to: localhost + preserveJobs: true + restartPolicy: Never + uid: 1001 +status: + JobStatus: Running + conditions: + - message: AnsibleExecutionJob is running + reason: Requested + severity: Info + status: "False" + type: Ready + - message: AnsibleExecutionJob is running + reason: Requested + severity: Info + status: "False" + type: AnsibleExecutionJobReady diff --git a/tests/kuttl/tests/dataplane-service-config/00-create.yaml b/tests/kuttl/tests/dataplane-service-config/00-create.yaml new file mode 100644 index 000000000..4ddea13d4 --- /dev/null +++ b/tests/kuttl/tests/dataplane-service-config/00-create.yaml @@ -0,0 +1,76 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: kuttl-service-cm-0 +data: + 00-kuttl-service.conf: | + a=b + c=d + 01-kuttl-service.conf: | + e=f + g=h + 00-ansibleVars: | + foo: bar + baz: blippy +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: kuttl-service-cm-1 +data: + 10-kuttl-service.conf: | + i=j + 20-kuttl-service.conf: | + k=l + 01-ansibleVars: | + baz: blippy + zed: zod +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: kuttl-service-cm-2 +binaryData: + 30-kuttl-service.conf: Cg== +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + name: kuttl-service +spec: + play: | + - hosts: localhost + gather_facts: no + name: kuttl play + tasks: + - name: Sleep + command: sleep infinity + delegate_to: localhost + configMaps: + - kuttl-service-cm-0 + - kuttl-service-cm-1 + - kuttl-service-cm-2 +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneNodeSet +metadata: + name: edpm-compute-no-nodes +spec: + preProvisioned: true + env: + - name: ANSIBLE_FORCE_COLOR + value: "True" + nodes: {} + nodeTemplate: + ansibleSSHPrivateKeySecret: dataplane-ansible-ssh-private-key-secret + services: + - kuttl-service +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneDeployment +metadata: + name: edpm-compute-no-nodes +spec: + nodeSets: + - edpm-compute-no-nodes diff --git a/tests/kuttl/tests/dataplane-service-custom-image/00-assert.yaml b/tests/kuttl/tests/dataplane-service-custom-image/00-assert.yaml new file mode 100644 index 000000000..e0872db82 --- /dev/null +++ b/tests/kuttl/tests/dataplane-service-custom-image/00-assert.yaml @@ -0,0 +1,99 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 600 +collectors: +- type: command + command: oc logs -n openstack-operators -l openstack.org/operator-name=dataplane + name: dataplane-operator-logs +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneNodeSet +metadata: + name: edpm-no-nodes-custom-svc +spec: + preProvisioned: true + services: + - custom-img-svc + nodes: {} + nodeTemplate: + ansible: + ansibleUser: cloud-admin + ansiblePort: 22 + ansibleSSHPrivateKeySecret: dataplane-ansible-ssh-private-key-secret +status: + observedGeneration: 1 + conditions: + - message: Deployment in progress + reason: Requested + severity: Info + status: "False" + type: Ready + - message: Deployment in progress + reason: Requested + severity: Info + status: "False" + type: DeploymentReady + - message: Input data complete + reason: Ready + status: "True" + type: InputReady + - message: ServiceAccount created + reason: Ready + status: "True" + type: ServiceAccountReady + - message: Setup complete + reason: Ready + status: "True" + type: SetupReady +--- +apiVersion: ansibleee.openstack.org/v1beta1 +kind: OpenStackAnsibleEE +metadata: + name: custom-img-svc-edpm-compute-no-nodes-edpm-no-nodes-custom-svc + namespace: openstack-kuttl-tests + ownerReferences: + - apiVersion: dataplane.openstack.org/v1beta1 + blockOwnerDeletion: true + controller: true + kind: OpenStackDataPlaneDeployment + name: edpm-compute-no-nodes +spec: + backoffLimit: 6 + extraMounts: + - mounts: + - mountPath: /runner/env/ssh_key + name: ssh-key + subPath: ssh_key + - mountPath: /runner/inventory/hosts + name: inventory + subPath: inventory + volumes: + - name: ssh-key + secret: + items: + - key: ssh-privatekey + path: ssh_key + secretName: dataplane-ansible-ssh-private-key-secret + - name: inventory + secret: + items: + - key: inventory + path: inventory + secretName: dataplanenodeset-edpm-no-nodes-custom-svc + image: example.com/repo/runner-image:latest + name: openstackansibleee + restartPolicy: Never + uid: 1001 +status: + JobStatus: Running + conditions: + - message: AnsibleExecutionJob is running + reason: Requested + severity: Info + status: "False" + type: Ready + - message: AnsibleExecutionJob is running + reason: Requested + severity: Info + status: "False" + type: AnsibleExecutionJobReady diff --git a/tests/kuttl/tests/dataplane-service-custom-image/00-dataplane-create.yaml b/tests/kuttl/tests/dataplane-service-custom-image/00-dataplane-create.yaml new file mode 100644 index 000000000..358f0c4c9 --- /dev/null +++ b/tests/kuttl/tests/dataplane-service-custom-image/00-dataplane-create.yaml @@ -0,0 +1,37 @@ +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneService +metadata: + name: custom-img-svc +spec: + openStackAnsibleEERunnerImage: example.com/repo/runner-image:latest + role: + name: "test role" + hosts: "all" + strategy: "linear" + tasks: + - name: "test task" + import_role: + name: "test role" +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneNodeSet +metadata: + name: edpm-no-nodes-custom-svc +spec: + preProvisioned: true + services: + - custom-img-svc + nodes: {} + nodeTemplate: + ansible: + ansibleUser: cloud-admin + ansiblePort: 22 + ansibleSSHPrivateKeySecret: dataplane-ansible-ssh-private-key-secret +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneDeployment +metadata: + name: edpm-compute-no-nodes +spec: + nodeSets: + - edpm-no-nodes-custom-svc diff --git a/tests/kuttl/tests/dataplane-with-ipam-create-test/00-assert.yaml b/tests/kuttl/tests/dataplane-with-ipam-create-test/00-assert.yaml new file mode 100644 index 000000000..610aa5da5 --- /dev/null +++ b/tests/kuttl/tests/dataplane-with-ipam-create-test/00-assert.yaml @@ -0,0 +1,202 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 600 +collectors: +- type: command + command: oc logs -n openstack-operators -l openstack.org/operator-name=dataplane + name: dataplane-operator-logs +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneNodeSet +metadata: + name: openstack-edpm-ipam +spec: + env: + - name: ANSIBLE_FORCE_COLOR + value: "True" + nodes: + edpm-compute-0: + hostName: edpm-compute-0 + networks: + - name: ctlplane + subnetName: subnet1 + defaultRoute: true + fixedIP: 192.168.122.100 + - name: internalapi + subnetName: subnet1 + - name: storage + subnetName: subnet1 + - name: tenant + subnetName: subnet1 + + nodeTemplate: + ansible: + ansiblePort: 22 + ansibleUser: cloud-admin + ansibleVarsFrom: + - prefix: edpm_ + configMapRef: + name: network-config-template + ansibleVars: + timesync_ntp_servers: + - hostname: clock.redhat.com + edpm_network_config_hide_sensitive_logs: false + edpm_nodes_validation_validate_controllers_icmp: false + edpm_nodes_validation_validate_gateway_icmp: false + edpm_selinux_mode: enforcing + edpm_sshd_allowed_ranges: + - 192.168.122.0/24 + edpm_sshd_configure_firewall: true + enable_debug: false + ansibleSSHPrivateKeySecret: dataplane-ansible-ssh-private-key-secret + preProvisioned: true + services: + - download-cache + - bootstrap + - configure-network + - validate-network + - install-os + - configure-os + - run-os + - install-certs + - ovn + - neutron-metadata + - libvirt + - nova +status: + observedGeneration: 1 + allHostnames: + edpm-compute-0: + ctlplane: edpm-compute-0.ctlplane.example.com + internalapi: edpm-compute-0.internalapi.example.com + storage: edpm-compute-0.storage.example.com + tenant: edpm-compute-0.tenant.example.com + allIPs: + edpm-compute-0: + ctlplane: 192.168.122.100 + internalapi: 172.17.0.100 + storage: 172.18.0.100 + tenant: 172.19.0.100 + ctlplaneSearchDomain: ctlplane.example.com + conditions: + - message: Deployment not started + reason: Requested + status: "False" + type: Ready + - message: Deployment not started + reason: Requested + status: "False" + type: DeploymentReady + - message: Input data complete + reason: Ready + status: "True" + type: InputReady + - message: NodeSetDNSDataReady ready + reason: Ready + status: "True" + type: NodeSetDNSDataReady + - message: NodeSetIPReservationReady ready + reason: Ready + status: "True" + type: NodeSetIPReservationReady + - message: ServiceAccount created + reason: Ready + status: "True" + type: ServiceAccountReady + - message: Setup complete + reason: Ready + status: "True" + type: SetupReady +--- +apiVersion: network.openstack.org/v1beta1 +kind: IPSet +metadata: + name: edpm-compute-0 +spec: + immutable: false + networks: + - defaultRoute: true + name: ctlplane + subnetName: subnet1 + - name: internalapi + subnetName: subnet1 + - name: storage + subnetName: subnet1 + - name: tenant + subnetName: subnet1 +status: + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: Input data complete + reason: Ready + status: "True" + type: InputReady + - message: Reservation successful + reason: Ready + status: "True" + type: ReservationReady + reservations: + - address: 192.168.122.100 + cidr: 192.168.122.0/24 + dnsDomain: ctlplane.example.com + gateway: 192.168.122.1 + mtu: 1500 + network: ctlplane + routes: + - destination: 0.0.0.0/0 + nexthop: 192.168.122.1 + subnet: subnet1 + - address: 172.17.0.100 + cidr: 172.17.0.0/24 + dnsDomain: internalapi.example.com + mtu: 1500 + network: internalapi + subnet: subnet1 + vlan: 20 + - address: 172.18.0.100 + cidr: 172.18.0.0/24 + dnsDomain: storage.example.com + mtu: 1500 + network: storage + subnet: subnet1 + vlan: 21 + - address: 172.19.0.100 + cidr: 172.19.0.0/24 + dnsDomain: tenant.example.com + mtu: 1500 + network: tenant + subnet: subnet1 + vlan: 22 +--- +apiVersion: network.openstack.org/v1beta1 +kind: DNSData +metadata: + name: openstack-edpm-ipam +spec: + dnsDataLabelSelectorValue: dnsdata + hosts: + - hostnames: + - edpm-compute-0.ctlplane.example.com + ip: 192.168.122.100 + - hostnames: + - edpm-compute-0.internalapi.example.com + ip: 172.17.0.100 + - hostnames: + - edpm-compute-0.storage.example.com + ip: 172.18.0.100 + - hostnames: + - edpm-compute-0.tenant.example.com + ip: 172.19.0.100 +status: + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: Input data complete + reason: Ready + status: "True" + type: ServiceConfigReady diff --git a/tests/kuttl/tests/dataplane-with-ipam-create-test/00-dataplane-create.yaml b/tests/kuttl/tests/dataplane-with-ipam-create-test/00-dataplane-create.yaml new file mode 100644 index 000000000..80d3d9cf2 --- /dev/null +++ b/tests/kuttl/tests/dataplane-with-ipam-create-test/00-dataplane-create.yaml @@ -0,0 +1,112 @@ +apiVersion: network.openstack.org/v1beta1 +kind: DNSMasq +metadata: + name: dnsmasq +spec: + replicas: 1 + options: + - key: server + values: + - 192.168.122.1 + debug: + service: false +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: network-config-template +data: + network_config_template: | + --- + {% set mtu_list = [ctlplane_mtu] %} + {% for network in nodeset_networks %} + {{ mtu_list.append(lookup('vars', networks_lower[network] ~ '_mtu')) }} + {%- endfor %} + {% set min_viable_mtu = mtu_list | max %} + network_config: + - type: ovs_bridge + name: {{ neutron_physical_bridge_name }} + mtu: {{ min_viable_mtu }} + use_dhcp: false + dns_servers: {{ ctlplane_dns_nameservers }} + domain: {{ dns_search_domains }} + addresses: + - ip_netmask: {{ ctlplane_ip }}/{{ ctlplane_cidr }} + routes: {{ ctlplane_host_routes }} + members: + - type: interface + name: nic1 + mtu: {{ min_viable_mtu }} + # force the MAC address of the bridge to this interface + primary: true + {% for network in nodeset_networks %} + - type: vlan + mtu: {{ lookup('vars', networks_lower[network] ~ '_mtu') }} + vlan_id: {{ lookup('vars', networks_lower[network] ~ '_vlan_id') }} + addresses: + - ip_netmask: + {{ lookup('vars', networks_lower[network] ~ '_ip') }}/{{ lookup('vars', networks_lower[network] ~ '_cidr') }} + routes: {{ lookup('vars', networks_lower[network] ~ '_host_routes') }} + {% endfor %} +--- +apiVersion: dataplane.openstack.org/v1beta1 +kind: OpenStackDataPlaneNodeSet +metadata: + name: openstack-edpm-ipam +spec: + env: + - name: ANSIBLE_FORCE_COLOR + value: "True" + services: + - download-cache + - bootstrap + - configure-network + - validate-network + - install-os + - configure-os + - run-os + - install-certs + - ovn + - neutron-metadata + - libvirt + - nova + preProvisioned: true + nodes: + edpm-compute-0: + hostName: edpm-compute-0 + networks: + - name: ctlplane + subnetName: subnet1 + defaultRoute: true + fixedIP: 192.168.122.100 + - name: internalapi + subnetName: subnet1 + - name: storage + subnetName: subnet1 + - name: tenant + subnetName: subnet1 + nodeTemplate: + ansibleSSHPrivateKeySecret: dataplane-ansible-ssh-private-key-secret + ansible: + ansibleUser: cloud-admin + ansiblePort: 22 + ansibleVarsFrom: + - prefix: edpm_ + configMapRef: + name: network-config-template + ansibleVars: + timesync_ntp_servers: + - hostname: clock.redhat.com + # edpm_network_config + # Default nic config template for a EDPM compute node + # These vars are edpm_network_config role vars + edpm_network_config_hide_sensitive_logs: false + edpm_nodes_validation_validate_controllers_icmp: false + edpm_nodes_validation_validate_gateway_icmp: false + gather_facts: false + enable_debug: false + # edpm firewall, change the allowed CIDR if needed + edpm_sshd_configure_firewall: true + edpm_sshd_allowed_ranges: ['192.168.122.0/24'] + # SELinux module + edpm_selinux_mode: enforcing