diff --git a/.github/workflows/build_unittest.yml b/.github/workflows/build_unittest.yml new file mode 100644 index 0000000..8a47a82 --- /dev/null +++ b/.github/workflows/build_unittest.yml @@ -0,0 +1,43 @@ +#(C) Copyright [2020] Hewlett Packard Enterprise Development LP +# +#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. +name: build_unittest +on: + pull_request: + branches: + - '**' + workflow_dispatch: +jobs: + build: + - name: Set up Go 1.19.5 + uses: actions/setup-go@v2 + with: + go-version: 1.19.5 + id: go + - name: Test + run: | + go test ./... + if [ $? -eq 0 ]; then + echo Unit Testing is Successful !!!! + else + echo Unit Testing Failed !!!! + fi + - name: Build + run: | + cd main + go build + if [ $? -eq 0 ]; then + echo Build is Successful !!!! + else + echo Build is Failing !!!! + fi \ No newline at end of file diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..a71c8d8 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,74 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ "development" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "development" ] + schedule: + - cron: '26 15 * * 1' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'go' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + + # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{matrix.language}}" \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..48b8bf9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +vendor/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6756b0b --- /dev/null +++ b/Makefile @@ -0,0 +1,265 @@ +# (C) Copyright [2023] Hewlett Packard Enterprise Development LP +# +# 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. + + +# VERSION defines the project version for the bundle. +# Update this value when you upgrade the version of your project. +# To re-generate a bundle for another specific version without changing the standard setup, you can: +# - use the VERSION as arg of the bundle target (e.g make bundle VERSION=0.0.2) +# - use environment variables to overwrite this value (e.g export VERSION=0.0.2) +VERSION ?= 1.0.0 + +# CHANNELS define the bundle channels used in the bundle. +# Add a new line here if you would like to change its default config. (E.g CHANNELS = "candidate,fast,stable") +# To re-generate a bundle for other specific channels without changing the standard setup, you can: +# - use the CHANNELS as arg of the bundle target (e.g make bundle CHANNELS=candidate,fast,stable) +# - use environment variables to overwrite this value (e.g export CHANNELS="candidate,fast,stable") +ifneq ($(origin CHANNELS), undefined) +BUNDLE_CHANNELS := --channels=$(CHANNELS) +endif + +# DEFAULT_CHANNEL defines the default channel used in the bundle. +# Add a new line here if you would like to change its default config. (E.g DEFAULT_CHANNEL = "stable") +# To re-generate a bundle for any other default channel without changing the default setup, you can: +# - use the DEFAULT_CHANNEL as arg of the bundle target (e.g make bundle DEFAULT_CHANNEL=stable) +# - use environment variables to overwrite this value (e.g export DEFAULT_CHANNEL="stable") +ifneq ($(origin DEFAULT_CHANNEL), undefined) +BUNDLE_DEFAULT_CHANNEL := --default-channel=$(DEFAULT_CHANNEL) +endif +BUNDLE_METADATA_OPTS ?= $(BUNDLE_CHANNELS) $(BUNDLE_DEFAULT_CHANNEL) + +# IMAGE_TAG_BASE defines the docker.io namespace and part of the image name for remote images. +# This variable is used to construct full image tags for bundle and catalog images. +# +# For example, running 'make bundle-build bundle-push catalog-build catalog-push' will build and push both +# odimra/bmc-operator-bundle:$VERSION and odimra/bmc-operator-catalog:$VERSION. +IMAGE_TAG_BASE_bmc ?= odimra/bmc-operator +# For example, running 'make bundle-build bundle-push catalog-build catalog-push' will build and push both + +# BUNDLE_IMG defines the image:tag used for the bundle. +# You can use it as an arg. (E.g make bundle-build BUNDLE_IMG=/:) +BUNDLE_IMG1 ?= $(IMAGE_TAG_BASE_bmc)-bundle:v$(VERSION) + +# BUNDLE_GEN_FLAGS are the flags passed to the operator-sdk generate bundle command +BUNDLE_GEN_FLAGS ?= -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS) + +# USE_IMAGE_DIGESTS defines if images are resolved via tags or digests +# You can enable this value if you would like to use SHA Based Digests +# To enable set flag to true +USE_IMAGE_DIGESTS ?= false +ifeq ($(USE_IMAGE_DIGESTS), true) + BUNDLE_GEN_FLAGS += --use-image-digests +endif + +# Image URL to use all building/pushing image targets +IMG1 ?= $(IMAGE_TAG_BASE_bmc):$(VERSION) +# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. +ENVTEST_K8S_VERSION = 1.23 + +# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) +ifeq (,$(shell go env GOBIN)) +GOBIN=$(shell go env GOPATH)/bin +else +GOBIN=$(shell go env GOBIN) +endif + +# Setting SHELL to bash allows bash commands to be executed by recipes. +# This is a requirement for 'setup-envtest.sh' in the test target. +# Options are set to exit when a recipe line exits non-zero or a piped command fails. +SHELL = /usr/bin/env bash -o pipefail +.SHELLFLAGS = -ec + +.PHONY: all +all: build + +##@ General + +# The help target prints out all targets with their descriptions organized +# beneath their categories. The categories are represented by '##@' and the +# target descriptions by '##'. The awk commands is responsible for reading the +# entire set of makefiles included in this invocation, looking for lines of the +# file as xyz: ## something, and then pretty-format the target and help. Then, +# if there's a line with ##@ something, that gets pretty-printed as a category. +# More info on the usage of ANSI control characters for terminal formatting: +# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters +# More info on the awk command: +# http://linuxcommand.org/lc3_adv_awk.php + +.PHONY: help +help: ## Display this help. + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + +##@ Development + +.PHONY: manifests +manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. + $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases + +.PHONY: generate +generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. + $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." + +.PHONY: fmt +fmt: ## Run go fmt against code. + go fmt ./... + +.PHONY: vet +vet: ## Run go vet against code. + go vet ./... + +.PHONY: test +test: manifests generate fmt vet envtest ## Run tests. + KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" go test ./... -coverprofile cover.out + +##@ Build + +.PHONY: build +build: generate fmt vet ## Build manager binary. + go build -o bin/manager ./rulebasedaggregate_main/main.go + +.PHONY: run +run: manifests generate fmt vet ## Run a controller from your host. + go run ./main/main.go + +.PHONY: docker-build +docker-build: test ## Build docker image with the manager. + docker build -t ${IMG1} . + docker build -t ${IMG2} . + +.PHONY: docker-push +docker-push: ## Push docker image with the manager. + docker push ${IMG1} + docker push ${IMG2} + +##@ Deployment + +ifndef ignore-not-found + ignore-not-found = false +endif + +.PHONY: install +install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. + $(KUSTOMIZE) build config/crd | kubectl apply -f - + +.PHONY: uninstall +uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. + $(KUSTOMIZE) build config/crd | kubectl delete --ignore-not-found=$(ignore-not-found) -f - + +.PHONY: deploy +deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. + cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG1} + $(KUSTOMIZE) build config/default | kubectl apply -f - + +.PHONY: undeploy +undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. + $(KUSTOMIZE) build config/default | kubectl delete --ignore-not-found=$(ignore-not-found) -f - + +CONTROLLER_GEN = $(shell pwd)/bin/controller-gen +.PHONY: controller-gen +controller-gen: ## Download controller-gen locally if necessary. + $(call go-get-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.8.0) + +KUSTOMIZE = $(shell pwd)/bin/kustomize +.PHONY: kustomize +kustomize: ## Download kustomize locally if necessary. + $(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v3@v3.8.7) + +ENVTEST = $(shell pwd)/bin/setup-envtest +.PHONY: envtest +envtest: ## Download envtest-setup locally if necessary. + $(call go-get-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest@latest) + +# go-get-tool will 'go get' any package $2 and install it to $1. +PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST)))) +define go-get-tool +@[ -f $(1) ] || { \ +set -e ;\ +TMP_DIR=$$(mktemp -d) ;\ +cd $$TMP_DIR ;\ +go mod init tmp ;\ +echo "Downloading $(2)" ;\ +GOBIN=$(PROJECT_DIR)/bin go get $(2) ;\ +rm -rf $$TMP_DIR ;\ +} +endef + +.PHONY: bundle +bundle: manifests kustomize ## Generate bundle manifests and metadata, then validate generated files. + operator-sdk generate kustomize manifests -q + cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG1) + $(KUSTOMIZE) build config/manifests | operator-sdk generate bundle $(BUNDLE_GEN_FLAGS) + operator-sdk bundle validate ./bundle + + operator-sdk generate kustomize manifests -q + cd config/manager_rba && $(KUSTOMIZE) edit set image controller=$(IMG2) + $(KUSTOMIZE) build config/manifests | operator-sdk generate bundle $(BUNDLE_GEN_FLAGS) + operator-sdk bundle validate ./bundle + +.PHONY: bundle-build +bundle-build: ## Build the bundle image. + docker build -f bundle.Dockerfile -t $(BUNDLE_IMG1) . + docker build -f bundle.Dockerfile -t $(BUNDLE_IMG2) . + +.PHONY: bundle-push +bundle-push: ## Push the bundle image. + $(MAKE) docker-push IMG1=$(BUNDLE_IMG1) + $(MAKE) docker-push IMG2=$(BUNDLE_IMG2) + +.PHONY: opm +OPM = ./bin/opm +opm: ## Download opm locally if necessary. +ifeq (,$(wildcard $(OPM))) +ifeq (,$(shell which opm 2>/dev/null)) + @{ \ + set -e ;\ + mkdir -p $(dir $(OPM)) ;\ + OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \ + curl -sSLo $(OPM) https://github.com/operator-framework/operator-registry/releases/download/v1.19.1/$${OS}-$${ARCH}-opm ;\ + chmod +x $(OPM) ;\ + } +else +OPM = $(shell which opm) +endif +endif + +# A comma-separated list of bundle images (e.g. make catalog-build BUNDLE_IMGS=example.com/operator-bundle:v0.1.0,example.com/operator-bundle:v0.2.0). +# These images MUST exist in a registry and be pull-able. +BUNDLE_IMGS1 ?= $(BUNDLE_IMG1) +BUNDLE_IMGS2 ?= $(BUNDLE_IMG2) + +# The image tag given to the resulting catalog image (e.g. make catalog-build CATALOG_IMG=example.com/operator-catalog:v0.2.0). +CATALOG_IMG1 ?= $(IMAGE_TAG_BASE_bmc)-catalog:v$(VERSION) +CATALOG_IMG2 ?= $(IMAGE_TAG_BASE_RBA)-catalog:v$(VERSION) + +# Set CATALOG_BASE_IMG to an existing catalog image tag to add $BUNDLE_IMGS to that image. +ifneq ($(origin CATALOG_BASE_IMG1), undefined) +FROM_INDEX_OPT1 := --from-index $(CATALOG_BASE_IMG1) +endif +ifneq ($(origin CATALOG_BASE_IMG2), undefined) +FROM_INDEX_OPT2 := --from-index $(CATALOG_BASE_IMG2) +endif + +# Build a catalog image by adding bundle images to an empty catalog using the operator package manager tool, 'opm'. +# This recipe invokes 'opm' in 'semver' bundle add mode. For more information on add modes, see: +# https://github.com/operator-framework/community-operators/blob/7f1438c/docs/packaging-operator.md#updating-your-existing-operator +.PHONY: catalog-build +catalog-build: opm ## Build a catalog image. + $(OPM) index add --container-tool docker --mode semver --tag $(CATALOG_IMG1) --bundles $(BUNDLE_IMGS1) $(FROM_INDEX_OPT1) + $(OPM) index add --container-tool docker --mode semver --tag $(CATALOG_IMG2) --bundles $(BUNDLE_IMGS2) $(FROM_INDEX_OPT2) + +# Push the catalog image. +.PHONY: catalog-push +catalog-push: ## Push a catalog image. + $(MAKE) docker-push IMG=$(CATALOG_IMG1) + $(MAKE) docker-push IMG=$(CATALOG_IMG2) diff --git a/PROJECT b/PROJECT new file mode 100644 index 0000000..9a6f15b --- /dev/null +++ b/PROJECT @@ -0,0 +1,50 @@ +# Code generated by tool. DO NOT EDIT. +# This file is used to track the info used to scaffold your project +# and allow the plugins properly work. +# More info: https://book.kubebuilder.io/reference/project-config.html +domain: odimra +layout: +- go.kubebuilder.io/v3 +plugins: + manifests.sdk.operatorframework.io/v2: {} + scorecard.sdk.operatorframework.io/v2: {} +projectName: bmc-operator +repo: github.com/ODIM-Project/BMCOperator +resources: +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: odimra + group: infra.io + kind: Odim + path: github.com/ODIM-Project/BMCOperator/api/v1 + version: v1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: odimra + group: infra.io + kind: RuleBasedAggregate + path: github.com/ODIM-Project/BMCOperator/api/v2 + version: v2 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: odimra + group: infra.io + kind: Eventsubscription + path: github.com/ODIM-Project/BMCOperator/api/v1 + version: v1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: odimra + group: infra.io + kind: EventsMessageRegistry + path: github.com/ODIM-Project/BMCOperator/api/v1 + version: v1 +version: "3" diff --git a/README.md b/README.md index 6a13f18..2962911 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,3000 @@ -# BMCOperator- -Repository to host the BMC operator of the ODIM project + + + + +# Table of contents + +[BMC Operator for Resource Aggregator for ODIM](#BMC-operator-for-Resource-Aggregator-for-ODIM) + +- [BMC Operator architecture](#BMC-Operator-architecture) +- [Compatibility matrix](#Compatibility-matrix) +- [Related documentation](#Related-documentation) + +[Pre-deployment operations](#Pre-deployment-operations) + +[Deploying BMC Operator for ODIM](#Deploying-BMC-Operator-for-ODIM) + +- [BMC Operator deployment configuration file](#BMC-operator-deployment-configuration-file) +- [Deployment configuration parameters](#Deployment-configuration-parameters) +- [Creating ODIM object](#Creating-ODIM-object) + +[Role-based access control](#Role-based-access-control) + +- [BMC Operator roles](#BMC-Operator-roles) + - [Administrator](#Administrator) + - [Operator](#Operator) + - [User with read-only access](#User-with-read-only-access) +- [Linking user or service account to roles](#Linking-user-or-service-account-to-roles) + +[BMC operations](#bmc-operations) + +- [Adding a BMC](#adding-a-bmc) +- [Updating a BMC password](#Updating-a-BMC-password) +- [Resetting a BMC](#resetting-a-bmc) +- [Scenarios for powerState and resetType combinations](#Scenarios-for-powerState-and-resetType-combinations) +- [Deleting a BMC](#deleting-a-bmc) + +[Applying BIOS settings on BMC](#Applying-BIOS-settings-on-BMC) + +[Applying boot order settings on BMC](#Applying-boot-order-settings-on-BMC) + +[Volume operations](#volume-operations) + +- [Adding a volume](#adding-a-volume) +- [Deleting a volume](#deleting-a-volume) + +[Firmware operations](#firmware-operations) + +- [Upgrading or downgrading firmware](#Upgrading-or-downgrading-firmware) +- [Verifying the firmware operation](#Verifying-the-firmware-operation) +- [Editing firmware](#Editing-firmware) +- [Deleting the firmware file](#Deleting-the-firmware-file) + +[Creating an event subscription](#creating-an-event-subscription) + +- [Validation of message IDs](#Validation-of-message-IDs) +- [Deleting an event subscription](#Deleting-an-event-subscription) + +[Reconciliation](#reconciliation) + +- [Reconciliation methods](#Reconciliation-methods) +- [Reconciliation operations](#Reconciliation-operations) + - [BMC reconciliation use cases](#BMC-reconciliation-use-cases) + - [BIOS reconciliation use cases](#BIOS-reconciliation-use-cases) + - [Volume reconciliation use cases](#Volume-reconciliation-use-cases) + - [Firmware reconciliation use cases](#firmware-reconciliation-use-cases) + +[Using APIs in BMC Operator](#Using-APIs-in-BMC-Operator) + +- [Prerequisites](#Prerequisites) +- [Sending API requests](#Sending-API-requests) + - [Examples of API requests](#examples-of-API-requests) + +[Uninstalling BMC Operator](#Uninstalling-BMC-operator) + +[Contributing to the open source community](#contributing-to-the-open-source-community) + +- [Creating a PR](#creating-a-pr) +- [Filing BMC Operator defects](#filing-bmc-operator-defects) +- [Licensing](#licensing) +- [Reference links](#reference-links) + +[Sample configuration files](#Sample-configuration-files) + +- [Sample BMC settings file](#sample-bmc-settings-file) +- [Sample BIOS settings file](#sample-bios-settings-file) +- [Sample boot order settings file](#sample-boot-order-settings-file) +- [Sample volume file](#sample-volume-file) +- [Sample firmware file](#Sample-firmware-file) + +[Sample output properties](#Sample-output-properties) + +- [BMC addition output](#BMC-addition-output) +- [Boot order settings output](#Boot-order-settings-output) +- [Volume addition output](#Volume-addition-output) +- [Firmware operation output](#Firmware-operation-output) +- [Event subscription output](#Event-subscription-output) + +[Troubleshooting BMC Operator issues](#Troubleshooting-BMC-operator-issues) + + + + +# BMC Operator for Resource Aggregator for ODIM + +Kubernetes operators introduce new object types through custom resource definitions (CRDs) that lists out all configurations available to the users. Users running workloads on Kubernetes often like to use automation to manage repeated tasks. They can use the custom resources (CRs) defined by the CRDs to define high-level configurations for the applications and resources. + +BMC Operator is the software extension of Kubernetes that uses CRs to manage various BMC operations that include the deployment and management of workloads of BIOS, boot order, volume, firmware, and event subscription operations. + +This document covers detailed, step-by-step instructions on the deployment procedures of BMC Operator on a deployment node. It also provides instructions on managing the supported resources. + +## BMC Operator architecture + +Here is the Resource Aggregator for ODIM architecture diagram with BMC operator. + +Clusternode + +For one-node deployment, the northbound management and orchestration systems access the BMC Operator through the controller IP address/hostname. BMC Operator further communicates with Resource Aggregator for ODIM through the same controller IP address/hostname. + +For three-node deployment, the northbound management and orchestration systems access the BMC Operator through the virtual IP address/hostname. BMC Operator further communicates with Resource Aggregator for ODIM through the same controller IP address/hostname. + +> **NOTE**: For information on Resource Aggregator for ODIM communications, see [*Resource Aggregator for Open Distributed Infrastructure Management™ Getting Started Readme*](https://github.com/ODIM-Project/ODIM/tree/development#readme). + +## Compatibility matrix + +The following table lists the software and the hardware components and their versions that are compatible with BMC Operator. Software is used to deploy the BMC Operator. Hardware refers to the devices on which the functions of the BMC Operator are tested. + +| Software | Version | +| ---------------------------- | -------- | +| Resource Aggregator for ODIM | 7.0.0 | +| Ubuntu LTS | 22.04.2 | +| Kubernetes | 1.24.6 | +| containerd | 1.6.8 | +| Docker | 20.10.12 | +| kubectl | 1.17.3 | +| Golang | 1.19.5 | + +| Hardware | Version | +| ------------------- | --------------- | +| HPE ProLiant server | HPE iLO 5 v2.78 | +| HPE ProLiant server | HPE iLO 6 v1.30 | + + + +## Related documentation + +This document is shipped with the following comprehensive set of Resource Aggregator for ODIM electronic documentation: + +- [*Resource Aggregator for Open Distributed Infrastructure Management™ API Readme*](https://github.com/ODIM-Project/ODIM/blob/development/docs/README.md)—This document covers usage information for Resource Aggregator for ODIM including reference information for the northbound REST APIs that the management and orchestration systems use to communicate with southbound infrastructure elements through their respective plugins. +- [*Resource Aggregator for Open Distributed Infrastructure Management™ Getting Started Readme*](https://github.com/ODIM-Project/ODIM/tree/development#readme)—This document covers detailed deployment instructions of Resource Aggregator for ODIM and the plugins. You can also find information on post-deployment instructions and various use cases of Resource Aggregator for ODIM. +- [*Resource Aggregator for Open Distributed Infrastructure Management™ Troubleshooting Readme*](https://github.com/ODIM-Project/ODIM/blob/development/docs/Troubleshooting.md)—This document covers information on any common issues you might experience while deploying or using Resource Aggregator for ODIM and their possible workarounds. You can also find answers to some of the Frequently Asked Questions related to Resource Aggregator for ODIM. + + + +# Pre-deployment operations + +> **PREREQUISITES**: +> +> Install kubectl on deployment node. +> See *https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/#install-kubectl-binary-with-curl-on-linux*. +> +> Link the deployment node to the Kubernetes cluster node using the following instructions: + +1. Run the following command on the deployment node: + + ``` + mkdir -p $HOME/.kube + ``` + +2. Copy `/etc/kubernetes/admin.conf` file from the cluster node to `$HOME/.kube` on deployment node. + + ``` + scp /etc/kubernetes/admin.conf bruce@{deploy_vm_ip}:/home/bruce/.kube + ``` + +3. Run the following command on the deployment node: + + ``` + mv $HOME/.kube/admin.conf $HOME/.kube/config + ``` + +4. Change the localhost IP address in the `config` file (check for `clusters`.`cluster`.`server` parameter) on the deployment node to that of the cluster node IP address, where Kubernetes cluster node is up. + +5. Run the following command on the deployment node: + + ``` + sudo chown $(id -u):$(id -g) $HOME/.kube/config + ``` + + + +# Deploying BMC Operator for ODIM + +1. Clone the project by running the following command: + + ``` + git clone https://github.hpe.com/Bruce/bmc-operator.git + ``` + +2. Switch to the project branch. + +3. Navigate to `/home/{user}/bmc-operator/bin` and change the permission of binaries to make it executable: + + ``` + chmod 775 * + ``` + +4. Deploy the BMC Operator for ODIM on the deployment node: + + + 1. Navigate to BMC operator directory on the deployment node: + + ``` + cd /home/{user}/bmc-operator + ``` + + 2. Build the Docker image of BMC operator: + + ``` + docker build -f dockerfiles/Dockerfile.bmcoperator -t odimra/bmc-operator:1.0.0 . + ``` + + > **NOTE**: Use the same image name. + + 3. Save the Docker image: + + ``` + docker save -o {tar_filename}.tar odimra/bmc-operator:1.0.0 + ``` + + 4. Copy this image to a desired location on the cluster node(s). + + *In this instance, we save the image to `/home/{user}` on cluster nodes.* + + *`xxx.xxx.xxx.xxx` is the IP address of each cluster.* + + ``` + scp {tar_filename}.tar {username}@{xxx.xxx.xxx.xxx}:/home/{user} + ``` + + 5. Log in to the cluster nodes and load the image on all cluster nodes in the home directory (three-node deployment): + + ``` + cd ssh {user}@{xxx.xxx.xxx.xxx} + ``` + + ``` + sudo ctr -n=k8s.io images import {tar_filename}.tar + ``` + + 6. On the deployment node, deploy the BMC operator: + + ``` + make deploy + ``` + + 7. Update the configuration parameters in the *[BMC Operator deployment configuration file](#BMC-operator-deployment-configuration-file)* and apply them. + + ``` + kubectl apply -f config/keys/config.yaml + ``` + + 8. Copy `rootCA.crt` and `rootCA.key` from `home/bruce/ODIM/odim-controller/scripts/certs/{deployment_name}/` to `/home/{user}/bmc-operator/bmc-templates/keys/`. + + 9. Run the following command to give permissions to generate keys and certificates: + + ``` + chmod 775 * + ``` + + 10. Download yq package for keys generation. See the instructions in the link *https://www.cyberithub.com/how-to-install-yq-command-line-tool-on-linux-in-5-easy-steps/#Step_3_Install_yq*. + + 11. Run the `gen_cert.sh` script to generate the required keys and signed certificates with rootCA. + + ``` + cd /home/{user}/bmc-operator/bmc-templates/keys + ``` + + ``` + ./gen_certs.sh {cluster_vm_ip} + ``` + + 12. Apply the secret: + ``` + cd /home/{user}/bmc-operator/bmc-templates/keys + ``` + ``` + kubectl apply -f secret.yaml + ``` + + 13. Navigate to BMC operator directory on the deployment node: + + ``` + cd /home/{user}/bmc-operator + ``` + + 14. Apply the Persistent Volume and Persistent Volume Claim: + + ``` + kubectl apply -f bmc-templates/pvs/bmc_pv.yaml + ``` + + ``` + kubectl apply -f bmc-templates/pvs/bmc_pvc.yaml + ``` + + 15. On each cluster node, create `operator_logs` directory in `/var/log` and give ownership to odimra user: + + ``` + sudo mkdir /var/log/operator_logs + ``` + + ``` + sudo chown odimra:odimra /var/log/operator_logs + ``` + + 16. Check if operator pods are up and running: + + ``` + kubectl get pods -n{namespace} + ``` + + The following sample output appears: + ``` + NAME READY STATUS RESTARTS AGE + bmc-opcontroller-manager-5f54c64cd7-qkqdz 2/2 Running 0 17h + ``` + + + +## BMC Operator deployment configuration file + +> **PREREQUISITE**: Resource Aggregator for ODIM must be deployed and running on your system. See the [*Resource Aggregator for Open Distributed Infrastructure Management™ Getting Started Readme*](https://github.com/ODIM-Project/ODIM/tree/development#readme) for instructions on deploying Resource Aggregator for ODIM. + +Before deploying the BMC Operator, update the deployment configuration file `config.yaml` available in the `config/keys` repository. It contains all the configuration information required to deploy the BMC Operator. + +``` +apiVersion: v1 +kind: ConfigMap +metadata: + name: {bmc-operator-config} + namespace: {bmc_namespace} + labels: + type: config +data: + config.yaml: | + reconciliation : Accommodate #Accommodate/Revert + eventSubReconciliation: Accommodate #Accommodate/Revert + reconcileInterval: "24" #Time in `Hours` (in string) + secretName: bmc-secret + metricsBindPort: "8080" # cannot change at runtime (in string) + healthProbeBindPort: "8081" # cannot change at runtime (in string) + eventClientPort: "45000" # cannot change at runtime (in string) + logLevel: warn + logFormat: syslog + kubeConfigPath: # provide kube config file path as value when running with 'make install run' command + namespace: {bmc_namespace} + operatorEventSubscriptionEventTypes: + - ResourceAdded + - ResourceRemoved + - Alert + operatorEventSubsciptionMessageIds: + - ResourceEvent.1.2.0.ResourceRemoved + - ResourceEvent.1.2.0.ResourceAdded + - iLOEvents.3.2.ServerPostDiscoveryComplete + - iLOEvents.3.2.ServerPostComplete + operatorEventSubsciptionResourceTypes: + - ComputerSystems +``` + +> **NOTE**: We recommend you to have a regular backup of the latest deployment configuration file. + + + +## Deployment configuration parameters + +The following table lists and describes all the configuration parameters required to deploy BMC Operator. + +| Parameter | Description | +| ------------------------------------- | ------------------------------------------------------------ | +| kind | Kubernetes resource. Value is always `ConfigMap`. | +| name | Name of the `ConfigMap` Kubernetes resource. | +| namespace | Namespace in which the `ConfigMap` is applied. | +| labels:type | Kubernetes object label for the BMC Operator `ConfigMap`. | +| data:config.yaml | Contains all the following parameters for the BMC Operator. | +| reconciliation | Type of reconciliation operation to be performed. For more information, see *[Reconciliation operations](#Reconciliation-operations)*. | +| reconcileInterval | Time interval in hours for which polling triggers. | +| secretName | Secret name of the encryption keys for the BMC Operator. | +| metricsBindPort | Metrics port for BMC operator. | +| healthProbeBindPort | Health port for BMC operator. | +| eventClientPort | HTTP server port listening to Resource Aggregator for ODIM events for event-based reconciliation. | +| logLevel | Enables you to set filters for your log levels based on your requirement. For more information, see *Log Levels* in *Appendix* in [*Resource Aggregator for Open Distributed Infrastructure Management™ Getting Started Readme*](https://github.com/ODIM-Project/ODIM/tree/development#readme). | +| logFormat | Enables logging in syslog format or JSON format. | +| kubeConfigPath | Used for development process when BMC Operator runs with `make install` command. Keep this value empty when you deploy BMC Operator as a pod. | +| namespace | Namespace in which the BMC Operator is deployed. | +| operatorEventSubscriptionEventTypes | Array of event types required for BMC Operator to trigger reconciliation. Supported values are `ResourceAdded`, `ResourceRemoved` , and `Alert`. | +| operatorEventSubsciptionMessageIds | Array of message IDs required for BMC Operator to trigger reconciliation. Supported values are `ResourceEvent.1.2.0.ResourceRemoved`, `ResourceEvent.1.2.0.ResourceAdded`, `iLOEvents.3.2.ServerPostDiscoveryComplete`, and `iLOEvents.3.2.ServerPostComplete`. | +| operatorEventSubsciptionResourceTypes | Array of resource types required for BMC Operator to trigger reconciliation. Supported value is `ComputerSystems`. | + + + +## Creating ODIM object + +This procedure is mandatory for managing BMCs. + +1. Navigate to the `bmc-templates` directory. + + ``` + cd /home/{user}/bmc-operator/bmc-templates + ``` + +2. Open the `odim.yaml` file. + + ``` + vi odim.yaml + ``` + + The following content is displayed. + + ``` + apiVersion: v1 + kind: Secret + metadata: + name: {odimauth} + namespace: {bmc_namespace} + type: BasicAuth/RedfishSessionAuth + data: + username: {base64_encoded_username} + password: {encrypted_and_base64_encoded_password} + --- + apiVersion: infra.io.odimra/v1 + kind: Odim + metadata: + name: odim # do not change this value + namespace: {bmc_namespace} + annotations: + infra.io/auth: {odimauth} # same as metadata.name + spec: + URL: https://{ip}:{port} + EventListenerHost: https://{cluster_node_ip}:{port} #port: 32123 + ``` + + For encrypting and base64 encoding of the password use the following commands: + + ``` + cd /home/{user}/bmc-operator/bmc-templates/keys + ``` + + ``` + echo -n '' | openssl pkeyutl -encrypt -pubin -inkey bmc.public -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha512| openssl base64 -A + ``` + +3. Navigate to the `bmc-templates` directory. + + ``` + cd /home/{user}/bmc-operator/bmc-templates + ``` + +4. Update the following parameters in the `odim.yaml` file. + + | Option | Definition | + | ------------------------- | ------------------------------------------------------------ | + | (Secret) type | Defines the mode of authentication to register ODIM. Enter either of the two: BasicAuth/RedfishSessionAuth. | + | (Secret) username | The base64 encoded username of Resource Aggregator for ODIM. | + | (Secret) password | Encrypted and base64 encoded password of Resource Aggregator for ODIM. | + | annotations.infra.io/auth | This value must be the same as secret `name`. | + | ip | IP address of Resource Aggregator for ODIM. | + | port | Port of Resource Aggregator for ODIM. | + | EventListenerHost | Listens to the events received from Resource Aggregator for ODIM to the BMC Operator. | + +5. Create instance of the ODIM custom resource: + ``` + kubectl apply -f odim.yaml + ``` + +6. Check if BMC Operator is able to connect to Resource Aggregator for ODIM: + + ``` + kubectl get odim -n{namespace} + ``` + + Upon successful connection, you get the following output: + ``` + NAME URL STATUS AGE + odim https://xxx.xxx.xxx.xxx:30080 Connected 21d + ``` + + If the connection is unsuccessful, you get the following output: + ``` + NAME URL STATUS AGE + odim https://xxx.xxx.xxx.xxx:30080 Not Connected 21d + ``` + + > **NOTE**: Check logs in `/var/log/operator_logs/bmc_operator.log` file in cluster VM. + + + +# Role-based access control + +Role-based access control (RBAC) is a method of managing access to computer or network resources based on the roles of individual users within your organization. RBAC authorization uses the `rbac.authorization.k8s.io` API group to drive authorization decisions, allowing you to configure policies through the Kubernetes API. + +BMC Operator supports the following three roles as part of the RBAC: + +- **bmc-op-admin**—Administrator who has authorization to perform all operations through BMC Operator. To view the authorized operations for this role, see *[Administrator](#Administrator)*. +- **bmc-op-operator**—User who has authorization to perform certain operations through BMC Operator. To view the authorized operations for this role, see *[Operator](#Operator)*. +- **bmc-op-readonly**—User with read-only access to the BMC Operator functions. To view the authorized operations for this role, see *[User with read-only access](#User-with-read-only-access)*. + + + +## BMC Operator roles + +This section lists the resources and the authorized operations of each user role. + +### Administrator + +| Resource | Operations | +| ------------------------------ | --------------------------------------- | +| biosschemaregistries | create, get, list, patch, update, watch | +| biosschemaregistries/status | get, patch, update | +| biossettings | create, get, list, patch, update, watch | +| biossettings/status | get, patch, update | +| bmcs | create, get, list, patch, update, watch | +| bmcs/status | get, patch, update | +| bootordersettings | create, get, list, patch, update, watch | +| bootordersettings/status | get, patch, update | +| eventsubscriptions | create, get, list, patch, update, watch | +| eventsubscriptions/status | get, patch, update | +| firmwares | create, get, list, patch, update, watch | +| firmwares/status | get, patch, update | +| odims | create, get, list, patch, update, watch | +| odims/status | get, patch, update | +| volumes | create, get, list, patch, update, watch | +| volumes/status | get, patch, update | +| eventsmessageregistries | create, get, list, patch, update, watch | +| eventsmessageregistries/status | get, patch, update | + +### Operator + +| Resource | Operations | +| --------------------------- | --------------------------------------- | +| biosschemaregistries | create, get, list, patch, update, watch | +| biosschemaregistries/status | get, patch, update | +| biossettings | create, get, list, patch, update, watch | +| biossettings/status | get, patch, update | +| bmcs | create, get, list, patch, update, watch | +| bmcs/status | get, patch, update | +| bootordersettings | create, get, list, patch, update, watch | +| bootordersettings/status | get, patch, update | +| firmwares | create, get, list, patch, update, watch | +| firmwares/status | get, patch, update | +| odims | get, list, watch | +| odims/status | get, patch, update | +| volumes | create, get, list, patch, update, watch | +| volumes/status | get, patch, update | + +### User with read-only access + +| Resource | operation | +| ------------------------------ | ---------------- | +| biosschemaregistries | get, list, watch | +| biosschemaregistries/status | get, list, watch | +| biossettings | get, list, watch | +| biossettings/status | get, list, watch | +| bmcs | get, list, watch | +| bmcs/status | get, list, watch | +| bootordersettings | get, list, watch | +| bootordersettings/status | get, list, watch | +| eventsubscriptions | get, list, watch | +| eventsubscriptions/status | get, list, watch | +| firmwares | get, list, watch | +| firmwares/status | get, list, watch | +| odims | get, list, watch | +| odims/status | get, list, watch | +| volumes | get, list, watch | +| volumes/status | get, list, watch | +| eventsmessageregistries | get, list, watch | +| eventsmessageregistries/status | get, list, watch | + + + +## Linking user or service account to roles + +For accessing the BMC Operator with predefined roles, the cluster administrator needs to create role binding and link the user or service account to that role. + +**Example** + +``` +cat < **PREREQUISITES**: +> +> - Before adding BMC, ensure BMC Operator is able to connect to Resource Aggregator for ODIM. Also, generate a certificate for it using the root CA certificate of Resource Aggregator for ODIM. +> - To use your own root CA certificate to generate a certificate, you must first append it to the existing root CA certificate. For instructions, see *Appending CA certificates to the existing Root CA certificate* section in [*Resource Aggregator for Open Distributed Infrastructure Management™ Getting Started Readme*](https://github.com/ODIM-Project/ODIM/tree/development#readme). + +1. Navigate to the BMC Operator directory: + + ``` + cd /home/{user}/bmc-operator + ``` + +2. Open the `bmc.yaml` file in the `bmc-templates` directory: + + ``` + vi bmc-templates/bmc.yaml + ``` + + The following content is displayed: + + ``` + apiVersion: infra.io.odimra/v1 + kind: BMC + metadata: + name: {bmc_object_name} # same as BMC IP address + namespace: {bmc_namespace} + spec: + bmc: + address: {bmc_ip} + connectionMethodVariant: {plugin_variant} + powerState: "" #by default it will be null, we need to update Kubernetes object for reset action + resetType: "" #by default it will be null, we need to update Kubernetes object for reset action + credentials: + username: {username} + password: {password} + ``` + + See a *[Sample BMC settings file](#sample-bmc-settings-file)*. + +4. Update the following parameters as required: + + | Option | Definition | + | ----------------------- | ------------------------------------------------------------ | + | kind | This parameter defines the kind of resource you want to add. In this instance, it is `BMC`. | + | name | Enter the BMC name. This name is used to create the object.
This value must be the same as BMC IP address. | + | namespace | Enter the namespace the BMC must come up in. | + | address | Enter the IP address of the BMC you want to add. | + | connectionMethodVariant | This parameter tells us which plugin is required for adding the BMC. | + | powerState | Enter the power state you want the BMC to be in. | + | resetType | Enter the reset type for BMC. | + | credentials | Enter the username and password of the BMC. | + | bmcAddStatus | This parameter gives the status of whether the BMC is added or not.
**NOTE**: This parameter is populated by the operator. | + +4. Apply the `bmc-templates/bmc.yaml` file: + + ``` + kubectl apply -f bmc-templates/bmc.yaml + ``` + + The following sample log message appears when BMC is successfully added. + + ``` + {bmc_object} BMC object is updated. + ``` + + > **NOTE**: Check logs in `/var/log/operator_logs/bmc_operator.log` file in cluster VM. + + After successful addition of BMC object, run `kubectl get bmc {bmc_object} -n {namespace} -o yaml` to view the output of the added BMC object. + + Run `kubectl get bmc -n {namespace} -o yaml` to view the output that lists all the BMC objects. See the following sample output: + + ``` + apiVersion: v1 + items: + - apiVersion: infra.io.odimra/v1 + kind: Bmc + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: '{"kind":"Bmc","apiVersion":"infra.io.odimra/v1","metadata":{"name":"xxx.xxx.xxx.xxx","namespace":"{bmc_namespace}","creationTimestamp":null,"labels":{"name":"xxx.xxx.xxx.xxx","systemId":"7acc7d7c-002e-4011-b42b-4dd12b4759cb.1"}},"spec":{"bmc":{"address":"xxx.xxx.xxx.xxx","connectionMethodVariant":"Compute:BasicAuth:ILO_v2.0.0","powerState":"","resetType":""},"credentials":{"username":"admin","password":""}},"status":{"bmcAddStatus":"","bmcSystemId":"7acc7d7c-002e-4011-b42b-4dd12b4759cb.1","serialNumber":"","vendorName":"","modelID":"","firmwareVersion":"","biosVersion":"","biosAttributeRegistry":"","systemReset":""}}' + old_password: Vg1V5mVwuMhBIaelpqbirUTwYjoH2cz05Y2ZxQp5V0CwNl8sROViZRnh5q2tWucGBki+5n/jsKq0IeJcDwWdE/mXTLiEqn8rKmFieDNixo4O1n6vsp1G/VkIY2MFETnNsXcOlXpVCQEgGcfYa/QAJOtNOJT/4DF3wX4O6lqg03Y4jrIbvAQoYTRZ2c8zYkwbqL5AFqvC7i+2Fw28b4vF2eQWc9IFS/6Bj0Gr68OSIg0RzBO3cD7gabw3xZ7npk/FleRtC98PJmMHN5BL5SrXhSUBAf+W8FQ+RWmJy5pAI2UCzgGlVI5eiW+qJWesIqr95kQAzhgFdC8CzjwBxndGSmfXpM9m8LlFkGt/m2SZzN1NPCwHRHxr85iQ+TS6Jagtdgj0dp52THVStIq5u/B30EJVmTYt0Jk3n1bks4N/S3yzcKkTv3T+XK7fQzxbAEbQwbNa07i2LLQOiPKt4ti6h17bmRXRlKmhAVng9FLwv6kh9v8FdSVNxUpaW4U/ASEcfcMLRb3MqNYv7MK6eD3XiCVrAfEjYFvRrPxS+4ZAIVbrelmRC5Qz4dkW53YBTQPREtOEqwtFKWfQTNIa2AOOBmcYn025EmSF4/TnYnTj0rxpGtNysaNKDQjMPyacc904DUaYs7IF9Ln1ADQgOxPhM+KtNJbgHskwqP8glMNs9Lg= + creationTimestamp: "2023-06-22T10:37:05Z" + finalizers: + - infra.io.bmc/finalizer + generation: 2 + labels: + firmwareVersion: iLO.5.v2.70 + modelId: ProLiant.DL360.Gen10 + name: xxx.xxx.xxx.xxx + serialNo: MXQ92104V7 + systemId: 7acc7d7c-002e-4011-b42b-4dd12b4759cb.1 + vendor: HPE + name: xxx.xxx.xxx.xxx + namespace: bmc-op + resourceVersion: "124307" + uid: 52f9524c-df4c-4fdf-8a03-f81104871432 + spec: + bmc: + address: xxx.xxx.xxx.xxx + connectionMethodVariant: Compute:BasicAuth:ILO_v2.0.0 + powerState: "" + resetType: "" + credentials: + password: Vg1V5mVwuMhBIaelpqbirUTwYjoH2cz05Y2ZxQp5V0CwNl8sROViZRnh5q2tWucGBki+5n/jsKq0IeJcDwWdE/mXTLiEqn8rKmFieDNixo4O1n6vsp1G/VkIY2MFETnNsXcOlXpVCQEgGcfYa/QAJOtNOJT/4DF3wX4O6lqg03Y4jrIbvAQoYTRZ2c8zYkwbqL5AFqvC7i+2Fw28b4vF2eQWc9IFS/6Bj0Gr68OSIg0RzBO3cD7gabw3xZ7npk/FleRtC98PJmMHN5BL5SrXhSUBAf+W8FQ+RWmJy5pAI2UCzgGlVI5eiW+qJWesIqr95kQAzhgFdC8CzjwBxndGSmfXpM9m8LlFkGt/m2SZzN1NPCwHRHxr85iQ+TS6Jagtdgj0dp52THVStIq5u/B30EJVmTYt0Jk3n1bks4N/S3yzcKkTv3T+XK7fQzxbAEbQwbNa07i2LLQOiPKt4ti6h17bmRXRlKmhAVng9FLwv6kh9v8FdSVNxUpaW4U/ASEcfcMLRb3MqNYv7MK6eD3XiCVrAfEjYFvRrPxS+4ZAIVbrelmRC5Qz4dkW53YBTQPREtOEqwtFKWfQTNIa2AOOBmcYn025EmSF4/TnYnTj0rxpGtNysaNKDQjMPyacc904DUaYs7IF9Ln1ADQgOxPhM+KtNJbgHskwqP8glMNs9Lg= + username: admin + status: + biosAttributeRegistry: BiosAttributeRegistryU32.v1_2_68 + biosVersion: U32 v2.68 (07/14/2022) + bmcAddStatus: "yes" + bmcSystemId: 7acc7d7c-002e-4011-b42b-4dd12b4759cb.1 + firmwareVersion: iLO 5 v2.70 + modelID: ProLiant DL360 Gen10 + serialNumber: MXQ92104V7 + storageControllers: + ArrayControllers-0: + drives: + "0": + capacityBytes: "1200000000000.000000" + usedInVolumes: [] + "1": + capacityBytes: "1200000000000.000000" + usedInVolumes: [] + "2": + capacityBytes: "1200000000000.000000" + usedInVolumes: [] + "3": + capacityBytes: "1200000000000.000000" + usedInVolumes: [] + "4": + capacityBytes: "1200000000000.000000" + usedInVolumes: [] + supportedRAIDLevel: + - RAID0 + - RAID1 + - RAID3 + - RAID4 + - RAID5 + - RAID6 + - RAID10 + - RAID01 + - RAID6TP + - RAID1E + - RAID50 + - RAID60 + - RAID00 + - RAID10E + - RAID1Triple + - RAID10Triple + - None + systemReset: "" + vendorName: HPE + kind: List + metadata: + resourceVersion: "" + ``` + + +For description of the properties in the sample output file, see the *[BMC addition output](#BMC-addition-output)* section. + + + +## Updating a BMC password + +1. Run the following command to edit the BMC object details in the output file: + + ``` + kubectl edit bmc -n {object_name} {bmc-namespace} + ``` + + For example: + + ``` + kubectl edit bmc -n xxx.xxx.xxx.xxx bmc-operator + ``` + +2. Change the password of BMC under `spec.credentials.password` property. + +3. Save the file. BMC Operator automatically updates the BMC server details. + + The following sample log message appears with the successful update message on BMC and ODIM: + + ``` + Updated password in {object_name} BMC + ``` + + ``` + Updated password in ODIM for {object_name} BMC + ``` + + > **NOTE**: Check logs in `/var/log/operator_logs/bmc_operator.log` file in cluster VM. + + + +## Resetting a BMC + +1. Run the following command to reset the BMC object details in the output file: + + ``` + kubectl edit bmc -n {bmc_namespace} {object_name} + ``` + + > **NOTE**: Please check the *[Scenarios for powerState and resetType combinations](#Scenarios-for-powerState-and-resetType-combinations)* section to understand the combinations that work for resetting BMC prior to setting the values. + +2. Edit the `powerState` parameter with one of the following values as required: + + | Parameter | Description | + | --------- | ---------------------- | + | On | Switches on the unit. | + | Off | Switches off the unit. | + +3. Edit the `resetType` parameter with one of the following values as required: + + | Parameter | Description | + | ---------------- | ------------------------------------------------------------ | + | ForceOff | Turns off the unit immediately (non-graceful shutdown). | + | ForceOn | Turns on the unit immediately. | + | ForceRestart | Shuts down immediately and non-gracefully and restarts the system. | + | GracefulRestart | Shuts down gracefully and restarts the system. | + | GracefulShutdown | Shuts down gracefully and powers off. | + | On | Turns on the unit. | + | Nmi | Generates a diagnostic interrupt, which is usually an NMI on x86 systems, to stop normal operations, complete diagnostic actions, and halt the system. | + | Pause | Pauses execution on the unit but does not remove power.
This is typically a feature of virtual machine hypervisors. | + | PowerCycle | Power cycles the unit. Behaves like a full power removal, followed by a power restore to the resource. | + | PushPowerButton | Simulates the pressing of the physical power button on this unit. | + | Resume | Resumes execution on the paused unit.
This is typically a feature of virtual machine hypervisors. | + | Suspend | Writes the state of the unit to disk before powering off.
This allows for the state to be restored when powered back on. | + +4. Save the file. BMC Operator for ODIM automatically resets the BMC based on the values you choose. + The following sample log message appears with the details of the reset BMC: + + ``` + successfully done computer system reset on {object_name} BMC + ``` + + > **NOTE**: Check logs in `/var/log/operator_logs/bmc_operator.log` file in cluster VM. + + + +## Scenarios for powerState and resetType combinations + +| currentsystemState | powerState | resetType | Reset allowed? | +| ------------------ | ---------- | ---------------- | ----------------------------------- | +| On | On | ForceRestart | Allowed | +| On | On | GracefulRestart | Allowed | +| On | On | GracefulShutdown | Not allowed | +| On | On | ForceOff | Not allowed | +| On | On | On | Not allowed | +| On | On | Pause | Allowed, if Pause is supported | +| On | On | Resume | Not allowed | +| On | On | ForceOn | Not allowed | +| On | On | Nmi | Allowed, if Nmi is supported | +| On | On | PowerCycle | Allowed, if PowerCycle is supported | +| On | On | PushPowerButton | Not allowed | +| On | On | Suspend | Allowed, if Suspend is supported | + +| currentsystemState | powerState | resetType | Reset allowed? | +| ------------------ | ---------- | ---------------- | -------------- | +| Off | Off | ForceRestart | Not allowed | +| Off | Off | GracefulRestart | Not allowed | +| Off | Off | GracefulShutdown | Not allowed | +| Off | Off | ForceOff | Not allowed | +| Off | Off | On | Not allowed | +| Off | Off | Pause | Not allowed | +| Off | Off | Resume | Not allowed | +| Off | Off | ForceOn | Not allowed | +| Off | Off | Nmi | Not allowed | +| Off | Off | PowerCycle | Not allowed | +| Off | Off | PushPowerButton | Not allowed | +| Off | Off | Suspend | Not allowed | + +| currentsystemState | powerState | resetType | Reset allowed? | +| ------------------ | ---------- | ---------------- | -------------------------------- | +| On | Off | ForceRestart | Not allowed | +| On | Off | GracefulRestart | Not allowed | +| On | Off | GracefulShutdown | Allowed | +| On | Off | ForceOff | Allowed | +| On | Off | On | Not allowed | +| On | Off | Pause | Allowed, if Pause is supported | +| On | Off | Resume | Not allowed | +| On | Off | ForceOn | Not allowed | +| On | Off | Nmi | Not allowed | +| On | Off | PowerCycle | Not allowed | +| On | Off | PushPowerButton | Allowed | +| On | Off | Suspend | Allowed, if Suspend is supported | + +| currentsystemState | powerState | resetType | Reset allowed? | +| ------------------ | ---------- | ---------------- | ----------------------------------- | +| Off | On | ForceRestart | Not allowed | +| Off | On | GracefulRestart | Not allowed | +| Off | On | GracefulShutdown | Not allowed | +| Off | On | ForceOff | Not allowed | +| Off | On | On | Allowed | +| Off | On | Pause | Not allowed | +| Off | On | Resume | Not allowed | +| Off | On | ForceOn | Allowed, if ForceOn is supported | +| Off | On | Nmi | Not allowed | +| Off | On | PowerCycle | Allowed, if PowerCycle is supported | +| Off | On | PushPowerButton | Allowed | +| Off | On | Suspend | Not allowed | + + + +## Deleting a BMC + +1. Navigate to the BMC Operator directory: + + ``` + cd /home/{user}/bmc-operator + ``` + +2. Open the `bmc-templates/bmc.yaml` object: + + ``` + vi bmc-templates/bmc.yaml + ``` + +3. Edit the `metadata.Name` to the BMC object you want to delete. + +4. Save the updates. + +5. Run the following command to delete the `bmc-templates/bmc.yaml` object: + + ``` + kubectl delete -f bmc-templates/bmc.yaml + ``` + + The following sample log message appears when BMC is successfully deleted. + + ``` + BMC {bmc_object_name} successfully deleted from ODIM + ``` + +Alternatively, run `kubectl delete bmc -n{namespace} {bmc_object_name}` to delete the BMC. + +> **NOTE**: Check logs in `/var/log/operator_logs/bmc_operator.log` file in cluster VM. + + + + # Applying BIOS settings on BMC + +1. Navigate to the `bmc-operator` directory: + + ``` + cd /home/{user}/bmc-operator + ``` + +2. Open the `bios.yaml` file: + + ``` + vi ~/bmc-templates/bios.yaml + ``` + + The following content is displayed: + + ``` + apiVersion: infra.io.odimra/v1 + kind: BiosSetting + metadata: + name: {biossetting_object_name} # name of bmc + namespace: {bmc_namespace} + spec: + bmcName: {bmc_ip} + systemID: {system_id} + serialNumber: {serial_number} + biosAttributes: + prop1: {value1} # ex : BootMode: "LegacyBios" + prop2: {value2} + ``` + + See a *[Sample BIOS settings file](#sample-bios-settings-file)*. + +3. Update the following parameters as required: + + | Option | Definition | + | ------------------- | ------------------------------------------------------------ | + | name | Enter the same metadata name as of BMC object. | + | bmcName | Enter the IP address of the BMC. | + | systemID | Enter the system ID of the BMC. | + | serialNumber | Enter the serial number of the BMC. | + | prop1,prop2,.. | Enter the BIOS attribute. | + | value1,value2,.. | Enter the BIOS attribute's value. | + + > **NOTE**: Specifying a value for either `bmcName`, `systemID`, or `serialNumber` is mandatory. + + For properties and their supported values, see the `biosschemaregistry` object by running the following command: + + ``` + kubectl get biosschemaregistry {biossetting_object_name} -n {bmc_namespace} + ``` + + To check the list of schemas, use the following command: + + ``` + kubectl get biosschemaregistry -n {bmc_namespace} + ``` + + 4. Apply the `bmc-templates/bios.yaml` file: + + ``` + kubectl apply -f bmc-templates/bios.yaml + ``` + + 5. Check logs in `/var/log/operator_logs/bmc_operator.log` in cluster VM. + + ``` + "Bios configured, Please reset system now." + ``` + +6. Reset BMC after the BIOS settings are applied. For instructions, see *[Resetting a BMC](#resetting-a-bmc)*. + + Here is the sample output of the updated BIOS settings on BMC: + + ``` + apiVersion: v1 + items: + - apiVersion: infra.io.odimra/v1 + kind: BiosSetting + metadata: + creationTimestamp: "2023-06-22T07:31:28Z" + generation: 1 + name: xxx.xxx.xxx.xxx + namespace: {bmc-operator} + resourceVersion: "127391" + uid: 24d9a0be-770c-4468-8c0c-1b296c5ee3a4 + spec: + biosAttributes: {} + status: + attributes: + AcpiHpet: Enabled + AcpiRootBridgePxm: Enabled + AcpiSlit: Enabled + AdjSecPrefetch: Enabled + AdminEmail: admin2@someorg.com + AdminName: "" + AdminOtherInfo: "" + AdminPhone: "" + AdvCrashDumpMode: Disabled + AdvancedMemProtection: FastFaultTolerantADDDC + AsrStatus: Enabled + AsrTimeoutMinutes: Timeout10 + AssetTagProtection: Unlocked + AutoPowerOn: RestoreLastState + BootMode: LegacyBios + BootOrderPolicy: RetryIndefinitely + ChannelInterleaving: Enabled + CollabPowerControl: Enabled + ConsistentDevNaming: LomsAndSlots + CustomPostMessage: "" + DaylightSavingsTime: Disabled + DcuIpPrefetcher: Enabled + DcuStreamPrefetcher: Enabled + Dhcpv4: Enabled + DirectToUpi: Auto + DynamicPowerCapping: Disabled + EmbNicEnable: Auto + EmbNicLinkSpeed: Auto + EmbNicPCIeOptionROM: Enabled + EmbSas1Aspm: Disabled + EmbSas1Boot: TwentyFourTargets + EmbSas1Enable: Auto + EmbSas1LinkSpeed: Auto + EmbSas1PcieOptionROM: Enabled + EmbSata1Aspm: Disabled + EmbSata2Aspm: Disabled + EmbVideoConnection: Auto + EmbeddedDiagnostics: Enabled + EmbeddedSata: Ahci + EmbeddedSerialPort: Com2Irq3 + EmbeddedUefiShell: Enabled + EmsConsole: Disabled + EnabledCoresPerProc: "0" + EnergyEfficientTurbo: Disabled + EnergyPerfBias: BalancedPerf + EraseUserDefaults: "No" + ExtendedAmbientTemp: Disabled + ExtendedMemTest: Disabled + F11BootMenu: Enabled + FCScanPolicy: CardConfig + FanFailPolicy: Shutdown + FanInstallReq: EnableMessaging + FlexLom1Aspm: Disabled + FlexLom1Enable: Auto + FlexLom1LinkSpeed: Auto + FlexLom1PCIeOptionROM: Enabled + HttpSupport: Auto + HwPrefetcher: Enabled + IODCConfiguration: Auto + IntelDmiLinkFreq: Auto + IntelNicDmaChannels: Enabled + IntelPerfMonitoring: Disabled + IntelProcVtd: Enabled + IntelUpiFreq: Auto + IntelUpiLinkEn: Auto + IntelUpiPowerManagement: Enabled + IntelligentProvisioning: Enabled + InternalSDCardSlot: Enabled + IpmiWatchdogTimerAction: PowerCycle + IpmiWatchdogTimerStatus: IpmiWatchdogTimerOff + IpmiWatchdogTimerTimeout: Timeout30Min + Ipv4Address: xxx.xxx.xxx.xxx + Ipv4Gateway: xxx.xxx.xxx.xxx + Ipv4PrimaryDNS: xxx.xxx.xxx.xxx + Ipv4SecondaryDNS: xxx.xxx.xxx.xxx + Ipv4SubnetMask: xxx.xxx.xxx.xxx + Ipv6Address: '::' + Ipv6ConfigPolicy: Automatic + Ipv6Duid: Auto + Ipv6Gateway: '::' + Ipv6PrimaryDNS: '::' + Ipv6SecondaryDNS: '::' + LLCDeadLineAllocation: Enabled + LlcPrefetch: Disabled + LocalRemoteThreshold: Auto + MaxMemBusFreqMHz: Auto + MaxPcieSpeed: PerPortCtrl + MemClearWarmReset: Disabled + MemFastTraining: Enabled + MemMirrorMode: Full + MemPatrolScrubbing: Enabled + MemRefreshRate: Refreshx1 + MemoryControllerInterleaving: Auto + MemoryRemap: NoAction + MinProcIdlePkgState: C6Retention + MinProcIdlePower: C6 + MixedPowerSupplyReporting: Enabled + NetworkBootRetry: Enabled + NetworkBootRetryCount: "20" + NicBoot1: NetworkBoot + NicBoot2: Disabled + NicBoot3: Disabled + NicBoot4: Disabled + NicBoot5: NetworkBoot + NicBoot6: Disabled + NodeInterleaving: Disabled + NumaGroupSizeOpt: Flat + NvmeOptionRom: Enabled + OpportunisticSelfRefresh: Disabled + PciPeerToPeerSerialization: Disabled + PciResourcePadding: Normal + PciSlot1Bifurcation: Auto + PciSlot2Bifurcation: Auto + PciSlot3Bifurcation: Auto + PersistentMemBackupPowerPolicy: WaitForBackupPower + PlatformCertificate: Enabled + PostBootProgress: Disabled + PostDiscoveryMode: Auto + PostF1Prompt: Delayed20Sec + PostVideoSupport: DisplayAll + PostedInterruptThrottle: Enabled + PowerButton: Enabled + PowerOnDelay: NoDelay + PowerRegulator: DynamicPowerSavings + PreBootNetwork: Auto + PrebootNetworkEnvPolicy: Auto + PrebootNetworkProxy: "" + ProcAes: Enabled + ProcHyperthreading: Enabled + ProcTurbo: Disabled + ProcVirtualization: Enabled + ProcX2Apic: Enabled + ProcessorConfigTDPLevel: Normal + ProcessorJitterControl: Disabled + ProcessorJitterControlFrequency: "0" + ProcessorJitterControlOptimization: ZeroLatency + ProductId: 867959-B21 + RedundantPowerSupply: BalancedMode + RefreshWatermarks: Auto + RemovableFlashBootSeq: ExternalKeysFirst + RestoreDefaults: "No" + RestoreManufacturingDefaults: "No" + RomSelection: CurrentRom + SataSecureErase: Disabled + SaveUserDefaults: "No" + SecStartBackupImage: Disabled + SecureBootStatus: Disabled + SerialConsoleBaudRate: BaudRate115200 + SerialConsoleEmulation: Vt100Plus + SerialConsolePort: Auto + SerialNumber: MXQ92104V7 + ServerAssetTag: "" + ServerConfigLockStatus: Disabled + ServerName: "" + ServerOtherInfo: "" + ServerPrimaryOs: "" + ServiceEmail: "" + ServiceName: "" + ServiceOtherInfo: "" + ServicePhone: "" + SetupBrowserSelection: Auto + Slot1MctpBroadcastSupport: Enabled + Slot2MctpBroadcastSupport: Enabled + Slot3MctpBroadcastSupport: Enabled + Sriov: Enabled + StaleAtoS: Disabled + SubNumaClustering: Disabled + ThermalConfig: OptimalCooling + ThermalShutdown: Enabled + TimeFormat: Utc + TimeZone: Unspecified + TpmChipId: None + TpmFips: NotSpecified + TpmState: NotPresent + TpmType: NoTpm + UefiOptimizedBoot: Disabled + UefiSerialDebugLevel: Disabled + UefiShellBootOrder: Disabled + UefiShellScriptVerification: Disabled + UefiShellStartup: Disabled + UefiShellStartupLocation: Auto + UefiShellStartupUrl: "" + UefiShellStartupUrlFromDhcp: Disabled + UefiVariableAccessFwControl: Disabled + UncoreFreqScaling: Auto + UpiPrefetcher: Enabled + UrlBootFile: "" + UrlBootFile2: "" + UrlBootFile3: "" + UrlBootFile4: "" + UsbBoot: Enabled + UsbControl: UsbEnabled + UserDefaultsState: Disabled + UtilityLang: English + VirtualInstallDisk: Disabled + VirtualSerialPort: Com1Irq4 + VlanControl: Disabled + VlanId: "0" + VlanPriority: "0" + WakeOnLan: Enabled + WorkloadProfile: Custom + XptPrefetcher: Auto + iSCSIPolicy: SoftwareInitiator + kind: List + metadata: + resourceVersion: "" + ``` + +7. View the list of new BIOS settings: + + ``` + kubectl get biossetting -n {bmc_namespace} + ``` + + View the new individual BIOS setting: + + ``` + kubectl get biossetting -n {bmc_namespace} {biossetting_object_name} -o yaml + ``` + + + +# Applying boot order settings on BMC + +1. Navigate to the home directory of the operator: + + ``` + cd /home/{user}/bmc-operator + ``` + +2. Open the `boot.yaml` file: + + ``` + vi bmc-templates/boot.yaml + ``` + + The following content is displayed: + + ``` + apiVersion: infra.io.odimra/v1 + kind: BootOrderSettings + metadata: + name: {bootordersetting_object_name} # name of bmc + namespace: {bmc_namespace} + spec: + bmcName: {bmc_ip} + systemID: {system_id} + serialNumber: {serial_number} + boot: #example: bootOrder: ["Cd","Usb","Hdd","Pxe"] + bootOrder: ["Usb","Hdd","Cd","Pxe"] + bootSourceOverrideTarget: "Hdd" + uefiTargetBootSourceOverride: "None" + bootSourceOverrideEnabled: "Once" + ``` + + See a *[Sample boot order settings file](#sample-boot-order-settings-file)*. + +3. Update the following parameters as required: + + | Option | Definition | + | ---------------------------- | ------------------------------------------------------------ | + | kind | This parameter defines the kind of resource you want to add/configure. In this instance, it is `BootOrderSettings`. | + | bmcName | Enter the IP address of the BMC. This is a mandatory field. | + | systemID | Enter the system ID of the BMC. | + | serialNumber | Enter the serial number of the BMC. | + | bootOrder | This property shall contain an array of `BootOptionReference` strings that represent the persistent boot order for this computer system. For UEFI systems, this is the UEFI Specification-defined UEFI BootOrder. | + | bootSourceOverrideTarget | This property shall contain the source to boot the system from, overriding the normal boot order. The @Redfish.AllowableValues annotation specifies the valid values for this property. `UefiTarget` indicates to boot from the UEFI device path found in `UefiTargetBootSourceOverride`. `UefiBootNext` indicates to boot from the UEFI `BootOptionReference` found in BootNext. Virtual devices for a target should take precedence over a physical device. Systems may attempt to boot from multiple devices that share a target identifier. Changes to this property do not alter the BIOS persistent boot order configuration. | + | uefiTargetBootSourceOverride | This property shall contain the UEFI device path of the override boot target. Changes to this property do not alter the BIOS persistent boot order configuration. | + | bootSourceOverrideEnabled | This property shall contain `Once` for a one-time boot override, and `Continuous` for a remain-active-until-cancelled override. If set to `Once`, the value is reset to `Disabled` after the `BootSourceOverrideTarget` actions have completed successfully. Changes to this property do not alter the BIOS persistent boot order configuration. | + + > **NOTE**: Specifying a value for either `bmcName`, `systemID`, or `serialNumber` is mandatory. + +4. Apply the `bmc-templates/boot.yaml` file. + + ``` + kubectl apply -f bmc-templates/boot.yaml + ``` + +5. Check logs in `/var/log/operator_logs/bmc_operator.log` in cluster VM. + + ``` + Boot order setting configured for {bmc_object_name} BMC + ``` + + After successful updation of `bootordersettings`, you can view the following sample output: + + ``` + apiVersion: v1 + items: + - apiVersion: infra.io.odimra/v1 + kind: BootOrderSetting + metadata: + creationTimestamp: "2023-06-22T07:31:28Z" + generation: 1 + name: xxx.xxx.xxx.xxx + namespace: {bmc_namespace} + resourceVersion: "106040" + uid: f223d226-94a5-465c-bf4c-a01fcae16f76 + spec: {} + status: + boot: + bootOrder: + - Cd + - Usb + - Hdd + - Pxe + bootSourceOverrideEnabled: Disabled + bootSourceOverrideMode: Legacy + bootSourceOverrideTarget: None + bootSourceOverrideTarget.AllowableValues: + - None + - Cd + - Hdd + - Usb + - Utilities + - Diags + - BiosSetup + - Pxe + - UefiShell + kind: List + metadata: + resourceVersion: "" + ``` + + For description of the properties in the sample output file, see the *[Boot order settings output](#boot-order-settings-output)* section. + +6. View the list of new boot order settings: + + ``` + kubectl get bootordersettings -n {bmc_namespace} + ``` + + View the new individual boot order setting: + + ``` + kubectl get bootordersettings -n {bmc_namespace} {bootordersetting_object_name} -o yaml + ``` + + + +# Volume operations + +## Adding a volume + +1. Navigate to the `bmc-operator` directory: + + ``` + cd /home/{user}/bmc-operator + ``` + +2. Open the `volume.yaml` file: + + ``` + vi ~/bmc-templates/volume.yaml + ``` + + The following content is displayed: + + ``` + apiVersion: infra.io.odimra/v1 + kind: Volume + metadata: + name: {bmc_name}.{volume_object_name} #{bmc_name} must be same as BMC IP address + namespace: {bmc_namespace} + spec: + storageControllerID: + RAIDType: # RAID0/RAID1/RAID6... + drives: [drive1,drive2,drive3...] + ``` + + See a *[Sample volume file](#sample-volume-file)*. + +3. Update the following parameters as required: + + | Option | Definition | + | ------------------- | ------------------------------------------------------------ | + | kind | This parameter defines the kind of resource you want to add/configure. In this instance, it is `Volume`. | + | name | Enter the BMC name, followed by the display name for the volume you want to create. BMC name value must be the same as BMC IP address. | + | namespace | Enter the namespace the Volume must come up in (same as the bmc object). | + | storageControllerID | Enter the storage controller in which you have the drives. | + | RAIDType | Enter the RAID type for the volume. | + | drives | Enter the drive IDs you want to have in the volume as array. | + +4. Apply the `bmc-templates/volume.yaml` file. + + ``` + kubectl apply -f bmc-templates/volume.yaml + ``` + + The following log appears. + + ``` + Volume {volume_object_name} created successfully. Please reset the system now + ``` + + The following sample output appears: + + ``` + apiVersion: v1 + items: + - apiVersion: infra.io.odimra/v1 + kind: Volume + metadata: + creationTimestamp: "2023-06-22T11:15:07Z" + deletionGracePeriodSeconds: 0 + deletionTimestamp: "2023-06-22T11:18:06Z" + finalizers: + - infra.io.volume/finalizer + generation: 2 + name: xxx.xxx.xxx.xxx.vol0 + namespace: {bmc_namespace} + resourceVersion: "127936" + uid: d48cf31a-7c6e-4990-a9fc-439dc2e0ca1e + spec: {} + status: + Identifiers: + DurableName: 600508B1001CBC06E6C76AF6B12C8FA8 + DurableNameFormat: NAA + RAIDType: RAID0 + capacityBytes: "1200209526784.000000" + drives: + - 0 + storageControllerID: ArrayControllers-0 + volumeID: "1" + volumeName: vol0 + kind: List + metadata: + resourceVersion: "" + ``` + + For description of the properties in the sample output file, see the *[Volume addition output](#Volume-addition-output])* section. + +5. View the list of new volume objects: + + ``` + kubectl get volume -n {bmc_namespace} + ``` + + View the new individual volume: + + ``` + kubectl get volume {volume_object_name} -n {bmc_namespace} -o yaml + ``` + + + +## Deleting a volume + +1. Navigate to the BMC Operator directory: + + ``` + cd /home/{user}/bmc-operator + ``` + +2. Edit `metadata.name` to the volume object name you wish to delete. + +3. Delete the `bmc-templates/volume.yaml` object: + + ``` + kubectl delete -f bmc-templates/volume.yaml + ``` + + The following sample log message appears when the volume is successfully deleted. + + ``` + Volume {volume_object_name} successfully deleted, Please reset the system now + ``` + +Alternatively, run `kubectl delete volume -n {namespace} {volume_object_name}` to delete the volume. + + > **NOTE**: Check logs in `/var/log/operator_logs/bmc_operator.log` file in cluster VM. + + + +# Firmware operations + +## Upgrading or downgrading firmware + +1. Navigate to the BMC Operator directory: + + ``` + cd /home/{user}/bmc-operator + ``` + +2. Open the `firmware.yaml` file: + + ``` + vi bmc-templates/firmware.yaml + ``` + + The following content is displayed: + + ``` + apiVersion: infra.io.odimra/v1 + kind: Firmware + metadata: + name: {firmware_object_name} # same as BMC IP address + namespace: {bmc_namespace} + spec: + image: + imageLocation: "http://{firmware_name}/{path of the BMC image}" + auth: + username: {username} + password: {password} + transferProtocol: {transfer_protocol} + ``` + + See a *[Sample firmware file](#Sample-firmware-file)*. + +3. Update the following parameters as required: + + | Option | Definition | + | ---------------- | ------------------------------------------------------------ | + | kind | This parameter defines the kind of resource you want to add. In this instance, it is `Firmware`. | + | name | Enter the firmware name. This name is used to create the object. This value must be the same as BMC IP address. | + | namespace | Enter the namespace of the BMC for which you apply the firmware. | + | imageLocation | Enter the location of the firmware image. | + | auth | Credentials of the web server where the firmware image is located.
If there are no credentials, the values can be empty. | + | transferProtocol | Enter the network protocol used to retrieve the firmware image file.
This is an optional parameter. | + +4. Apply the `bmc-templates/firmware.yaml` file. + + ``` + kubectl apply -f bmc-templates/firmware.yaml + ``` + + You receive notification messages upon successful completion of the operation. + + **NOTE**: Check logs in `/var/log/operator_logs/bmc_operator.log` file in cluster VM. + + ``` + Firmware and Bmc object updation completed! + ``` + + If the upgrade fails, you get the following message: + + ``` + Unable to update firmware, try again + ``` + + After the successful firmware operation, you can view the following sample output: + + ``` + apiVersion: v1 + items: + - apiVersion: infra.io.odimra/v1 + kind: Firmware + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"infra.io.odimra/v1","kind":"Firmware","metadata":{"annotations":{},"name":"xxx.xxx.xxx.xxx","namespace":"{bmc_namespace}"},"spec":{"image":{"imageLocation":"http://xxx.xxx.xxx.xxx/ISO/ilo5_270.bin"}}} + old_firmware: http://xxx.xxx.xxx.xxx/ISO/ilo5_270.bin + creationTimestamp: "2023-07-10T10:39:50Z" + generation: 3 + labels: + firmwareVersion: iLO-5-v2.70 + name: xxx.xxx.xxx.xxx + namespace: {bmc_namespace} + resourceVersion: "2647535" + uid: 5d06c526-7f4f-482d-b481-4c98f5d51834 + spec: + image: + auth: {} + imageLocation: http://xxx.xxx.xxx.xxx/ISO/ilo5_270.bin + status: + firmwareVersion: iLO 5 v2.70 + imagePath: http://xxx.xxx.xxx.xxx/ISO/ilo5_270.bin + status: Success + kind: List + metadata: + resourceVersion: "" + ``` + + For description of the properties in the sample output file, see the *[Firmware operation output](#Firmware-operation-output)* section. + +5. View the list of new firmware objects: + + ``` + kubectl get firmware -n {bmc_namespace} + ``` + + View the new individual firmware: + + ``` + kubectl get firmware {firmware_object_name} -n {bmc_namespace} -o yaml + ``` + + + +## Verifying the firmware operation + +To verify the successful firmware upgrade or downgrade, type `kubectl get firmware -n{namespace} {firmware_object_name} -o yaml`. The following content is appended in the firmware object: + +``` +status: + firmwareVersion: {latest_firmware_version} + status: Success +``` + +You can also verify the firmware upgrade or downgrade in BMC object by viewing the firmware version in `firmwareVersion` in `labels` or in `firmwareVersion` in `status`. + + + +## Editing firmware + +1. Run the following command: + + ``` + kubectl edit firmware {firmware_object_name} -n {namespace} + ``` + + The following content is displayed: + + ``` + apiVersion: v1 + items: + - apiVersion: infra.io.odimra/v1 + kind: Firmware + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"infra.io.odimra/v1","kind":"Firmware","metadata":{"annotations":{},"name":"xxx.xxx.xxx.xxx","namespace":"{bmc_namespace}"},"spec":{"image":{"imageLocation":"http://xxx.xxx.xxx.xxx/ISO/ilo5_260.bin"}}} + old_firmware: http://xxx.xxx.xxx.xxx/ISO/ilo5_248.bin + creationTimestamp: "2023-06-15T13:20:59Z" + generation: 3 + labels: + firmwareVersion: iLO-5-v2.48 + name: xxx.xxx.xxx.xxx + namespace: {bmc_namespace} + resourceVersion: "10929469" + uid: bf610676-f57b-44d8-8b26-bdb7e89aa0b4 + spec: + image: + auth: {} + imageLocation: http://xxx.xxx.xxx.xxx/ISO/ilo5_248.bin + status: + firmwareVersion: iLO 5 v2.60 + status: Success + ``` + +2. Edit the `imageLocation` with the new path. +3. Save the file and close it. +4. *[Verify the edited firmware version](#Verifying-the-firmware-operation)*. + + + +## Deleting the firmware file + +> **NOTE**: Deleting the firmware file does not revert to the previous version. + +1. Navigate to the BMC Operator directory: + + ``` + cd /home/{user}/bmc-operator + ``` + +2. To delete the firmware file of a specific BMC, make sure you edit the `name` parameter in the firmware file to match the name of the BMC. + +3. Edit `metadata.name` to the firmware object name you wish to delete. + +4. Delete the `bmc-templates/firmware.yaml` object: + + ``` + kubectl delete -f bmc-templates/firmware.yaml + ``` + + The following sample log message appears when the firmware is successfully deleted. + + ``` + firmware {firmware_object_name} successfully deleted + ``` + +Alternatively, run `kubectl delete firmware -n {namespace} {firmware_object_name}` to delete the volume. + + > **NOTE**: Check logs in `/var/log/operator_logs/bmc_operator.log` file in cluster VM. + + + +# Creating an event subscription + +1. Navigate to the BMC Operator directory: + + ``` + cd /home/{user}/bmc-operator + ``` + +2. Open the `eventsubscription.yaml` file: + + ``` + vi bmc-templates/eventsubscription.yaml + ``` + + The following content is displayed: + + ``` + apiVersion: infra.io.odimra/v1 + kind: Eventsubscription + metadata: + name: {eventsubscription_object_name} + namespace: {bmc_namespace} + spec: + name: event-sub-client + destination: 'https://{IP address}:{port}/Destination' + eventTypes: + - Alert + - ResourceAdded + - ResourceRemoved + - ResourceUpdated + messageIds: + resourceTypes: + - ComputerSystem + context: ODIMRA_Event + eventFormatType: Event + subordinateResources: # true or false + originResources: + - 'Bmc/{bmc_object_name}' + - 'managerCollection' + ``` + + See a *[Sample event subscription file](#Sample-event-subscription-file)*. + +3. Update the following parameters as required: + + | Option | Definition | + | -------------------- | ------------------------------------------------------------ | + | kind | This parameter defines the kind of resource you want to add. In this instance, it is `Eventsubscription`. | + | name | Enter the subscription name. This name is used to create the object. | + | namespace | Enter the namespace of the BMC for which you apply the event subscription. | + | destination | The URL of the destination event listener that listens to events. Destination is unique to a subscription. | + | context | A string that is stored with the event destination subscription. | + | eventFormatType | The content types of the message that this service can send to the event destination. For possible values, see *EventFormat type* table in *Resource Aggregator for ODIM API Reference and User Guide*. | + | subordinateResources | Indicates whether the service supports the `SubordinateResource` property on event subscriptions or not. If it is set to `true`, the service creates subscription for an event originating from the specified `OriginResoures` and also from its subordinate resources. | + | originResources | Array of resources for which the service sends related events. If this property is absent or the array is empty, events originating from any resource is sent to the subscriber. Supported values are:
- managerCollection
- chassisCollection
- systemCollection
- taskCollection
- fabricCollection
- allResources
- allResources/{bmc_object_name}
- Bmc/{bmc_object_name}
- BootOrderSetting/{bmc_object_name}
- BiosSetting/{bmc_object_name}
- Firmware/{bmc_object_name}
- Volume/{bmc_object_name} | + +4. Apply the `bmc-templates/eventsubscription.yaml` file. + + ``` + kubectl apply -f bmc-templates/eventsubscription.yaml + ``` + + You receive notification messages upon successful completion of the operation. + + **NOTE**: Check logs in `/var/log/operator_logs/bmc_operator.log` file in cluster VM. + + ``` + Eventsubscription created successfully! + ``` + + After the successful event subscription operation, you can view the following sample output: + + ``` + apiVersion: infra.io.odimra/v1 + kind: Eventsubscription + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"infra.io.odimra/v1","kind":"Eventsubscription","metadata":{"annotations":{},"name":"subscription1","namespace":"{bmc_namespace}"},"spec":{"context":"ODIMRA_Event","destination":"https://xxx.xxx.xxx.xxx:{port}/Destination","eventFormatType":"Event","eventTypes":["Alert","ResourceAdded","ResourceRemoved","ResourceUpdated"],"messageIds":null,"name":"event-sub-client","originResources":["Bmc/xxx.xxx.xxx.xxx"],"resourceTypes":["ComputerSystem"],"subordinateResources":true}} + creationTimestamp: "2023-07-18T06:49:01Z" + finalizers: + - infra.io.eventsubscription/finalizer + generation: 1 + name: {subscription_name} + namespace: {bmc_namespace} + resourceVersion: "4644830" + uid: 8c254815-ab45-4aae-b7b2-ccdb5a5f3ab8 + spec: + context: ODIMRA_Event + destination: https://xxx.xxx.xxx.xxx:{port}/Destination + eventFormatType: Event + eventTypes: + - Alert + - ResourceAdded + - ResourceRemoved + - ResourceUpdated + name: event-sub-client + originResources: + - Bmc/xxx.xxx.xxx.xxx + resourceTypes: + - ComputerSystem + subordinateResources: true + status: + context: ODIMRA_Event + destination: https://xxx.xxx.xxx.xxx:{port}/Destination + eventSubscriptionID: 6cc68983-fbf8-49c8-b0fb-db58322eb439 + name: event-sub-client + originResources: + - Bmc/xxx.xxx.xxx.xxx + protocol: Redfish + subscriptionType: RedfishEvent + ``` + + For description of the properties in the sample output file, see the *[Event subscription output](#event-subscription-output)* section. + +5. View the list of new event subscriptions: + + ``` + kubectl get eventsubscription -n {bmc_namespace} + ``` + + View the new individual event subscription: + + ``` + kubectl get eventsubscription {eventsubscription_object_name} -n {bmc_namespace} -o yaml + ``` + + + +## Validation of message IDs + +For message IDs and their supported values, see the `eventsmessageregistry ` object by running the following command: + +``` +kubectl get eventsmessageregistry {registry_name} -n {bmc_namespace} +``` + +To check the list of message registries, use the following command: + +``` +kubectl get eventsmessageregistry -n {bmc_namespace} +``` + + + +## Deleting an event subscription + +1. Navigate to the BMC Operator directory: + + ``` + cd /home/{user}/bmc-operator + ``` + +2. Open the `bmc-templates/eventsubscription.yaml` object: + + ``` + vi bmc-templates/eventsubscription.yaml + ``` + +3. Edit `metadata.Name` to the event subscription object you want to delete. + +4. Save the updates. + +5. Run the following command: + + ``` + kubectl delete -f bmc-templates/eventsubscription.yaml + ``` + + The following sample log message appears when event subscription is successfully deleted. + + ``` + Successfully deleted event subscription with ID {subscriptionID} + ``` + + > **NOTE**: Check logs in `/var/log/operator_logs/bmc_operator.log` file in cluster VM. + + Alternatively, run `kubectl delete eventsubscription {subscription_name} -n {bmc_namespace}` to delete the event subscription. + + + +# Reconciliation + +Reconciliation refers to the process of comparing the desired state of a resource with its current state, and taking necessary actions to bring them into alignment. It ensures that the actual state of resources matches the state defined by the user or the system. +In the context of a BMC Operator managing various objects in the Kubernetes environment, reconciliation involves synchronizing the configurations of these objects with the desired state specified in the operator. The operator continuously monitors the current state of the objects and performs actions to reconcile any discrepancies between the current and desired states. + +## Reconciliation methods + +The reconciliation process is repeated periodically by a trigger at specific times to ensure that the objects remain in the desired state. The BMC Operator handles concurrency and ensures that reconciliation actions are performed in a consistent and reliable manner. Following are the different ways of performing reconciliation. + +**Event-driven reconciliation** + +Event-driven reconciliation provides a reactive approach, allowing the operator to respond quickly to the changes or events impacting the server. The operator reacts to specific events or triggers related to the server. Events can be generated by changes in the server's environment, incoming requests, or other relevant signals. The operator listens for these events and initiates reconciliation actions in response. + +**Time-based triggers** + +The reconciliation process is triggered at fixed intervals or specific points in time. The BMC Operator uses a timer to periodically initiate the reconciliation logic. This approach ensures regular checks and updates to the server's state, regardless of external events or changes. + + + +## Reconciliation operations + +**Accommodating configuration changes** + +When a desired configuration change is detected during reconciliation in Resource Aggregator for ODIM, the BMC Operator applies the changes to the Kubernetes objects. The BMC Operator ensures that the server configuration is updated according to the desired state specified by the system. This involves making API calls to Resource Aggregator for ODIM to change the object settings. + +**Reverting to prior configuration** + +The BMC Operator can also revert the server configuration back to Resource Aggregator for ODIM, undoing the changes that were attempted. If the object configuration is different from that of the configuration in Resource Aggregator for ODIM, it automatically reverts the configuration in Resource Aggregator for ODIM similar to the configuration present in the Kubernetes object. Reverting to the previous configuration ensures that the server remains in a consistent and functioning state. + +> **NOTE**: You can specify the reconciliation configuration using the parameter `data.reconciliation` in `config/keys/config.yaml` file. + + + +### BMC reconciliation use cases + +**Accommodate** + +When you add a BMC in Resource Aggregator for ODIM and if its object is not present in BMC Operator, the reconciliation process automatically creates the BMC object in BMC Operator, when triggered. + +**Revert** + +When BMC object is present in BMC Operator and if the BMC is not added in Resource Aggregator for ODIM, the reconciliation process automatically adds the BMC in Resource Aggregator for ODIM, when triggered. + +### BIOS reconciliation use cases + +**Accommodate** + +In case of any BIOS configuration changes made in Resource Aggregator for ODIM, the same configuration changes are applied in BIOS object in the BMC Operator. + +**Revert** + +In case of a mismatch/change in BIOS configurations between the operator object and Resource Aggregator for ODIM, the changes in the operator must be applied on Resource Aggregator for ODIM. + + + +### Volume reconciliation use cases + +**Accommodate** + +In case of any volume configuration changes made in Resource Aggregator for ODIM, the same configuration changes are applied in volume object in the BMC Operator. + +**Revert** + +In case of a mismatch/change in volume configurations between the operator object and Resource Aggregator for ODIM, the changes in the operator must be applied on Resource Aggregator for ODIM. + + + +### Firmware reconciliation use cases + +**Accommodate** + +In case of any firmware configuration changes made in Resource Aggregator for ODIM, the same configuration changes are applied in firmware object in the BMC Operator. + +**Revert** + +In case of a mismatch/change in firmware configurations between the operator object and Resource Aggregator for ODIM, the changes in the operator must be applied on Resource Aggregator for ODIM. + + + +# Using APIs in BMC Operator + +Besides the kubectl tool, you can also use APIs to perform the BMC Operator operations using curl commands. Curl is a command-line tool that helps you get or send information through URLs using supported protocols. + +The BMC Operator APIs are designed as per DMTF's *[Redfish® Scalable Platforms API (Redfish) specification 1.15.1](https://www.dmtf.org/sites/default/files/standards/documents/DSP0266_1.15.1.pdf)* and are Redfish-compliant. The Redfish® standard is a suite of specifications that deliver an industry standard protocol providing a RESTful interface for the simple and secure management of servers, storage, networking, multivendor, converged and hybrid IT infrastructure. Redfish uses JSON and OData. + +For more information on using curl commands and APIs, see *API usage and access guidelines* section in [*Resource Aggregator for Open Distributed Infrastructure Management™ API Readme*](https://github.com/ODIM-Project/ODIM/blob/development/docs/README.md). + + + +## Prerequisites + +Run the following commands on the deployment node to get the data needed for authentication: + +``` +cat $HOME/.kube/config | yq -r '.clusters[0].cluster.certificate-authority-data' | base64 -d >ca-cert.crt +``` + +``` +cat $HOME/.kube/config | yq -r '.users[0].user.client-key-data' |base64 -d > key.crt +``` + +``` +cat $HOME/.kube/config | yq -r '.users[0].client-certificate-data' |base64 -d > cert.crt +``` + + + +## Sending API requests + +To send API service requests to access any resource in BMC Operator, use the following syntax: + +``` +curl -k --key --cert https://{api_server}/apis/{group}/{version}/namespaces/{namespace}/{plural form of resource} +``` + +| Properties | Description | +| ------------ | ------------------------------------------------------------ | +| {api_server} | IP address and port of the cluster node, each separated by a colon.
For example: `xxx.xxx.xxx.xxx:6443` | +| {group} | Group to which the BMC objects belong. | +| {version} | API version of the objects. | +| {namespace} | Namespace where the BMC Operator is installed. | +| {plural} | Plural form of the resource.
For example: For bmc resource, it is `bmcs`. | + +> **NOTE**: If you want to access APIs globally, you don't need to specify namespace in the curl commands. + + + +### Examples of API requests + +#### Viewing the list of all BMC resources in a specific namespace + +``` +curl https://xxx.xxx.xxx.xxx:6443/apis/infra.io.odimra/v1/namespaces/bmc-op/bmcs/ -k --cert cert.crt --key key.crt +``` + +**Sample response body** + +``` +{ + "apiVersion": "infra.io.odimra/v1", + "items": [ + { + "apiVersion": "infra.io.odimra/v1", + "kind": "Bmc", + "metadata": { + "annotations": { + "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"infra.io.odimra/v1\",\"kind\":\"Bmc\",\"metadata\":{\"annotations\":{},\"name\":\"xxx.xxx.xxx.xxx\",\"namespace\":\"server-op\"},\"spec\":{\"bmc\":{\"address\":\"xxx.xxx.xxx.xxx\",\"connectionMethodVariant\":\"Compute:BasicAuth:ILO_v2.0.0\",\"powerState\":\"\",\"resetType\":\"\"},\"credentials\":{\"password\":\"*********\",\"username\":\"admin\"}}}\n", + "old_password": "JNDeO1ZaifEqxnXpY7+u9gb0ThWl0Uy959JKpEtQm1dkkguvDHEoMx1klTyMzXSi/+DiNPc246R4v7qVnYMisyse/KnFxEhAhHfVDGZyl0+kWQCe/oHzJEJ4P3U5g90Lxln9T0iMdJTXtLhI/Q70g37iX+fM42iL/a1VuBPXWL9S90JiPaFgxOus/pzVugT+0x52Cm1MBgO0AbdRaV5pFsat+Xpw7duqujASbQzJfAN0KNs55Ap4jj3MX87SFV4NurvVqaZWJ3E9l2Hcuu0IqsmBp/3ijcZDZY+eqx1cml4obmLkYCtXQF8sFwEKLvqjO/F76RPPLx3fgavpvmmwnGUWZAMw/gW19IB65wnanc/7/EAVHFvr/fswnXz0zP1N0Cn/DxvsfHhPxtqk+kfZtnNtsFh8ZGfztO5MRyXbOUeNuLb+vd4RlGXlF4L+5ub17eyrwrlnx+rcMRbIExIMC3ighBC7uStJ8c2Pac2wORMAJNOPBweHEqFOqcx5oM7WQT1SVWMxcGNluLQ523sLYeCoLIi/HUdadkYO+3X38ARUtMbbHavDhJfAYSMp6iAoOTu+EMv6waLcWn9v0KVF0t0AFInVdWjyMAvecQ1BmdCVoN3s4yQrXE/osimsQp47RUXN3NHB5O6QK8hq1OawZHTx1QkQsOv38IR+ynkmBCM=" + }, + "creationTimestamp": "2023-07-12T12:15:27Z", + "deletionGracePeriodSeconds": 0, + "deletionTimestamp": "2023-07-13T07:08:55Z", + "finalizers": [ + "infra.io.bmc/finalizer" + ], + "generation": 3, + "labels": { + "firmwareVersion": "iLO-5-v2.70", + "modelId": "ProLiant.DL360.Gen10", + "name": "xxx.xxx.xxx.xxx", + "serialNo": "MXQ92104V7", + "systemId": "07320501-c983-4768-8bd3-8752bf99f717.1", + "vendor": "HPE" + }, + "managedFields": [ + { + "apiVersion": "infra.io.odimra/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:annotations": { + ".": {}, + "f:kubectl.kubernetes.io/last-applied-configuration": {} + } + }, + "f:spec": { + ".": {}, + "f:bmc": { + ".": {}, + "f:address": {}, + "f:connectionMethodVariant": {}, + "f:powerState": {}, + "f:resetType": {} + }, + "f:credentials": { + ".": {}, + "f:username": {} + } + } + }, + "manager": "kubectl-client-side-apply", + "operation": "Update", + "time": "2023-07-12T12:15:27Z" + }, + { + "apiVersion": "infra.io.odimra/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:status": { + ".": {}, + "f:biosAttributeRegistry": {}, + "f:biosVersion": {}, + "f:bmcAddStatus": {}, + "f:bmcSystemId": {}, + "f:firmwareVersion": {}, + "f:modelID": {}, + "f:serialNumber": {}, + "f:storageControllers": { + ".": {}, + "f:ArrayControllers-0": { + ".": {}, + "f:drives": { + ".": {}, + "f:0": { + ".": {}, + "f:capacityBytes": {}, + "f:usedInVolumes": {} + }, + "f:1": { + ".": {}, + "f:capacityBytes": {}, + "f:usedInVolumes": {} + }, + "f:2": { + ".": {}, + "f:capacityBytes": {}, + "f:usedInVolumes": {} + }, + "f:3": { + ".": {}, + "f:capacityBytes": {}, + "f:usedInVolumes": {} + }, + "f:4": { + ".": {}, + "f:capacityBytes": {}, + "f:usedInVolumes": {} + } + }, + "f:supportedRAIDLevel": {} + } + }, + "f:systemReset": {}, + "f:vendorName": {} + } + }, + "manager": "main", + "operation": "Update", + "subresource": "status", + "time": "2023-07-12T12:18:15Z" + }, + { + "apiVersion": "infra.io.odimra/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:annotations": { + "f:old_password": {} + }, + "f:finalizers": { + ".": {}, + "v:\"infra.io.bmc/finalizer\"": {} + }, + "f:labels": { + ".": {}, + "f:firmwareVersion": {}, + "f:modelId": {}, + "f:name": {}, + "f:serialNo": {}, + "f:systemId": {}, + "f:vendor": {} + } + }, + "f:spec": { + "f:credentials": { + "f:password": {} + } + } + }, + "manager": "main", + "operation": "Update", + "time": "2023-07-12T12:19:25Z" + } + ], + "name": "xxx.xxx.xxx.xxx", + "namespace": "server-op", + "resourceVersion": "2750179", + "uid": "7355b4ce-c729-4096-b0e9-a95e181f4016" + }, + "spec": { + "bmc": { + "address": "xxx.xxx.xxx.xxx", + "connectionMethodVariant": "Compute:BasicAuth:ILO_v2.0.0", + "powerState": "", + "resetType": "" + }, + "credentials": { + "password": "JNDeO1ZaifEqxnXpY7+u9gb0ThWl0Uy959JKpEtQm1dkkguvDHEoMx1klTyMzXSi/+DiNPc246R4v7qVnYMisyse/KnFxEhAhHfVDGZyl0+kWQCe/oHzJEJ4P3U5g90Lxln9T0iMdJTXtLhI/Q70g37iX+fM42iL/a1VuBPXWL9S90JiPaFgxOus/pzVugT+0x52Cm1MBgO0AbdRaV5pFsat+Xpw7duqujASbQzJfAN0KNs55Ap4jj3MX87SFV4NurvVqaZWJ3E9l2Hcuu0IqsmBp/3ijcZDZY+eqx1cml4obmLkYCtXQF8sFwEKLvqjO/F76RPPLx3fgavpvmmwnGUWZAMw/gW19IB65wnanc/7/EAVHFvr/fswnXz0zP1N0Cn/DxvsfHhPxtqk+kfZtnNtsFh8ZGfztO5MRyXbOUeNuLb+vd4RlGXlF4L+5ub17eyrwrlnx+rcMRbIExIMC3ighBC7uStJ8c2Pac2wORMAJNOPBweHEqFOqcx5oM7WQT1SVWMxcGNluLQ523sLYeCoLIi/HUdadkYO+3X38ARUtMbbHavDhJfAYSMp6iAoOTu+EMv6waLcWn9v0KVF0t0AFInVdWjyMAvecQ1BmdCVoN3s4yQrXE/osimsQp47RUXN3NHB5O6QK8hq1OawZHTx1QkQsOv38IR+ynkmBCM=", + "username": "admin" + } + }, + "status": { + "biosAttributeRegistry": "BiosAttributeRegistryU32.v1_2_68", + "biosVersion": "U32 v2.68 (07/14/2022)", + "bmcAddStatus": "yes", + "bmcSystemId": "07320501-c983-4768-8bd3-8752bf99f717.1", + "firmwareVersion": "iLO 5 v2.70", + "modelID": "ProLiant DL360 Gen10", + "serialNumber": "MXQ92104V7", + "storageControllers": { + "ArrayControllers-0": { + "drives": { + "0": { + "capacityBytes": "1200000000000.000000", + "usedInVolumes": [ + 1 + ] + }, + "1": { + "capacityBytes": "1200000000000.000000", + "usedInVolumes": [ + 2 + ] + }, + "2": { + "capacityBytes": "1200000000000.000000", + "usedInVolumes": [ + 3 + ] + }, + "3": { + "capacityBytes": "1200000000000.000000", + "usedInVolumes": [ + 4 + ] + }, + "4": { + "capacityBytes": "1200000000000.000000", + "usedInVolumes": [] + } + }, + "supportedRAIDLevel": [ + "RAID0", + "RAID1", + "RAID3", + "RAID4", + "RAID5", + "RAID6", + "RAID10", + "RAID01", + "RAID6TP", + "RAID1E", + "RAID50", + "RAID60", + "RAID00", + "RAID10E", + "RAID1Triple", + "RAID10Triple", + "None" + ] + } + }, + "systemReset": "", + "vendorName": "HPE" + } + } + ], + "kind": "BmcList", + "metadata": { + "continue": "", + "resourceVersion": "2773500" + } +} +``` + + + +#### Viewing individual BMC resources + +``` +curl https://xxx.xxx.xxx.xxx:6443/apis/infra.io.odimra/v1/namespaces/bmc-op/bmcs/xxx.xxx.xxx.xxx -k --cert cert.crt --key key.crt +``` + +**Sample response body** + +``` +{ + "apiVersion": "infra.io.odimra/v1", + "kind": "Bmc", + "metadata": { + "annotations": { + "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"infra.io.odimra/v1\",\"kind\":\"Bmc\",\"metadata\":{\"annotations\":{},\"name\":\"xxx.xxx.xxx.xxx\",\"namespace\":\"server-op\"},\"spec\":{\"bmc\":{\"address\":\"xxx.xxx.xxx.xxx\",\"connectionMethodVariant\":\"Compute:BasicAuth:ILO_v2.0.0\",\"powerState\":\"\",\"resetType\":\"\"},\"credentials\":{\"password\":\"*******\",\"username\":\"admin\"}}}\n", + "old_password": "JNDeO1ZaifEqxnXpY7+u9gb0ThWl0Uy959JKpEtQm1dkkguvDHEoMx1klTyMzXSi/+DiNPc246R4v7qVnYMisyse/KnFxEhAhHfVDGZyl0+kWQCe/oHzJEJ4P3U5g90Lxln9T0iMdJTXtLhI/Q70g37iX+fM42iL/a1VuBPXWL9S90JiPaFgxOus/pzVugT+0x52Cm1MBgO0AbdRaV5pFsat+Xpw7duqujASbQzJfAN0KNs55Ap4jj3MX87SFV4NurvVqaZWJ3E9l2Hcuu0IqsmBp/3ijcZDZY+eqx1cml4obmLkYCtXQF8sFwEKLvqjO/F76RPPLx3fgavpvmmwnGUWZAMw/gW19IB65wnanc/7/EAVHFvr/fswnXz0zP1N0Cn/DxvsfHhPxtqk+kfZtnNtsFh8ZGfztO5MRyXbOUeNuLb+vd4RlGXlF4L+5ub17eyrwrlnx+rcMRbIExIMC3ighBC7uStJ8c2Pac2wORMAJNOPBweHEqFOqcx5oM7WQT1SVWMxcGNluLQ523sLYeCoLIi/HUdadkYO+3X38ARUtMbbHavDhJfAYSMp6iAoOTu+EMv6waLcWn9v0KVF0t0AFInVdWjyMAvecQ1BmdCVoN3s4yQrXE/osimsQp47RUXN3NHB5O6QK8hq1OawZHTx1QkQsOv38IR+ynkmBCM=" + }, + "creationTimestamp": "2023-07-12T12:15:27Z", + "deletionGracePeriodSeconds": 0, + "deletionTimestamp": "2023-07-13T07:08:55Z", + "finalizers": [ + "infra.io.bmc/finalizer" + ], + "generation": 3, + "labels": { + "firmwareVersion": "iLO-5-v2.70", + "modelId": "ProLiant.DL360.Gen10", + "name": "xxx.xxx.xxx.xxx", + "serialNo": "MXQ92104V7", + "systemId": "07320501-c983-4768-8bd3-8752bf99f717.1", + "vendor": "HPE" + }, + "managedFields": [ + { + "apiVersion": "infra.io.odimra/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:annotations": { + ".": {}, + "f:kubectl.kubernetes.io/last-applied-configuration": {} + } + }, + "f:spec": { + ".": {}, + "f:bmc": { + ".": {}, + "f:address": {}, + "f:connectionMethodVariant": {}, + "f:powerState": {}, + "f:resetType": {} + }, + "f:credentials": { + ".": {}, + "f:username": {} + } + } + }, + "manager": "kubectl-client-side-apply", + "operation": "Update", + "time": "2023-07-12T12:15:27Z" + }, + { + "apiVersion": "infra.io.odimra/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:status": { + ".": {}, + "f:biosAttributeRegistry": {}, + "f:biosVersion": {}, + "f:bmcAddStatus": {}, + "f:bmcSystemId": {}, + "f:firmwareVersion": {}, + "f:modelID": {}, + "f:serialNumber": {}, + "f:storageControllers": { + ".": {}, + "f:ArrayControllers-0": { + ".": {}, + "f:drives": { + ".": {}, + "f:0": { + ".": {}, + "f:capacityBytes": {}, + "f:usedInVolumes": {} + }, + "f:1": { + ".": {}, + "f:capacityBytes": {}, + "f:usedInVolumes": {} + }, + "f:2": { + ".": {}, + "f:capacityBytes": {}, + "f:usedInVolumes": {} + }, + "f:3": { + ".": {}, + "f:capacityBytes": {}, + "f:usedInVolumes": {} + }, + "f:4": { + ".": {}, + "f:capacityBytes": {}, + "f:usedInVolumes": {} + } + }, + "f:supportedRAIDLevel": {} + } + }, + "f:systemReset": {}, + "f:vendorName": {} + } + }, + "manager": "main", + "operation": "Update", + "subresource": "status", + "time": "2023-07-12T12:18:15Z" + }, + { + "apiVersion": "infra.io.odimra/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:annotations": { + "f:old_password": {} + }, + "f:finalizers": { + ".": {}, + "v:\"infra.io.bmc/finalizer\"": {} + }, + "f:labels": { + ".": {}, + "f:firmwareVersion": {}, + "f:modelId": {}, + "f:name": {}, + "f:serialNo": {}, + "f:systemId": {}, + "f:vendor": {} + } + }, + "f:spec": { + "f:credentials": { + "f:password": {} + } + } + }, + "manager": "main", + "operation": "Update", + "time": "2023-07-12T12:19:25Z" + } + ], + "name": "xxx.xxx.xxx.xxx", + "namespace": "bmc-op", + "resourceVersion": "2750179", + "uid": "7355b4ce-c729-4096-b0e9-a95e181f4016" + }, + "spec": { + "bmc": { + "address": "xxx.xxx.xxx.xxx", + "connectionMethodVariant": "Compute:BasicAuth:ILO_v2.0.0", + "powerState": "", + "resetType": "" + }, + "credentials": { + "password": "JNDeO1ZaifEqxnXpY7+u9gb0ThWl0Uy959JKpEtQm1dkkguvDHEoMx1klTyMzXSi/+DiNPc246R4v7qVnYMisyse/KnFxEhAhHfVDGZyl0+kWQCe/oHzJEJ4P3U5g90Lxln9T0iMdJTXtLhI/Q70g37iX+fM42iL/a1VuBPXWL9S90JiPaFgxOus/pzVugT+0x52Cm1MBgO0AbdRaV5pFsat+Xpw7duqujASbQzJfAN0KNs55Ap4jj3MX87SFV4NurvVqaZWJ3E9l2Hcuu0IqsmBp/3ijcZDZY+eqx1cml4obmLkYCtXQF8sFwEKLvqjO/F76RPPLx3fgavpvmmwnGUWZAMw/gW19IB65wnanc/7/EAVHFvr/fswnXz0zP1N0Cn/DxvsfHhPxtqk+kfZtnNtsFh8ZGfztO5MRyXbOUeNuLb+vd4RlGXlF4L+5ub17eyrwrlnx+rcMRbIExIMC3ighBC7uStJ8c2Pac2wORMAJNOPBweHEqFOqcx5oM7WQT1SVWMxcGNluLQ523sLYeCoLIi/HUdadkYO+3X38ARUtMbbHavDhJfAYSMp6iAoOTu+EMv6waLcWn9v0KVF0t0AFInVdWjyMAvecQ1BmdCVoN3s4yQrXE/osimsQp47RUXN3NHB5O6QK8hq1OawZHTx1QkQsOv38IR+ynkmBCM=", + "username": "admin" + } + }, + "status": { + "biosAttributeRegistry": "BiosAttributeRegistryU32.v1_2_68", + "biosVersion": "U32 v2.68 (07/14/2022)", + "bmcAddStatus": "yes", + "bmcSystemId": "07320501-c983-4768-8bd3-8752bf99f717.1", + "firmwareVersion": "iLO 5 v2.70", + "modelID": "ProLiant DL360 Gen10", + "serialNumber": "MXQ92104V7", + "storageControllers": { + "ArrayControllers-0": { + "drives": { + "0": { + "capacityBytes": "1200000000000.000000", + "usedInVolumes": [ + 1 + ] + }, + "1": { + "capacityBytes": "1200000000000.000000", + "usedInVolumes": [ + 2 + ] + }, + "2": { + "capacityBytes": "1200000000000.000000", + "usedInVolumes": [ + 3 + ] + }, + "3": { + "capacityBytes": "1200000000000.000000", + "usedInVolumes": [ + 4 + ] + }, + "4": { + "capacityBytes": "1200000000000.000000", + "usedInVolumes": [] + } + }, + "supportedRAIDLevel": [ + "RAID0", + "RAID1", + "RAID3", + "RAID4", + "RAID5", + "RAID6", + "RAID10", + "RAID01", + "RAID6TP", + "RAID1E", + "RAID50", + "RAID60", + "RAID00", + "RAID10E", + "RAID1Triple", + "RAID10Triple", + "None" + ] + } + }, + "systemReset": "", + "vendorName": "HPE" + } +} +``` + + + +#### Adding a BMC + +``` +curl -X POST https://xxx.xxx.xxx.xxx:6443/apis/infra.io.odimra/v1/namespaces/bmc-op/bmcs \ + -H "Content-Type: application/json" \ + -k --cert cert.crt --key key.crt \ + -d ' { + "kind": "Bmc", + "apiVersion": "infra.io.odimra/v1", + "metadata": { + "name": "xxx.xxx.xxx.xxx", + "namespace": "bmc-op" + }, + "spec": { + "bmc": { + "address": "xxx.xxx.xxx.xxx", + "connectionMethodVariant": "Compute:BasicAuth:ILO_v2.0.0", + "powerState": "", + "resetType": "" + }, + "credentials": { + "password": "********", + "username": "administrator" + } + } + }' +``` + +**Sample response body** + +``` +{ + "apiVersion": "infra.io.odimra/v1", + "kind": "Bmc", + "metadata": { + "creationTimestamp": "2023-07-13T07:20:05Z", + "generation": 1, + "managedFields": [ + { + "apiVersion": "infra.io.odimra/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:spec": { + ".": {}, + "f:bmc": { + ".": {}, + "f:address": {}, + "f:connectionMethodVariant": {}, + "f:powerState": {}, + "f:resetType": {} + }, + "f:credentials": { + ".": {}, + "f:password": {}, + "f:username": {} + } + } + }, + "manager": "curl", + "operation": "Update", + "time": "2023-07-13T07:20:05Z" + } + ], + "name": "xxx.xxx.xxx.xxx", + "namespace": "bmc-op", + "resourceVersion": "2751199", + "uid": "e321eec9-8346-48c5-aaa6-4a73fe6ea753" + }, + "spec": { + "bmc": { + "address": "xxx.xxx.xxx.xxx", + "connectionMethodVariant": "Compute:BasicAuth:ILO_v2.0.0", + "powerState": "", + "resetType": "" + }, + "credentials": { + "password": "********", + "username": "administrator" + } + } +``` + + + +#### Deleting a BMC + +``` +curl -kX DELETE https://xxx.xxx.xxx.xxx:6443/apis/infra.io.odimra/v1/namespaces/bmc-op/bmcs/xxx.xxx.xxx.xxx --cert cert.crt --key key.crt + +``` + +**Sample response body** + +``` +{ + "kind": "Status", + "apiVersion": "v1", + "metadata": {}, + "status": "Success", + "details": { + "name": "xxx.xxx.xxx.xxx", + "group": "infra.io.odimra", + "kind": "bmcs", + "uid": "e321eec9-8346-48c5-aaa6-4a73fe6ea753" +} +``` + +> **NOTE**: If you do not want to use -k in the API requests, get the cacert from the configuration file and run the following command: + +``` +curl https://xxx.xxx.xxx.xxx:6443/apis/infra.io.odimra/v1/namespaces/bmc-op/bmcs/xxx.xxx.xxx.xxx --cert cert.crt --key key.crt --cacert ca.crt +``` + + + +# Uninstalling BMC Operator + + +1. Navigate to the home directory of bmc-operator: + + ``` + cd /home/{user}/bmc-operator + ``` + +2. Make sure you have deleted all bmc files you applied. See *[Deleting a BMC](#deleting-a-bmc)* for instructions. + +3. Delete the Resource Aggregator for ODIM connection. + + ``` + kubectl delete -f bmc-templates/odim.yaml + ``` + +4. Undeploy the bmc-operator: + + ``` + make undeploy + ``` + +5. Delete PVC and PV: + + ``` + kubectl delete -f bmc-templates/pvs/bmc_pvc.yaml + ``` + + ``` + kubectl delete -f bmc-templates/pvs/bmc_pv.yaml + ``` + + + +# Contributing to the open source community + +Welcome to the GitHub open source community for BMC Operator! + +If you want to contribute to the project to make it better, your help is welcome and highly appreciated. +Contribution is a great way of extending the understanding and value of open-source software and development models, towards a common goal. Apart from learning more about social coding on GitHub, new technologies and their ecosystems, you can keep the discussion forums active by sharing knowledge, asking right questions, finding information through effective collaborations as well as make constructive, helpful bug reports, feature requests, and the noblest of all contributions—a good, clean pull request (PR). +All bugs or feature requests must be submitted through a PR to the development branch and are expected to have unit tests and/or integration tests with the PR. + +## Creating a PR + +> **PREREQUISITE**: Follow the project's contribution instructions, if any. + +1. Clone the project repository on your local machine. + +2. Pull the development branch updates into your local repository. + +3. Create a new branch from development to work on. + +4. Implement/fix your feature, comment your code. + +5. Follow the code style of the project, including indentation. + +6. If the project has tests, run them! + +7. Write or adapt tests as needed. + +8. Add or change the documentation as needed. + +9. Commit your changes. + +10. Create a PR in your branch. Target the project's development branch as the base branch. + +11. Once the pull request is approved and merged, pull the changes from development branch to your local repository. + + > **TIP**: Write your commit messages in the present tense. Your commit message should describe what the commit is, when is it applied, and what it does to the code. + + + +## Filing BMC Operator defects + +In case of any unforeseen issues you experience while deploying or using BMC Operator, log on to the following website and file your defect by clicking **Create**. + +> **PREREQUISITE**: You must have valid LFN Jira credentials to create defects. + +- Website: *https://jira.lfnetworking.org/secure/Dashboard.jspa* + +- Discussion Forums: *https://odim.slack.com/archives/C01DG9MH479* + + + +## Licensing + +The specification and code is licensed under the Apache 2.0 license, and is found in the LICENSE file of this repository. + + + +## Reference links + +If you want to make your first contribution on GitHub, refer one of the following procedures: + +- *https://github.com/firstcontributions/first-contributions/blob/main/README.md* + +- *https://www.dataschool.io/how-to-contribute-on-github/* + + + +# Sample configuration files + +This section comprises configuration files of the BMC Operator resources with sample values. + +## Sample BMC settings file + +``` +apiVersion: infra.io.odimra/v1 +kind: BMC +metadata: + name: xxx.xxx.xxx.xxx + namespace: bmc-operator +spec: + bmc: + address: xxx.xxx.xxx.xxx + connectionMethodVariant: Compute:BasicAuth:ILO_v2.0.0 + powerState: On #by default it will be null, we need to update k8 object for reset action + resetType: On #by default it will be null, we need to update k8 object for reset action + credentials: + username: bmc_user + password: ************ +``` + + ## Sample BIOS settings file + + ``` + apiVersion: infra.io.odimra/v1 + kind: BiosSetting + metadata: + name: xxx.xxx.xxx.xxx + namespace: bmc-operator + spec: + bmcName: xxx.xxx.xxx.xxx + systemID: d48cf31a-7c6e-4990-a9fc-439dc2e0ca1e.1 + serialNumber: 2M2129034R # Specify any one of the values - bmcName, systemID, or serialNumber + biosAttributes: + BootMode: "LegacyBios" + AcpiHpet: "Enabled" + ``` + +## Sample boot order settings file + +``` +apiVersion: infra.io.odimra/v1 +kind: BootOrderSettings +metadata: + name: xxx.xxx.xxx.xxx + namespace: bmc-operator +spec: + bmcName: xxx.xxx.xxx.xxx + systemID: d48cf31b-7c6e-4990-a9fc-439dc2e0ca1e.1 + serialNumber: 2M2129034R + boot: + bootOrder: ["Usb","Hdd","Cd","Pxe"] + bootSourceOverrideTarget: "Hdd" + uefiTargetBootSourceOverride: "None" + bootSourceOverrideEnabled: "Once" +``` + +> NOTE: Specifying any one of the values `bmcName`, `systemID`, or `serialNumber` is mandatory. + + +## Sample volume file + +``` +apiVersion: infra.io.odimra/v1 +kind: Volume +metadata: + name: xxx.xxx.xxx.xxx.volume_name + namespace: bmc-operator +spec: + storageControllerID: ArrayControllers-0 + RAIDType: RAID0 + drives: [1] +``` + + +## Sample firmware file + +``` +apiVersion: infra.io.odimra/v1 +kind: Firmware +metadata: + name: xxx.xxx.xxx.xxx + namespace: bmc-operator +spec: + image: + imageLocation: "http://xxx.xxx.xxx.xxx/ISO/ilo5_248.bin" + auth: + username: bmc_user + password: ************ +``` + +## Sample event subscription file + +``` +apiVersion: infra.io.odimra/v1 +kind: Eventsubscription +metadata: + name: bmc_name + namespace: bmc-operator +spec: + name: event-sub-client + destination: 'https://xxx.xxx.xxx.xxx:{port}/Destination' + eventTypes: + - Alert + - ResourceAdded + - ResourceRemoved + - ResourceUpdated + messageIds: + resourceTypes: + - ComputerSystem + context: ODIMRA_Event + eventFormatType: Event + subordinateResources: true + originResources: + - 'Bmc/xxx.xxx.xxx.xxx' + - 'BiosSetting/xxx.xxx.xxx.xxx' +``` + + + +# Sample output properties + +This section describes important properties, mainly the ones in the `status` property of the sample output files. + +## BMC addition output + +| Parameter | Description | +| --------------------- | ------------------------------------------------------------ | +| old_password | Stores the current working password. | +| labels | Key value pairs for bmc object to filter out the object | +| biosAttributeRegistry | Name of the BIOS schema registry. | +| biosVersion | Current BIOS version. | +| bmcAddStatus | Status message of whether the BMC object is added or not. Values are `yes` and `no`. | +| bmcSystemId | System ID of the BMC system. | +| firmwareVersion | Current firmware version. | +| modelID | Model ID of the BMC server. | +| serialNumber | Serial number of the system\. | +| storageControllers | Details of array controllers and supported RAID level. | +| systemReset | Status message of whether the system has been reset or not. | +| vendorName | Name of the vendor of the server. | + +## Boot order settings output + +| Parameter | Description | +| ---------------------------------------- | ------------------------------------------------------------ | +| boot.bootOrder | You can change the boot order in the array.
**For example**: In `["Cd", "Usb", "Hdd", "Pxe"]`, you can re-arrange the boot order values as required. | +| bootSourceOverrideEnabled | You can enable or disable the state of the boot source override feature. Values are `Enabled` or`Disabled`. | +| bootSourceOverrideMode | The BIOS boot mode you want to use when the system boots. Values of the modes of BIOS boot source override feature are `Legacy` or `UEFI`. | +| bootSourceOverrideTarget | The current boot source to use at the next boot instead of the normal boot device, if BootSourceOverrideEnabled is `Enabled`. | +| bootSourceOverrideTarget.AllowableValues | The @redfish.AllowableValues annotation specifies the valid values for this property. `UefiTarget` indicates to boot from the UEFI device path found in `UefiTargetBootSourceOverride`. `UefiBootNext` indicates to boot from the `UEFI BootOptionReference`. | + +## Volume addition output + +| Parameter | Description | +| ------------------- | ------------------------------------------------------------ | +| Identifiers | Can include array of objects such as `DurableName`, `DurableNameFormat` and so on. | +| RAIDType | Duplication mechanism of storing data to protect data in the case of a drive failure. | +| capacityBytes | Volume capacity in bytes. | +| drives | Links to all the drives. | +| storageControllerID | Storage controller ID. | +| volumeID | ID of the volume added. | +| volumeName | Name of the volume added. | + +The values of these parameters are picked from the Resource Aggregator for ODIM responses and are displayed here. + +## Firmware operation output + +| Parameter | Description | +| --------------- | ------------------------------------------------------------ | +| firmwareVersion | Current firmware version. | +| imagePath | Path of the firmware image. | +| status | Status of the operation whether it was a success or failure. | + +## Event subscription output + +| Parameter | Description | +| ------------------- | ------------------------------------------------------------ | +| eventSubscriptionID | ID of the event subscription. | +| name | Name of the subscription. | +| context | A string that is stored with the event destination subscription. | +| destination | The URL of the destination event listener that is listening to events. | +| protocol | The protocol type of the event connection. | +| eventTypes | The types of events that are received on the destination. | +| messageIDs | The key used to find the message in a Message Registry. | +| subscriptionType | Subscription type for events. | +| resourceTypes | The list of resource type values that correspond to the OriginResources. | +| originResources | Resources for which the event listener will receive related events | + + + +# Troubleshooting BMC Operator issues + +This section helps you troubleshoot any common issues you might experience while deploying and using BMC Operator. Troubleshooting information is listed in the form of Questions and Answers. You can also find answers to some of the Frequently Asked Questions related to BMC Operator. + +Questions and the associated error messages are in **bold** font. Solutions, workarounds and answers are in regular font. + +**1. Undeployment of the BMC Operator fails. Running the `make undeploy` command freezes your system.** + +**Solution** + +You experience this issue if you do not delete the BMC prior to undeploying it. Delete the BMC by running the following command: + +``` +kubectl delete -f bmc-templates/bmc.yaml +``` + +**Additional information**: + +The BMC added through API request cannot be deleted because the operator will not have its details. You get the following message: + +``` +Bmc already Added!! +``` + +In such instances, force delete the BMC by performing the following tasks: + +1. Edit the bmc object and remove finalizer. + + ``` + kubectl edit bmc -n{namespace} {bmc_object_name} + ``` + +2. Delete the following lines: + + ``` + finalizers: + - infra.io.bmc/finalizer + ``` + +3. Delete the `bmc.yaml` file. + + ``` + kubectl delete -f bmc-templates/bmc.yaml + ``` + + BMC object is deleted and you can undeploy it. + +------ + +**2. Applying boot order settings operation crashes and I get the following log:** + + + ``` + <14>1 2023-08-25T02:18:42Z mas22 bmc-operator bmc-opcontroller-manager-659b59dd47-ltknx_7 BootOrderSettings [process@1 processName="bmc-opcontroller-manager-659b59dd47-ltknx" transactionID="e4dbc641-8872-420a-8239-ed793c285ac8" actionID="003" actionName="BootOrderSettings" threadID="0" threadName="bmc-operator"] Creating new rest client for ODIM + <14>1 2023-08-25T02:18:42Z mas22 bmc-operator bmc-opcontroller-manager-659b59dd47-ltknx_7 BootOrderSettings [process@1 processName="bmc-opcontroller-manager-659b59dd47-ltknx" transactionID="e4dbc641-8872-420a-8239-ed793c285ac8" actionID="003" actionName="BootOrderSettings" threadID="0" threadName="bmc-operator"] Set boot order settings for BMC + panic: runtime error: invalid memory address or nil pointer dereference + [signal SIGSEGV: segmentation violation code=0x1 addr=0x20 pc=0x1336844] + + goroutine 445 [running]: + github.com/ODIM-Project/BMCOperator/controllers/boot.(*bootUtils).UpdateBootDetails(0xc0009cfc30, {0x1b11c98, 0xc00096b6b0}, {0xc0000437d0, 0x26}, {0x0, 0x0}, 0xc0004e83c0, 0x0) + /bmc-operator/controllers/boot/bootordersettings_controller.go:188 +0x5a4 + github.com/ODIM-Project/BMCOperator/controllers/boot.(*bootUtils).updateBootSettings(0xc0009cfc30) + /bmc-operator/controllers/boot/bootordersettings_controller.go:121 +0x245 + github.com/ODIM-Project/BMCOperator/controllers/boot.(*BootOrderSettingsReconciler).Reconcile(0xc0005fa2a0, {0x1b11c98, 0xc00096b590}, {{{0xc00047b690?, 0x17903c0?}, {0xc00047b680?, 0x30?}}}) + /bmc-operator/controllers/boot/bootordersettings_controller.go:87 +0x525 + sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller).Reconcile(0xc0001b5130, {0x1b11c98, 0xc00096b560}, {{{0xc00047b690?, 0x17903c0?}, {0xc00047b680?, 0x4045d4?}}}) + /go/pkg/mod/sigs.k8s.io/controller-runtime@v0.11.0/pkg/internal/controller/controller.go:114 +0x28b + sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller).reconcileHandler(0xc0001b5130, {0x1b11bf0, 0xc0004fc100}, {0x16c62e0?, 0xc000138e20?}) + /go/pkg/mod/sigs.k8s.io/controller-runtime@v0.11.0/pkg/internal/controller/controller.go:311 +0x352 + sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller).processNextWorkItem(0xc0001b5130, {0x1b11bf0, 0xc0004fc100}) + /go/pkg/mod/sigs.k8s.io/controller-runtime@v0.11.0/pkg/internal/controller/controller.go:266 +0x1d9 + sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller).Start.func2.2() + /go/pkg/mod/sigs.k8s.io/controller-runtime@v0.11.0/pkg/internal/controller/controller.go:227 +0x85 + created by sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller).Start.func2 + /go/pkg/mod/sigs.k8s.io/controller-runtime@v0.11.0/pkg/internal/controller/controller.go:223 +0x31c + ``` + +**Solution** + +You experience this issue if the `bmcName` property in `boot.yaml` file is blank. +Specify a value for the `bmcName` property, because it is mandatory. diff --git a/api/v1/biosSchemaregistry_types.go b/api/v1/biosSchemaregistry_types.go new file mode 100644 index 0000000..90332b6 --- /dev/null +++ b/api/v1/biosSchemaregistry_types.go @@ -0,0 +1,70 @@ +//(C) Copyright [2022] Hewlett Packard Enterprise Development LP +// +//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 v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// BiosSchemaRegistrySpec defines the desired state of BiosSchemaRegistry +type BiosSchemaRegistrySpec struct { + Name string `json:"Name,omitempty"` + ID string `json:"ID,omitempty"` + OwningEntity string `json:"OwningEntity,omitempty"` + RegistryVersion string `json:"RegistryVersion,omitempty"` + Attributes []map[string]string `json:"Attributes,omitempty"` + SupportedSystems []SupportedSystems `json:"SupportedSystems,omitempty"` +} + +// BiosSchemaRegistryStatus defines the observed state of BiosSchemaRegistry +type BiosSchemaRegistryStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file +} + +// SupportedSystems defines all the supported system for schema +type SupportedSystems struct { + ProductName string `json:"ProductName,omitempty"` + SystemID string `json:"SystemId,omitempty"` + FirmwareVersion string `json:"FirmwareVersion,omitempty"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// BiosSchemaRegistry is the Schema for the biosschemaregistries API +type BiosSchemaRegistry struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec BiosSchemaRegistrySpec `json:"spec,omitempty"` + Status BiosSchemaRegistryStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// BiosSchemaRegistryList contains a list of BiosSchemaRegistry +type BiosSchemaRegistryList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []BiosSchemaRegistry `json:"items"` +} + +func init() { + SchemeBuilder.Register(&BiosSchemaRegistry{}, &BiosSchemaRegistryList{}) +} diff --git a/api/v1/biossettings_types.go b/api/v1/biossettings_types.go new file mode 100644 index 0000000..623a395 --- /dev/null +++ b/api/v1/biossettings_types.go @@ -0,0 +1,60 @@ +//(C) Copyright [2022] Hewlett Packard Enterprise Development LP +// +//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 v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// BiosSettingSpec defines the desired state of BiosSetting +type BiosSettingSpec struct { + BmcName string `json:"bmcName,omitempty"` + SystemID string `json:"systemID,omitempty"` + SerialNo string `json:"serialNumber,omitempty"` + Bios map[string]string `json:"biosAttributes"` +} + +// BiosSettingStatus defines the observed state of BiosSetting +type BiosSettingStatus struct { + BiosAttributes map[string]string `json:"attributes,omitempty"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// BiosSetting is the Schema for the biossettings API +type BiosSetting struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec BiosSettingSpec `json:"spec,omitempty"` + Status BiosSettingStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// BiosSettingList contains a list of BiosSetting +type BiosSettingList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []BiosSetting `json:"items"` +} + +func init() { + SchemeBuilder.Register(&BiosSetting{}, &BiosSettingList{}) +} diff --git a/api/v1/bmc_types.go b/api/v1/bmc_types.go new file mode 100644 index 0000000..9e8969e --- /dev/null +++ b/api/v1/bmc_types.go @@ -0,0 +1,194 @@ +//(C) Copyright [2022] Hewlett Packard Enterprise Development LP +// +//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 v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// DriveDetails defines the drive details for specific Array Controller +type DriveDetails struct { + CapacityBytes string `json:"capacityBytes"` + UsedInVolumes []int `json:"usedInVolumes"` +} + +// ArrayControllers defines the storage controllers for BMC +type ArrayControllers struct { + SupportedRAIDLevel []string `json:"supportedRAIDLevel"` + Drives map[string]DriveDetails `json:"drives"` +} + +// BMC defines a struct to hold basic properties of a BMC +type BMC struct { + Address string `json:"address"` + ConnMethVariant string `json:"connectionMethodVariant"` + PowerState string `json:"powerState"` + ResetType string `json:"resetType"` +} + +// Credential struct holds username and password for the BMC +type Credential struct { + Username string `json:"username"` + Password string `json:"password"` +} + +// BmcSpec defines the desired state of Bmc +type BmcSpec struct { + BmcDetails BMC `json:"bmc"` + Credentials Credential `json:"credentials,omitempty"` +} + +// BmcStatus defines the observed state of Bmc +type BmcStatus struct { + BmcAddStatus string `json:"bmcAddStatus"` + BmcSystemID string `json:"bmcSystemId"` + SerialNumber string `json:"serialNumber"` + VendorName string `json:"vendorName"` + ModelID string `json:"modelID"` + FirmwareVersion string `json:"firmwareVersion"` + BiosVersion string `json:"biosVersion"` + BiosAttributeRegistry string `json:"biosAttributeRegistry"` + EventsMessageRegistry string `json:"eventsMessageRegistry,omitempty"` + StorageControllers map[string]ArrayControllers `json:"storageControllers,omitempty"` + SystemReset string `json:"systemReset"` + PowerState string `json:"powerState"` +} + +// SystemDetail struct defines basic properties of a system +type SystemDetail struct { + Vendor string + ModelID string +} + +// Vendor defines supportable vendors +var Vendor = []string{"HPE", "DELL", "LENOVO"} + +// AllowableResetValues defines allowable reset values for a bmc +var AllowableResetValues = map[SystemDetail][]string{ + {"HPE", "ProLiant DL380 Gen10 Plus"}: {"On", "ForceOff", "GracefulShutdown", "ForceRestart", "Nmi", "PushPowerButton", "GracefulRestart"}, + {"HPE", "ProLiant DL380 Gen10"}: {"On", "ForceOff", "GracefulShutdown", "ForceRestart", "Nmi", "PushPowerButton", "GracefulRestart"}, + {"HPE", "ProLiant DL360 Gen10"}: {"On", "ForceOff", "GracefulShutdown", "ForceRestart", "Nmi", "PushPowerButton", "GracefulRestart"}, + {"HPE", "ProLiant DL360 Gen10 Plus"}: {"On", "ForceOff", "GracefulShutdown", "ForceRestart", "Nmi", "PushPowerButton", "GracefulRestart"}, + {"HPE", "ProLiant DL385 Gen10"}: {"On", "ForceOff", "GracefulShutdown", "ForceRestart", "Nmi", "PushPowerButton", "GracefulRestart"}, + {"HPE", "ProLiant e910"}: {"On", "ForceOff", "GracefulShutdown", "ForceRestart", "Nmi", "PushPowerButton", "GracefulRestart"}, + {"HPE", "ProLiant DL110 Gen10 Plus"}: {"On", "ForceOff", "GracefulShutdown", "ForceRestart", "Nmi", "PushPowerButton", "GracefulRestart"}, + {"DELL", "PowerEdge R440"}: {"On", "ForceOff", "ForceRestart", "GracefulShutdown", "PushPowerButton", "Nmi", "PowerCycle"}, + {"LENOVO", "ThinkSystem SR630"}: {"On", "Nmi", "GracefulShutdown", "GracefulRestart", "ForceOn", "ForceOff", "ForceRestart"}, +} + +// State defines the state to be applied by user +type State struct { + PowerState string + DesiredPowerState string + ResetType string +} + +// ResetWhenOnOn map defines if reset can be applied on bmc based on different state +var ResetWhenOnOn = map[State]bool{ + {"ON", "ON", "ForceRestart"}: true, + {"ON", "ON", "GracefulRestart"}: true, + {"ON", "ON", "GracefulShutdown"}: false, + {"ON", "ON", "ForceOff"}: false, + {"ON", "ON", "On"}: false, + {"ON", "ON", "Pause"}: true, + {"ON", "ON", "Resume"}: false, + {"ON", "ON", "ForceOn"}: false, + {"ON", "ON", "Nmi"}: true, + {"ON", "ON", "PowerCycle"}: true, + {"ON", "ON", "PushPowerButton"}: false, + {"ON", "ON", "Suspend"}: true, +} + +// ResetWhenOffOff map defines if reset can be applied on bmc based on different state +var ResetWhenOffOff = map[State]bool{ + {"OFF", "OFF", "ForceRestart"}: false, + {"OFF", "OFF", "GracefulRestart"}: false, + {"OFF", "OFF", "GracefulShutdown"}: false, + {"OFF", "OFF", "ForceOff"}: false, + {"OFF", "OFF", "On"}: false, + {"OFF", "OFF", "Pause"}: false, + {"OFF", "OFF", "Resume"}: false, + {"OFF", "OFF", "ForceOn"}: false, + {"OFF", "OFF", "Nmi"}: false, + {"OFF", "OFF", "PowerCycle"}: false, + {"OFF", "OFF", "PushPowerButton"}: false, + {"OFF", "OFF", "Suspend"}: false, +} + +// ResetWhenOnOff map defines if reset can be applied on bmc based on different state +var ResetWhenOnOff = map[State]bool{ + {"ON", "OFF", "ForceRestart"}: false, + {"ON", "OFF", "GracefulRestart"}: false, + {"ON", "OFF", "GracefulShutdown"}: true, + {"ON", "OFF", "ForceOff"}: true, + {"ON", "OFF", "On"}: false, + {"ON", "OFF", "Pause"}: true, + {"ON", "OFF", "Resume"}: false, + {"ON", "OFF", "ForceOn"}: false, + {"ON", "OFF", "Nmi"}: false, + {"ON", "OFF", "PowerCycle"}: false, + {"ON", "OFF", "PushPowerButton"}: true, + {"ON", "OFF", "Suspend"}: true, +} + +// ResetWhenOffOn map defines if reset can be applied on bmc based on different state +var ResetWhenOffOn = map[State]bool{ + {"OFF", "ON", "ForceRestart"}: false, + {"OFF", "ON", "GracefulRestart"}: false, + {"OFF", "ON", "GracefulShutdown"}: false, + {"OFF", "ON", "ForceOff"}: false, + {"OFF", "ON", "On"}: true, + {"OFF", "ON", "Pause"}: false, + {"OFF", "ON", "Resume"}: false, + {"OFF", "ON", "ForceOn"}: true, + {"OFF", "ON", "Nmi"}: false, + {"OFF", "ON", "PowerCycle"}: true, + {"OFF", "ON", "PushPowerButton"}: true, + {"OFF", "ON", "Suspend"}: false, +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// Bmc is the Schema for the bmcs API +// +kubebuilder:printcolumn:name="SystemID",type="string",JSONPath=".status.bmcSystemId" +// +kubebuilder:printcolumn:name="SerialNumber",type="string",JSONPath=".status.serialNumber" +// +kubebuilder:printcolumn:name="Vendor",type="string",JSONPath=".status.vendorName" +// +kubebuilder:printcolumn:name="ModelID",type="string",JSONPath=".status.modelID" +// +kubebuilder:printcolumn:name="FirmwareVersion",type="string",JSONPath=".status.firmwareVersion" +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +type Bmc struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec BmcSpec `json:"spec,omitempty"` + Status BmcStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// BmcList contains a list of Bmc +type BmcList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Bmc `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Bmc{}, &BmcList{}) +} diff --git a/api/v1/bootordersettings_types.go b/api/v1/bootordersettings_types.go new file mode 100644 index 0000000..59f3731 --- /dev/null +++ b/api/v1/bootordersettings_types.go @@ -0,0 +1,71 @@ +//(C) Copyright [2022] Hewlett Packard Enterprise Development LP +// +//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 v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// BootOrderSettingsSpec defines the desired state of BootOrderSettings +type BootOrderSettingsSpec struct { + BmcName string `json:"bmcName,omitempty"` + SystemID string `json:"systemID,omitempty"` + SerialNo string `json:"serialNumber,omitempty"` + Boot *BootSetting `json:"boot,omitempty"` +} + +// BootOrderSettingsStatus defines the observed state of BootOrderSettings +type BootOrderSettingsStatus struct { + Boot BootSetting `json:"boot,omitempty"` +} + +// BootSetting defines the different settings for boot +type BootSetting struct { + BootOrder []string `json:"bootOrder,omitempty"` + BootSourceOverrideEnabled string `json:"bootSourceOverrideEnabled,omitempty"` + BootSourceOverrideMode string `json:"bootSourceOverrideMode,omitempty"` + BootSourceOverrideTarget string `json:"bootSourceOverrideTarget,omitempty"` + BootTargetAllowableValues []string `json:"bootSourceOverrideTarget.AllowableValues,omitempty"` + UefiTargetBootSourceOverride string `json:"uefiTargetBootSourceOverride,omitempty"` + UefiTargetAllowableValues []string `json:"uefiTargetBootSourceOverride.AllowableValues,omitempty"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// BootOrderSetting is the Schema for the bootordersetting API +type BootOrderSetting struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec BootOrderSettingsSpec `json:"spec,omitempty"` + Status BootOrderSettingsStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// BootOrderSettingList contains a list of BootOrderSettings +type BootOrderSettingList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []BootOrderSetting `json:"items"` +} + +func init() { + SchemeBuilder.Register(&BootOrderSetting{}, &BootOrderSettingList{}) +} diff --git a/api/v1/eventsmessageregistry_types.go b/api/v1/eventsmessageregistry_types.go new file mode 100644 index 0000000..6944ea5 --- /dev/null +++ b/api/v1/eventsmessageregistry_types.go @@ -0,0 +1,81 @@ +//(C) Copyright [2023] Hewlett Packard Enterprise Development LP +// +//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 v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// EventsMessageRegistrySpec defines the desired state of EventsMessageRegistry +type EventsMessageRegistrySpec struct { + Name string `json:"Name,omitempty"` + ID string `json:"ID,omitempty"` + OwningEntity string `json:"OwningEntity,omitempty"` + RegistryVersion string `json:"RegistryVersion,omitempty"` + RegistryPrefix string `json:"RegistryPrefix,omitempty"` + Messages map[string]EventMessage `json:"Messages,omitempty"` +} + +// EventsMessageRegistryStatus defines the observed state of EventsMessageRegistry +type EventsMessageRegistryStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file +} + +// EventMessage defines the message struct +type EventMessage struct { + Description string `json:"Description,omitempty"` + Message string `json:"Message,omitempty"` + NumberOfArgs string `json:"NumberOfArgs,omitempty"` + Oem map[string]Oem `json:"Oem,omitempty"` + ParamTypes []string `json:"ParamTypes,omitempty"` + Resolution string `json:"Resolution,omitempty"` + Severity string `json:"Severity,omitempty"` +} + +// Oem defines the Oem property struct +type Oem struct { + OdataType string `json:"odataType,omitempty"` + HealthCategory string `json:"HealthCategory,omitempty"` + Type string `json:"Type,omitempty"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// EventsMessageRegistry is the Schema for the eventsmessageregistries API +type EventsMessageRegistry struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec EventsMessageRegistrySpec `json:"spec,omitempty"` + Status EventsMessageRegistryStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// EventsMessageRegistryList contains a list of EventsMessageRegistry +type EventsMessageRegistryList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []EventsMessageRegistry `json:"items"` +} + +func init() { + SchemeBuilder.Register(&EventsMessageRegistry{}, &EventsMessageRegistryList{}) +} diff --git a/api/v1/eventsubscription_types.go b/api/v1/eventsubscription_types.go new file mode 100644 index 0000000..2d8caef --- /dev/null +++ b/api/v1/eventsubscription_types.go @@ -0,0 +1,78 @@ +//(C) Copyright [2023] Hewlett Packard Enterprise Development LP +// +//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 v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// EventsubscriptionSpec defines the desired state of Eventsubscription +type EventsubscriptionSpec struct { + Name string `json:"name,omitempty"` + Destination string `json:"destination"` + EventTypes []string `json:"eventTypes,omitempty"` + MessageIds []string `json:"messageIds,omitempty"` + ResourceTypes []string `json:"resourceTypes,omitempty"` + Context string `json:"context"` + EventFormatType string `json:"eventFormatType,omitempty"` + SubordinateResources bool `json:"subordinateResources,omitempty"` + OriginResources []string `json:"originResources,omitempty"` +} + +// EventsubscriptionStatus defines the observed state of Eventsubscription +type EventsubscriptionStatus struct { + ID string `json:"eventSubscriptionID"` + Name string `json:"name,omitempty"` + Destination string `json:"destination"` + Context string `json:"context"` + Protocol string `json:"protocol"` + EventTypes []string `json:"eventTypes,omitempty"` + MessageIds []string `json:"messageIds,omitempty"` + SubscriptionType string `json:"subscriptionType"` + ResourceTypes []string `json:"resourceTypes,omitempty"` + OriginResources []string `json:"originResources,omitempty"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// Eventsubscription is the Schema for the eventsubscriptions API +// +kubebuilder:printcolumn:name="EventSubscriptionID",type="string",JSONPath=".status.eventSubscriptionID" +// +kubebuilder:printcolumn:name="Destination",type="string",JSONPath=".status.destination" +// +kubebuilder:printcolumn:name="Context",type="string",JSONPath=".status.context" +// +kubebuilder:printcolumn:name="SubscriptionType",type="string",JSONPath=".status.subscriptionType" +type Eventsubscription struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec EventsubscriptionSpec `json:"spec,omitempty"` + Status EventsubscriptionStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// EventsubscriptionList contains a list of Eventsubscription +type EventsubscriptionList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Eventsubscription `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Eventsubscription{}, &EventsubscriptionList{}) +} diff --git a/api/v1/firmware_types.go b/api/v1/firmware_types.go new file mode 100644 index 0000000..569b3ae --- /dev/null +++ b/api/v1/firmware_types.go @@ -0,0 +1,69 @@ +//(C) Copyright [2023] Hewlett Packard Enterprise Development LP +// +//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 v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// AuthDetails struct contains information required for authentication +type AuthDetails struct { + Password string `json:"password,omitempty"` + Username string `json:"username,omitempty"` +} + +// ImageDetails holds information for firmware image +type ImageDetails struct { + ImageLocation string `json:"imageLocation"` + Auth AuthDetails `json:"auth,omitempty"` +} + +// FirmwareSpec defines the desired state of Firmware +type FirmwareSpec struct { + Image ImageDetails `json:"image"` + TransferProtocol string `json:"transferProtocol,omitempty"` +} + +// FirmwareStatus defines the observed state of Firmware +type FirmwareStatus struct { + Status string `json:"status,omitempty"` + FirmwareVersion string `json:"firmwareVersion,omitempty"` + ImagePath string `json:"imagePath,omitempty"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// Firmware is the Schema for the firmwares API +type Firmware struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec FirmwareSpec `json:"spec,omitempty"` + Status FirmwareStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// FirmwareList contains a list of Firmware +type FirmwareList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Firmware `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Firmware{}, &FirmwareList{}) +} diff --git a/api/v1/groupversion_info.go b/api/v1/groupversion_info.go new file mode 100644 index 0000000..c264bb5 --- /dev/null +++ b/api/v1/groupversion_info.go @@ -0,0 +1,34 @@ +//(C) Copyright [2022] Hewlett Packard Enterprise Development LP +// +//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 v1 contains API Schema definitions for the infra.io v1 API group +// +kubebuilder:object:generate=true +// +groupName=infra.io.odimra +package v1 + +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: "infra.io.odimra", Version: "v1"} + + // 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/api/v1/odim_types.go b/api/v1/odim_types.go new file mode 100644 index 0000000..a482ba2 --- /dev/null +++ b/api/v1/odim_types.go @@ -0,0 +1,59 @@ +//(C) Copyright [2022] Hewlett Packard Enterprise Development LP +// +//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 v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// OdimSpec defines the desired state of Odim +type OdimSpec struct { + URL string `json:"URL"` + EventListenerHost string `json:"EventListenerHost"` +} + +// OdimStatus defines the observed state of Odim +type OdimStatus struct { + Status string `json:"status,omitempty"` + ConnMethVariants map[string]string `json:"connectionMethodVariants,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// Odim is the Schema for the odims API +// +kubebuilder:printcolumn:name="URL",type="string",JSONPath=".spec.URL" +// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.status" +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +type Odim struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Spec OdimSpec `json:"spec,omitempty"` + Status OdimStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true +// OdimList contains a list of Odim +type OdimList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Odim `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Odim{}, &OdimList{}) +} diff --git a/api/v1/volume_types.go b/api/v1/volume_types.go new file mode 100644 index 0000000..884a800 --- /dev/null +++ b/api/v1/volume_types.go @@ -0,0 +1,73 @@ +//(C) Copyright [2022] Hewlett Packard Enterprise Development LP +// +//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 v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// VolumeSpec defines the desired state of Volume +// +kubebuilder:validation:XPreserveUnknownFields +type VolumeSpec struct { + StorageControllerID string `json:"storageControllerID,omitempty"` + RAIDType string `json:"RAIDType,omitempty"` + Drives []int `json:"drives,omitempty"` +} + +type Identifier struct { + DurableName string `json:"DurableName"` + DurableNameFormat string `json:"DurableNameFormat"` +} + +// VolumeStatus defines the observed state of Volume +// +kubebuilder:validation:XPreserveUnknownFields +type VolumeStatus struct { + VolumeID string `json:"volumeID"` + VolumeName string `json:"volumeName"` + RAIDType string `json:"RAIDType"` + StorageControllerID string `json:"storageControllerID"` + Drives []int `json:"drives"` + CapacityBytes string `json:"capacityBytes"` + Identifiers Identifier `json:"Identifiers"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// Volume is the Schema for the volumes API +// +kubebuilder:printcolumn:name="VolumeName",type="string",JSONPath=".status.volumeName" +// +kubebuilder:printcolumn:name="VolumeID",type="string",JSONPath=".status.volumeID" +// +kubebuilder:printcolumn:name="RAIDType",type="string",JSONPath=".status.RAIDType" +// +kubebuilder:printcolumn:name="CapacityBytes",type="string",JSONPath=".status.capacityBytes" +type Volume struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Spec VolumeSpec `json:"spec,omitempty"` + Status VolumeStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true +// VolumeList contains a list of Volume +type VolumeList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Volume `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Volume{}, &VolumeList{}) +} diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go new file mode 100644 index 0000000..168260d --- /dev/null +++ b/api/v1/zz_generated.deepcopy.go @@ -0,0 +1,1175 @@ +//(C) Copyright [2023] Hewlett Packard Enterprise Development LP +// +//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 v1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ArrayControllers) DeepCopyInto(out *ArrayControllers) { + *out = *in + if in.SupportedRAIDLevel != nil { + in, out := &in.SupportedRAIDLevel, &out.SupportedRAIDLevel + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Drives != nil { + in, out := &in.Drives, &out.Drives + *out = make(map[string]DriveDetails, len(*in)) + for key, val := range *in { + (*out)[key] = *val.DeepCopy() + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArrayControllers. +func (in *ArrayControllers) DeepCopy() *ArrayControllers { + if in == nil { + return nil + } + out := new(ArrayControllers) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AuthDetails) DeepCopyInto(out *AuthDetails) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthDetails. +func (in *AuthDetails) DeepCopy() *AuthDetails { + if in == nil { + return nil + } + out := new(AuthDetails) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BMC) DeepCopyInto(out *BMC) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BMC. +func (in *BMC) DeepCopy() *BMC { + if in == nil { + return nil + } + out := new(BMC) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BiosSchemaRegistry) DeepCopyInto(out *BiosSchemaRegistry) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BiosSchemaRegistry. +func (in *BiosSchemaRegistry) DeepCopy() *BiosSchemaRegistry { + if in == nil { + return nil + } + out := new(BiosSchemaRegistry) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BiosSchemaRegistry) 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 *BiosSchemaRegistryList) DeepCopyInto(out *BiosSchemaRegistryList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]BiosSchemaRegistry, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BiosSchemaRegistryList. +func (in *BiosSchemaRegistryList) DeepCopy() *BiosSchemaRegistryList { + if in == nil { + return nil + } + out := new(BiosSchemaRegistryList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BiosSchemaRegistryList) 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 *BiosSchemaRegistrySpec) DeepCopyInto(out *BiosSchemaRegistrySpec) { + *out = *in + if in.Attributes != nil { + in, out := &in.Attributes, &out.Attributes + *out = make([]map[string]string, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + } + } + if in.SupportedSystems != nil { + in, out := &in.SupportedSystems, &out.SupportedSystems + *out = make([]SupportedSystems, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BiosSchemaRegistrySpec. +func (in *BiosSchemaRegistrySpec) DeepCopy() *BiosSchemaRegistrySpec { + if in == nil { + return nil + } + out := new(BiosSchemaRegistrySpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BiosSchemaRegistryStatus) DeepCopyInto(out *BiosSchemaRegistryStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BiosSchemaRegistryStatus. +func (in *BiosSchemaRegistryStatus) DeepCopy() *BiosSchemaRegistryStatus { + if in == nil { + return nil + } + out := new(BiosSchemaRegistryStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BiosSetting) DeepCopyInto(out *BiosSetting) { + *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 BiosSetting. +func (in *BiosSetting) DeepCopy() *BiosSetting { + if in == nil { + return nil + } + out := new(BiosSetting) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BiosSetting) 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 *BiosSettingList) DeepCopyInto(out *BiosSettingList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]BiosSetting, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BiosSettingList. +func (in *BiosSettingList) DeepCopy() *BiosSettingList { + if in == nil { + return nil + } + out := new(BiosSettingList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BiosSettingList) 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 *BiosSettingSpec) DeepCopyInto(out *BiosSettingSpec) { + *out = *in + if in.Bios != nil { + in, out := &in.Bios, &out.Bios + *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 BiosSettingSpec. +func (in *BiosSettingSpec) DeepCopy() *BiosSettingSpec { + if in == nil { + return nil + } + out := new(BiosSettingSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BiosSettingStatus) DeepCopyInto(out *BiosSettingStatus) { + *out = *in + if in.BiosAttributes != nil { + in, out := &in.BiosAttributes, &out.BiosAttributes + *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 BiosSettingStatus. +func (in *BiosSettingStatus) DeepCopy() *BiosSettingStatus { + if in == nil { + return nil + } + out := new(BiosSettingStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Bmc) DeepCopyInto(out *Bmc) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Bmc. +func (in *Bmc) DeepCopy() *Bmc { + if in == nil { + return nil + } + out := new(Bmc) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Bmc) 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 *BmcList) DeepCopyInto(out *BmcList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Bmc, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BmcList. +func (in *BmcList) DeepCopy() *BmcList { + if in == nil { + return nil + } + out := new(BmcList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BmcList) 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 *BmcSpec) DeepCopyInto(out *BmcSpec) { + *out = *in + out.BmcDetails = in.BmcDetails + out.Credentials = in.Credentials +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BmcSpec. +func (in *BmcSpec) DeepCopy() *BmcSpec { + if in == nil { + return nil + } + out := new(BmcSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BmcStatus) DeepCopyInto(out *BmcStatus) { + *out = *in + if in.StorageControllers != nil { + in, out := &in.StorageControllers, &out.StorageControllers + *out = make(map[string]ArrayControllers, len(*in)) + for key, val := range *in { + (*out)[key] = *val.DeepCopy() + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BmcStatus. +func (in *BmcStatus) DeepCopy() *BmcStatus { + if in == nil { + return nil + } + out := new(BmcStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BootOrderSetting) DeepCopyInto(out *BootOrderSetting) { + *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 BootOrderSetting. +func (in *BootOrderSetting) DeepCopy() *BootOrderSetting { + if in == nil { + return nil + } + out := new(BootOrderSetting) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BootOrderSetting) 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 *BootOrderSettingList) DeepCopyInto(out *BootOrderSettingList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]BootOrderSetting, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BootOrderSettingList. +func (in *BootOrderSettingList) DeepCopy() *BootOrderSettingList { + if in == nil { + return nil + } + out := new(BootOrderSettingList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BootOrderSettingList) 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 *BootOrderSettingsSpec) DeepCopyInto(out *BootOrderSettingsSpec) { + *out = *in + if in.Boot != nil { + in, out := &in.Boot, &out.Boot + *out = new(BootSetting) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BootOrderSettingsSpec. +func (in *BootOrderSettingsSpec) DeepCopy() *BootOrderSettingsSpec { + if in == nil { + return nil + } + out := new(BootOrderSettingsSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BootOrderSettingsStatus) DeepCopyInto(out *BootOrderSettingsStatus) { + *out = *in + in.Boot.DeepCopyInto(&out.Boot) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BootOrderSettingsStatus. +func (in *BootOrderSettingsStatus) DeepCopy() *BootOrderSettingsStatus { + if in == nil { + return nil + } + out := new(BootOrderSettingsStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BootSetting) DeepCopyInto(out *BootSetting) { + *out = *in + if in.BootOrder != nil { + in, out := &in.BootOrder, &out.BootOrder + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.BootTargetAllowableValues != nil { + in, out := &in.BootTargetAllowableValues, &out.BootTargetAllowableValues + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.UefiTargetAllowableValues != nil { + in, out := &in.UefiTargetAllowableValues, &out.UefiTargetAllowableValues + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BootSetting. +func (in *BootSetting) DeepCopy() *BootSetting { + if in == nil { + return nil + } + out := new(BootSetting) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Credential) DeepCopyInto(out *Credential) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Credential. +func (in *Credential) DeepCopy() *Credential { + if in == nil { + return nil + } + out := new(Credential) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DriveDetails) DeepCopyInto(out *DriveDetails) { + *out = *in + if in.UsedInVolumes != nil { + in, out := &in.UsedInVolumes, &out.UsedInVolumes + *out = make([]int, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DriveDetails. +func (in *DriveDetails) DeepCopy() *DriveDetails { + if in == nil { + return nil + } + out := new(DriveDetails) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EventMessage) DeepCopyInto(out *EventMessage) { + *out = *in + if in.Oem != nil { + in, out := &in.Oem, &out.Oem + *out = make(map[string]Oem, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.ParamTypes != nil { + in, out := &in.ParamTypes, &out.ParamTypes + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EventMessage. +func (in *EventMessage) DeepCopy() *EventMessage { + if in == nil { + return nil + } + out := new(EventMessage) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EventsMessageRegistry) DeepCopyInto(out *EventsMessageRegistry) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EventsMessageRegistry. +func (in *EventsMessageRegistry) DeepCopy() *EventsMessageRegistry { + if in == nil { + return nil + } + out := new(EventsMessageRegistry) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *EventsMessageRegistry) 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 *EventsMessageRegistryList) DeepCopyInto(out *EventsMessageRegistryList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]EventsMessageRegistry, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EventsMessageRegistryList. +func (in *EventsMessageRegistryList) DeepCopy() *EventsMessageRegistryList { + if in == nil { + return nil + } + out := new(EventsMessageRegistryList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *EventsMessageRegistryList) 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 *EventsMessageRegistrySpec) DeepCopyInto(out *EventsMessageRegistrySpec) { + *out = *in + if in.Messages != nil { + in, out := &in.Messages, &out.Messages + *out = make(map[string]EventMessage, len(*in)) + for key, val := range *in { + (*out)[key] = *val.DeepCopy() + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EventsMessageRegistrySpec. +func (in *EventsMessageRegistrySpec) DeepCopy() *EventsMessageRegistrySpec { + if in == nil { + return nil + } + out := new(EventsMessageRegistrySpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EventsMessageRegistryStatus) DeepCopyInto(out *EventsMessageRegistryStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EventsMessageRegistryStatus. +func (in *EventsMessageRegistryStatus) DeepCopy() *EventsMessageRegistryStatus { + if in == nil { + return nil + } + out := new(EventsMessageRegistryStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Eventsubscription) DeepCopyInto(out *Eventsubscription) { + *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 Eventsubscription. +func (in *Eventsubscription) DeepCopy() *Eventsubscription { + if in == nil { + return nil + } + out := new(Eventsubscription) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Eventsubscription) 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 *EventsubscriptionList) DeepCopyInto(out *EventsubscriptionList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Eventsubscription, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EventsubscriptionList. +func (in *EventsubscriptionList) DeepCopy() *EventsubscriptionList { + if in == nil { + return nil + } + out := new(EventsubscriptionList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *EventsubscriptionList) 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 *EventsubscriptionSpec) DeepCopyInto(out *EventsubscriptionSpec) { + *out = *in + if in.EventTypes != nil { + in, out := &in.EventTypes, &out.EventTypes + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.MessageIds != nil { + in, out := &in.MessageIds, &out.MessageIds + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ResourceTypes != nil { + in, out := &in.ResourceTypes, &out.ResourceTypes + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.OriginResources != nil { + in, out := &in.OriginResources, &out.OriginResources + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EventsubscriptionSpec. +func (in *EventsubscriptionSpec) DeepCopy() *EventsubscriptionSpec { + if in == nil { + return nil + } + out := new(EventsubscriptionSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EventsubscriptionStatus) DeepCopyInto(out *EventsubscriptionStatus) { + *out = *in + if in.EventTypes != nil { + in, out := &in.EventTypes, &out.EventTypes + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.MessageIds != nil { + in, out := &in.MessageIds, &out.MessageIds + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ResourceTypes != nil { + in, out := &in.ResourceTypes, &out.ResourceTypes + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.OriginResources != nil { + in, out := &in.OriginResources, &out.OriginResources + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EventsubscriptionStatus. +func (in *EventsubscriptionStatus) DeepCopy() *EventsubscriptionStatus { + if in == nil { + return nil + } + out := new(EventsubscriptionStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Firmware) DeepCopyInto(out *Firmware) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Firmware. +func (in *Firmware) DeepCopy() *Firmware { + if in == nil { + return nil + } + out := new(Firmware) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Firmware) 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 *FirmwareList) DeepCopyInto(out *FirmwareList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Firmware, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirmwareList. +func (in *FirmwareList) DeepCopy() *FirmwareList { + if in == nil { + return nil + } + out := new(FirmwareList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *FirmwareList) 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 *FirmwareSpec) DeepCopyInto(out *FirmwareSpec) { + *out = *in + out.Image = in.Image +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirmwareSpec. +func (in *FirmwareSpec) DeepCopy() *FirmwareSpec { + if in == nil { + return nil + } + out := new(FirmwareSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FirmwareStatus) DeepCopyInto(out *FirmwareStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirmwareStatus. +func (in *FirmwareStatus) DeepCopy() *FirmwareStatus { + if in == nil { + return nil + } + out := new(FirmwareStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Identifier) DeepCopyInto(out *Identifier) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Identifier. +func (in *Identifier) DeepCopy() *Identifier { + if in == nil { + return nil + } + out := new(Identifier) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ImageDetails) DeepCopyInto(out *ImageDetails) { + *out = *in + out.Auth = in.Auth +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageDetails. +func (in *ImageDetails) DeepCopy() *ImageDetails { + if in == nil { + return nil + } + out := new(ImageDetails) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Odim) DeepCopyInto(out *Odim) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Odim. +func (in *Odim) DeepCopy() *Odim { + if in == nil { + return nil + } + out := new(Odim) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Odim) 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 *OdimList) DeepCopyInto(out *OdimList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Odim, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OdimList. +func (in *OdimList) DeepCopy() *OdimList { + if in == nil { + return nil + } + out := new(OdimList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *OdimList) 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 *OdimSpec) DeepCopyInto(out *OdimSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OdimSpec. +func (in *OdimSpec) DeepCopy() *OdimSpec { + if in == nil { + return nil + } + out := new(OdimSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OdimStatus) DeepCopyInto(out *OdimStatus) { + *out = *in + if in.ConnMethVariants != nil { + in, out := &in.ConnMethVariants, &out.ConnMethVariants + *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 OdimStatus. +func (in *OdimStatus) DeepCopy() *OdimStatus { + if in == nil { + return nil + } + out := new(OdimStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Oem) DeepCopyInto(out *Oem) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Oem. +func (in *Oem) DeepCopy() *Oem { + if in == nil { + return nil + } + out := new(Oem) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *State) DeepCopyInto(out *State) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new State. +func (in *State) DeepCopy() *State { + if in == nil { + return nil + } + out := new(State) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SupportedSystems) DeepCopyInto(out *SupportedSystems) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SupportedSystems. +func (in *SupportedSystems) DeepCopy() *SupportedSystems { + if in == nil { + return nil + } + out := new(SupportedSystems) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SystemDetail) DeepCopyInto(out *SystemDetail) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SystemDetail. +func (in *SystemDetail) DeepCopy() *SystemDetail { + if in == nil { + return nil + } + out := new(SystemDetail) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Volume) DeepCopyInto(out *Volume) { + *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 Volume. +func (in *Volume) DeepCopy() *Volume { + if in == nil { + return nil + } + out := new(Volume) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Volume) 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 *VolumeList) DeepCopyInto(out *VolumeList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Volume, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeList. +func (in *VolumeList) DeepCopy() *VolumeList { + if in == nil { + return nil + } + out := new(VolumeList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VolumeList) 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 *VolumeSpec) DeepCopyInto(out *VolumeSpec) { + *out = *in + if in.Drives != nil { + in, out := &in.Drives, &out.Drives + *out = make([]int, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeSpec. +func (in *VolumeSpec) DeepCopy() *VolumeSpec { + if in == nil { + return nil + } + out := new(VolumeSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VolumeStatus) DeepCopyInto(out *VolumeStatus) { + *out = *in + if in.Drives != nil { + in, out := &in.Drives, &out.Drives + *out = make([]int, len(*in)) + copy(*out, *in) + } + out.Identifiers = in.Identifiers +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeStatus. +func (in *VolumeStatus) DeepCopy() *VolumeStatus { + if in == nil { + return nil + } + out := new(VolumeStatus) + in.DeepCopyInto(out) + return out +} diff --git a/bin/controller-gen b/bin/controller-gen new file mode 100644 index 0000000..1c81e89 Binary files /dev/null and b/bin/controller-gen differ diff --git a/bin/kustomize b/bin/kustomize new file mode 100644 index 0000000..391adb2 Binary files /dev/null and b/bin/kustomize differ diff --git a/bmc-templates/bios.yaml b/bmc-templates/bios.yaml new file mode 100644 index 0000000..8d04dbe --- /dev/null +++ b/bmc-templates/bios.yaml @@ -0,0 +1,10 @@ +apiVersion: infra.io.odimra/v1 +kind: BiosSetting +metadata: + name: + namespace: bmc-op +spec: + bmcName: + systemID: + serialNumber: + biosAttributes: #example: BootMode: "LegacyBios" \ No newline at end of file diff --git a/bmc-templates/bmc.yaml b/bmc-templates/bmc.yaml new file mode 100644 index 0000000..d8a0ebb --- /dev/null +++ b/bmc-templates/bmc.yaml @@ -0,0 +1,14 @@ +apiVersion: infra.io.odimra/v1 +kind: Bmc +metadata: + name: + namespace: bmc-op +spec: + bmc: + address: + connectionMethodVariant: + powerState: "" #by default it will be null, we need to update k8 object for reset action + resetType: "" #by default it will be null, we need to update k8 object for reset action + credentials: + username: + password: \ No newline at end of file diff --git a/bmc-templates/boot.yaml b/bmc-templates/boot.yaml new file mode 100644 index 0000000..19a69a6 --- /dev/null +++ b/bmc-templates/boot.yaml @@ -0,0 +1,10 @@ +apiVersion: infra.io.odimra/v1 +kind: BootOrderSetting +metadata: + name: # name same as bmc + namespace: bmc-op #NameSpace same as bmc +spec: + bmcName: + systemID: + serialNumber: + boot: #example: bootOrder: ["Cd","Usb","Hdd","Pxe"] \ No newline at end of file diff --git a/bmc-templates/eventsubscription.yaml b/bmc-templates/eventsubscription.yaml new file mode 100644 index 0000000..e35d9e4 --- /dev/null +++ b/bmc-templates/eventsubscription.yaml @@ -0,0 +1,15 @@ +apiVersion: infra.io.odimra/v1 +kind: Eventsubscription +metadata: + name: + namespace: bmc-op +spec: + name: + destination: + eventTypes: + messageIds: + resourceTypes: + context: + eventFormatType: + subordinateResources: + originResources: \ No newline at end of file diff --git a/bmc-templates/firmware.yaml b/bmc-templates/firmware.yaml new file mode 100644 index 0000000..5888099 --- /dev/null +++ b/bmc-templates/firmware.yaml @@ -0,0 +1,12 @@ +apiVersion: infra.io.odimra/v1 +kind: Firmware +metadata: + name: # same as bmc name + namespace: bmc-op +spec: + image: + imageLocation: + auth: + password: + username: + transferProtocol: http/https diff --git a/bmc-templates/keys/gen_certs.sh b/bmc-templates/keys/gen_certs.sh new file mode 100644 index 0000000..d77b426 --- /dev/null +++ b/bmc-templates/keys/gen_certs.sh @@ -0,0 +1,79 @@ +# (C) Copyright [2023] Hewlett Packard Enterprise Development LP +# +# 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. + +#!/bin/bash + +FileName=client +clientIP=$1 +if [ -z "$1" ] +then + echo "Please provide clientIP" + exit -1 +else + echo "clientIP is $1" +fi + + +#generate server private key +openssl genrsa -out ${FileName}.key 4096 +echo +echo +#generate server csr +openssl req -new -sha512 -key ${FileName}.key -subj "/C=US/ST=CA/O=ODIMRA/CN=Event Client Cert" -config <(cat < secret1.yaml +yq eval ".data.eventClientCert = \"$clientCrt\"" secret1.yaml > secret2.yaml +yq eval ".data.eventClientKey = \"$clientKey\"" secret2.yaml > secret3.yaml +rm secret.yaml secret1.yaml secret2.yaml +mv secret3.yaml secret.yaml + +openssl genrsa -out bmc.private 4096 +openssl rsa -in bmc.private -out bmc.public -pubout -outform PEM +publicKey=$(cat bmc.public | base64) +privateKey=$(cat bmc.private | base64) +yq eval ".data.privateKey = \"$privateKey\"" secret.yaml > secret1.yaml +yq eval ".data.publicKey = \"$publicKey\"" secret1.yaml > secret2.yaml +rm secret.yaml secret1.yaml +mv secret2.yaml secret.yaml \ No newline at end of file diff --git a/bmc-templates/keys/secret.yaml b/bmc-templates/keys/secret.yaml new file mode 100644 index 0000000..c27fa05 --- /dev/null +++ b/bmc-templates/keys/secret.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Secret +metadata: + name: bmc-secret + namespace: bmc-op + labels: + app: bmc-secret +type: Opaque +data: + publicKey: + privateKey: + rootCACert: + eventClientCert: + eventClientKey: \ No newline at end of file diff --git a/bmc-templates/odim.yaml b/bmc-templates/odim.yaml new file mode 100644 index 0000000..29a66de --- /dev/null +++ b/bmc-templates/odim.yaml @@ -0,0 +1,22 @@ +apiVersion: v1 +kind: Secret +metadata: + name: odimauth + namespace: bmc-op +type: BasicAuth/RedfishSessionAuth +data: + username: + password: + +--- + +apiVersion: infra.io.odimra/v1 +kind: Odim +metadata: + name: odim + namespace: bmc-op + annotations: + infra.io/auth: odimauth +spec: + URL: https://: + EventListenerHost: https://: \ No newline at end of file diff --git a/bmc-templates/pvs/bmc_pv.yaml b/bmc-templates/pvs/bmc_pv.yaml new file mode 100644 index 0000000..1ac9803 --- /dev/null +++ b/bmc-templates/pvs/bmc_pv.yaml @@ -0,0 +1,18 @@ +kind: PersistentVolume +apiVersion: v1 +metadata: + name: operator-log + namespace: bmc-op + labels: + type: local +spec: + storageClassName: manual + capacity: + storage: 100Mi + accessModes: + - ReadWriteOnce + claimRef: + namespace: bmc-op + name: operator-log-claim + hostPath: + path: /var/log/operator_logs \ No newline at end of file diff --git a/bmc-templates/pvs/bmc_pvc.yaml b/bmc-templates/pvs/bmc_pvc.yaml new file mode 100644 index 0000000..1ccabb1 --- /dev/null +++ b/bmc-templates/pvs/bmc_pvc.yaml @@ -0,0 +1,12 @@ +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: operator-log-claim + namespace: bmc-op +spec: + storageClassName: manual + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 100Mi \ No newline at end of file diff --git a/bmc-templates/volume.yaml b/bmc-templates/volume.yaml new file mode 100644 index 0000000..5c09186 --- /dev/null +++ b/bmc-templates/volume.yaml @@ -0,0 +1,9 @@ +apiVersion: infra.io.odimra/v1 +kind: Volume +metadata: + name: . + namespace: +spec: + storageControllerID: + RAIDType: + drives: \ No newline at end of file diff --git a/config/constants/constants.go b/config/constants/constants.go new file mode 100644 index 0000000..45abe40 --- /dev/null +++ b/config/constants/constants.go @@ -0,0 +1,79 @@ +//(C) Copyright [2022] Hewlett Packard Enterprise Development LP +// +//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 constants + +const ( + // FirmwareSimpleUpdateURI : uri of firmware simple update API + FirmwareSimpleUpdateURI = "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate" + // FirmwareStartUpdateURI : uri of firmware start update API + FirmwareStartUpdateURI = "/redfish/v1/UpdateService/Actions/UpdateService.StartUpdate" + // BMCOPERATOR : bmc-operator folder name + BMCOPERATOR = "bmc-operator" + // DefaultTimeTicker defines the default time set for ticker + DefaultTimeTicker = 24 + // SystemURI defines the URI for systems collection + SystemURI = "/redfish/v1/Systems/" + // Firmware contains string for firmware kind + Firmware = "Firmware" + // Accommodate contails string for Accommodate poll Action + Accommodate = "Accommodate" + // Revert contails string for Revert poll Action + Revert = "Revert" + // ConfigMapName + ConfigMapName = "config" + // ILOManufacturer is the manufacturer name for iLO BMC + ILOManufacturer = "HPE" + + SleepTime = 30 //sec + RetryCount = 10 + PluginSyncInSeconds = 120 //sec + + // Used for indexing in List() + MetadataName = "metadata.name" + StatusSerialNumber = "status.serialNumber" + SpecBmcAddress = "spec.bmc.address" + StatusBmcSystemID = "status.bmcSystemId" + StatusEventsubscriptionID = "status.eventSubscriptionID" + + // systemReset event + PendingForResetEvent = "Pending for" + + //Volume request details + ApplyTime = "OnReset" + + // EventsubscriptionFinalizer is the finalizer for eventsubscription + EventsubscriptionFinalizer = "infra.io.eventsubscription/finalizer" + + //Logging operation ActionID and ActionName + BmcOperator = "bmc-operator" + BMCSettingActionID = "001" + BMCSettingActionName = "BMCOperations" + BIOSSettingActionID = "002" + BIOSSettingActionName = "BIOSSettings" + BootOrderActionID = "003" + BootOrderActionName = "BootOrderSettings" + VolumeActionID = "004" + VolumeActionName = "VolumeOperations" + ODIMObjectOperationActionID = "000" + ODIMObjectOperationActionName = "ODIMRegistration" + PollingActionID = "006" + PollingActionName = "GetPollingData" + EventClientActionID = "006" + EventClientActionName = "EventClient" + EventSubscriptionActionName = "EventSubscription" + EventSubscriptionActionID = "007" + TrackFileConfigActionName = "TrackConfigChanges" + TrackFileConfigActionID = "008" +) diff --git a/config/crd/bases/infra.io.odimra_biosschemaregistries.yaml b/config/crd/bases/infra.io.odimra_biosschemaregistries.yaml new file mode 100644 index 0000000..76b1f79 --- /dev/null +++ b/config/crd/bases/infra.io.odimra_biosschemaregistries.yaml @@ -0,0 +1,75 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: biosschemaregistries.infra.io.odimra +spec: + group: infra.io.odimra + names: + kind: BiosSchemaRegistry + listKind: BiosSchemaRegistryList + plural: biosschemaregistries + singular: biosschemaregistry + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: BiosSchemaRegistry is the Schema for the biosschemaregistries + API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Bmcs should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Bmcs may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: BiosSchemaRegistrySpec defines the desired state of BiosSchemaRegistry + properties: + Attributes: + items: + additionalProperties: + type: string + type: object + type: array + ID: + type: string + Name: + type: string + OwningEntity: + type: string + RegistryVersion: + type: string + SupportedSystems: + items: + description: SupportedSystems defines all the supported system for + schema + properties: + FirmwareVersion: + type: string + ProductName: + type: string + SystemId: + type: string + type: object + type: array + type: object + status: + description: BiosSchemaRegistryStatus defines the observed state of BiosSchemaRegistry + type: object + type: object + served: true + storage: true + subresources: + status: {} + diff --git a/config/crd/bases/infra.io.odimra_biossettings.yaml b/config/crd/bases/infra.io.odimra_biossettings.yaml new file mode 100644 index 0000000..c0ef43e --- /dev/null +++ b/config/crd/bases/infra.io.odimra_biossettings.yaml @@ -0,0 +1,64 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: biossettings.infra.io.odimra +spec: + group: infra.io.odimra + names: + kind: BiosSetting + listKind: BiosSettingList + plural: biossettings + singular: biossetting + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: BiosSetting is the Schema for the biossettings API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Bmcs should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Bmcs may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: BiosSettingSpec defines the desired state of BiosSetting + properties: + biosAttributes: + additionalProperties: + type: string + type: object + bmcName: + type: string + serialNumber: + type: string + systemID: + type: string + required: + - biosAttributes + type: object + status: + description: BiosSettingStatus defines the observed state of BiosSetting + properties: + attributes: + additionalProperties: + type: string + type: object + type: object + type: object + served: true + storage: true + subresources: + status: {} + diff --git a/config/crd/bases/infra.io.odimra_bmcs.yaml b/config/crd/bases/infra.io.odimra_bmcs.yaml new file mode 100644 index 0000000..b33f777 --- /dev/null +++ b/config/crd/bases/infra.io.odimra_bmcs.yaml @@ -0,0 +1,157 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: bmcs.infra.io.odimra +spec: + group: infra.io.odimra + names: + kind: Bmc + listKind: BmcList + plural: bmcs + singular: bmc + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.bmcSystemId + name: SystemID + type: string + - jsonPath: .status.serialNumber + name: SerialNumber + type: string + - jsonPath: .status.vendorName + name: Vendor + type: string + - jsonPath: .status.modelID + name: ModelID + type: string + - jsonPath: .status.firmwareVersion + name: FirmwareVersion + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: Bmc is the Schema for the bmcs API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: BmcSpec defines the desired state of Bmc + properties: + bmc: + properties: + address: + type: string + connectionMethodVariant: + type: string + powerState: + type: string + resetType: + type: string + required: + - address + - connectionMethodVariant + - powerState + - resetType + type: object + credentials: + properties: + password: + type: string + username: + type: string + required: + - password + - username + type: object + required: + - bmc + type: object + status: + description: BmcStatus defines the observed state of Bmc + properties: + biosAttributeRegistry: + type: string + biosVersion: + type: string + bmcAddStatus: + type: string + bmcSystemId: + type: string + eventsMessageRegistry: + type: string + firmwareVersion: + type: string + modelID: + type: string + powerState: + type: string + serialNumber: + type: string + storageControllers: + additionalProperties: + description: ArrayControllers defines the storage controllers for + BMC + properties: + drives: + additionalProperties: + description: DriveDetails defines the drive details for specific + Array Controller + properties: + capacityBytes: + type: string + usedInVolumes: + items: + type: integer + type: array + required: + - capacityBytes + - usedInVolumes + type: object + type: object + supportedRAIDLevel: + items: + type: string + type: array + required: + - drives + - supportedRAIDLevel + type: object + type: object + systemReset: + type: string + vendorName: + type: string + required: + - biosAttributeRegistry + - biosVersion + - bmcAddStatus + - bmcSystemId + - firmwareVersion + - modelID + - powerState + - serialNumber + - systemReset + - vendorName + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/infra.io.odimra_bootordersettings.yaml b/config/crd/bases/infra.io.odimra_bootordersettings.yaml new file mode 100644 index 0000000..f82154c --- /dev/null +++ b/config/crd/bases/infra.io.odimra_bootordersettings.yaml @@ -0,0 +1,102 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: bootordersettings.infra.io.odimra +spec: + group: infra.io.odimra + names: + kind: BootOrderSetting + listKind: BootOrderSettingList + plural: bootordersettings + singular: bootordersetting + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: BootOrderSetting is the Schema for the bootordersetting API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Bmcs should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Bmcs may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: BootOrderSettingsSpec defines the desired state of BootOrderSettings + properties: + bmcName: + type: string + boot: + description: BootSetting defines the different settings for boot + properties: + bootOrder: + items: + type: string + type: array + bootSourceOverrideEnabled: + type: string + bootSourceOverrideMode: + type: string + bootSourceOverrideTarget: + type: string + bootSourceOverrideTarget.AllowableValues: + items: + type: string + type: array + uefiTargetBootSourceOverride: + type: string + uefiTargetBootSourceOverride.AllowableValues: + items: + type: string + type: array + type: object + serialNumber: + type: string + systemID: + type: string + type: object + status: + description: BootOrderSettingsStatus defines the observed state of BootOrderSettings + properties: + boot: + description: BootSetting defines the different settings for boot + properties: + bootOrder: + items: + type: string + type: array + bootSourceOverrideEnabled: + type: string + bootSourceOverrideMode: + type: string + bootSourceOverrideTarget: + type: string + bootSourceOverrideTarget.AllowableValues: + items: + type: string + type: array + uefiTargetBootSourceOverride: + type: string + uefiTargetBootSourceOverride.AllowableValues: + items: + type: string + type: array + type: object + type: object + type: object + served: true + storage: true + subresources: + status: {} + diff --git a/config/crd/bases/infra.io.odimra_eventsmessageregistries.yaml b/config/crd/bases/infra.io.odimra_eventsmessageregistries.yaml new file mode 100644 index 0000000..a82d1fa --- /dev/null +++ b/config/crd/bases/infra.io.odimra_eventsmessageregistries.yaml @@ -0,0 +1,88 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: eventsmessageregistries.infra.io.odimra +spec: + group: infra.io.odimra + names: + kind: EventsMessageRegistry + listKind: EventsMessageRegistryList + plural: eventsmessageregistries + singular: eventsmessageregistry + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: EventsMessageRegistry is the Schema for the eventsmessageregistries + API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: EventsMessageRegistrySpec defines the desired state of EventsMessageRegistry + properties: + ID: + type: string + Messages: + additionalProperties: + properties: + Description: + type: string + Message: + type: string + NumberOfArgs: + type: string + Oem: + additionalProperties: + properties: + HealthCategory: + type: string + Type: + type: string + odataType: + type: string + type: object + type: object + ParamTypes: + items: + type: string + type: array + Resolution: + type: string + Severity: + type: string + type: object + type: object + Name: + type: string + OwningEntity: + type: string + RegistryPrefix: + type: string + RegistryVersion: + type: string + type: object + status: + description: EventsMessageRegistryStatus defines the observed state of + EventsMessageRegistry + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/infra.io.odimra_eventsubscriptions.yaml b/config/crd/bases/infra.io.odimra_eventsubscriptions.yaml new file mode 100644 index 0000000..4c8a3d9 --- /dev/null +++ b/config/crd/bases/infra.io.odimra_eventsubscriptions.yaml @@ -0,0 +1,123 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: eventsubscriptions.infra.io.odimra +spec: + group: infra.io.odimra + names: + kind: Eventsubscription + listKind: EventsubscriptionList + plural: eventsubscriptions + singular: eventsubscription + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.eventSubscriptionID + name: EventSubscriptionID + type: string + - jsonPath: .status.destination + name: Destination + type: string + - jsonPath: .status.context + name: Context + type: string + - jsonPath: .status.subscriptionType + name: SubscriptionType + type: string + name: v1 + schema: + openAPIV3Schema: + description: Eventsubscription is the Schema for the eventsubscriptions API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: EventsubscriptionSpec defines the desired state of Eventsubscription + properties: + context: + type: string + destination: + type: string + eventFormatType: + type: string + eventTypes: + items: + type: string + type: array + messageIds: + items: + type: string + type: array + name: + type: string + originResources: + items: + type: string + type: array + resourceTypes: + items: + type: string + type: array + subordinateResources: + type: boolean + required: + - context + - destination + type: object + status: + description: EventsubscriptionStatus defines the observed state of Eventsubscription + properties: + context: + type: string + destination: + type: string + eventSubscriptionID: + type: string + eventTypes: + items: + type: string + type: array + messageIds: + items: + type: string + type: array + name: + type: string + originResources: + items: + type: string + type: array + protocol: + type: string + resourceTypes: + items: + type: string + type: array + subscriptionType: + type: string + required: + - context + - destination + - eventSubscriptionID + - protocol + - subscriptionType + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/infra.io.odimra_firmwares.yaml b/config/crd/bases/infra.io.odimra_firmwares.yaml new file mode 100644 index 0000000..c12ca2b --- /dev/null +++ b/config/crd/bases/infra.io.odimra_firmwares.yaml @@ -0,0 +1,71 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: firmwares.infra.io.odimra +spec: + group: infra.io.odimra + names: + kind: Firmware + listKind: FirmwareList + plural: firmwares + singular: firmware + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: Firmware is the Schema for the firmwares API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Bmcs should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Bmcs may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: FirmwareSpec defines the desired state of Firmware + properties: + image: + properties: + auth: + properties: + password: + type: string + username: + type: string + type: object + imageLocation: + type: string + required: + - imageLocation + type: object + transferProtocol: + type: string + required: + - image + type: object + status: + description: FirmwareStatus defines the observed state of Firmware + properties: + firmwareVersion: + type: string + imagePath: + type: string + status: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/infra.io.odimra_odims.yaml b/config/crd/bases/infra.io.odimra_odims.yaml new file mode 100644 index 0000000..03ac5d0 --- /dev/null +++ b/config/crd/bases/infra.io.odimra_odims.yaml @@ -0,0 +1,70 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: odims.infra.io.odimra +spec: + group: infra.io.odimra + names: + kind: Odim + listKind: OdimList + plural: odims + singular: odim + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.URL + name: URL + type: string + - jsonPath: .status.status + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: Odim is the Schema for the odims API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Bmcs should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Bmcs may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: OdimSpec defines the desired state of Odim + properties: + EventListenerHost: + type: string + URL: + type: string + required: + - EventListenerHost + - URL + type: object + status: + description: OdimStatus defines the observed state of Odim + properties: + connectionMethodVariants: + additionalProperties: + type: string + type: object + status: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/infra.io.odimra_volumes.yaml b/config/crd/bases/infra.io.odimra_volumes.yaml new file mode 100644 index 0000000..83ec64d --- /dev/null +++ b/config/crd/bases/infra.io.odimra_volumes.yaml @@ -0,0 +1,102 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: volumes.infra.io.odimra +spec: + group: infra.io.odimra + names: + kind: Volume + listKind: VolumeList + plural: volumes + singular: volume + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.volumeName + name: VolumeName + type: string + - jsonPath: .status.volumeID + name: VolumeID + type: string + - jsonPath: .status.RAIDType + name: RAIDType + type: string + - jsonPath: .status.capacityBytes + name: CapacityBytes + type: string + name: v1 + schema: + openAPIV3Schema: + description: Volume is the Schema for the volumes API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Bmcs should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Bmcs may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: VolumeSpec defines the desired state of Volume + properties: + RAIDType: + type: string + drives: + items: + type: integer + type: array + storageControllerID: + type: string + type: object + x-kubernetes-preserve-unknown-fields: true + status: + description: VolumeStatus defines the observed state of Volume + properties: + Identifiers: + properties: + DurableName: + type: string + DurableNameFormat: + type: string + required: + - DurableName + - DurableNameFormat + type: object + RAIDType: + type: string + capacityBytes: + type: string + drives: + items: + type: integer + type: array + storageControllerID: + type: string + volumeID: + type: string + volumeName: + type: string + required: + - Identifiers + - RAIDType + - capacityBytes + - drives + - storageControllerID + - volumeID + - volumeName + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml new file mode 100644 index 0000000..aefd5fd --- /dev/null +++ b/config/crd/kustomization.yaml @@ -0,0 +1,15 @@ +resources: +- bases/infra.io.odimra_odims.yaml +- bases/infra.io.odimra_bmcs.yaml +- bases/infra.io.odimra_biossettings.yaml +- bases/infra.io.odimra_biosschemaregistries.yaml +- bases/infra.io.odimra_bootordersettings.yaml +- bases/infra.io.odimra_volumes.yaml +- bases/infra.io.odimra_firmwares.yaml +- bases/infra.io.odimra_eventsubscriptions.yaml +- bases/infra.io.odimra_eventsmessageregistries.yaml + +patchesStrategicMerge: + +configurations: +- kustomizeconfig.yaml diff --git a/config/crd/kustomizeconfig.yaml b/config/crd/kustomizeconfig.yaml new file mode 100644 index 0000000..e0dd298 --- /dev/null +++ b/config/crd/kustomizeconfig.yaml @@ -0,0 +1,18 @@ +nameReference: +- kind: Service + version: v1 + fieldSpecs: + - kind: CustomResourceDefinition + version: v1 + group: apiextensions.k8s.io + path: spec/conversion/webhook/clientConfig/service/name + +namespace: +- kind: CustomResourceDefinition + version: v1 + group: apiextensions.k8s.io + path: spec/conversion/webhook/clientConfig/service/namespace + create: false + +varReference: +- path: metadata/annotations diff --git a/config/crd/patches/cainjection_in_eventsmessageregistries.yaml b/config/crd/patches/cainjection_in_eventsmessageregistries.yaml new file mode 100644 index 0000000..01706fd --- /dev/null +++ b/config/crd/patches/cainjection_in_eventsmessageregistries.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: eventsmessageregistries.infra.io.odimra diff --git a/config/crd/patches/cainjection_in_eventsubscriptions.yaml b/config/crd/patches/cainjection_in_eventsubscriptions.yaml new file mode 100644 index 0000000..f96747c --- /dev/null +++ b/config/crd/patches/cainjection_in_eventsubscriptions.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: eventsubscriptions.infra.io.odimra diff --git a/config/crd/patches/cainjection_in_odims.yaml b/config/crd/patches/cainjection_in_odims.yaml new file mode 100644 index 0000000..7e52fe7 --- /dev/null +++ b/config/crd/patches/cainjection_in_odims.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: odims.infra.io.odimra diff --git a/config/crd/patches/cainjection_in_servers.yaml b/config/crd/patches/cainjection_in_servers.yaml new file mode 100644 index 0000000..d5e06ec --- /dev/null +++ b/config/crd/patches/cainjection_in_servers.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: servers.infra.io.odimra diff --git a/config/crd/patches/webhook_in_eventsmessageregistries.yaml b/config/crd/patches/webhook_in_eventsmessageregistries.yaml new file mode 100644 index 0000000..97f5c29 --- /dev/null +++ b/config/crd/patches/webhook_in_eventsmessageregistries.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: eventsmessageregistries.infra.io.odimra +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/crd/patches/webhook_in_eventsubscriptions.yaml b/config/crd/patches/webhook_in_eventsubscriptions.yaml new file mode 100644 index 0000000..f76f79a --- /dev/null +++ b/config/crd/patches/webhook_in_eventsubscriptions.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: eventsubscriptions.infra.io.odimra +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/crd/patches/webhook_in_odims.yaml b/config/crd/patches/webhook_in_odims.yaml new file mode 100644 index 0000000..26dc7af --- /dev/null +++ b/config/crd/patches/webhook_in_odims.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: odims.infra.io.odimra +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/crd/patches/webhook_in_servers.yaml b/config/crd/patches/webhook_in_servers.yaml new file mode 100644 index 0000000..e525566 --- /dev/null +++ b/config/crd/patches/webhook_in_servers.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: servers.infra.io.odimra +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml new file mode 100644 index 0000000..1c7944a --- /dev/null +++ b/config/default/kustomization.yaml @@ -0,0 +1,13 @@ +namespace: bmc-op + +namePrefix: bmc-op + +bases: +- ../crd +- ../rbac +- ../manager + + +patchesStrategicMerge: + +- manager_auth_proxy_patch.yaml diff --git a/config/default/manager_auth_proxy_patch.yaml b/config/default/manager_auth_proxy_patch.yaml new file mode 100644 index 0000000..ea502cf --- /dev/null +++ b/config/default/manager_auth_proxy_patch.yaml @@ -0,0 +1,32 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: controller-manager + namespace: system +spec: + template: + spec: + containers: + - name: kube-rbac-proxy + image: gcr.io/kubebuilder/kube-rbac-proxy:v0.8.0 + args: + - "--secure-listen-address=0.0.0.0:8443" + - "--upstream=http://127.0.0.1:8080/" + - "--logtostderr=true" + - "--v=0" + ports: + - containerPort: 8443 + protocol: TCP + name: https + resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 5m + memory: 64Mi + - name: manager + args: + - "--health-probe-bind-address=:8081" + - "--metrics-bind-address=127.0.0.1:8080" + - "--leader-elect" diff --git a/config/default/manager_config_patch.yaml b/config/default/manager_config_patch.yaml new file mode 100644 index 0000000..6c40015 --- /dev/null +++ b/config/default/manager_config_patch.yaml @@ -0,0 +1,20 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: controller-manager + namespace: system +spec: + template: + spec: + containers: + - name: manager + args: + - "--config=controller_manager_config.yaml" + volumeMounts: + - name: manager-config + mountPath: /controller_manager_config.yaml + subPath: controller_manager_config.yaml + volumes: + - name: manager-config + configMap: + name: manager-config diff --git a/config/keys/config.yaml b/config/keys/config.yaml new file mode 100644 index 0000000..3aaadbe --- /dev/null +++ b/config/keys/config.yaml @@ -0,0 +1,31 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: bmc-operator-config + namespace: bmc-op + labels: + type: config +data: + config.yaml: | + reconciliation : Accommodate #Accommodate/Revert + eventSubReconciliation: Accommodate #Accommodate/Revert + reconcileInterval: "24" #Time in `Hours` (in string) + secretName: bmc-secret + metricsBindPort: "8080" # cannot change at runtime (in string) + healthProbeBindPort: "8081" # cannot change at runtime (in string) + eventClientPort: "45000" # cannot change at runtime (in string) + logLevel: warn + logFormat: syslog + kubeConfigPath: # provide kube config file path as value when running with 'make install run' command + namespace: bmc-op + operatorEventSubscriptionEventTypes: + - ResourceAdded + - ResourceRemoved + - Alert + operatorEventSubsciptionMessageIds: + - ResourceEvent.1.2.0.ResourceRemoved + - ResourceEvent.1.2.0.ResourceAdded + - iLOEvents.3.2.ServerPostDiscoveryComplete + - iLOEvents.3.2.ServerPostComplete + operatorEventSubsciptionResourceTypes: + - ComputerSystems diff --git a/config/manager/controller_manager_config.yaml b/config/manager/controller_manager_config.yaml new file mode 100644 index 0000000..7e2010c --- /dev/null +++ b/config/manager/controller_manager_config.yaml @@ -0,0 +1,11 @@ +apiVersion: controller-runtime.sigs.k8s.io/v1alpha1 +kind: ControllerManagerConfig +health: + healthProbeBindAddress: :8081 +metrics: + bindAddress: 127.0.0.1:8080 +webhook: + port: 9443 +leaderElection: + leaderElect: true + resourceName: a247721e.odimra diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml new file mode 100644 index 0000000..0671cfe --- /dev/null +++ b/config/manager/kustomization.yaml @@ -0,0 +1,24 @@ +resources: +- manager.yaml +- service.yaml + +vars: +- name: MY_SERVICE_NAME + objref: + kind: Service + name: controller-manager + apiVersion: v1 + +generatorOptions: + disableNameSuffixHash: true + +configMapGenerator: +- files: + - controller_manager_config.yaml + name: manager-config +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +images: +- name: controller + newName: odimra/bmc-operator + newTag: 1.0.0 diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml new file mode 100644 index 0000000..68709c0 --- /dev/null +++ b/config/manager/manager.yaml @@ -0,0 +1,89 @@ +apiVersion: v1 +kind: Namespace +metadata: + labels: + control-plane: controller-manager + app: manager + name: system +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: controller-manager + namespace: system + labels: + control-plane: controller-manager + app: manager +spec: + selector: + matchLabels: + control-plane: controller-manager + app: manager + replicas: 1 + template: + metadata: + annotations: + kubectl.kubernetes.io/default-container: manager + labels: + control-plane: controller-manager + app: manager + spec: + volumes: + - name: bmc-operator-config-vol + projected: + sources: + - configMap: + name: bmc-operator-config + - name: operator-log + persistentVolumeClaim: + claimName: operator-log-claim + securityContext: + runAsNonRoot: true + containers: + - command: + - ./start_bmc_operator.sh + args: + - --leader-elect + image: controller:latest + volumeMounts: + - name: bmc-operator-config-vol + mountPath: /etc/bmc-operator-config + - name: operator-log + mountPath: /var/log/operator_logs + name: manager + securityContext: + allowPrivilegeEscalation: false + env: + - name: HOST_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + livenessProbe: + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 15 + periodSeconds: 20 + readinessProbe: + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 10 + ports: + - containerPort: 45000 + # TODO(user): Configure the resources accordingly based on the project requirements. + # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 10m + memory: 64Mi + serviceAccountName: controller-manager + terminationGracePeriodSeconds: 10 \ No newline at end of file diff --git a/config/manager/service.yaml b/config/manager/service.yaml new file mode 100644 index 0000000..303cf24 --- /dev/null +++ b/config/manager/service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: controller-manager + namespace: system + labels: + control-plane: controller-manager + app: manager +spec: + ports: + - nodePort: 32123 + port: 45000 + selector: + control-plane: controller-manager + app: manager + type: NodePort \ No newline at end of file diff --git a/config/manifests/kustomization.yaml b/config/manifests/kustomization.yaml new file mode 100644 index 0000000..bd72712 --- /dev/null +++ b/config/manifests/kustomization.yaml @@ -0,0 +1,5 @@ +resources: +- bases/odim-operator.clusterserviceversion.yaml +- ../default +- ../samples +- ../scorecard diff --git a/config/prometheus/kustomization.yaml b/config/prometheus/kustomization.yaml new file mode 100644 index 0000000..ed13716 --- /dev/null +++ b/config/prometheus/kustomization.yaml @@ -0,0 +1,2 @@ +resources: +- monitor.yaml diff --git a/config/prometheus/monitor.yaml b/config/prometheus/monitor.yaml new file mode 100644 index 0000000..44165dc --- /dev/null +++ b/config/prometheus/monitor.yaml @@ -0,0 +1,18 @@ +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + labels: + control-plane: controller-manager + name: controller-manager-metrics-monitor + namespace: system +spec: + endpoints: + - path: /metrics + port: https + scheme: https + bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token + tlsConfig: + insecureSkipVerify: true + selector: + matchLabels: + control-plane: controller-manager diff --git a/config/rbac/auth_proxy_client_clusterrole.yaml b/config/rbac/auth_proxy_client_clusterrole.yaml new file mode 100644 index 0000000..51a75db --- /dev/null +++ b/config/rbac/auth_proxy_client_clusterrole.yaml @@ -0,0 +1,9 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: metrics-reader +rules: +- nonResourceURLs: + - "/metrics" + verbs: + - get diff --git a/config/rbac/auth_proxy_role.yaml b/config/rbac/auth_proxy_role.yaml new file mode 100644 index 0000000..80e1857 --- /dev/null +++ b/config/rbac/auth_proxy_role.yaml @@ -0,0 +1,17 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: proxy-role +rules: +- apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create +- apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create diff --git a/config/rbac/auth_proxy_role_binding.yaml b/config/rbac/auth_proxy_role_binding.yaml new file mode 100644 index 0000000..ec7acc0 --- /dev/null +++ b/config/rbac/auth_proxy_role_binding.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: proxy-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: proxy-role +subjects: +- kind: ServiceAccount + name: controller-manager + namespace: system diff --git a/config/rbac/auth_proxy_service.yaml b/config/rbac/auth_proxy_service.yaml new file mode 100644 index 0000000..71f1797 --- /dev/null +++ b/config/rbac/auth_proxy_service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + control-plane: controller-manager + name: controller-manager-metrics-service + namespace: system +spec: + ports: + - name: https + port: 8443 + protocol: TCP + targetPort: https + selector: + control-plane: controller-manager diff --git a/config/rbac/biosschemaregistry_editor_role.yaml b/config/rbac/biosschemaregistry_editor_role.yaml new file mode 100644 index 0000000..69a394a --- /dev/null +++ b/config/rbac/biosschemaregistry_editor_role.yaml @@ -0,0 +1,24 @@ +# permissions for end users to edit biosschemaregistries. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: biosschemaregistry-editor-role +rules: +- apiGroups: + - infra.io.odimra + resources: + - biosschemaregistries + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - infra.io.odimra + resources: + - biosschemaregistries/status + verbs: + - get diff --git a/config/rbac/biosschemaregistry_viewer_role.yaml b/config/rbac/biosschemaregistry_viewer_role.yaml new file mode 100644 index 0000000..46bbf15 --- /dev/null +++ b/config/rbac/biosschemaregistry_viewer_role.yaml @@ -0,0 +1,20 @@ +# permissions for end users to view biosschemaregistries. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: biosschemaregistry-viewer-role +rules: +- apiGroups: + - infra.io.odimra + resources: + - biosschemaregistries + verbs: + - get + - list + - watch +- apiGroups: + - infra.io.odimra + resources: + - biosschemaregistries/status + verbs: + - get diff --git a/config/rbac/biossetting_editor_role.yaml b/config/rbac/biossetting_editor_role.yaml new file mode 100644 index 0000000..583a232 --- /dev/null +++ b/config/rbac/biossetting_editor_role.yaml @@ -0,0 +1,24 @@ +# permissions for end users to edit biossettings. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: biossetting-editor-role +rules: +- apiGroups: + - infra.io.odimra + resources: + - biossettings + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - infra.io.odimra + resources: + - biossettings/status + verbs: + - get diff --git a/config/rbac/biossetting_viewer_role.yaml b/config/rbac/biossetting_viewer_role.yaml new file mode 100644 index 0000000..8bd1350 --- /dev/null +++ b/config/rbac/biossetting_viewer_role.yaml @@ -0,0 +1,20 @@ +# permissions for end users to view biossettings. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: biossetting-viewer-role +rules: +- apiGroups: + - infra.io.odimra + resources: + - biossettings + verbs: + - get + - list + - watch +- apiGroups: + - infra.io.odimra + resources: + - biossettings/status + verbs: + - get diff --git a/config/rbac/bmcop_admin_role.yaml b/config/rbac/bmcop_admin_role.yaml new file mode 100644 index 0000000..54f96df --- /dev/null +++ b/config/rbac/bmcop_admin_role.yaml @@ -0,0 +1,281 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + creationTimestamp: null + name: admin +rules: +- apiGroups: + - "" + resources: + - secrets + verbs: + - get + - list + - patch + - update + - watch +- apiGroups: + - '*' + resources: + - secrets + verbs: + - get + - list + - patch + - update + - watch +- apiGroups: + - apps + resources: + - deployments + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - "" + resources: + - pods + verbs: + - get + - list + - watch +- apiGroups: + - infra.io.odimra + resources: + - biosschemaregistries + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - infra.io.odimra + resources: + - biosschemaregistries/finalizers + verbs: + - update +- apiGroups: + - infra.io.odimra + resources: + - biosschemaregistries/status + verbs: + - get + - patch + - update +- apiGroups: + - infra.io.odimra + resources: + - biossettings + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - infra.io.odimra + resources: + - biossettings/finalizers + verbs: + - update +- apiGroups: + - infra.io.odimra + resources: + - biossettings/status + verbs: + - get + - patch + - update +- apiGroups: + - infra.io.odimra + resources: + - bmcs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - infra.io.odimra + resources: + - bmcs/finalizers + verbs: + - update +- apiGroups: + - infra.io.odimra + resources: + - bmcs/status + verbs: + - get + - patch + - update +- apiGroups: + - infra.io.odimra + resources: + - bootordersettings + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - infra.io.odimra + resources: + - bootordersettings/finalizers + verbs: + - update +- apiGroups: + - infra.io.odimra + resources: + - bootordersettings/status + verbs: + - get + - patch + - update +- apiGroups: + - infra.io.odimra + resources: + - eventsubscriptions + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - infra.io.odimra + resources: + - eventsubscriptions/finalizers + verbs: + - update +- apiGroups: + - infra.io.odimra + resources: + - eventsubscriptions/status + verbs: + - get + - patch + - update +- apiGroups: + - infra.io.odimra + resources: + - firmwares + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - infra.io.odimra + resources: + - firmwares/finalizers + verbs: + - update +- apiGroups: + - infra.io.odimra + resources: + - firmwares/status + verbs: + - get + - patch + - update +- apiGroups: + - infra.io.odimra + resources: + - odims + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - infra.io.odimra + resources: + - odims/finalizers + verbs: + - update +- apiGroups: + - infra.io.odimra + resources: + - odims/status + verbs: + - get + - patch + - update +- apiGroups: + - infra.io.odimra + resources: + - volumes + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - infra.io.odimra + resources: + - volumes/finalizers + verbs: + - update +- apiGroups: + - infra.io.odimra + resources: + - volumes/status + verbs: + - get + - patch + - update +- apiGroups: + - infra.io.odimra + resources: + - eventsmessageregistries + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - infra.io.odimra + resources: + - eventsmessageregistries/finalizers + verbs: + - update +- apiGroups: + - infra.io.odimra + resources: + - eventsmessageregistries/status + verbs: + - get + - patch + - update diff --git a/config/rbac/bmcop_operator_role.yaml b/config/rbac/bmcop_operator_role.yaml new file mode 100644 index 0000000..978f769 --- /dev/null +++ b/config/rbac/bmcop_operator_role.yaml @@ -0,0 +1,246 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + creationTimestamp: null + name: operator +rules: +- apiGroups: + - "" + resources: + - secrets + verbs: + - get + - list + - patch + - update + - watch +- apiGroups: + - '*' + resources: + - secrets + verbs: + - get + - list + - patch + - update + - watch +- apiGroups: + - apps + resources: + - deployments + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - "" + resources: + - pods + verbs: + - get + - list + - watch +- apiGroups: + - infra.io.odimra + resources: + - biosschemaregistries + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - infra.io.odimra + resources: + - biosschemaregistries/finalizers + verbs: + - update +- apiGroups: + - infra.io.odimra + resources: + - biosschemaregistries/status + verbs: + - get + - patch + - update +- apiGroups: + - infra.io.odimra + resources: + - biossettings + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - infra.io.odimra + resources: + - biossettings/finalizers + verbs: + - update +- apiGroups: + - infra.io.odimra + resources: + - biossettings/status + verbs: + - get + - patch + - update +- apiGroups: + - infra.io.odimra + resources: + - bmcs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - infra.io.odimra + resources: + - bmcs/finalizers + verbs: + - update +- apiGroups: + - infra.io.odimra + resources: + - bmcs/status + verbs: + - get + - patch + - update +- apiGroups: + - infra.io.odimra + resources: + - bootordersettings + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - infra.io.odimra + resources: + - bootordersettings/finalizers + verbs: + - update +- apiGroups: + - infra.io.odimra + resources: + - bootordersettings/status + verbs: + - get + - patch + - update +- apiGroups: + - infra.io.odimra + resources: + - firmwares + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - infra.io.odimra + resources: + - firmwares/finalizers + verbs: + - update +- apiGroups: + - infra.io.odimra + resources: + - firmwares/status + verbs: + - get + - patch + - update +- apiGroups: + - infra.io.odimra + resources: + - odims + verbs: + - get + - list + - patch + - watch +- apiGroups: + - infra.io.odimra + resources: + - odims/finalizers + verbs: + - update +- apiGroups: + - infra.io.odimra + resources: + - odims/status + verbs: + - get + - patch + - update +- apiGroups: + - infra.io.odimra + resources: + - volumes + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - infra.io.odimra + resources: + - volumes/finalizers + verbs: + - update +- apiGroups: + - infra.io.odimra + resources: + - volumes/status + verbs: + - get + - patch + - update +- apiGroups: + - infra.io.odimra + resources: + - eventsmessageregistries + verbs: + - get + - list + - watch +- apiGroups: + - infra.io.odimra + resources: + - eventsmessageregistries/finalizers + verbs: + - update +- apiGroups: + - infra.io.odimra + resources: + - eventsmessageregistries/status + verbs: + - get diff --git a/config/rbac/bmcop_readonly_role.yaml b/config/rbac/bmcop_readonly_role.yaml new file mode 100644 index 0000000..a8377d8 --- /dev/null +++ b/config/rbac/bmcop_readonly_role.yaml @@ -0,0 +1,232 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + creationTimestamp: null + name: readonly +rules: +- apiGroups: + - "" + resources: + - secrets + verbs: + - get + - list + - patch + - update + - watch +- apiGroups: + - '*' + resources: + - secrets + verbs: + - get + - list + - patch + - update + - watch +- apiGroups: + - apps + resources: + - deployments + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - "" + resources: + - pods + verbs: + - get + - list + - watch +- apiGroups: + - infra.io.odimra + resources: + - biosschemaregistries + verbs: + - get + - list + - watch +- apiGroups: + - infra.io.odimra + resources: + - biosschemaregistries/finalizers + verbs: + - update +- apiGroups: + - infra.io.odimra + resources: + - biosschemaregistries/status + verbs: + - get + - patch + apiGroups: + - infra.io.odimra + resources: + - biossettings + verbs: + - get + - list + - watch +- apiGroups: + - infra.io.odimra + resources: + - biossettings/finalizers + verbs: + - update +- apiGroups: + - infra.io.odimra + resources: + - biossettings/status + verbs: + - get + - patch + - update +- apiGroups: + - infra.io.odimra + resources: + - bmcs + verbs: + - get + - list + - watch +- apiGroups: + - infra.io.odimra + resources: + - bmcs/finalizers + verbs: + - update +- apiGroups: + - infra.io.odimra + resources: + - bmcs/status + verbs: + - get + apiGroups: + - infra.io.odimra + resources: + - bootordersettings + verbs: + - get + - list + - watch +- apiGroups: + - infra.io.odimra + resources: + - bootordersettings/finalizers + verbs: + - update +- apiGroups: + - infra.io.odimra + resources: + - bootordersettings/status + verbs: + - get + apiGroups: + - infra.io.odimra + resources: + - eventsubscriptions + verbs: + - get + - list + - watch +- apiGroups: + - infra.io.odimra + resources: + - eventsubscriptions/finalizers + verbs: + - update +- apiGroups: + - infra.io.odimra + resources: + - eventsubscriptions/status + verbs: + - get + apiGroups: + - infra.io.odimra + resources: + - firmwares + verbs: + - get + - list + - watch +- apiGroups: + - infra.io.odimra + resources: + - firmwares/finalizers + verbs: + - update +- apiGroups: + - infra.io.odimra + resources: + - firmwares/status + verbs: + - get + apiGroups: + - infra.io.odimra + resources: + - odims + verbs: + - get + - list + - watch +- apiGroups: + - infra.io.odimra + resources: + - odims/finalizers + verbs: + - update +- apiGroups: + - infra.io.odimra + resources: + - odims/status + verbs: + - get + - patch + - update +- apiGroups: + - infra.io.odimra + resources: + - volumes + verbs: + - get + - list + - watch +- apiGroups: + - infra.io.odimra + resources: + - volumes/finalizers + verbs: + - update +- apiGroups: + - infra.io.odimra + resources: + - volumes/status + verbs: + - get +- apiGroups: + - infra.io.odimra + resources: + - eventsmessageregistries + verbs: + - get + - list + - watch +- apiGroups: + - infra.io.odimra + resources: + - eventsmessageregistries/finalizers + verbs: + - update +- apiGroups: + - infra.io.odimra + resources: + - eventsmessageregistries/status + verbs: + - get diff --git a/config/rbac/bootordersettings_editor_role.yaml b/config/rbac/bootordersettings_editor_role.yaml new file mode 100644 index 0000000..56f6304 --- /dev/null +++ b/config/rbac/bootordersettings_editor_role.yaml @@ -0,0 +1,24 @@ +# permissions for end users to edit bootordersettings. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: bootordersettings-editor-role +rules: +- apiGroups: + - infra.io.odimra + resources: + - bootordersettings + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - infra.io.odimra + resources: + - bootordersettings/status + verbs: + - get diff --git a/config/rbac/bootordersettings_viewer_role.yaml b/config/rbac/bootordersettings_viewer_role.yaml new file mode 100644 index 0000000..8a07244 --- /dev/null +++ b/config/rbac/bootordersettings_viewer_role.yaml @@ -0,0 +1,20 @@ +# permissions for end users to view bootordersettings. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: bootordersettings-viewer-role +rules: +- apiGroups: + - infra.io.odimra + resources: + - bootordersettings + verbs: + - get + - list + - watch +- apiGroups: + - infra.io.odimra + resources: + - bootordersettings/status + verbs: + - get diff --git a/config/rbac/eventsmessageregistry_editor_role.yaml b/config/rbac/eventsmessageregistry_editor_role.yaml new file mode 100644 index 0000000..8d81c80 --- /dev/null +++ b/config/rbac/eventsmessageregistry_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit eventsmessageregistries. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: eventsmessageregistry-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: bmc-operator + app.kubernetes.io/part-of: bmc-operator + app.kubernetes.io/managed-by: kustomize + name: eventsmessageregistry-editor-role +rules: +- apiGroups: + - infra.io.odimra + resources: + - eventsmessageregistries + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - infra.io.odimra + resources: + - eventsmessageregistries/status + verbs: + - get diff --git a/config/rbac/eventsmessageregistry_viewer_role.yaml b/config/rbac/eventsmessageregistry_viewer_role.yaml new file mode 100644 index 0000000..d2e4cdf --- /dev/null +++ b/config/rbac/eventsmessageregistry_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view eventsmessageregistries. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: eventsmessageregistry-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: bmc-operator + app.kubernetes.io/part-of: bmc-operator + app.kubernetes.io/managed-by: kustomize + name: eventsmessageregistry-viewer-role +rules: +- apiGroups: + - infra.io.odimra + resources: + - eventsmessageregistries + verbs: + - get + - list + - watch +- apiGroups: + - infra.io.odimra + resources: + - eventsmessageregistries/status + verbs: + - get diff --git a/config/rbac/eventsubscription_editor_role.yaml b/config/rbac/eventsubscription_editor_role.yaml new file mode 100644 index 0000000..fc4bf88 --- /dev/null +++ b/config/rbac/eventsubscription_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit eventsubscriptions. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: eventsubscription-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: server-operator + app.kubernetes.io/part-of: server-operator + app.kubernetes.io/managed-by: kustomize + name: eventsubscription-editor-role +rules: +- apiGroups: + - infra.io.odimra + resources: + - eventsubscriptions + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - infra.io.odimra + resources: + - eventsubscriptions/status + verbs: + - get diff --git a/config/rbac/eventsubscription_viewer_role.yaml b/config/rbac/eventsubscription_viewer_role.yaml new file mode 100644 index 0000000..cc8d232 --- /dev/null +++ b/config/rbac/eventsubscription_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view eventsubscriptions. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: eventsubscription-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: server-operator + app.kubernetes.io/part-of: server-operator + app.kubernetes.io/managed-by: kustomize + name: eventsubscription-viewer-role +rules: +- apiGroups: + - infra.io.odimra + resources: + - eventsubscriptions + verbs: + - get + - list + - watch +- apiGroups: + - infra.io.odimra + resources: + - eventsubscriptions/status + verbs: + - get diff --git a/config/rbac/kustomization.yaml b/config/rbac/kustomization.yaml new file mode 100644 index 0000000..1bac7f5 --- /dev/null +++ b/config/rbac/kustomization.yaml @@ -0,0 +1,21 @@ +resources: +# All RBAC will be applied under this service account in +# the deployment namespace. You may comment out this resource +# if your manager will use a service account that exists at +# runtime. Be sure to update RoleBinding and ClusterRoleBinding +# subjects if changing service account names. +- service_account.yaml +- role.yaml +- role_binding.yaml +- leader_election_role.yaml +- leader_election_role_binding.yaml +# Comment the following 4 lines if you want to disable +# the auth proxy (https://github.com/brancz/kube-rbac-proxy) +# which protects your /metrics endpoint. +- auth_proxy_service.yaml +- auth_proxy_role.yaml +- auth_proxy_role_binding.yaml +- auth_proxy_client_clusterrole.yaml +- bmcop_admin_role.yaml +- bmcop_operator_role.yaml +- bmcop_readonly_role.yaml diff --git a/config/rbac/leader_election_role.yaml b/config/rbac/leader_election_role.yaml new file mode 100644 index 0000000..4190ec8 --- /dev/null +++ b/config/rbac/leader_election_role.yaml @@ -0,0 +1,37 @@ +# permissions to do leader election. +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: leader-election-role +rules: +- apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch diff --git a/config/rbac/leader_election_role_binding.yaml b/config/rbac/leader_election_role_binding.yaml new file mode 100644 index 0000000..1d1321e --- /dev/null +++ b/config/rbac/leader_election_role_binding.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: leader-election-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: leader-election-role +subjects: +- kind: ServiceAccount + name: controller-manager + namespace: system diff --git a/config/rbac/odim_editor_role.yaml b/config/rbac/odim_editor_role.yaml new file mode 100644 index 0000000..3ec4095 --- /dev/null +++ b/config/rbac/odim_editor_role.yaml @@ -0,0 +1,24 @@ +# permissions for end users to edit odims. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: odim-editor-role +rules: +- apiGroups: + - infra.io.odimra + resources: + - odims + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - infra.io.odimra + resources: + - odims/status + verbs: + - get diff --git a/config/rbac/odim_viewer_role.yaml b/config/rbac/odim_viewer_role.yaml new file mode 100644 index 0000000..e5cc230 --- /dev/null +++ b/config/rbac/odim_viewer_role.yaml @@ -0,0 +1,20 @@ +# permissions for end users to view odims. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: odim-viewer-role +rules: +- apiGroups: + - infra.io.odimra + resources: + - odims + verbs: + - get + - list + - watch +- apiGroups: + - infra.io.odimra + resources: + - odims/status + verbs: + - get diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml new file mode 100644 index 0000000..d23f01f --- /dev/null +++ b/config/rbac/role.yaml @@ -0,0 +1,281 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + creationTimestamp: null + name: manager-role +rules: +- apiGroups: + - "" + resources: + - secrets + verbs: + - get + - list + - patch + - update + - watch +- apiGroups: + - '*' + resources: + - secrets + verbs: + - get + - list + - patch + - update + - watch +- apiGroups: + - apps + resources: + - deployments + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - "" + resources: + - pods + verbs: + - get + - list + - watch +- apiGroups: + - infra.io.odimra + resources: + - biosschemaregistries + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - infra.io.odimra + resources: + - biosschemaregistries/finalizers + verbs: + - update +- apiGroups: + - infra.io.odimra + resources: + - biosschemaregistries/status + verbs: + - get + - patch + - update +- apiGroups: + - infra.io.odimra + resources: + - biossettings + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - infra.io.odimra + resources: + - biossettings/finalizers + verbs: + - update +- apiGroups: + - infra.io.odimra + resources: + - biossettings/status + verbs: + - get + - patch + - update +- apiGroups: + - infra.io.odimra + resources: + - bmcs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - infra.io.odimra + resources: + - bmcs/finalizers + verbs: + - update +- apiGroups: + - infra.io.odimra + resources: + - bmcs/status + verbs: + - get + - patch + - update +- apiGroups: + - infra.io.odimra + resources: + - bootordersettings + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - infra.io.odimra + resources: + - bootordersettings/finalizers + verbs: + - update +- apiGroups: + - infra.io.odimra + resources: + - bootordersettings/status + verbs: + - get + - patch + - update +- apiGroups: + - infra.io.odimra + resources: + - eventsmessageregistries + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - infra.io.odimra + resources: + - eventsmessageregistries/finalizers + verbs: + - update +- apiGroups: + - infra.io.odimra + resources: + - eventsmessageregistries/status + verbs: + - get + - patch + - update +- apiGroups: + - infra.io.odimra + resources: + - eventsubscriptions + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - infra.io.odimra + resources: + - eventsubscriptions/finalizers + verbs: + - update +- apiGroups: + - infra.io.odimra + resources: + - eventsubscriptions/status + verbs: + - get + - patch + - update +- apiGroups: + - infra.io.odimra + resources: + - firmwares + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - infra.io.odimra + resources: + - firmwares/finalizers + verbs: + - update +- apiGroups: + - infra.io.odimra + resources: + - firmwares/status + verbs: + - get + - patch + - update +- apiGroups: + - infra.io.odimra + resources: + - odims + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - infra.io.odimra + resources: + - odims/finalizers + verbs: + - update +- apiGroups: + - infra.io.odimra + resources: + - odims/status + verbs: + - get + - patch + - update +- apiGroups: + - infra.io.odimra + resources: + - volumes + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - infra.io.odimra + resources: + - volumes/finalizers + verbs: + - update +- apiGroups: + - infra.io.odimra + resources: + - volumes/status + verbs: + - get + - patch + - update diff --git a/config/rbac/role_binding.yaml b/config/rbac/role_binding.yaml new file mode 100644 index 0000000..2070ede --- /dev/null +++ b/config/rbac/role_binding.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: manager-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: manager-role +subjects: +- kind: ServiceAccount + name: controller-manager + namespace: system diff --git a/config/rbac/service_account.yaml b/config/rbac/service_account.yaml new file mode 100644 index 0000000..7cd6025 --- /dev/null +++ b/config/rbac/service_account.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: controller-manager + namespace: system diff --git a/config/rbac/volume_editor_role.yaml b/config/rbac/volume_editor_role.yaml new file mode 100644 index 0000000..b43cf56 --- /dev/null +++ b/config/rbac/volume_editor_role.yaml @@ -0,0 +1,24 @@ +# permissions for end users to edit volumes. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: volume-editor-role +rules: +- apiGroups: + - infra.io.odimra + resources: + - volumes + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - infra.io.odimra + resources: + - volumes/status + verbs: + - get \ No newline at end of file diff --git a/config/rbac/volume_viewer_role.yaml b/config/rbac/volume_viewer_role.yaml new file mode 100644 index 0000000..9d2c78d --- /dev/null +++ b/config/rbac/volume_viewer_role.yaml @@ -0,0 +1,20 @@ +# permissions for end users to view volumes. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: volume-viewer-role +rules: +- apiGroups: + - infra.io.odimra + resources: + - volumes + verbs: + - get + - list + - watch +- apiGroups: + - infra.io.odimra + resources: + - volumes/status + verbs: + - get diff --git a/config/scorecard/bases/config.yaml b/config/scorecard/bases/config.yaml new file mode 100644 index 0000000..c770478 --- /dev/null +++ b/config/scorecard/bases/config.yaml @@ -0,0 +1,7 @@ +apiVersion: scorecard.operatorframework.io/v1alpha3 +kind: Configuration +metadata: + name: config +stages: +- parallel: true + tests: [] diff --git a/config/scorecard/kustomization.yaml b/config/scorecard/kustomization.yaml new file mode 100644 index 0000000..ee55799 --- /dev/null +++ b/config/scorecard/kustomization.yaml @@ -0,0 +1,15 @@ +resources: +- bases/config.yaml +patchesJson6902: +- path: patches/basic.config.yaml + target: + group: scorecard.operatorframework.io + version: v1alpha3 + kind: Configuration + name: config +- path: patches/olm.config.yaml + target: + group: scorecard.operatorframework.io + version: v1alpha3 + kind: Configuration + name: config diff --git a/config/scorecard/patches/basic.config.yaml b/config/scorecard/patches/basic.config.yaml new file mode 100644 index 0000000..64c713b --- /dev/null +++ b/config/scorecard/patches/basic.config.yaml @@ -0,0 +1,10 @@ +- op: add + path: /stages/0/tests/- + value: + entrypoint: + - scorecard-test + - basic-check-spec + image: quay.io/operator-framework/scorecard-test:v1.20.1 + labels: + suite: basic + test: basic-check-spec-test diff --git a/config/scorecard/patches/olm.config.yaml b/config/scorecard/patches/olm.config.yaml new file mode 100644 index 0000000..4d9b6de --- /dev/null +++ b/config/scorecard/patches/olm.config.yaml @@ -0,0 +1,50 @@ +- op: add + path: /stages/0/tests/- + value: + entrypoint: + - scorecard-test + - olm-bundle-validation + image: quay.io/operator-framework/scorecard-test:v1.20.1 + labels: + suite: olm + test: olm-bundle-validation-test +- op: add + path: /stages/0/tests/- + value: + entrypoint: + - scorecard-test + - olm-crds-have-validation + image: quay.io/operator-framework/scorecard-test:v1.20.1 + labels: + suite: olm + test: olm-crds-have-validation-test +- op: add + path: /stages/0/tests/- + value: + entrypoint: + - scorecard-test + - olm-crds-have-resources + image: quay.io/operator-framework/scorecard-test:v1.20.1 + labels: + suite: olm + test: olm-crds-have-resources-test +- op: add + path: /stages/0/tests/- + value: + entrypoint: + - scorecard-test + - olm-spec-descriptors + image: quay.io/operator-framework/scorecard-test:v1.20.1 + labels: + suite: olm + test: olm-spec-descriptors-test +- op: add + path: /stages/0/tests/- + value: + entrypoint: + - scorecard-test + - olm-status-descriptors + image: quay.io/operator-framework/scorecard-test:v1.20.1 + labels: + suite: olm + test: olm-status-descriptors-test diff --git a/controllers/bios/biosUtils.go b/controllers/bios/biosUtils.go new file mode 100644 index 0000000..60646d1 --- /dev/null +++ b/controllers/bios/biosUtils.go @@ -0,0 +1,41 @@ +//(C) Copyright [2023] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +package controllers + +import ( + "context" + + infraiov1 "github.com/ODIM-Project/BMCOperator/api/v1" + common "github.com/ODIM-Project/BMCOperator/controllers/common" + restclient "github.com/ODIM-Project/BMCOperator/controllers/restclient" + utils "github.com/ODIM-Project/BMCOperator/controllers/utils" +) + +type BiosInterface interface { + ValidateBiosAttributes(biosID string) (bool, map[string]interface{}) + getBiosLinkAndBody(resp map[string]interface{}, biosProps map[string]interface{}, biosBmcIP string) (string, []byte) + UpdateBiosAttributesOnReset(biosBmcIP string, updatedBiosAttributes map[string]string) + GetBiosAttributes(bmcObj *infraiov1.Bmc) map[string]string + GetBiosAttributeID(biosVersion string, bmcObj *infraiov1.Bmc) (string, map[string]interface{}) +} + +type biosUtils struct { + ctx context.Context + biosObj *infraiov1.BiosSetting + commonRec utils.ReconcilerInterface + biosRestClient restclient.RestClientInterface + commonUtil common.CommonInterface + namespace string +} diff --git a/controllers/bios/biosschemaregistry_controller.go b/controllers/bios/biosschemaregistry_controller.go new file mode 100644 index 0000000..a7b62ef --- /dev/null +++ b/controllers/bios/biosschemaregistry_controller.go @@ -0,0 +1,60 @@ +//(C) Copyright [2022] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +package controllers + +import ( + "context" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + infraioodimrav1 "github.com/ODIM-Project/BMCOperator/api/v1" +) + +// BiosSchemaRegistryReconciler reconciles a BiosSchemaRegistry object +type BiosSchemaRegistryReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=infra.io.odimra,resources=biosschemaregistries,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=infra.io.odimra,resources=biosschemaregistries/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=infra.io.odimra,resources=biosschemaregistries/finalizers,verbs=update + +// 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 BiosSchemaRegistry 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.11.0/pkg/reconcile +func (r *BiosSchemaRegistryReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = log.FromContext(ctx) + + // TODO(user): your logic here + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *BiosSchemaRegistryReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&infraioodimrav1.BiosSchemaRegistry{}). + Complete(r) +} diff --git a/controllers/bios/biossetting_controller.go b/controllers/bios/biossetting_controller.go new file mode 100644 index 0000000..1e77422 --- /dev/null +++ b/controllers/bios/biossetting_controller.go @@ -0,0 +1,357 @@ +//(C) Copyright [2022] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +package controllers + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "os" + "strconv" + "strings" + + Error "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + infraiov1 "github.com/ODIM-Project/BMCOperator/api/v1" + "github.com/ODIM-Project/BMCOperator/config/constants" + common "github.com/ODIM-Project/BMCOperator/controllers/common" + restclient "github.com/ODIM-Project/BMCOperator/controllers/restclient" + utils "github.com/ODIM-Project/BMCOperator/controllers/utils" + l "github.com/ODIM-Project/BMCOperator/logs" + "github.com/google/uuid" +) + +// BiosSettingReconciler reconciles a BiosSetting object +type BiosSettingReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +var ( + biosSchemaObject *infraiov1.BiosSchemaRegistry +) + +// AttributeEnumValue struct defines list of attribute values +type AttributeEnumValue struct { + ValueDisplayName string `json:"ValueDisplayName"` + ValueName string `json:"ValueName"` +} + +var podName = os.Getenv("POD_NAME") + +//+kubebuilder:rbac:groups=infra.io.odimra,resources=biossettings,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=infra.io.odimra,resources=biossettings/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=infra.io.odimra,resources=biossettings/finalizers,verbs=update + +// 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 BiosSetting 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.11.0/pkg/reconcile +func (r *BiosSettingReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + transactionId := uuid.New() + ctx = l.CreateContextForLogging(ctx, transactionId.String(), constants.BmcOperator, constants.BIOSSettingActionID, constants.BIOSSettingActionName, podName) + //common reconciler ddeclaration + commonRec := utils.GetCommonReconciler(r.Client, r.Scheme) + //create rest client + odimObj := commonRec.GetOdimObject(ctx, constants.MetadataName, "odim", req.Namespace) + biosRestClient, err := restclient.NewRestClient(ctx, odimObj, commonRec.(*utils.CommonReconciler), constants.BMCOPERATOR) + if err != nil { + l.LogWithFields(ctx).Errorf("Failed to get rest client for Bios: %s", err.Error()) + return ctrl.Result{}, err + } + biosObj := &infraiov1.BiosSetting{} + // Fetch the Bios instance. + err = r.Get(ctx, req.NamespacedName, biosObj) + if err != nil { + if Error.IsNotFound(err) { + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } + // check to skip reconcilation from running while bisosetting object is created when bmc is added + if len(biosObj.Spec.Bios) == 0 { + return ctrl.Result{}, nil + } + biosUtil := GetBiosUtils(ctx, biosObj, commonRec, biosRestClient, req.Namespace) + // if user applies bios.yaml + // Check if any one of the details is present and properties is not empty + if biosObj.Spec.BmcName != "" || biosObj.Spec.SerialNo != "" || biosObj.Spec.SystemID != "" { + //get system URI + // var systemURI, systemID, biosID string + var bmcObject *infraiov1.Bmc + if biosObj.Spec.SystemID != "" { + bmcObject = commonRec.GetBmcObject(ctx, constants.StatusBmcSystemID, biosObj.Spec.SystemID, req.Namespace) + if bmcObject == nil { + l.LogWithFields(ctx).Info("Check if BMC is registered..") + return ctrl.Result{}, nil + } + } else if biosObj.Spec.BmcName != "" { + bmcObject = commonRec.GetBmcObject(ctx, constants.SpecBmcAddress, biosObj.Spec.BmcName, req.Namespace) + if bmcObject == nil { + l.LogWithFields(ctx).Info("Check if BMC is registered..") + return ctrl.Result{}, nil + } + } else if biosObj.Spec.SerialNo != "" { + bmcObject = commonRec.GetBmcObject(ctx, constants.StatusSerialNumber, biosObj.Spec.SerialNo, req.Namespace) + if bmcObject.Status.SerialNumber == "" { + l.LogWithFields(ctx).Info("Check if BMC is registered..") + return ctrl.Result{}, nil + } + } + biosBmcIP := bmcObject.Spec.BmcDetails.Address + systemID := bmcObject.Status.BmcSystemID + biosID := bmcObject.Status.BiosAttributeRegistry + if systemID == "" { + return ctrl.Result{}, nil + } + systemURI := fmt.Sprintf("/redfish/v1/Systems/%s", systemID) + systemsGetResp, _, err := biosRestClient.Get(systemURI, "Getting response on systems") + if err != nil { + l.LogWithFields(ctx).Errorf("Error on GET: %s, try again : %s", systemURI, err.Error()) + return ctrl.Result{}, nil + } + ok, body := biosUtil.ValidateBiosAttributes(biosID) + if !ok { + return ctrl.Result{}, nil + } + if systemsGetResp["Bios"] != nil { + //get bios url + biosLink, biosBody := biosUtil.getBiosLinkAndBody(systemsGetResp, body, biosBmcIP) + if biosBody != nil && biosLink != "" { + //send patch req on bios url + patchResponse, err := biosRestClient.Patch(biosLink, fmt.Sprintf("Patching bios payload for %s BMC", biosBmcIP), biosBody) + if err != nil { + l.LogWithFields(ctx).Error(fmt.Sprintf("error while patching bios for %s BMC: ", bmcObject.Spec.BmcDetails.Address), err.Error()) + return ctrl.Result{}, nil + } + if patchResponse.StatusCode == http.StatusAccepted { + //add taskmon here + done, _ := biosUtil.(*biosUtils).commonUtil.MoniteringTaskmon(patchResponse.Header, ctx, common.BIOSSETTING, bmcObject.ObjectMeta.Name) + if done { + l.LogWithFields(ctx).Info("Bios configured, Please reset system now.") + commonRec.UpdateBmcObjectOnReset(ctx, bmcObject, fmt.Sprintf("%s Bios", constants.PendingForResetEvent)) + return ctrl.Result{}, nil + } else { + err = commonRec.GetCommonReconcilerClient().Delete(ctx, biosObj) + if err != nil { + l.LogWithFields(ctx).Error(fmt.Sprintf("error while deleteing bios setting object for %s BMC:", biosBmcIP), err.Error()) + } + } + } else { + l.LogWithFields(ctx).Info(fmt.Sprintf("Could not update bios settings for %s BMC, try again!", bmcObject.Spec.BmcDetails.Address)) + err = r.Delete(ctx, biosObj) + if err != nil { + l.LogWithFields(ctx).Error("error while deleting bios object" + err.Error()) + } + return ctrl.Result{}, nil + } + l.LogWithFields(ctx).Errorf("Error in patching, bios not configured properly for %s BMC, try again : %s", biosBmcIP, err.Error()) + } else { + l.LogWithFields(ctx).Info("Unable to configure bios settings, Please try again.") + } + } else { + l.LogWithFields(ctx).Infof("Couldn't get bios details on %s, Please check SystemID again", systemURI) + } + } else { + l.LogWithFields(ctx).Info("Please enter either of BmcIP/SerialNo/SystemID") + } + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *BiosSettingReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&infraiov1.BiosSetting{}). + WithEventFilter(utils.IgnoreStatusUpdate()). + Complete(r) +} + +// ValidateBiosAttributes is used to validate the input data for bios attributes. +func (bs *biosUtils) ValidateBiosAttributes(biosID string) (bool, map[string]interface{}) { + l.LogWithFields(bs.ctx).Info("Validating BIOS Attributes..") + schemaName := utils.RemoveSpecialChar(biosID) + biosSchemaObject = bs.commonRec.GetBiosSchemaObject(bs.ctx, constants.MetadataName, schemaName, bs.namespace) + resBody := make(map[string]interface{}) + if biosSchemaObject.Spec.Attributes == nil { + l.LogWithFields(bs.ctx).Errorf("No object with schema name %s present!", schemaName) + return false, nil + } + attributeColl := biosSchemaObject.Spec.Attributes + for attr, value := range bs.biosObj.Spec.Bios { + for _, val := range attributeColl { + if val["AttributeName"] == attr { + if val["ReadOnly"] == "true" { + continue + } + if val["Type"] == "String" { + i, _ := strconv.Atoi(val["MaxLength"]) + if len(value) > i { + l.LogWithFields(bs.ctx).Error(fmt.Sprintf("Attribute %s max length size exceeded, required is %s", attr, val["MaxLength"])) + return false, nil + } + resBody[attr] = value + } + if val["Type"] == "Integer" { + i, err := strconv.Atoi(value) + if err != nil { + l.LogWithFields(bs.ctx).Error(fmt.Sprintf("Attribute %s should be of type Integer %s: %s", attr, value, err.Error())) + return false, nil + } + lowerBound, _ := strconv.Atoi(val["LowerBound"]) + upperBound, _ := strconv.Atoi(val["UpperBound"]) + if i < lowerBound || i > upperBound { + l.LogWithFields(bs.ctx).Error(fmt.Sprintf("Attribute %s should be within the bound limit", attr)) + return false, nil + } + resBody[attr] = i + } + if val["Type"] == "Enumeration" { + var mapData []AttributeEnumValue + var isPresent bool + res := val["Value"] + json.Unmarshal([]byte(res), &mapData) + for _, mapValue := range mapData { + if value == mapValue.ValueName { + isPresent = true + } + } + if !isPresent { + l.LogWithFields(bs.ctx).Errorf("Invalid attribute value %s", value) + return false, nil + } + resBody[attr] = value + } + } + } + } + return true, resBody +} + +// getBiosLinkAndBody fetches the bios uri and builds the bios body +func (bs *biosUtils) getBiosLinkAndBody(resp map[string]interface{}, biosProps map[string]interface{}, biosBmcIP string) (string, []byte) { + biosId := resp["Bios"].(map[string]interface{})["@odata.id"].(string) + bs.biosObj.ObjectMeta.Annotations["odata.id"] = biosId + err := bs.commonRec.GetCommonReconcilerClient().Update(bs.ctx, bs.biosObj) + if err != nil { + l.LogWithFields(bs.ctx).Error(fmt.Sprintf("Error: Updating %s BIOS object annotations of bmc: %s", biosBmcIP, err.Error())) + } + biosResp, _, err := bs.biosRestClient.Get(biosId, fmt.Sprintf("Fetching Bios Details for %s BMC", biosBmcIP)) + if err != nil { + l.LogWithFields(bs.ctx).Errorf("Error fetching on Bios odata.id of %s bmc: %s", biosBmcIP, err.Error()) + return "", nil + } + biosLink := biosResp["@Redfish.Settings"].(map[string]interface{})["SettingsObject"].(map[string]interface{})["@odata.id"].(string) + bios_settings := map[string]map[string]interface{}{"Attributes": biosProps} + biosBody, err := json.Marshal(bios_settings) + if err != nil { + l.LogWithFields(bs.ctx).Errorf("Failed to marshal Bios Payload: %s", err.Error()) + biosBody = nil + } + return biosLink, biosBody +} + +// updateBiosAttributesOnReset updates the bios attributes with latest bios values +func (bs *biosUtils) UpdateBiosAttributesOnReset(biosBmcIP string, updatedBiosAttributes map[string]string) { + // biosObj := bs.commonRec.GetBiosObject(common.MetadataName, bmcName, namespace) + bs.biosObj.Spec.Bios = map[string]string{} + err := bs.commonRec.(*utils.CommonReconciler).Client.Update(bs.ctx, bs.biosObj) + if err != nil { + l.LogWithFields(bs.ctx).Errorf("Error: Updating Spec of Bios Setting of %s: %s", biosBmcIP, err.Error()) + } + + biosObj := bs.commonRec.GetBiosObject(bs.ctx, constants.MetadataName, bs.biosObj.ObjectMeta.Name, bs.namespace) + bs.biosObj = biosObj + bs.biosObj.Status = infraiov1.BiosSettingStatus{ + BiosAttributes: updatedBiosAttributes, + } + err = bs.commonRec.(*utils.CommonReconciler).Client.Status().Update(bs.ctx, bs.biosObj) + if err != nil { + l.LogWithFields(bs.ctx).Errorf("Error: Updating Status of Bios Setting of %s: %s", biosBmcIP, err.Error()) + } +} + +// getBiosAttributes is used to get bios attributes for added system +func (bs *biosUtils) GetBiosAttributes(bmcObj *infraiov1.Bmc) map[string]string { + biosAttributesMap := make(map[string]string) + uri := "/redfish/v1/Systems/" + bmcObj.Status.BmcSystemID + "/Bios" + resp, sCode, err := bs.biosRestClient.Get(uri, fmt.Sprintf("Fetching BIOS details for %s BMC", bmcObj.Spec.BmcDetails.Address)) + if err != nil { + l.LogWithFields(bs.ctx).Errorf("failed to fetch BIOS attribute details: %v", err) + return nil + } + if sCode != http.StatusOK { + l.LogWithFields(bs.ctx).Errorf("failed to fetch BIOS attribute details for BMC: got status %d", sCode) + return nil + } + attributes, ok := resp["Attributes"].(map[string]interface{}) + if !ok { + l.LogWithFields(bs.ctx).Errorf("failed to parse BIOS attributes from response") + return nil + } + for key, attributeVal := range attributes { + biosAttributesMap[key] = fmt.Sprintf("%v", attributeVal) + } + return biosAttributesMap +} + +// GetBiosAttributeID used to get bios attribute ID +func (bs *biosUtils) GetBiosAttributeID(biosVersion string, bmcObj *infraiov1.Bmc) (string, map[string]interface{}) { + uri := "/redfish/v1/Registries/" + resp, sCode, _ := bs.biosRestClient.Get(uri, fmt.Sprintf("Fetching registries details for %s BMC", bmcObj.Spec.BmcDetails.Address)) + if sCode == http.StatusOK { + for _, member := range resp["Members"].([]interface{}) { + val := member.(map[string]interface{})["@odata.id"].(string) + if strings.Contains(val, "BiosAttributeRegistry") { + res, statusCode, _ := bs.biosRestClient.Get(val, fmt.Sprintf("Fetching bios attribute registry details for %s BMC", bmcObj.Spec.BmcDetails.Address)) + if statusCode == http.StatusOK { + for _, mem := range res["Location"].([]interface{}) { + if mem.(map[string]interface{})["Language"].(string) == "en" { + url := mem.(map[string]interface{})["Uri"].(string) + r, s, _ := bs.biosRestClient.Get(url, fmt.Sprintf("Fetching bios attribute registry JSON for %s BMC", bmcObj.Spec.BmcDetails.Address)) + if s == http.StatusOK { + for _, ss := range r["SupportedSystems"].([]interface{}) { + if ss.(map[string]interface{})["FirmwareVersion"].(string) == biosVersion { + return r["Id"].(string), r + } + } + } + } + } + } + } + } + } + return "", nil +} + +func GetBiosUtils(ctx context.Context, biosObj *infraiov1.BiosSetting, commonRec utils.ReconcilerInterface, biosRestClient restclient.RestClientInterface, ns string) BiosInterface { + return &biosUtils{ + ctx: ctx, + biosObj: biosObj, + commonRec: commonRec, + biosRestClient: biosRestClient, + commonUtil: common.GetCommonUtils(biosRestClient), + namespace: ns, + } +} diff --git a/controllers/bmc/bmcUtils.go b/controllers/bmc/bmcUtils.go new file mode 100644 index 0000000..c462db0 --- /dev/null +++ b/controllers/bmc/bmcUtils.go @@ -0,0 +1,121 @@ +//(C) Copyright [2023] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +package controllers + +import ( + "context" + "crypto/rsa" + "fmt" + + infraiov1 "github.com/ODIM-Project/BMCOperator/api/v1" + common "github.com/ODIM-Project/BMCOperator/controllers/common" + restclient "github.com/ODIM-Project/BMCOperator/controllers/restclient" + utils "github.com/ODIM-Project/BMCOperator/controllers/utils" + "sigs.k8s.io/controller-runtime/pkg/client" + + // bios "github.com/ODIM-Project/BMCOperator/controllers/bios" + // boot "github.com/ODIM-Project/BMCOperator/controllers/boot" + l "github.com/ODIM-Project/BMCOperator/logs" + corev1 "k8s.io/api/core/v1" + types "k8s.io/apimachinery/pkg/types" +) + +const ( + bmcFinalizer = "infra.io.bmc/finalizer" + biosGroup = "infra.io.odimra" + biosKind = "BiosSetting" + biosVersion = "v1" + bootGroup = "infra.io.odimra" + bootKind = "BootOrderSetting" + bootVersion = "v1" + volFinalizer = "infra.io.volume/finalizer" +) + +// links struct is used in add bmc body +type links struct { + ConnectionMethod map[string]string +} + +// addBmcPayloadBody struct is used for creating add bmc payload +type addBmcPayloadBody struct { + HostName string + UserName string + Password string + Links links +} + +// modifyPassword struct is used for creating modify password request +type modifyPassword struct { + Password string +} + +type BmcInterface interface { + addBmc(body []byte, namespaceName types.NamespacedName, sysID string) bool + PrepareAddBmcPayload(connectionMeth string) ([]byte, error) + updateBmcLabels() + updateDriveDetails() map[string]infraiov1.ArrayControllers + // GetSystemDetails() map[string]interface{} + GetManagerDetails() map[string]interface{} + unregisterBmc() bool + loggingBmcDeletionActivity(isDeleted error, objectName string) + removeBmcFinalizerAndDeleteDependencies() + getAllowableResetValues() []string + validateResetData(powerState string, allowableValues []string) bool + ResetSystem(isBiosUpdation bool, updateBmcDependents bool) bool + updateOdimWithNewPassword() (bool, error) + updateBmcWithNewPassword() (bool, error) + encryptPassword(publicKey *rsa.PublicKey) + GetConnectionMethod(odimObj *infraiov1.Odim) string + UpdateBmcObject(bmcObj *infraiov1.Bmc) + deleteBMCObject() +} + +type bmcUtils struct { + ctx context.Context + bmcObj *infraiov1.Bmc + namespace string + commonRec utils.ReconcilerInterface + bmcRestClient restclient.RestClientInterface + commonUtil common.CommonInterface + updateBMCObject bool +} + +// ----------- Get Keys for BMC encryption-------------- +// getPemKeys will read the pem files and parse the private and public key +func GetPemKeys(ctx context.Context, pubKey, privKey string) (*rsa.PrivateKey, *rsa.PublicKey) { + privateKey, err := utils.ParseRsaPrivateKeyFromPemStr(privKey) + if err != nil { + l.LogWithFields(ctx).Error("Error parsing private key: " + err.Error()) + return nil, nil + } + publicKey, err := utils.ParseRsaPublicKeyFromPemStr(pubKey) + if err != nil { + l.LogWithFields(ctx).Error("Error parsing public key: " + err.Error()) + return nil, nil + } + return privateKey, publicKey +} + +// getKeysSecret retreives the keys from secret +func GetEncryptedPemKeysFromSecret(ctx context.Context, secret *corev1.Secret, secretName, namespace string, r client.Client) (string, string) { + ns := types.NamespacedName{Namespace: namespace, Name: secretName} + err := r.Get(context.TODO(), ns, secret) + if err != nil { + l.LogWithFields(ctx).Error(fmt.Sprintf("Error fetching %s secret:", secretName), err.Error()) + } + publicKey := string(secret.Data["publicKey"]) + privateKey := string(secret.Data["privateKey"]) + return publicKey, privateKey +} diff --git a/controllers/bmc/bmc_controller.go b/controllers/bmc/bmc_controller.go new file mode 100644 index 0000000..91ff816 --- /dev/null +++ b/controllers/bmc/bmc_controller.go @@ -0,0 +1,833 @@ +//(C) Copyright [2022] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +package controllers + +import ( + "context" + "crypto/rsa" + "encoding/json" + "fmt" + "net/http" + "os" + "reflect" + "regexp" + "strconv" + "strings" + "time" + + corev1 "k8s.io/api/core/v1" + Error "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + infraiov1 "github.com/ODIM-Project/BMCOperator/api/v1" + "github.com/ODIM-Project/BMCOperator/config/constants" + bios "github.com/ODIM-Project/BMCOperator/controllers/bios" + boot "github.com/ODIM-Project/BMCOperator/controllers/boot" + "github.com/google/uuid" + + common "github.com/ODIM-Project/BMCOperator/controllers/common" + config "github.com/ODIM-Project/BMCOperator/controllers/config" + eventsubscription "github.com/ODIM-Project/BMCOperator/controllers/eventsubscription" + restclient "github.com/ODIM-Project/BMCOperator/controllers/restclient" + utils "github.com/ODIM-Project/BMCOperator/controllers/utils" + volume "github.com/ODIM-Project/BMCOperator/controllers/volume" + l "github.com/ODIM-Project/BMCOperator/logs" +) + +// BmcReconciler reconciles a Bmc object +type BmcReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +var podName = os.Getenv("POD_NAME") + +//+kubebuilder:rbac:groups=infra.io.odimra,resources=bmcs,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=infra.io.odimra,resources=bmcs/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=infra.io.odimra,resources=bmcs/finalizers,verbs=update + +// 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 Bmc 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.11.0/pkg/reconcile +func (r *BmcReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + transactionId := uuid.New() + ctx = l.CreateContextForLogging(ctx, transactionId.String(), constants.BmcOperator, constants.BMCSettingActionID, constants.BMCSettingActionName, podName) + var updateBMCObject bool + commonRec := utils.GetCommonReconciler(r.Client, r.Scheme) + odimObject := commonRec.GetOdimObject(ctx, constants.MetadataName, "odim", req.Namespace) //field name has to be taken from odim object + bmcRestClient, err := restclient.NewRestClient(ctx, odimObject, commonRec.(*utils.CommonReconciler), constants.BMCOPERATOR) + if err != nil { + l.LogWithFields(ctx).Errorf("Failed to get rest client for BMC: %s", err.Error()) + return ctrl.Result{}, err + } + // Fetch the Bmc instance. + bmcObj := &infraiov1.Bmc{} + err = r.Get(ctx, req.NamespacedName, bmcObj) + if err != nil { + if Error.IsNotFound(err) { + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } + commonUtil := common.GetCommonUtils(bmcRestClient) + bmcUtil := GetBmcUtils(ctx, bmcObj, req.Namespace, &commonRec, &bmcRestClient, commonUtil, updateBMCObject) + //get config details + + if err != nil { + l.LogWithFields(ctx).Errorf("Unable to get configuration details, please check if config.yaml is applied: %s", err.Error()) + return ctrl.Result{}, err + } + //get keys from secret + secret := &corev1.Secret{} + encryptedPrivateKey, encryptedPublicKey := GetEncryptedPemKeysFromSecret(ctx, secret, config.Data.SecretName, config.Data.Namespace, r.Client) + privateKey, publicKey := GetPemKeys(ctx, encryptedPrivateKey, encryptedPublicKey) + if privateKey == nil || publicKey == nil { + l.LogWithFields(ctx).Info("Could not find the private and public keys, please apply the secret and try again") + commonRec.DeleteBmcObject(ctx, bmcObj) + return ctrl.Result{}, nil + } + //Checking for deletion + isBmcMarkedToBeDeleted := bmcObj.GetDeletionTimestamp() != nil + if isBmcMarkedToBeDeleted { + if controllerutil.ContainsFinalizer(bmcObj, bmcFinalizer) { + isBmcDeleted := bmcUtil.unregisterBmc() + if isBmcDeleted { + bmcUtil.removeBmcFinalizerAndDeleteDependencies() + err := r.Update(ctx, bmcObj) + if err != nil { + return ctrl.Result{}, err + } + } else { + l.LogWithFields(ctx).Info(fmt.Sprintf("BMC %s not deleted! Retrying..", bmcObj.Spec.BmcDetails.Address)) + return ctrl.Result{Requeue: true}, nil // Calling Reconcile again for delete to execute + } + } + return ctrl.Result{}, nil + } + + // Add finalizer for this CR + if !controllerutil.ContainsFinalizer(bmcObj, bmcFinalizer) { + controllerutil.AddFinalizer(bmcObj, bmcFinalizer) + updateBMCObject = true + } + + //Len check: to check if user modified the encrypted pass for password change reconcile to run + // SystemResetStatus check : To stop reconcile flow through add bmc code for bios reset change + if len(bmcObj.Spec.Credentials.Password) < 20 && bmcObj.Status.SystemReset != fmt.Sprintf("%s Bios", constants.PendingForResetEvent) { + updateBMCObject = true + if bmcObj.ObjectMeta.Annotations["old_password"] != "" { + var updatedInBmc, updatedInOdim bool + //decrypt the encrypted old pass + decryptedPass := utils.DecryptWithPrivateKey(ctx, bmcObj.ObjectMeta.Annotations["old_password"], *utils.PrivateKey, true) //doBase64Decode = true, because bmc password is encrypted n encoded + // update password change + if decryptedPass != bmcObj.Spec.Credentials.Password { + //update bmc with new pass + updatedInBmc, err = bmcUtil.updateBmcWithNewPassword() + if err != nil || !updatedInBmc { + encryptedPassword := utils.EncryptWithPublicKey(ctx, decryptedPass, *utils.PublicKey) + if encryptedPassword != "" { + bmcObj.Spec.Credentials.Password = encryptedPassword + } + } else { + //update odim with new pass if its updated in bmc + updatedInOdim, err = bmcUtil.updateOdimWithNewPassword() + if err != nil || !updatedInOdim { + //check this case + if err != nil { + l.LogWithFields(ctx).Errorf("Error: Updating new password in Odim for %s BMC: %s", bmcObj.Spec.BmcDetails.Address, err.Error()) + } else { + l.LogWithFields(ctx).Info(fmt.Sprintf("Updating new password in Odim for %s BMC failed, Try again", bmcObj.Spec.BmcDetails.Address)) + } + encryptedPassword := utils.EncryptWithPublicKey(ctx, decryptedPass, *utils.PublicKey) + if encryptedPassword != "" { + bmcObj.Spec.Credentials.Password = encryptedPassword + } + } else { + l.LogWithFields(ctx).Info(fmt.Sprintf("Updated password in ODIM for %s BMC", bmcObj.Spec.BmcDetails.Address)) + } + //update annotations with old password and encrypt exisiting password + if updatedInBmc && updatedInOdim { + l.LogWithFields(ctx).Info(fmt.Sprintf("Updating old password and encrypting current password for %s BMC", bmcObj.Spec.BmcDetails.Address)) + bmcUtil.encryptPassword(utils.PublicKey) + l.LogWithFields(ctx).Info(fmt.Sprintf("Successfully completed updating password for %s BMC!", bmcObj.Spec.BmcDetails.Address)) + } + } + } else { + l.LogWithFields(ctx).Info(fmt.Sprintf("Password set is the same as before for %s BMC", bmcObj.Spec.BmcDetails.Address)) + bmcUtil.encryptPassword(utils.PublicKey) + } + } else { + // retrive conn method for bmc + // odimObj := commonRec.GetOdimObject(constants.MetadataName, "odim", req.Namespace) + connMeth := bmcUtil.GetConnectionMethod(odimObject) + if connMeth != "" { + body, err := bmcUtil.PrepareAddBmcPayload(connMeth) + if err != nil { + l.LogWithFields(ctx).Errorf("Error: Creating the request body for adding %s BMC: %s", bmcObj.Spec.BmcDetails.Address, err.Error()) + } + // add bmc + added := bmcUtil.addBmc(body, req.NamespacedName, bmcObj.Labels["systemId"]) + //update annotations with old password and encrypt exisiting password if bmc added + if added { + // check if Annotations is not equal to nil in case of reconciliation + if bmcObj.ObjectMeta.Annotations != nil { + bmcObj.ObjectMeta.Annotations["old_password"] = "" + l.LogWithFields(ctx).Info(fmt.Sprintf("Updating old password and encrypting Current password for %s BMC", bmcObj.Spec.BmcDetails.Address)) + bmcUtil.encryptPassword(publicKey) + } + // update labels + l.LogWithFields(ctx).Info("Updating labels..") + bmcUtil.updateBmcLabels() + l.LogWithFields(ctx).Info("Successfully completed") + } else { + l.LogWithFields(ctx).Info(fmt.Sprintf("BMC %s not added, try again", bmcObj.Spec.BmcDetails.Address)) + bmcUtil.deleteBMCObject() + return ctrl.Result{}, nil + } + } else { + l.LogWithFields(ctx).Info(fmt.Sprintf("Odim doesn't support the plugin required for this %s Bmc!", bmcObj.Spec.BmcDetails.Address)) + updateBMCObject = false + bmcUtil.deleteBMCObject() + } + } + } + // reset code + if bmcObj.Spec.BmcDetails.PowerState != "" && bmcObj.Spec.BmcDetails.ResetType != "" { + updateBMCObject = true + var currentPowerState string + allowableVal := bmcUtil.getAllowableResetValues() + if allowableVal == nil { + l.LogWithFields(ctx).Info(fmt.Sprintf("Allowable values not available for %s BMC", bmcObj.Spec.BmcDetails.Address)) + return ctrl.Result{}, nil + } + sysRes := bmcUtil.(*bmcUtils).commonUtil.GetBmcSystemDetails(ctx, bmcObj) //GetSystemDetails() + if val, ok := sysRes["PowerState"]; ok { + currentPowerState = val.(string) + l.LogWithFields(ctx).Info(fmt.Sprintf("Current power state for %s BMC: `%s`", bmcObj.Spec.BmcDetails.Address, currentPowerState)) + } + isValid := bmcUtil.validateResetData(currentPowerState, allowableVal) + if !isValid { + bmcObj.Spec.BmcDetails.PowerState = "" + bmcObj.Spec.BmcDetails.ResetType = "" + } else { + resetDone := bmcUtil.ResetSystem(false, true) + if resetDone { + bmcObj.Status.SystemReset = "Done" + bmcObj.Status.PowerState = bmcObj.Spec.BmcDetails.PowerState + commonRec.UpdateBmcStatus(ctx, bmcObj) + l.LogWithFields(ctx).Info(fmt.Sprintf("successfully done computer system reset on %s BMC", bmcObj.Spec.BmcDetails.Address)) + } + bmcObj.Spec.BmcDetails.PowerState = "" + bmcObj.Spec.BmcDetails.ResetType = "" + } + } + annotations := bmcObj.GetAnnotations() + if _, exists := annotations["kubectl.kubernetes.io/last-applied-configuration"]; exists { + // Remove the last-applied-configuration annotation if it exists. + delete(annotations, "kubectl.kubernetes.io/last-applied-configuration") + bmcObj.SetAnnotations(annotations) + updateBMCObject = true + } + //flag to update bmc object after all modifications + if updateBMCObject { + l.LogWithFields(ctx).Info(fmt.Sprintf("Updating %s BMC object...", bmcObj.Spec.BmcDetails.Address)) + err := r.Client.Update(ctx, bmcObj) + if err != nil { + l.LogWithFields(ctx).Error(fmt.Sprintf("Error: Updating %s BMC object: %s", bmcObj.Spec.BmcDetails.Address, err.Error())) + return ctrl.Result{}, err + } + time.Sleep(time.Duration(constants.SleepTime) * time.Second) // NOTE: Do not delete this, this helps in proper update of the object + l.LogWithFields(ctx).Info(fmt.Sprintf("%s BMC object is updated.", bmcObj.Spec.BmcDetails.Address)) + } + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *BmcReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&infraiov1.Bmc{}). + WithEventFilter(utils.IgnoreStatusUpdate()). + Complete(r) +} + +// --------Add BMC------ +// addBmc used to add BMC +func (bu *bmcUtils) addBmc(body []byte, namespaceName types.NamespacedName, sysID string) bool { + biosUtil := bios.GetBiosUtils(bu.ctx, nil, bu.commonRec, bu.bmcRestClient, bu.namespace) //getting biosUtil + bootUtil := boot.GetBootUtils(bu.ctx, nil, bu.commonRec, bu.bmcRestClient, bu.commonUtil, bu.namespace) + eventSubUtils := eventsubscription.GetEventSubscriptionUtils(bu.ctx, bu.bmcRestClient, bu.commonRec, nil, bu.namespace) + l.LogWithFields(bu.ctx).Info(fmt.Sprintf("Adding %s BMC", bu.bmcObj.Spec.BmcDetails.Address)) + if sysID == "" { + done, taskResp := bu.commonUtil.BmcAddition(bu.ctx, bu.bmcObj, body, bu.bmcRestClient) + if done { + if taskResp != nil && taskResp["Name"].(string) == "Aggregation Source" { + return updateBmcDetails(taskResp["Id"].(string), bu, biosUtil, bootUtil, eventSubUtils) + } + } + } else { + return updateBmcDetails(sysID, bu, biosUtil, bootUtil, eventSubUtils) + } + return false +} + +// updateBmcDetails is used to get bmc details and update in kubernetes object +func updateBmcDetails(sysId string, bu *bmcUtils, biosUtil bios.BiosInterface, bootUtil boot.BootInterface, eventSubUtils eventsubscription.EventSubscriptionInterface) bool { + bu.bmcObj.Status.BmcAddStatus = "yes" + bu.bmcObj.Status.BmcSystemID = sysId + l.LogWithFields(bu.ctx).Info(fmt.Sprintf("Systems ID for %s Bmc is %s", bu.bmcObj.Spec.BmcDetails.Address, sysId)) + systemDetails := bu.commonUtil.GetBmcSystemDetails(bu.ctx, bu.bmcObj) + mgrDetails := bu.GetManagerDetails() + if systemDetails != nil { + bu.bmcObj.ObjectMeta.Annotations["odata.id"] = systemDetails["@odata.id"].(string) + err := bu.commonRec.GetCommonReconcilerClient().Update(bu.ctx, bu.bmcObj) + if err != nil { + l.LogWithFields(bu.ctx).Error(fmt.Sprintf("Error: Updating %s BMC object : %s", bu.bmcObj.Spec.BmcDetails.Address, err.Error())) + } + + bu.bmcObj.Status.BmcAddStatus = "yes" + bu.bmcObj.Status.BmcSystemID = sysId + if serialNumber, ok := systemDetails["SerialNumber"].(string); ok { + bu.bmcObj.Status.SerialNumber = serialNumber + } + if powerState, ok := systemDetails["PowerState"].(string); ok { + bu.bmcObj.Status.PowerState = powerState + } + if vendorName, ok := systemDetails["Manufacturer"].(string); ok { + val, vOk := utils.CaseInsensitiveContains(vendorName) + if vOk { + bu.bmcObj.Status.VendorName = val + } + } + if modelID, ok := systemDetails["Model"].(string); ok { + bu.bmcObj.Status.ModelID = modelID + } + if biosVersion, ok := systemDetails["BiosVersion"].(string); ok { + bu.bmcObj.Status.BiosVersion = biosVersion + biosID, registryResp := biosUtil.GetBiosAttributeID(biosVersion, bu.bmcObj) + if biosID != "" { + bu.bmcObj.Status.BiosAttributeRegistry = biosID + bu.commonRec.CheckAndCreateBiosSchemaObject(bu.ctx, registryResp, bu.bmcObj) + } + } + bootAttribute := bootUtil.GetBootAttributes(systemDetails) + biosAttribute := biosUtil.GetBiosAttributes(bu.bmcObj) + isObjCreated := bu.commonRec.CreateBiosSettingObject(bu.ctx, biosAttribute, bu.bmcObj) + if isObjCreated { + l.LogWithFields(bu.ctx).Info(fmt.Sprintf("Bios setting object created for %s BMC", bu.bmcObj.Spec.BmcDetails.Address)) + } + if bootAttribute != nil { + isBootObjCreated := bu.commonRec.CreateBootOrderSettingObject(bu.ctx, bootAttribute, bu.bmcObj) + if isBootObjCreated { + l.LogWithFields(bu.ctx).Info(fmt.Sprintf("Boot order object created for %s BMC", bu.bmcObj.Spec.BmcDetails.Address)) + } + } + } + + var registryID []string + var messageRegistryResp []map[string]interface{} + if systemDetails["Manufacturer"].(string) == constants.ILOManufacturer { + registryID, messageRegistryResp = eventSubUtils.GetMessageRegistryDetails(bu.bmcObj, "iLOEvents") + } + + if len(registryID) > 0 { + bu.bmcObj.Status.EventsMessageRegistry = strings.Join(registryID, ",") + for _, resp := range messageRegistryResp { + bu.commonRec.CheckAndCreateEventMessageObject(bu.ctx, resp, bu.bmcObj) + } + } + + if mgrDetails != nil { + if firmwareVersion, ok := mgrDetails["FirmwareVersion"].(string); ok { + bu.bmcObj.Status.FirmwareVersion = firmwareVersion + } + } + storageDetails := bu.updateDriveDetails() + if len(storageDetails) != 0 { + bu.bmcObj.Status.StorageControllers = storageDetails + } else { + l.LogWithFields(bu.ctx).Info(fmt.Sprintf("Could not successfully get list of drives for %s bmc", bu.bmcObj.Spec.BmcDetails.Address)) + } + bu.commonRec.UpdateBmcStatus(bu.ctx, bu.bmcObj) + common.MapOfBmc["/redfish/v1/Systems/"+sysId] = common.BmcState{IsAdded: false, IsDeleted: false, IsObjCreated: true} + return true +} + +// prepareBody will prepare the body for add bmc +func (bu *bmcUtils) PrepareAddBmcPayload(connectionMeth string) ([]byte, error) { + connMeth := map[string]string{"@odata.id": connectionMeth} + link := links{ConnectionMethod: connMeth} + details := addBmcPayloadBody{HostName: bu.bmcObj.Spec.BmcDetails.Address, UserName: bu.bmcObj.Spec.Credentials.Username, Password: bu.bmcObj.Spec.Credentials.Password, Links: link} + body, err := json.Marshal(details) + if err != nil { + l.LogWithFields(bu.ctx).Error(fmt.Sprintf("Error marshalling add request body for %s BMC: %s", bu.bmcObj.Spec.BmcDetails.Address, err.Error())) + return nil, err + } + return body, nil +} + +func (bu *bmcUtils) updateBmcLabels() { + bu.bmcObj.ObjectMeta.Labels = map[string]string{} + bu.bmcObj.ObjectMeta.Labels["name"] = bu.bmcObj.Spec.BmcDetails.Address + bu.bmcObj.ObjectMeta.Labels["systemId"] = bu.bmcObj.Status.BmcSystemID + bu.bmcObj.ObjectMeta.Labels["serialNo"] = bu.bmcObj.Status.SerialNumber + bu.bmcObj.ObjectMeta.Labels["vendor"] = bu.bmcObj.Status.VendorName + re, err := regexp.Compile(`[^\w]`) + if err != nil { + l.LogWithFields(bu.ctx).Error("Could not compile regex : " + err.Error()) + } + bu.bmcObj.ObjectMeta.Labels["modelId"] = re.ReplaceAllString(bu.bmcObj.Status.ModelID, ".") + bu.bmcObj.ObjectMeta.Labels["firmwareVersion"] = re.ReplaceAllString(bu.bmcObj.Status.FirmwareVersion, ".") +} + +func (bu *bmcUtils) updateDriveDetails() map[string]infraiov1.ArrayControllers { + storageControllers := map[string]infraiov1.ArrayControllers{} + drives := map[string]infraiov1.DriveDetails{} + var arrayController infraiov1.ArrayControllers + raidLevels := []string{} + storageDetails, _, err := bu.bmcRestClient.Get(fmt.Sprintf("/redfish/v1/Systems/%s/Storage/", bu.bmcObj.Status.BmcSystemID), fmt.Sprintf("Fetching Storage details for %s BMC..", bu.bmcObj.Spec.BmcDetails.Address)) + if err != nil { + l.LogWithFields(bu.ctx).Error(err, "Error getting storage details") + return storageControllers + } + if storageDetails["Members"] == nil { + l.LogWithFields(bu.ctx).Info("No storage controllers present..") + return storageControllers + } else { + arrContrDetails := storageDetails["Members"].([]interface{}) + for _, ac := range arrContrDetails { + raidLevels = []string{} + arrContlink := ac.(map[string]interface{})["@odata.id"].(string) + arrContDriveLinks, _, err := bu.bmcRestClient.Get(arrContlink, fmt.Sprintf("Fetching array controller details for %s BMC", bu.bmcObj.Spec.BmcDetails.Address)) + if err != nil { + l.LogWithFields(bu.ctx).Error(err, "Error getting array controllers") + return storageControllers + } + if _, ok := arrContDriveLinks["Id"].(string); ok { + arrContID := arrContDriveLinks["Id"].(string) + if len(arrContDriveLinks["Drives"].([]interface{})) == 0 { + l.LogWithFields(bu.ctx).Info(fmt.Sprintf("No drives present for %s", arrContID)) + arrayController.Drives = nil + } else { + drivesMap := arrContDriveLinks["Drives"].([]interface{}) + for _, driveLink := range drivesMap { + drive := driveLink.(map[string]interface{}) + volumes := []int{} + driveDetails, _, err := bu.bmcRestClient.Get(drive["@odata.id"].(string), fmt.Sprintf("Fetching drive details for %s BMC..", bu.bmcObj.Spec.BmcDetails.Address)) + if err != nil { + l.LogWithFields(bu.ctx).Error(err, "Error getting drive details..") + return storageControllers + } + driveID := driveDetails["Id"].(string) + capacityBytes := driveDetails["CapacityBytes"].(float64) + if len(driveDetails["Links"].(map[string]interface{})) == 0 { + l.LogWithFields(bu.ctx).Info(fmt.Sprintf("No volumes are used for %s drive", driveID)) + } else { + volumeLinks := driveDetails["Links"].(map[string]interface{})["Volumes"].([]interface{}) + for _, vl := range volumeLinks { + volLink := vl.(map[string]interface{})["@odata.id"].(string) + volID, _ := strconv.Atoi(volLink[len(volLink)-1:]) + volumes = append(volumes, volID) + } + } + driveDet := infraiov1.DriveDetails{CapacityBytes: fmt.Sprintf("%f", capacityBytes), UsedInVolumes: volumes} + drives[driveID] = driveDet + } + arrayController.Drives = drives + } + if len(arrContDriveLinks["Volumes"].(map[string]interface{})) == 0 { + l.LogWithFields(bu.ctx).Info(fmt.Sprintf("No volumes are created for %s BMC", bu.bmcObj.Spec.BmcDetails.Address)) + } else { + volumeLink := arrContDriveLinks["Volumes"].(map[string]interface{})["@odata.id"].(string) + volumeDetails, sCode, err := bu.bmcRestClient.Get(volumeLink, fmt.Sprintf("Fetching volume details for %s BMC..", bu.bmcObj.Spec.BmcDetails.Address)) + if err != nil { + l.LogWithFields(bu.ctx).Error(err, "Error getting volume details..") + return storageControllers + } else if sCode == http.StatusOK { + var capabilityDetails []interface{} + if collectionCap, ok := volumeDetails["@Redfish.CollectionCapabilities"]; ok { + capabilityDetails = collectionCap.(map[string]interface{})["Capabilities"].([]interface{}) + } + var capLink string + for _, capObj := range capabilityDetails { + capLink = capObj.(map[string]interface{})["CapabilitiesObject"].(map[string]interface{})["@odata.id"].(string) + break + } + if capLink != "" { + capDet, sCode, err := bu.bmcRestClient.Get(capLink, fmt.Sprintf("Fetching capability details for %s BMC..", bu.bmcObj.Spec.BmcDetails.Address)) + if err != nil { + l.LogWithFields(bu.ctx).Error(err, "Error getting capability details..") + return storageControllers + } else if sCode == http.StatusOK { + rl := capDet["RAIDType@Redfish.AllowableValues"].([]interface{}) + for _, raidType := range rl { + raidLevels = append(raidLevels, raidType.(string)) + } + } else { + l.LogWithFields(bu.ctx).Info("Unable to get capability details, Try again..") + } + } else { + l.LogWithFields(bu.ctx).Info(fmt.Sprintf("Capability Object not found for %s volume", arrContID)) + } + } else { + l.LogWithFields(bu.ctx).Info(fmt.Sprintf("Unable to get volume details for %s BMC", bu.bmcObj.Spec.BmcDetails.Address)) + } + } + arrayController.SupportedRAIDLevel = raidLevels + storageControllers[arrContID] = arrayController + } + } + } + return storageControllers +} + +// GetManagerDetails used to get manager response +func (bu *bmcUtils) GetManagerDetails() map[string]interface{} { + var uri string + if strings.Contains(bu.bmcObj.Spec.BmcDetails.ConnMethVariant, "DELL") { + uri = "/redfish/v1/Managers/" + strings.Replace(bu.bmcObj.Status.BmcSystemID, "System", "iDRAC", -1) + } else { + uri = "/redfish/v1/Managers/" + bu.bmcObj.Status.BmcSystemID + } + resp, sCode, err := bu.bmcRestClient.Get(uri, fmt.Sprintf("Fetching manager details for %s BMC", bu.bmcObj.Spec.BmcDetails.Address)) + if sCode == http.StatusOK { + return resp + } + l.LogWithFields(bu.ctx).Error(fmt.Sprintf("Failed getting manager details for %s BMC: Got %d from odim: %s", bu.bmcObj.Spec.BmcDetails.Address, sCode, err.Error())) + return nil +} + +// ---------Delete BMC-------------- +// unregisterBmc used to unregister BMC +func (bu *bmcUtils) unregisterBmc() bool { + deleteURI := fmt.Sprintf("/redfish/v1/AggregationService/AggregationSources/%s", bu.bmcObj.Status.BmcSystemID) + l.LogWithFields(bu.ctx).Info(fmt.Sprintf("Deleting %s BMC from odim", bu.bmcObj.Spec.BmcDetails.Address)) + ok := bu.commonUtil.BmcDeleteOperation(bu.ctx, deleteURI, bu.bmcRestClient, bu.bmcObj.ObjectMeta.Name) + if !ok { + l.LogWithFields(bu.ctx).Info(fmt.Sprintf("Error in getting delete response for BMC %s", bu.bmcObj.Spec.BmcDetails.Address)) + return false + } + return true +} + +// loggingBmcDeletionActivity will log deletion activity +func (bu *bmcUtils) loggingBmcDeletionActivity(isDeleted error, objectName string) { + if isDeleted != nil { + l.LogWithFields(bu.ctx).Errorf("Error while deleting %s object for %s BMC!: %s", objectName, bu.bmcObj.Spec.BmcDetails.Address, isDeleted.Error()) + } else { + l.LogWithFields(bu.ctx).Info(fmt.Sprintf("%s object deleted for %s BMC", objectName, bu.bmcObj.Spec.BmcDetails.Address)) + } +} + +func (bu *bmcUtils) removeBmcFinalizerAndDeleteDependencies() { + controllerutil.RemoveFinalizer(bu.bmcObj, bmcFinalizer) + biosObj := bu.commonRec.GetBiosObject(bu.ctx, constants.MetadataName, bu.bmcObj.ObjectMeta.Name, bu.namespace) + bootObj := bu.commonRec.GetBootObject(bu.ctx, constants.MetadataName, bu.bmcObj.ObjectMeta.Name, bu.namespace) + volObj := bu.commonRec.GetVolumeObject(bu.ctx, bu.bmcObj.Spec.BmcDetails.Address, bu.namespace) + firmwareObj := bu.commonRec.GetFirmwareObject(bu.ctx, constants.MetadataName, bu.bmcObj.ObjectMeta.Name, bu.namespace) + isBiosDeleted := bu.commonRec.GetCommonReconcilerClient().Delete(bu.ctx, biosObj) + bu.loggingBmcDeletionActivity(isBiosDeleted, "BiosSetting") + isBootDeleted := bu.commonRec.GetCommonReconcilerClient().Delete(bu.ctx, bootObj) + bu.loggingBmcDeletionActivity(isBootDeleted, "BootOrderSetting") + isFirmwareDeleted := bu.commonRec.GetCommonReconcilerClient().Delete(bu.ctx, firmwareObj) + bu.loggingBmcDeletionActivity(isFirmwareDeleted, "Firmware") + if volObj != nil { + controllerutil.RemoveFinalizer(volObj, volFinalizer) + err := bu.commonRec.GetCommonReconcilerClient().Update(bu.ctx, volObj) + if err != nil { + l.LogWithFields(bu.ctx).Errorf("Error while updating volume object for %s BMC!: %s", bu.bmcObj.Spec.BmcDetails.Address, err) + } + isVolumeDeleted := bu.commonRec.GetCommonReconcilerClient().Delete(bu.ctx, volObj) + bu.loggingBmcDeletionActivity(isVolumeDeleted, "Volume") + } +} + +func (bu *bmcUtils) deleteBMCObject() { + sysID := bu.bmcObj.Status.BmcSystemID + err := bu.commonRec.GetCommonReconcilerClient().Delete(context.Background(), bu.bmcObj) + if err != nil { + l.LogWithFields(bu.ctx).Errorf("Error: Deleting the BMC object: %s", err.Error()) + } + delete(common.MapOfBmc, "/redfish/v1/Systems/"+sysID) +} + +// ---------------Reset BMC----------------- +// getAllowableResetValues used to get allowable reset values from map +func (bu *bmcUtils) getAllowableResetValues() []string { + key := infraiov1.SystemDetail{ + Vendor: bu.bmcObj.Status.VendorName, + ModelID: bu.bmcObj.Status.ModelID, + } + if val, ok := infraiov1.AllowableResetValues[key]; ok { + return val + } + return nil +} + +// validateResetData used to validate the input given by user for reset operation +// validateResetData(currentPowerState, bmcObj.Spec.BmcDetails.PowerState, bmcObj.Spec.BmcDetails.ResetType, allowableVal,*bmcObj) +func (bu *bmcUtils) validateResetData(powerState string, allowableValues []string) bool { + allowedVal := make(map[string]bool) + powerState, desiredPowerState := strings.ToUpper(powerState), strings.ToUpper(bu.bmcObj.Spec.BmcDetails.PowerState) + for _, av := range allowableValues { + allowedVal[av] = true + } + if !allowedVal[bu.bmcObj.Spec.BmcDetails.ResetType] { + l.LogWithFields(bu.ctx).Info(fmt.Sprintf("Reset value `%s` is not allowed by %s BMC, current system state is `%s`", bu.bmcObj.Spec.BmcDetails.ResetType, bu.bmcObj.Spec.BmcDetails.Address, powerState)) + return false + } + state := infraiov1.State{PowerState: powerState, DesiredPowerState: desiredPowerState, ResetType: bu.bmcObj.Spec.BmcDetails.ResetType} + if ResetWhenOnOn, ok := infraiov1.ResetWhenOnOn[state]; ok { + if !ResetWhenOnOn { + l.LogWithFields(bu.ctx).Info(fmt.Sprintf("Reset with value `%s` is not allowed by %s BMC, current system state is `%s`", bu.bmcObj.Spec.BmcDetails.ResetType, bu.bmcObj.Spec.BmcDetails.Address, powerState)) + return false + } + return true + } + if ResetWhenOffOff, ok := infraiov1.ResetWhenOffOff[state]; ok { + if !ResetWhenOffOff { + l.LogWithFields(bu.ctx).Info(fmt.Sprintf("Reset with value `%s` is not allowed for %s BMC", bu.bmcObj.Spec.BmcDetails.ResetType, bu.bmcObj.Spec.BmcDetails.Address)) + return false + } + return true + } + if ResetWhenOnOff, ok := infraiov1.ResetWhenOnOff[state]; ok { + if !ResetWhenOnOff { + l.LogWithFields(bu.ctx).Info(fmt.Sprintf("Reset with value `%s` is not allowed for %s BMC", bu.bmcObj.Spec.BmcDetails.ResetType, bu.bmcObj.Spec.BmcDetails.Address)) + return false + } + return true + } + if ResetWhenOffOn, ok := infraiov1.ResetWhenOffOn[state]; ok { + if !ResetWhenOffOn { + l.LogWithFields(bu.ctx).Info(fmt.Sprintf("Reset with value `%s` is not allowed for %s BMC", bu.bmcObj.Spec.BmcDetails.ResetType, bu.bmcObj.Spec.BmcDetails.Address)) + return false + } + return true + } + + return false +} + +// resetSystem used to reset computer system +func (bu *bmcUtils) ResetSystem(isBiosUpdation bool, updateBmcDependents bool) bool { + l.LogWithFields(bu.ctx).Info(fmt.Sprintf("Resetting system with reset type as: %s", bu.bmcObj.Spec.BmcDetails.ResetType)) + uri := "/redfish/v1/Systems/" + bu.bmcObj.Status.BmcSystemID + "/Actions/ComputerSystem.Reset" + reqBody := &common.ComputerSystemReset{ + ResetType: bu.bmcObj.Spec.BmcDetails.ResetType, + } + body, jsonerr := json.Marshal(reqBody) + if jsonerr != nil { + l.LogWithFields(bu.ctx).Error(fmt.Sprintf("Error marshalling reset payload for %s BMC: %s", bu.bmcObj.Spec.BmcDetails.Address, jsonerr.Error())) + return false + } + response, err := bu.bmcRestClient.Post(uri, fmt.Sprintf("Posting reset payload to %s BMC", bu.bmcObj.Spec.BmcDetails.Address), body) + if err != nil || response.StatusCode != http.StatusAccepted { + l.LogWithFields(bu.ctx).Error(fmt.Sprintf("Error while resetting %s BMC: %s: %s", bu.bmcObj.Spec.BmcDetails.Address, strconv.Itoa(response.StatusCode), err.Error())) + return false + } + if response.StatusCode == http.StatusUnauthorized { + l.LogWithFields(bu.ctx).Info(fmt.Sprintf("Password is not authorised for %s BMC", bu.bmcObj.Spec.BmcDetails.Address)) + return false + } + done, _ := bu.commonUtil.MoniteringTaskmon(response.Header, bu.ctx, common.RESETBMC, bu.bmcObj.ObjectMeta.Name) + if done { + if updateBmcDependents { // this is introduced to skip updating of bmc dependents when revert of volume deleted scenario takes place. + biosObj := bu.commonRec.GetBiosObject(bu.ctx, constants.MetadataName, bu.bmcObj.ObjectMeta.Name, bu.namespace) + biosUtil := bios.GetBiosUtils(bu.ctx, biosObj, bu.commonRec, bu.bmcRestClient, bu.namespace) + biosAttribute := getUpdatedBiosAttributes(bu.ctx, biosObj.Status.BiosAttributes, bu.bmcObj, biosUtil) + biosUtil.UpdateBiosAttributesOnReset(bu.bmcObj.Spec.BmcDetails.Address, biosAttribute) + sysDetails := bu.commonUtil.GetBmcSystemDetails(bu.ctx, bu.bmcObj) + if sysDetails != nil { + bootUtil := boot.GetBootUtils(bu.ctx, nil, bu.commonRec, bu.bmcRestClient, bu.commonUtil, bu.namespace) + bootAttribute := bootUtil.GetBootAttributes(sysDetails) + if bootAttribute != nil { + bootUtil.UpdateBootAttributesOnReset(bu.bmcObj.ObjectMeta.Name, bootAttribute) + } + } + if strings.Contains(bu.bmcObj.Status.SystemReset, "Volume") { + strArr := strings.Split(bu.bmcObj.Status.SystemReset, " ") + volName := strArr[len(strArr)-2] + volumeObj := bu.commonRec.GetVolumeObject(bu.ctx, bu.bmcObj.Spec.BmcDetails.Address, bu.namespace) + volUtil := volume.GetVolumeUtils(bu.ctx, bu.bmcRestClient, bu.commonRec, volumeObj, bu.namespace) + volUpdated, updateMsg := volUtil.UpdateVolumeStatusAndClearSpec(bu.bmcObj, volName) + if !volUpdated { + l.LogWithFields(bu.ctx).Info(updateMsg) + } + } + + } + delete(common.RestartRequired, bu.bmcObj.Status.BmcSystemID) + return true + } + return false +} + +func getUpdatedBiosAttributes(ctx context.Context, oldBiosAttributes map[string]string, bmcObj *infraiov1.Bmc, biosUtil bios.BiosInterface) map[string]string { + var updatedBiosAttributes map[string]string + for i := 0; i < 5; i++ { + biosAttribute := biosUtil.GetBiosAttributes(bmcObj) + if !reflect.DeepEqual(biosAttribute, oldBiosAttributes) { + updatedBiosAttributes = biosAttribute + break + } + time.Sleep(time.Duration(constants.SleepTime) * time.Second) + } + return updatedBiosAttributes +} + +// ----------Update New Password on BMC,ODIM---------------- +// updateOdimWithNewPassword will update ODIM with new working password of bmc +func (bu *bmcUtils) updateOdimWithNewPassword() (bool, error) { + aggUri := fmt.Sprintf("/redfish/v1/AggregationService/AggregationSources/%s", bu.bmcObj.Status.BmcSystemID) + l.LogWithFields(bu.ctx).Info("Updating password on ODIM") + bdy := modifyPassword{Password: bu.bmcObj.Spec.Credentials.Password} + body, err := json.Marshal(bdy) + if err != nil { + l.LogWithFields(bu.ctx).Error("Error marshaling request payload to ODIM" + err.Error()) + return false, err + } + patchResp, err := bu.bmcRestClient.Patch(aggUri, fmt.Sprintf("Patching ODIM with new password for %s BMC..", bu.bmcObj.Spec.BmcDetails.Address), body) + if err == nil && patchResp.StatusCode == http.StatusOK { + return true, nil + } + if patchResp.StatusCode == http.StatusUnauthorized { + l.LogWithFields(bu.ctx).Info(fmt.Sprintf("Password is not authorised for %s BMC", bu.bmcObj.Spec.BmcDetails.Address)) + return false, nil + } + return false, err +} + +// updateBmc will update the bmc with new user entered password +func (bu *bmcUtils) updateBmcWithNewPassword() (bool, error) { + remoteAccUri := fmt.Sprintf("/redfish/v1/Managers/%s/RemoteAccountService/Accounts", bu.bmcObj.Status.BmcSystemID) + l.LogWithFields(bu.ctx).Info(fmt.Sprintf("Updating password on %s BMC", bu.bmcObj.Spec.BmcDetails.Address)) + var changePassUri string + getResp, _, _ := bu.bmcRestClient.Get(remoteAccUri, "Fetching remote account members..") + accounts := getResp["Members"].([]interface{}) + for _, acc := range accounts { + accUri := acc.(map[string]interface{})["@odata.id"].(string) + accGet, _, _ := bu.bmcRestClient.Get(accUri, "Fetching remote account details..") + if accGet["UserName"].(string) == bu.bmcObj.Spec.Credentials.Username { + changePassUri = accUri + break + } + } + bdy := modifyPassword{Password: bu.bmcObj.Spec.Credentials.Password} + body, err := json.Marshal(bdy) + if err != nil { + l.LogWithFields(bu.ctx).Error("Error marshaling request payload to BMC: " + err.Error()) + return false, err + } + patchResp, err := bu.bmcRestClient.Patch(changePassUri, fmt.Sprintf("Patching new password on %s BMC", bu.bmcObj.Spec.BmcDetails.Address), body) + if err != nil { + return false, err + } + done, _ := bu.commonUtil.MoniteringTaskmon(patchResp.Header, bu.ctx, common.UPDATEBMC, bu.bmcObj.ObjectMeta.Name) + if done { + return true, nil + } + return false, err +} + +// -------------Utils------------------- +// encryptPassword will encrypt the current password of the bmc +func (bu *bmcUtils) encryptPassword(publicKey *rsa.PublicKey) { + encryptedPassword := utils.EncryptWithPublicKey(bu.ctx, bu.bmcObj.Spec.Credentials.Password, *utils.PublicKey) + if encryptedPassword != "" { + bu.bmcObj.Spec.Credentials.Password = encryptedPassword + bu.bmcObj.ObjectMeta.Annotations["old_password"] = encryptedPassword + } + l.LogWithFields(bu.ctx).Info("Could not encrypt password") +} + +// GetConnectionMethod is used to get connection method +func (bu *bmcUtils) GetConnectionMethod(odimObj *infraiov1.Odim) string { + connMethInfo := odimObj.Status.ConnMethVariants + for connMethVar, connMeth := range connMethInfo { + if bu.bmcObj.Spec.BmcDetails.ConnMethVariant == connMethVar { + return connMeth + } + } + return "" +} + +func (bu *bmcUtils) UpdateBmcObject(bmcObj *infraiov1.Bmc) { + bu.bmcObj = bmcObj +} + +func GetBmcUtils(ctx context.Context, bmcObj *infraiov1.Bmc, ns string, commonRec *utils.ReconcilerInterface, bmcRestClient *restclient.RestClientInterface, commonUtil common.CommonInterface, updateBmcObject bool) BmcInterface { + return &bmcUtils{ + ctx: ctx, + bmcObj: bmcObj, + namespace: ns, + commonRec: *commonRec, + bmcRestClient: *bmcRestClient, + commonUtil: commonUtil, + updateBMCObject: updateBmcObject, + } +} + +// TODO: Will be used to get reset allowable values in future + +/* +var AllowableResetValues = make(map[string]interface{}) +var vendor = []string{"HPE", "DELL", "LENOVO"} +// getAllowableResetValues used to check and store allowable reset values of added bmc in map +func getAllowableResetValues() { + uri := "/redfish/v1/Systems/" + bmcObj.Status.BmcSystemID + resp, sCode, _ := odimClient.Get(uri) + if sCode == http.StatusOK { + if resp["Manufacturer"] == nil || resp["Model"] == nil { + l.Log.Info("Vendor and model details missing") + return + if resp["PowerState"].(string) != bmcObj.Spec.BmcDetails.PowerState { + l.Log.Info(fmt.Sprintf("current power state %s", resp["PowerState"].(string))) + resetSystem(bmcObj.Spec.BmcDetails.PowerState, bmcObj.Status.BmcSystemID) + bmcObj.Spec.BmcDetails.PowerState = resp["PowerState"].(string) + } + if substr, ok := CaseInsensitiveContains(resp["Manufacturer"].(string)); ok { + key := substr + "-" + resp["Model"].(string) + if _, ok := AllowableResetValues[key]; !ok { + if resp["Actions"] != nil || resp["Actions"].(map[string]interface{})["#ComputerSystem.Reset"] != nil { + val := resp["Actions"].(map[string]interface{})["#ComputerSystem.Reset"].(map[string]interface{})["ResetType@Redfish.AllowableValues"].([]interface{}) + AllowableResetValues[key] = val + l.Log.Info(fmt.Sprintf("allowable values added to map with key as %s", key)) + } + } + } else { + l.Log.Info(fmt.Sprintf("%s vendor is not valid", resp["Manufacturer"].(string))) + } + } + l.Log.Info(fmt.Sprintf("Map of allowable values %s", AllowableResetValues)) +} +func CaseInsensitiveContains(s string) (string, bool) { + for _, substr := range vendor { + s, substr = strings.ToUpper(s), strings.ToUpper(substr) + if strings.Contains(s, substr) { + return substr, true + } + } + return "", false +} */ diff --git a/controllers/boot/bootUtils.go b/controllers/boot/bootUtils.go new file mode 100644 index 0000000..e924c3f --- /dev/null +++ b/controllers/boot/bootUtils.go @@ -0,0 +1,53 @@ +//(C) Copyright [2023] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +package controllers + +import ( + "context" + + infraiov1 "github.com/ODIM-Project/BMCOperator/api/v1" + common "github.com/ODIM-Project/BMCOperator/controllers/common" + restclient "github.com/ODIM-Project/BMCOperator/controllers/restclient" + utils "github.com/ODIM-Project/BMCOperator/controllers/utils" +) + +// BootOrderSetting defines boot order details +type BootOrderSetting struct { + Boot Boot `json:"Boot,omitempty"` +} + +// Boot defines boot order setting details +type Boot struct { + BootOrder []string `json:"BootOrder,omitempty"` + BootSourceOverrideEnabled string `json:"BootSourceOverrideEnabled,omitempty"` + BootSourceOverrideTarget string `json:"BootSourceOverrideTarget,omitempty"` + UefiTargetBootSourceOverride string `json:"UefiTargetBootSourceOverride,omitempty"` +} + +type BootInterface interface { + GetBootAttributes(sysDetails map[string]interface{}) *infraiov1.BootSetting + UpdateBootAttributesOnReset(bmcName string, bootSetting *infraiov1.BootSetting) + updateBootSettings() bool + UpdateBootDetails(ctx context.Context, systemID, bootBmcIP string, bootObj *infraiov1.BootOrderSetting, bootBmcObj *infraiov1.Bmc) bool +} + +type bootUtils struct { + ctx context.Context + bootObj *infraiov1.BootOrderSetting + commonRec utils.ReconcilerInterface + bootRestClient restclient.RestClientInterface + commonUtil common.CommonInterface + namespace string +} diff --git a/controllers/boot/bootordersettings_controller.go b/controllers/boot/bootordersettings_controller.go new file mode 100644 index 0000000..2061d43 --- /dev/null +++ b/controllers/boot/bootordersettings_controller.go @@ -0,0 +1,267 @@ +//(C) Copyright [2022] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +package controllers + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "os" + + Error "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + infraiov1 "github.com/ODIM-Project/BMCOperator/api/v1" + "github.com/ODIM-Project/BMCOperator/config/constants" + common "github.com/ODIM-Project/BMCOperator/controllers/common" + restclient "github.com/ODIM-Project/BMCOperator/controllers/restclient" + utils "github.com/ODIM-Project/BMCOperator/controllers/utils" + l "github.com/ODIM-Project/BMCOperator/logs" + "github.com/google/uuid" +) + +// BootOrderSettingsReconciler reconciles a BootOrderSetting object +type BootOrderSettingsReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +var podName = os.Getenv("POD_NAME") + +//+kubebuilder:rbac:groups=infra.io.odimra,resources=bootordersettings,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=infra.io.odimra,resources=bootordersettings/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=infra.io.odimra,resources=bootordersettings/finalizers,verbs=update + +// 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 BootOrderSetting 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.11.0/pkg/reconcile +func (r *BootOrderSettingsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + transactionId := uuid.New() + ctx = l.CreateContextForLogging(ctx, transactionId.String(), constants.BmcOperator, constants.BootOrderActionID, constants.BootOrderActionName, podName) + bootObj := &infraiov1.BootOrderSetting{} + commonRec := utils.GetCommonReconciler(r.Client, r.Scheme) + err := r.Get(ctx, req.NamespacedName, bootObj) + if err != nil { + if Error.IsNotFound(err) { + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } + odimObject := commonRec.GetOdimObject(ctx, constants.MetadataName, "odim", req.Namespace) + bootRestClient, err := restclient.NewRestClient(ctx, odimObject, commonRec.(*utils.CommonReconciler), constants.BMCOPERATOR) + if err != nil { + l.LogWithFields(ctx).Errorf("Failed to get rest client for BMC: %s", err.Error()) + return ctrl.Result{}, err + } + bootUtil := GetBootUtils(ctx, bootObj, commonRec, bootRestClient, common.GetCommonUtils(bootRestClient), req.Namespace) + if bootObj.Spec.Boot != nil && (len(bootObj.Spec.Boot.BootTargetAllowableValues) > 0 || len(bootObj.Spec.Boot.UefiTargetAllowableValues) > 0 || + bootObj.Spec.Boot.BootSourceOverrideMode != "") { + l.LogWithFields(ctx).Info("Unmodify values passed in input") + + return ctrl.Result{}, nil + } + if bootObj.Spec.BmcName != "" || bootObj.Spec.SerialNo != "" || bootObj.Spec.SystemID != "" { + if bootObj.Spec.Boot != nil && (len(bootObj.Spec.Boot.BootOrder) > 0 || bootObj.Spec.Boot.BootSourceOverrideEnabled != "" || + bootObj.Spec.Boot.BootSourceOverrideTarget != "" || bootObj.Spec.Boot.UefiTargetBootSourceOverride != "") { + res := bootUtil.updateBootSettings() + if !res { + return ctrl.Result{}, nil + } + } + } + + return ctrl.Result{}, nil +} + +// updateBootSettings is used to update the boot order setting with input given by user +func (bo *bootUtils) updateBootSettings() bool { + l.LogWithFields(bo.ctx).Info("Set boot order settings for BMC") + var systemID, bootBmcIP string + var bootBmcObj *infraiov1.Bmc + if bo.bootObj.Spec.BmcName != "" { + bootBmcObj = bo.commonRec.GetBmcObject(bo.ctx, "spec.bmc.address", bo.bootObj.Spec.BmcName, bo.bootObj.Namespace) + if bootBmcObj.Spec.BmcDetails.Address == "" { + l.LogWithFields(bo.ctx).Info("Check if BMC is registered..") + return false + } + systemID = bootBmcObj.Status.BmcSystemID + } else if bo.bootObj.Spec.SystemID != "" { + systemID = bo.bootObj.Spec.SystemID + } else if bo.bootObj.Spec.SerialNo != "" { + bootBmcObj := bo.commonRec.GetBmcObject(bo.ctx, "status.serialNumber", bo.bootObj.Spec.SerialNo, bo.bootObj.Namespace) + if bootBmcObj.Status.SerialNumber == "" { + l.LogWithFields(bo.ctx).Info("Check if BMC is registered..") + return false + } + bootBmcIP = bootBmcObj.Spec.BmcDetails.Address + systemID = bootBmcObj.Status.BmcSystemID + } + + ok := bo.UpdateBootDetails(bo.ctx, systemID, bootBmcIP, bo.bootObj, bootBmcObj) + if ok { + sysDetails := bo.commonUtil.GetBmcSystemDetails(bo.ctx, bootBmcObj) + bo.bootObj.ObjectMeta.Annotations["odata.id"] = getBootOID(sysDetails) + err := bo.commonRec.GetCommonReconcilerClient().Update(bo.ctx, bo.bootObj) + if err != nil { + l.LogWithFields(bo.ctx).Error(fmt.Sprintf("Error: Updating %s boot order setting object annotations: %s of bmc", bootBmcObj.Name, err.Error())) + } + bootSetting := bo.GetBootAttributes(sysDetails) + bo.bootObj.Spec = infraiov1.BootOrderSettingsSpec{} + bo.commonRec.GetCommonReconcilerClient().Update(bo.ctx, bo.bootObj) + bo.bootObj.Status.Boot = *bootSetting + err = bo.commonRec.GetCommonReconcilerClient().Status().Update(bo.ctx, bo.bootObj) + if err != nil { + l.LogWithFields(bo.ctx).Error(fmt.Sprintf("Error while updating boot order setting object for %s BMC: %s", bootBmcObj.Name, err.Error())) + return false + } + l.LogWithFields(bo.ctx).Info(fmt.Sprintf("Boot order setting configured for %s BMC", bootBmcObj.Name)) + return true + } + return false +} + +func getBootOID(sysDetails map[string]interface{}) string { + boot := sysDetails["Boot"].(map[string]interface{}) + bootOptions := boot["BootOptions"].(map[string]interface{}) + return bootOptions["@odata.id"].(string) +} + +// UpdateBootDetails function is used to update the boot details in server +func (bo *bootUtils) UpdateBootDetails(ctx context.Context, systemID, bootBmcIP string, bootObj *infraiov1.BootOrderSetting, bootBmcObj *infraiov1.Bmc) bool { + var boot = Boot{} + if len(bootObj.Spec.Boot.BootOrder) > 0 { + if !utils.CompareArray(bootObj.Status.Boot.BootOrder, bootObj.Spec.Boot.BootOrder) { + l.LogWithFields(bo.ctx).Error("Invalid value passed for BootOrder") + return false + } + boot.BootOrder = bootObj.Spec.Boot.BootOrder + } + if bootObj.Spec.Boot.BootSourceOverrideTarget != "" { + if !utils.ContainsValue(bootObj.Status.Boot.BootTargetAllowableValues, bootObj.Spec.Boot.BootSourceOverrideTarget) { + l.LogWithFields(bo.ctx).Error(fmt.Sprintf("Invalid value passed for BootSourceOverrideTarget for %s BMC", bootBmcIP)) + return false + } + boot.BootSourceOverrideTarget = bootObj.Spec.Boot.BootSourceOverrideTarget + } + if bootObj.Spec.Boot.BootSourceOverrideEnabled != "" { + boot.BootSourceOverrideEnabled = bootObj.Spec.Boot.BootSourceOverrideEnabled + } + if bootObj.Spec.Boot.UefiTargetBootSourceOverride != "" { + if !utils.ContainsValue(bootObj.Status.Boot.UefiTargetAllowableValues, bootObj.Spec.Boot.UefiTargetBootSourceOverride) { + l.LogWithFields(bo.ctx).Error(fmt.Sprintf("Invalid value passed for UefiTargetBootSourceOverride for %s BMC", bootBmcIP)) + return false + } + boot.UefiTargetBootSourceOverride = bootObj.Spec.Boot.UefiTargetBootSourceOverride + } + bootSetting := BootOrderSetting{ + Boot: boot, + } + bootBody, err := json.Marshal(bootSetting) + if err != nil { + l.LogWithFields(bo.ctx).Error(fmt.Sprintf("Error marshalling boot request body for %s BMC: %s", bootBmcObj.Name, err.Error())) + bootBody = nil + } + bootLink := "/redfish/v1/Systems/" + systemID + patchResponse, err := bo.bootRestClient.Patch(bootLink, "Patching boot payload..", bootBody) + if err == nil && patchResponse.StatusCode == http.StatusAccepted { + ok, _ := bo.commonUtil.MoniteringTaskmon(patchResponse.Header, ctx, common.BOOTSETTING, bootBmcObj.Name) + return ok + } + + return false +} + +// SetupWithManager sets up the controller with the Manager. +func (r *BootOrderSettingsReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&infraiov1.BootOrderSetting{}). + Complete(r) +} + +// updateBootAttributesOnReset updates the boot attributes with latest values +func (bo *bootUtils) UpdateBootAttributesOnReset(bmcName string, bootSetting *infraiov1.BootSetting) { + bootObj := bo.commonRec.GetBootObject(bo.ctx, constants.MetadataName, bmcName, bo.namespace) + if bootObj != nil { + bootObj.Status.Boot = *bootSetting + } + err := bo.commonRec.GetCommonReconcilerClient().Status().Update(bo.ctx, bootObj) + if err != nil { + l.LogWithFields(bo.ctx).Error(fmt.Sprintf("Error: while updating boot order object for %s BMC: %s", bootObj.ObjectMeta.Name, err.Error())) + } +} + +// getBootAttributes is used to get boot attributes value from bmc +func (bo *bootUtils) GetBootAttributes(sysDetails map[string]interface{}) *infraiov1.BootSetting { + var bootOrder, targetAllowableVal, uefiAllowableVal []string + bootDetails := infraiov1.BootSetting{} + if sysDetails != nil { + boot := sysDetails["Boot"].(map[string]interface{}) + if boot["BootOrder"].([]interface{}) != nil { + bootOrder = utils.ConvertToArray(boot["BootOrder"].([]interface{})) + } + if boot["BootSourceOverrideTarget@Redfish.AllowableValues"] != nil { + targetAllowableVal = utils.ConvertToArray(boot["BootSourceOverrideTarget@Redfish.AllowableValues"].([]interface{})) + } + if boot["UefiTargetBootSourceOverride@Redfish.AllowableValues"] != nil { + uefiAllowableVal = utils.ConvertToArray(boot["UefiTargetBootSourceOverride@Redfish.AllowableValues"].([]interface{})) + } + bootDetails = infraiov1.BootSetting{ + BootOrder: bootOrder, + BootTargetAllowableValues: targetAllowableVal, + UefiTargetAllowableValues: uefiAllowableVal, + } + if val, ok := boot["BootSourceOverrideEnabled"]; ok { + if s, ok := val.(string); ok { + bootDetails.BootSourceOverrideEnabled = s + } + } + if val, ok := boot["BootSourceOverrideMode"]; ok { + if s, ok := val.(string); ok { + bootDetails.BootSourceOverrideMode = s + } + } + if val, ok := boot["UefiTargetBootSourceOverride"]; ok { + if s, ok := val.(string); ok { + bootDetails.UefiTargetBootSourceOverride = s + } + } + if val, ok := boot["BootSourceOverrideTarget"]; ok { + if s, ok := val.(string); ok { + bootDetails.BootSourceOverrideTarget = s + } + } + } + return &bootDetails +} + +func GetBootUtils(ctx context.Context, bootObj *infraiov1.BootOrderSetting, commonRec utils.ReconcilerInterface, bootRestClient restclient.RestClientInterface, commonUtil common.CommonInterface, ns string) BootInterface { + return &bootUtils{ + ctx: ctx, + bootObj: bootObj, + commonRec: commonRec, + bootRestClient: bootRestClient, + commonUtil: commonUtil, + namespace: ns, + } +} diff --git a/controllers/common/common.go b/controllers/common/common.go new file mode 100644 index 0000000..bb2942e --- /dev/null +++ b/controllers/common/common.go @@ -0,0 +1,147 @@ +//(C) Copyright [2023] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +package controllers + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "strconv" + "time" + + infraiov1 "github.com/ODIM-Project/BMCOperator/api/v1" + v1 "github.com/ODIM-Project/BMCOperator/api/v1" + "github.com/ODIM-Project/BMCOperator/config/constants" + restclient "github.com/ODIM-Project/BMCOperator/controllers/restclient" + l "github.com/ODIM-Project/BMCOperator/logs" +) + +// Created common folder to avoid import cycle issue +// Functions of bmc that are used in other folders are places here for access + +// GetSystemDetails used to get system response +func (bu *CommonUtils) GetBmcSystemDetails(ctx context.Context, bmcObj *infraiov1.Bmc) map[string]interface{} { + uri := "/redfish/v1/Systems/" + bmcObj.Status.BmcSystemID + resp, sCode, err := bu.restClient.Get(uri, fmt.Sprintf("Fetching system details for %s BMC", bmcObj.Spec.BmcDetails.Address)) + if sCode == http.StatusOK { + return resp + } + if sCode == http.StatusNotFound { + l.LogWithFields(ctx).Warnf("No systems found with %s", bmcObj.Status.BmcSystemID) + return nil + } + l.LogWithFields(ctx).Error(fmt.Sprintf("Failed getting system details for %s BMC: Got %d from odim: %s", bmcObj.Spec.BmcDetails.Address, sCode, err.Error())) + return nil +} + +func (bu *CommonUtils) MoniteringTaskmon(headerInfo http.Header, ctx context.Context, operation, resourceName string) (bool, map[string]interface{}) { + retries := 0 + for retries < constants.RetryCount { + retries = retries + 1 + time.Sleep(time.Duration(constants.SleepTime) * time.Second) + taskResp, sc, err := bu.restClient.Get(headerInfo["Location"][0], "Monitoring taskmon...") + if err != nil { + l.LogWithFields(ctx).Error(fmt.Sprintf(TaskmonLogsTable["Err:"+operation], resourceName), err.Error()) + } else { + l.LogWithFields(ctx).Info(fmt.Sprintf(TaskmonLogsTable[strconv.Itoa(sc)+":"+operation], resourceName)) + if sc == http.StatusAccepted { // taskmon in progress + continue + } else if sc == http.StatusOK { // Reset success : 200 + return true, taskResp + } else if sc == http.StatusCreated && operation == ADDBMC { // Reset success : 201 + return true, taskResp + } else if sc == http.StatusNotFound && operation == DELETEBMC { // Resource not present for deletion + return true, taskResp + } else if sc == http.StatusCreated && operation == EVENTSUBSCRIPTION { + return true, taskResp + } else if sc == http.StatusNoContent && operation == DELETEBMC { // Delete success : 204 + return true, taskResp + } else if sc == http.StatusNotFound { // Resource not present : 404 + return false, taskResp + } else if sc == http.StatusBadRequest { // Bad Request : 400 + return false, taskResp + } else if sc == http.StatusConflict { //conflict : 409 + return false, taskResp + } else { + return false, taskResp + } + } + } + return false, nil +} + +func GetCommonUtils(restClient restclient.RestClientInterface) CommonInterface { + return &CommonUtils{restClient: restClient} +} + +// BmcAddition is used to add bmc in ODIM +func (bu *CommonUtils) BmcAddition(ctx context.Context, bmcObject *v1.Bmc, body []byte, restClient restclient.RestClientInterface) (bool, map[string]interface{}) { + uri := "/redfish/v1/AggregationService/AggregationSources/" + resp, err := restClient.Post(uri, "Posting add bmc payload..", body) + if err != nil { + l.LogWithFields(ctx).Info(fmt.Sprintf("Error adding %s BMC", bmcObject.Spec.BmcDetails.Address)) + return false, nil + } + if resp.StatusCode == http.StatusUnauthorized { + l.LogWithFields(ctx).Info(fmt.Sprintf("password is not authorised for %s BMC", bmcObject.Spec.BmcDetails.Address)) + return false, nil + } + if resp.StatusCode == http.StatusAccepted { + return bu.MoniteringTaskmon(resp.Header, ctx, ADDBMC, bmcObject.ObjectMeta.Name) + } + return false, nil +} + +// BmcDeleteOperation will delete the bmc from ODIM +func (bu *CommonUtils) BmcDeleteOperation(ctx context.Context, aggregationURL string, restClient restclient.RestClientInterface, resourceName string) bool { + delResp, err := restClient.Delete(aggregationURL, "Deleting BMC..") + if err != nil { + l.LogWithFields(ctx).Warnf("could not delete bmc, error occurred %v", err) + return false + } + if err == nil && delResp.StatusCode == http.StatusAccepted { + done, _ := bu.MoniteringTaskmon(delResp.Header, ctx, DELETEBMC, resourceName) + if done { + return true + } + } + return false +} + +func BiosAttributeUpdation(ctx context.Context, body map[string]interface{}, systemID string, client restclient.RestClientInterface) bool { + bios_settings := map[string]map[string]interface{}{"Attributes": body} + biosBody, err := json.Marshal(bios_settings) + if err != nil { + l.LogWithFields(ctx).Errorf("Failed to marshal Bios Payload: %s", err.Error()) + return false + } + patchResponse, err := client.Patch("/redfish/v1/Systems/"+systemID+"/Bios/Settings/", fmt.Sprintf("Patching bios payload for %s BMC", systemID), biosBody) + if err == nil && patchResponse.StatusCode == http.StatusAccepted { + headerInfo := patchResponse.Header + //monitering taskmon + retries := 0 + for retries < constants.RetryCount { + retries = retries + 1 + time.Sleep(time.Duration(constants.SleepTime) * time.Second) + _, statusCode, err := client.Get(headerInfo["Location"][0], "Monitoring taskmon for bios patch..") + if err == nil && statusCode == http.StatusOK { + l.LogWithFields(ctx).Info(fmt.Sprintf("Reset the system: %s", systemID)) + return true + } + } + } + return false +} diff --git a/controllers/common/commonUtils.go b/controllers/common/commonUtils.go new file mode 100644 index 0000000..86f0826 --- /dev/null +++ b/controllers/common/commonUtils.go @@ -0,0 +1,142 @@ +//(C) Copyright [2023] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +package controllers + +import ( + "context" + "net/http" + + infraiov1 "github.com/ODIM-Project/BMCOperator/api/v1" + v1 "github.com/ODIM-Project/BMCOperator/api/v1" + restclient "github.com/ODIM-Project/BMCOperator/controllers/restclient" +) + +// defines the operations +const ( + ADDBMC = "AddBMC" + DELETEBMC = "DeleteBMC" + UPDATEBMC = "UpdateBMC" + RESETBMC = "ResetBMC" + BOOTSETTING = "BootSetting" + BIOSSETTING = "BiosSetting" + CREATEVOLUME = "CreateVolume" + DELETEVOLUME = "DeleteVolume" + FIRMWARE = "Firmware" + EVENTSUBSCRIPTION = "CreateEventSubscription" +) + +var ( + KubeConfigPath string +) + +type CommonInterface interface { + GetBmcSystemDetails(context.Context, *infraiov1.Bmc) map[string]interface{} + MoniteringTaskmon(headerInfo http.Header, ctx context.Context, operation, resourceName string) (bool, map[string]interface{}) + BmcAddition(ctx context.Context, bmcObject *v1.Bmc, body []byte, restClient restclient.RestClientInterface) (bool, map[string]interface{}) + BmcDeleteOperation(ctx context.Context, aggregationURL string, restClient restclient.RestClientInterface, resourceName string) bool +} + +type CommonUtils struct { + restClient restclient.RestClientInterface +} + +// ComputerSystemReset struct is used for creating reset request +type ComputerSystemReset struct { + ResetType string +} + +// MapOfBmc contains list of bmc's with there current state +var MapOfBmc = make(map[string]BmcState) + +// trackVolumeStatus will tell us the current state of volume +// Defines volume of each storage controller of specific bmc +// {"bmc1,ArrayController-0,1":{true,false},"bmc1,ArrayController-0,2":{true,false},"ArrayController-1":VolumeStatus{"1":{true,false},"2":{false,true},...},..},...} +var TrackVolumeStatus = map[BmcStorageControllerVolumesStatus]VolumeStatus{} + +// BmcStorageControllerVolumesStatus stores volume status for each storageController for each volume for each bmc +type BmcStorageControllerVolumesStatus struct { + Bmc string + StorageController string + VolumeID string +} + +// RestartRequired is to check if restart is required or not +var RestartRequired = make(map[string]bool) + +// MapOfFirmware defines the state of firmware update +var MapOfFirmware = make(map[string]bool) + +// BmcState defines current state of BMC +type BmcState struct { + IsAdded bool + IsDeleted bool + IsObjCreated bool +} + +// VolumeStatus stores the status for each volume +type VolumeStatus struct { + IsGettingCreated bool + IsGettingDeleted bool +} + +var TaskmonLogsTable = map[string]string{ + "201:AddBMC": "BMC %s is successfully added, updating bmc object...", + "202:AddBMC": "BMC %s addition is ongoing...", + "409:AddBMC": "%s BMC already added!!", + "Err:AddBMC": "error while adding %s BMC", + "200:ResetBMC": "BMC %s reset is successful, updating bmc object...", + "202:ResetBMC": "BMC %s reset ongoing...", + "404:ResetBMC": "%s BMC not present", + "400:ResetBMC": "Invalid request passed while resetting %s BMC", + "409:ResetBMC": "Cannot reset %s BMC, value conflicting", + "Err:ResetBMC": "error while performing reset for %s BMC", + "204:DeleteBMC": "BMC %s deletion is successful", + "202:DeleteBMC": "BMC %s deletion inprogress...", + "404:DeleteBMC": "%s BMC is not added to ODIM, forbidding delete on it", + "409:DeleteBMC": "Already a delete request is in progress for %s BMC", + "Err:DeleteBMC": "error while deleting %s BMC", + "202:UpdateBMC": "BMC %s password updation inprogress...", + "200:UpdateBMC": "Updated new password in %s BMC", + "401:UpdateBMC": "Password is not authorised for %s BMC", + "Err:UpdateBMC": "error while updating password for %s BMC", + "200:BootSetting": "Boot order setting configured for %s BMC, updating boot order setting object...", + "202:BootSetting": "Configuring boot order setting for %s BMC...", + "404:BootSetting": "Cannot configure boot order setting for %s BMC, since %s BMC is not found!", + "400:BootSetting": "Invalid request passed while configuring boot setting %s BMC!", + "Err:BootSetting": "Error while configuring boot order setting for %s BMC", + "200:BiosSetting": "Bios setting configured for %s BMC, updating bios order setting object...", + "202:BiosSetting": "Configuring bios setting for %s BMC...", + "404:BiosSetting": "Cannot configure bios setting for %s BMC, since %s BMC is not found!", + "400:BiosSetting": "Invalid request passed while configuring bios setting %s BMC!", + "Err:BiosSetting": "Error in patching, bios not configured properly for %s BMC, try again", + "200:CreateVolume": "Volume %s successfully created, Please reset the system now", + "202:CreateVolume": "%s volume creation in progress... ", + "Err:CreateVolume": "error while creating %s volume", + "400:CreateVolume": "Invalid request passed while creating volume for %s! ", + "404:CreateVolume": "The drives requested for %s volume creation is not present", + "204:DeleteVolume": "Volume %s successfully deleted, Please reset the system now", + "202:DeleteVolume": "%s volume deletion in progress... ", + "404:DeleteVolume": "%s volume does not exist, hence deletion is not possible", + "Err:DeleteVolume": "error while deleteing %s volume", + "200:Firmware": "Firmware update completed for %s BMC, updating firmware object...", + "400:Firmware": "Firmware update unsuccessful for %s BMC, try again", + "404:Firmware": "Firmware update unsuccessful for %s BMC, try again", + "202:Firmware": "Firmware update in progress for %s BMC...", + "Err:Firmware": "error while applying firmware for %s BMC", + "202:CreateEventSubscription": "EventSubscription %s creation in progress", + "201:CreateEventSubscription": "EventSubscription %s created successfully", + "400:CreateEventSubscription": "Invalid request for creating %s eventSubscription", + "409:CreateEventSubscription": "Subscription already exists with destination %s", +} diff --git a/controllers/config/getConfigDetails.go b/controllers/config/getConfigDetails.go new file mode 100644 index 0000000..ecbeab0 --- /dev/null +++ b/controllers/config/getConfigDetails.go @@ -0,0 +1,159 @@ +//(C) Copyright [2022] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +package controllers + +import ( + "context" + "fmt" + "io/ioutil" + "os" + "strconv" + "sync" + "time" + + constants "github.com/ODIM-Project/BMCOperator/config/constants" + "github.com/ODIM-Project/BMCOperator/logs" + l "github.com/ODIM-Project/BMCOperator/logs" + "github.com/fsnotify/fsnotify" + "github.com/google/uuid" + log "github.com/sirupsen/logrus" + "gopkg.in/yaml.v2" +) + +var podName = os.Getenv("POD_NAME") + +const configFilePath = "/etc/bmc-operator-config/config.yaml" + +// Data is a global variable to store the configmap +var Data ConfigModel +var confMutex = &sync.RWMutex{} + +// ConfigModel contains config values (Mandatory values) +type ConfigModel struct { + Reconciliation string `yaml:"reconciliation"` + ReconcileInterval string `yaml:"reconcileInterval"` + SecretName string `yaml:"secretName"` + MetricPort string `yaml:"metricsBindPort"` + HealthPort string `yaml:"healthProbeBindPort"` + EventClientPort string `yaml:"eventClientPort"` + LogLevel log.Level `yaml:"logLevel"` + LogFormat l.LogFormat `yaml:"logFormat"` + KubeConfigPath string `yaml:"kubeConfigPath"` + EventSubReconciliation string `yaml:"eventSubReconciliation"` + Namespace string `yaml:"namespace"` + OperatorEventSubscriptionMeesageIds []string `yaml:"operatorEventSubsciptionMessageIds"` + OperatorEventSubscriptionEventTypes []string `yaml:"operatorEventSubscriptionEventTypes"` + OperatorEventSubscriptionResourceTypes []string `yaml:" operatorEventSubsciptionResourceTypes"` +} + +var ( + // TickerTime: time to be set on hourly basis + TickerTime int + // Ticker holds a channel that delivers “ticks” of a clock at intervals + Ticker *time.Ticker +) + +// SetConfiguration will extract the config data from file +func SetConfiguration() error { + + configData, err := ioutil.ReadFile(configFilePath) + if err != nil { + return fmt.Errorf("Cannot read the config path %v", err) + } + err = yaml.Unmarshal(configData, &Data) + if err != nil { + return fmt.Errorf("error parsing config yaml file %v", err) + } + return nil +} + +// TrackConfigListener listes to the chanel on the config file changes +func TrackConfigListener(errChan chan error) { + eventChan := make(chan interface{}) + format := Data.LogFormat + reconciliation := Data.Reconciliation + reconciliationInterval := Data.ReconcileInterval + transactionID := uuid.New() + ctx := l.CreateContextForLogging(context.Background(), transactionID.String(), constants.BmcOperator, constants.TrackFileConfigActionID, constants.TrackFileConfigActionName, podName) + go TrackConfigFileChanges(eventChan, errChan) + for { + select { + case info := <-eventChan: + l.LogWithFields(ctx).Info(info) // new data arrives through eventChan channel + if l.Log.Level != Data.LogLevel { + l.Log.Logger.SetLevel(Data.LogLevel) + l.LogWithFields(ctx).Info("Log level is updated, new log level is ", Data.LogLevel) + } + if format != Data.LogFormat { + logs.SetLogFormat(Data.LogFormat) + format = Data.LogFormat + l.LogWithFields(ctx).Info("Log format is updated, new log format is ", Data.LogFormat) + } + if reconciliation != Data.Reconciliation { + reconciliation = Data.Reconciliation + l.LogWithFields(ctx).Info("Reconciliation action is updated, new action is ", Data.Reconciliation) + } + if reconciliationInterval != Data.ReconcileInterval { + reconciliation = Data.ReconcileInterval + TickerTime, _ = strconv.Atoi(Data.ReconcileInterval) + if Ticker != nil { + Ticker.Reset(time.Duration(time.Duration(TickerTime) * time.Hour)) + } + l.LogWithFields(ctx).Info("Reconciliation interval is updated, ticker is set to ", TickerTime) + } + case err := <-errChan: + l.LogWithFields(ctx).Error(err) + } + } +} + +// TrackConfigFileChanges monitors the config changes using fsnotfiy +func TrackConfigFileChanges(eventChan chan<- interface{}, errChan chan<- error) { + watcher, err := fsnotify.NewWatcher() + if err != nil { + errChan <- err + } + err = watcher.Add(configFilePath) + if err != nil { + errChan <- err + } + + go func() { + for { + select { + case fileEvent, ok := <-watcher.Events: + if !ok { + continue + } + if fileEvent.Op&fsnotify.Write == fsnotify.Write || fileEvent.Op&fsnotify.Remove == fsnotify.Remove { + // update the config + confMutex.Lock() + if err := SetConfiguration(); err != nil { + errChan <- fmt.Errorf("error while trying to set configuration: %s", err.Error()) + } + confMutex.Unlock() + eventChan <- "config file modified" + fileEvent.Name + } + //Reading file to continue the watch + watcher.Add(configFilePath) + case err, _ := <-watcher.Errors: + if err != nil { + errChan <- err + defer watcher.Close() + } + } + } + }() +} diff --git a/controllers/eventsClient/certificates.go b/controllers/eventsClient/certificates.go new file mode 100644 index 0000000..97a04cd --- /dev/null +++ b/controllers/eventsClient/certificates.go @@ -0,0 +1,79 @@ +//(C) Copyright [2023] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +package controllers + +import ( + "context" + "crypto/tls" + "crypto/x509" + "fmt" + "net" + "net/http" + + config "github.com/ODIM-Project/BMCOperator/controllers/config" + l "github.com/ODIM-Project/BMCOperator/logs" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" +) + +// GetHTTPServerObj is for obtaining a server instance to start the event listener using iris helper +func GetHTTPServerObj(ecr *EventsClientReconciler) (*http.Server, error) { + tlsConfig := &tls.Config{} + if err := LoadCertificates(tlsConfig, ecr); err != nil { + return nil, err + } + tlsConfig.MinVersion = tls.VersionTLS12 + tlsConfig.MaxVersion = tls.VersionTLS12 + + return &http.Server{ + Addr: net.JoinHostPort("", config.Data.EventClientPort), + TLSConfig: tlsConfig, + }, nil +} + +// LoadCertificates is for including passed certificates in tls.Config +func LoadCertificates(tlsConfig *tls.Config, ecr *EventsClientReconciler) error { + + rootCA, eventClientCert, eventClientKey := GetSignedCertificatesForEventClient(context.TODO(), ecr) + + cert, err := tls.X509KeyPair(eventClientCert, eventClientKey) + if err != nil { + return fmt.Errorf("error: failed to load key pair: %v", err) + } + tlsConfig.Certificates = []tls.Certificate{cert} + + capool := x509.NewCertPool() + if !capool.AppendCertsFromPEM(rootCA) { + return fmt.Errorf("error: failed to load CA certificate") + } + + tlsConfig.RootCAs = capool + tlsConfig.ClientCAs = capool + return nil +} + +// GetSignedCertificatesForEventClient retrieves the certificates from secret +func GetSignedCertificatesForEventClient(ctx context.Context, ecr *EventsClientReconciler) (rootCA, eventClientCert, eventClientKey []byte) { + secret := &corev1.Secret{} + ns := types.NamespacedName{Namespace: config.Data.Namespace, Name: config.Data.SecretName} + err := ecr.Get(context.TODO(), ns, secret) + if err != nil { + l.LogWithFields(ctx).Error(fmt.Sprintf("Error fetching %s secret", config.Data.SecretName), err.Error()) + return + } + return secret.Data["rootCACert"], + secret.Data["eventClientCert"], + secret.Data["eventClientKey"] +} diff --git a/controllers/eventsClient/client.go b/controllers/eventsClient/client.go new file mode 100644 index 0000000..1b3824a --- /dev/null +++ b/controllers/eventsClient/client.go @@ -0,0 +1,114 @@ +//(C) Copyright [2023] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +package controllers + +import ( + "context" + "encoding/json" + "flag" + "net/http" + "os" + + "github.com/ODIM-Project/BMCOperator/config/constants" + sync "github.com/ODIM-Project/BMCOperator/controllers/pollData" + l "github.com/ODIM-Project/BMCOperator/logs" + "github.com/google/uuid" + "github.com/kataras/iris/v12" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +var podName = os.Getenv("POD_NAME") + +// EventsClientReconciler helps to process the events receive from ODIM +type EventsClientReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +var ecr *EventsClientReconciler + +// Start initialize the event client listener +func Start(client client.Client, scheme *runtime.Scheme) { + + podName := os.Getenv("POD_NAME") + ecr = &EventsClientReconciler{ + Client: client, + Scheme: scheme, + } + + transactionID := uuid.New().String() + ctx := context.Background() + ctx = l.CreateContextForLogging(ctx, transactionID, constants.BMCOPERATOR, + constants.EventClientActionID, constants.EventClientActionName, podName) + + flag.Parse() + app := newApp() + + apiServer, err := GetHTTPServerObj(ecr) + if err != nil { + l.LogWithFields(ctx).Fatal("service initialization failed: " + err.Error()) + } + + err = app.Run(iris.Server(apiServer)) + if err != nil { + l.LogWithFields(ctx).Error("Error while starting the server", err.Error()) + } +} + +// newApp creates new iris instance for REST API +func newApp() *iris.Application { + app := iris.New() + app.Post("/OdimEvents", triggerReconciler) + + return app +} + +// triggerReconciler initiate event process and trigger the appropriate reconciler +func triggerReconciler(ctx iris.Context) { + var data interface{} + ctxt := ctx.Request().Context() + transactionID := uuid.New().String() + ctxt = l.CreateContextForLogging(ctxt, transactionID, constants.BMCOPERATOR, + constants.EventClientActionID, constants.EventClientActionName, podName) + err := ctx.ReadJSON(&data) + if err != nil { + l.LogWithFields(ctxt).Error("Error while trying to collect data from request: ", err) + ctx.StatusCode(http.StatusInternalServerError) + return + } + + event, err := json.Marshal(data) + if err != nil { + l.LogWithFields(ctxt).Error("error while marshalling event from ODIM: " + err.Error()) + ctx.StatusCode(http.StatusInternalServerError) + } + + l.LogWithFields(ctxt).Debug("Received an event from ODIM :- " + string(event)) + + var messageData sync.OdimEventMessage + err = json.Unmarshal(event, &messageData) + if err != nil { + l.LogWithFields(ctxt).Error("error while un-marshalling event from ODIM: " + err.Error()) + ctx.StatusCode(http.StatusInternalServerError) + return + } + + for _, event := range messageData.Events { + sync.ProcessOdimEvent(ctxt, ecr.Client, ecr.Scheme, + messageData.Name, event) + } + ctx.StatusCode(http.StatusOK) +} diff --git a/controllers/eventsubscription/eventsmessageregistry_controller.go b/controllers/eventsubscription/eventsmessageregistry_controller.go new file mode 100644 index 0000000..05deb6e --- /dev/null +++ b/controllers/eventsubscription/eventsmessageregistry_controller.go @@ -0,0 +1,60 @@ +//(C) Copyright [2023] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +package controllers + +import ( + "context" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + infraiov1 "github.com/ODIM-Project/BMCOperator/api/v1" +) + +// EventsMessageRegistryReconciler reconciles a EventsMessageRegistry object +type EventsMessageRegistryReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=infra.io.odimra,resources=eventsmessageregistries,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=infra.io.odimra,resources=eventsmessageregistries/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=infra.io.odimra,resources=eventsmessageregistries/finalizers,verbs=update + +// 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 EventsMessageRegistry 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.14.1/pkg/reconcile +func (r *EventsMessageRegistryReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = log.FromContext(ctx) + + // TODO(user): your logic here + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *EventsMessageRegistryReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&infraiov1.EventsMessageRegistry{}). + Complete(r) +} diff --git a/controllers/eventsubscription/eventsubscriptionUtils.go b/controllers/eventsubscription/eventsubscriptionUtils.go new file mode 100644 index 0000000..813eabe --- /dev/null +++ b/controllers/eventsubscription/eventsubscriptionUtils.go @@ -0,0 +1,81 @@ +//(C) Copyright [2023] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +package controllers + +import ( + "context" + + infraiov1 "github.com/ODIM-Project/BMCOperator/api/v1" + common "github.com/ODIM-Project/BMCOperator/controllers/common" + restclient "github.com/ODIM-Project/BMCOperator/controllers/restclient" + utils "github.com/ODIM-Project/BMCOperator/controllers/utils" + "k8s.io/apimachinery/pkg/types" +) + +// EventSubscriptionInterface declares method signatures for EventSubscription +type EventSubscriptionInterface interface { + CreateEventsubscription(eventSubObj *infraiov1.Eventsubscription, namespacedName types.NamespacedName) (bool, error) + UpdateEventsubscriptionStatus(eventSubscriptionID string) bool + GetEventSubscriptionRequest() ([]byte, error) + DeleteEventsubscription() bool + GetSystemIDFromURI(resourceURIArr []string, resourceOID string) string + UpdateEventSubscriptionLabels() + MapOriginResources(subscriptionDetails map[string]interface{}) ([]string, error) + GetMessageRegistryDetails(bmcObj *infraiov1.Bmc, registryName string) ([]string, []map[string]interface{}) + ValidateMessageIDs(messageIDs []string) error +} + +// MapCollectionToResourceURI maps collection to corresponding collection URI used while sending create request to ODIM +var MapCollectionToResourceURI = map[string]string{ + "systemCollection": "/redfish/v1/Systems", + "managerCollection": "/redfish/v1/Managers", + "chassisCollection": "/redfish/v1/Chassis", + "taskCollection": "/redfish/v1/TaskService/Tasks", + "fabricCollection": "/redfish/v1/Fabrics", + "allResources": "All", +} + +// MapResourceURIToCollection maps collection URI to collection name used to map the eventsubscription response from ODIM +var MapResourceURIToCollection = map[string]string{ + "/redfish/v1/Systems": "systemCollection", + "/redfish/v1/Managers": "managerCollection", + "/redfish/v1/Chassis": "chassisCollection", + "/redfish/v1/TaskService/Tasks": "taskCollection", + "/redfish/v1/Fabrics": "fabricCollection", +} + +type eventsubscriptionUtils struct { + ctx context.Context + eventsubRestClient restclient.RestClientInterface + commonRec utils.ReconcilerInterface + commonUtil common.CommonInterface + eventSubObj *infraiov1.Eventsubscription + namespace string +} + +type eventsubscriptionRequest struct { + Name string `json:"Name,omitempty"` + Destination string `json:"Destination"` + EventTypes []string `json:"EventTypes,omitempty"` + MessageIds []string `json:"MessageIds,omitempty"` + ResourceTypes []string `json:"ResourceTypes,omitempty"` + Context string `json:"Context"` + EventFormatType string `json:"EventFormatType,omitempty"` + SubordinateResources bool `json:"SubordinateResources"` + OriginResources []map[string]string `json:"OriginResources,omitempty"` + Protocol string `json:"Protocol"` + SubscriptionType string `json:"SubscriptionType"` + DeliveryRetryPolicy string `json:"DeliveryRetryPolicy"` +} diff --git a/controllers/eventsubscription/eventsubscription_controller.go b/controllers/eventsubscription/eventsubscription_controller.go new file mode 100644 index 0000000..462659c --- /dev/null +++ b/controllers/eventsubscription/eventsubscription_controller.go @@ -0,0 +1,519 @@ +//(C) Copyright [2023] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +package controllers + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "os" + "reflect" + "strings" + "time" + + restclient "github.com/ODIM-Project/BMCOperator/controllers/restclient" + "golang.org/x/exp/slices" + Error "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + infraiov1 "github.com/ODIM-Project/BMCOperator/api/v1" + "github.com/ODIM-Project/BMCOperator/config/constants" + common "github.com/ODIM-Project/BMCOperator/controllers/common" + utils "github.com/ODIM-Project/BMCOperator/controllers/utils" + l "github.com/ODIM-Project/BMCOperator/logs" + "github.com/google/uuid" +) + +var podName = os.Getenv("POD_NAME") + +// EventsubscriptionReconciler reconciles a Eventsubscription object +type EventsubscriptionReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=infra.io.odimra,resources=eventsubscriptions,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=infra.io.odimra,resources=eventsubscriptions/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=infra.io.odimra,resources=eventsubscriptions/finalizers,verbs=update + +// 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 Eventsubscription 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.14.1/pkg/reconcile +func (r *EventsubscriptionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + transactionID := uuid.New() + ctx = l.CreateContextForLogging(ctx, transactionID.String(), constants.BmcOperator, constants.EventSubscriptionActionID, constants.EventSubscriptionActionName, podName) + commonRec := utils.GetCommonReconciler(r.Client, r.Scheme) + eventSubscriptionObj := &infraiov1.Eventsubscription{} + err := r.Get(ctx, req.NamespacedName, eventSubscriptionObj) + if err != nil { + if Error.IsNotFound(err) { + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } + odimObject := commonRec.GetOdimObject(ctx, constants.MetadataName, "odim", req.Namespace) + + eventSubRestClient, err := restclient.NewRestClient(ctx, odimObject, commonRec.(*utils.CommonReconciler), constants.BMCOPERATOR) + if err != nil { + l.LogWithFields(ctx).Errorf("Failed to get rest client for Eventsubscription: %s", err.Error()) + return ctrl.Result{}, err + } + + //get eventsubscriptionUtil object + eventSubscriptionUtil := GetEventSubscriptionUtils(ctx, eventSubRestClient, commonRec, eventSubscriptionObj, req.Namespace) + + //Checking for deletion + isEventSubscriptionMarkedForDelete := eventSubscriptionObj.GetDeletionTimestamp() != nil + if isEventSubscriptionMarkedForDelete { + if controllerutil.ContainsFinalizer(eventSubscriptionObj, constants.EventsubscriptionFinalizer) { + isEventSubscriptionDeleted := eventSubscriptionUtil.DeleteEventsubscription() + if isEventSubscriptionDeleted { + controllerutil.RemoveFinalizer(eventSubscriptionObj, constants.EventsubscriptionFinalizer) + if err := r.Update(ctx, eventSubscriptionObj); err != nil { + return ctrl.Result{}, err + } + } else { + l.LogWithFields(ctx).Info(fmt.Sprintf("EventSubscription with ID %s not deleted! Retrying..", eventSubscriptionObj.Status.ID)) + return ctrl.Result{Requeue: true}, nil + } + } + return ctrl.Result{}, nil + } + + if reflect.DeepEqual(eventSubscriptionObj.Spec, infraiov1.EventsubscriptionSpec{}) { + return ctrl.Result{}, nil + } + + if eventSubscriptionObj.Spec.Destination != "" && eventSubscriptionObj.Spec.Context != "" { + + if !controllerutil.ContainsFinalizer(eventSubscriptionObj, constants.EventsubscriptionFinalizer) { + controllerutil.AddFinalizer(eventSubscriptionObj, constants.EventsubscriptionFinalizer) + err := r.Client.Update(ctx, eventSubscriptionObj) + if err != nil { + l.LogWithFields(ctx).Error(fmt.Sprintf("Error: Updating %s eventsunscription object: %s", eventSubscriptionObj.Status.Name, err.Error())) + return ctrl.Result{}, err + } + time.Sleep(time.Duration(constants.SleepTime) * time.Second) + } + + //create Eventsubscription + eventSubCreated, eventErr := eventSubscriptionUtil.CreateEventsubscription(eventSubscriptionObj, req.NamespacedName) + if !eventSubCreated { + return ctrl.Result{}, eventErr + } + l.LogWithFields(ctx).Info("Updating labels for event subscription object..") + eventSubscriptionUtil.UpdateEventSubscriptionLabels() + l.LogWithFields(ctx).Info("Successfully updated lables for event subscription object") + + } else { + l.LogWithFields(ctx).Info("Please enter all the required details for creating a event subscription") + } + + return ctrl.Result{}, nil +} + +func (esu *eventsubscriptionUtils) CreateEventsubscription(eventSubObj *infraiov1.Eventsubscription, namespacedName types.NamespacedName) (bool, error) { + esu.commonRec.GetUpdatedEventsubscriptionObjects(esu.ctx, namespacedName, esu.eventSubObj) + if esu.eventSubObj.Status.ID != "" { + return true, nil + } + // eventsubscription creation request + eventSubReq, err := esu.GetEventSubscriptionRequest() + if err == nil { + //send request for event subscription creation + resp, err := esu.eventsubRestClient.Post("/redfish/v1/EventService/Subscriptions", "Posting eventsubscription creation payload...", eventSubReq) + if err != nil { + l.LogWithFields(esu.ctx).Error("error while creating eventsubscription: " + err.Error()) + return false, err + } + if resp.StatusCode == http.StatusAccepted { + done, _ := esu.commonUtil.MoniteringTaskmon(resp.Header, esu.ctx, common.EVENTSUBSCRIPTION, esu.eventSubObj.ObjectMeta.Name) + if done { + l.LogWithFields(esu.ctx).Info("\nEventsubscription created successfully!\n") + l.LogWithFields(esu.ctx).Info("Updating Eventsubscription status...") + statusUpdated := esu.UpdateEventsubscriptionStatus("") + if !statusUpdated { + l.LogWithFields(esu.ctx).Info("Eventsubscription created successfully but failed to update the status") + } else { + l.LogWithFields(esu.ctx).Info("Eventsubscription status updated successfully!") + } + return true, nil + } + } + } + + controllerutil.RemoveFinalizer(esu.eventSubObj, constants.EventsubscriptionFinalizer) + err = esu.commonRec.GetCommonReconcilerClient().Update(esu.ctx, esu.eventSubObj) + if err != nil { + l.LogWithFields(esu.ctx).Error("error while updating eventsubscription object for deletion:" + err.Error()) + } + err = esu.commonRec.GetCommonReconcilerClient().Delete(esu.ctx, esu.eventSubObj) + if err != nil { + l.LogWithFields(esu.ctx).Error("error while deleting eventsubscription object:" + err.Error()) + } + l.LogWithFields(esu.ctx).Info(fmt.Sprintf("Could not create %s eventsubscription, try again", esu.eventSubObj.ObjectMeta.Name)) + return false, nil +} + +func (esu *eventsubscriptionUtils) UpdateEventsubscriptionStatus(eventSubscriptionID string) bool { + + subscriptionList, statusCode, err := esu.eventsubRestClient.Get("/redfish/v1/EventService/Subscriptions", "Fetching all subsciptions..") + if err != nil { + l.LogWithFields(esu.ctx).Error("error while fetching all subscriptions:" + err.Error()) + return false + } else if statusCode != http.StatusOK { + l.LogWithFields(esu.ctx).Info("Failed to get all the subscriptions, try again..") + return false + } + + if subscriptions, ok := subscriptionList["Members"].([]interface{}); ok { + for _, subscription := range subscriptions { + reqURL := subscription.(map[string]interface{})["@odata.id"].(string) + + subscriptionDetails, statusCode, err := esu.eventsubRestClient.Get(reqURL, fmt.Sprintf("Fetching %s eventsubscription details..", reqURL[len(reqURL)-1:])) + + if err != nil { + l.LogWithFields(esu.ctx).Error(fmt.Sprintf("error while fetching %s eventsubscription details:", reqURL[len(reqURL)-1:]) + err.Error()) + return false + } else if statusCode != http.StatusOK { + l.LogWithFields(esu.ctx).Info(fmt.Sprintf("Failed to get %s eventsubscription details, try again.", reqURL[len(reqURL)-1:])) + return false + } else { + + if subscriptionDetails["Destination"].(string) == esu.eventSubObj.Spec.Destination && subscriptionDetails["Context"].(string) == esu.eventSubObj.Spec.Context { + originResources, err := esu.MapOriginResources(subscriptionDetails) + if err != nil { + l.LogWithFields(esu.ctx).Error(err.Error()) + } + esu.commonRec.UpdateEventsubscriptionStatus(esu.ctx, esu.eventSubObj, subscriptionDetails, originResources) + return true + } + } + } + } + return true +} + +func (esu *eventsubscriptionUtils) GetSystemIDFromURI(resourceURIArr []string, resourceOID string) string { + systemID := resourceURIArr[4] + if strings.Contains(resourceOID, "/TaskService/Tasks") { + systemID = resourceURIArr[5] + } + if strings.Contains(resourceOID, "UpdateService/FirmwareInventory") { + systemID = strings.Split(resourceURIArr[5], ".")[0] + } + return systemID +} + +func (esu *eventsubscriptionUtils) GetEventSubscriptionRequest() ([]byte, error) { + l.Log.Info("Creating eventsubscription request payload...") + var ( + bmcObj *infraiov1.Bmc + volumeDetails []string + subscribedCollection = map[string]bool{} + ) + err := esu.ValidateMessageIDs(esu.eventSubObj.Spec.MessageIds) + if err != nil { + return []byte{}, err + } + reqPayload := eventsubscriptionRequest{ + Name: esu.eventSubObj.Spec.Name, + Destination: esu.eventSubObj.Spec.Destination, + EventTypes: esu.eventSubObj.Spec.EventTypes, + MessageIds: esu.eventSubObj.Spec.MessageIds, + ResourceTypes: esu.eventSubObj.Spec.ResourceTypes, + Context: esu.eventSubObj.Spec.Context, + EventFormatType: esu.eventSubObj.Spec.EventFormatType, + SubordinateResources: esu.eventSubObj.Spec.SubordinateResources, + Protocol: "Redfish", + SubscriptionType: "RedfishEvent", + DeliveryRetryPolicy: "RetryForever", + OriginResources: []map[string]string{}, + } + + for _, resource := range esu.eventSubObj.Spec.OriginResources { + if collection, ok := MapCollectionToResourceURI[resource]; ok { + if collection == "All" { + addSubscriptionToAllCollections(subscribedCollection) + break + } else { + subscribedCollection[collection] = true + } + } else { + objectDetails := strings.Split(resource, "/") + kind := objectDetails[0] + objName := objectDetails[len(objectDetails)-1] + if kind == "Volume" { + volumeDetails = strings.Split(objName, ".") + objName = volumeDetails[0] + } + bmcObj = esu.commonRec.GetBmcObject(esu.ctx, constants.MetadataName, objName, esu.namespace) + switch kind { + case "BiosSetting": + if _, ok := subscribedCollection["/redfish/v1/Systems"]; !ok { + if _, ok = subscribedCollection["/redfish/v1/Systems/"+bmcObj.Status.BmcSystemID]; !ok { + resourceObj := esu.commonRec.GetBiosObject(esu.ctx, constants.MetadataName, objName, esu.namespace) + if resourceObj != nil { + subscribedCollection[resourceObj.ObjectMeta.Annotations["odata.id"]] = true + + } else { + return []byte{}, fmt.Errorf("failed to create eventsubscription request: Could not find Bios object ") + } + } + } + case "Bmc": + if _, ok := subscribedCollection["/redfish/v1/Systems"]; !ok { + if bmcObj != nil { + subscribedCollection[bmcObj.ObjectMeta.Annotations["odata.id"]] = true + removeSubordinateResourceSubscriptions(subscribedCollection, bmcObj.Status.BmcSystemID) + } else { + return []byte{}, fmt.Errorf("failed to create eventsubscription request: Could not find Bmc object ") + } + + } + case "BootOrderSetting": + if _, ok := subscribedCollection["/redfish/v1/Systems"]; !ok { + if _, ok = subscribedCollection["/redfish/v1/Systems/"+bmcObj.Status.BmcSystemID]; !ok { + resourceObj := esu.commonRec.GetBootObject(esu.ctx, constants.MetadataName, objName, esu.namespace) + if resourceObj != nil { + subscribedCollection[resourceObj.ObjectMeta.Annotations["odata.id"]] = true + } else { + return []byte{}, fmt.Errorf("failed to create eventsubscription request: Could not find Boot object ") + } + } + } + case "Firmware": + if _, ok := subscribedCollection["/redfish/v1/Managers"]; !ok { + resourceObj := esu.commonRec.GetFirmwareObject(esu.ctx, constants.MetadataName, objName, esu.namespace) + if resourceObj != nil { + inventories := resourceObj.ObjectMeta.Annotations["odata.id"] + getOriginResourcesForFirmware(subscribedCollection, inventories) + } else { + return []byte{}, fmt.Errorf("failed to create eventsubscription request: Could not find Firmware object ") + } + } + case "Volume": + if _, ok := subscribedCollection["/redfish/v1/Systems"]; !ok { + if _, ok = subscribedCollection["/redfish/v1/Systems/"+bmcObj.Status.BmcSystemID]; !ok { + volumeDetails := strings.Split(objName, ".") + resourceObj := esu.commonRec.GetVolumeObject(esu.ctx, volumeDetails[0], esu.namespace) + if resourceObj != nil { + subscribedCollection[resourceObj.ObjectMeta.Annotations["odata.id"]] = true + } else { + return []byte{}, fmt.Errorf("failed to create eventsubscription request: Could not find Volume object ") + } + } + } + case "allResources": + addSubscriptionToAllCollectionForBmc(subscribedCollection, bmcObj.Status.BmcSystemID) + } + } + } + + for key := range subscribedCollection { + resourceLink := map[string]string{ + "@odata.id": key, + } + reqPayload.OriginResources = append(reqPayload.OriginResources, resourceLink) + } + reqBody, err := json.Marshal(reqPayload) + if err != nil { + l.LogWithFields(esu.ctx).Error("Failed to marshal eventsubscription request payload " + err.Error()) + } + return reqBody, nil +} + +func removeSubordinateResourceSubscriptions(subscribingCollection map[string]bool, bmcSystemID string) { + if _, ok := subscribingCollection["/redfish/v1/Systems/"+bmcSystemID+"/Bios"]; ok { + delete(subscribingCollection, "/redfish/v1/Systems/"+bmcSystemID+"/Bios") + } + if _, ok := subscribingCollection["/redfish/v1/Systems/"+bmcSystemID+"/BootOptions"]; ok { + delete(subscribingCollection, "/redfish/v1/Systems/"+bmcSystemID+"/BootOptions") + } +} + +func (esu *eventsubscriptionUtils) DeleteEventsubscription() bool { + deleteResp, err := esu.eventsubRestClient.Delete(fmt.Sprintf("/redfish/v1/EventService/Subscriptions/%s", esu.eventSubObj.Status.ID), fmt.Sprintf("Deleting event subscription with ID %s", esu.eventSubObj.Status.ID)) + if err != nil { + l.LogWithFields(esu.ctx).Error(fmt.Sprintf("error while deleting event subscription with ID %s", esu.eventSubObj.Status.ID)) + return false + } + if deleteResp.StatusCode == http.StatusOK { + l.LogWithFields(esu.ctx).Info(fmt.Sprintf("Successfully deleted event subscription with ID %s", esu.eventSubObj.Status.ID)) + return true + } + l.LogWithFields(esu.ctx).Info(fmt.Sprintf("Could not delete event subsscription with ID %s. Please try again..", esu.eventSubObj.Status.ID)) + return false +} + +func (esu *eventsubscriptionUtils) UpdateEventSubscriptionLabels() { + esu.eventSubObj.ObjectMeta.Labels = map[string]string{} + esu.eventSubObj.ObjectMeta.Labels["eventSubscriptionID"] = esu.eventSubObj.Status.ID +} + +func (esu *eventsubscriptionUtils) MapOriginResources(subscriptionDetails map[string]interface{}) ([]string, error) { + var originResources []string + var resource string + if originResourcesLinks, ok := subscriptionDetails["OriginResources"].([]interface{}); ok { + for _, originResource := range originResourcesLinks { + resourceOID := originResource.(map[string]interface{})["@odata.id"].(string) + + if value, ok := MapResourceURIToCollection[resourceOID]; ok { + resource = value + } else { + resourceURI := strings.Split(resourceOID, "/") + systemID := esu.GetSystemIDFromURI(resourceURI, resourceOID) + bmcObj := esu.commonRec.GetBmcObject(esu.ctx, constants.StatusBmcSystemID, systemID, esu.namespace) + if bmcObj != nil { + objectName := bmcObj.ObjectMeta.Name + switch { + case strings.Contains(resourceOID, "/Volumes/"): + volumeID := resourceURI[len(resourceURI)-1] + volObjs := esu.commonRec.GetAllVolumeObjects(esu.ctx, bmcObj.Spec.BmcDetails.Address, esu.namespace) + if volObjs != nil { + for _, volObj := range volObjs { + if volObj.Status.VolumeID == volumeID { + objectName = bmcObj.Spec.BmcDetails.Address + "/" + volObj.Status.VolumeName + resource = "Volume/" + objectName + } + } + } + case strings.Contains(resourceOID, "/Bios"): + resource = "BiosSetting/" + objectName + case strings.Contains(resourceOID, "/BootOptions"): + resource = "BootOrderSetting/" + objectName + case strings.Contains(resourceOID, "UpdateService/FirmwareInventory"): + + resource = "Firmware/" + objectName + case strings.Contains(resourceOID, "/Systems/"): + resource = "Bmc/" + objectName + } + } else { + return []string{}, fmt.Errorf("Bmc with systemID %s does not exist in system", systemID) + } + } + if !slices.Contains(originResources, resource) { + originResources = append(originResources, resource) + } + } + } + return originResources, nil +} + +func (esu *eventsubscriptionUtils) GetMessageRegistryDetails(bmcObj *infraiov1.Bmc, registryName string) ([]string, []map[string]interface{}) { + uri := "/redfish/v1/Registries/" + var registryIDs []string + var registryResponses []map[string]interface{} + resp, sCode, _ := esu.eventsubRestClient.Get(uri, fmt.Sprintf("Fetching registries details for %s BMC", bmcObj.Spec.BmcDetails.Address)) + if sCode == http.StatusOK { + for _, member := range resp["Members"].([]interface{}) { + val := member.(map[string]interface{})["@odata.id"].(string) + if strings.Contains(val, registryName) { + res, statusCode, _ := esu.eventsubRestClient.Get(val, fmt.Sprintf("Fetching bios attribute registry details for %s BMC", bmcObj.Spec.BmcDetails.Address)) + if statusCode == http.StatusOK { + for _, mem := range res["Location"].([]interface{}) { + if mem.(map[string]interface{})["Language"].(string) == "en" { + url := mem.(map[string]interface{})["Uri"].(string) + r, s, _ := esu.eventsubRestClient.Get(url, fmt.Sprintf("Fetching bios attribute registry JSON for %s BMC", bmcObj.Spec.BmcDetails.Address)) + + if s == http.StatusOK { + registryIDs = append(registryIDs, r["RegistryPrefix"].(string)+"."+r["RegistryVersion"].(string)) + registryResponses = append(registryResponses, r) + } + } + } + } + } + } + } + return registryIDs, registryResponses +} + +func (esu *eventsubscriptionUtils) ValidateMessageIDs(messageIDs []string) error { + for _, messageID := range messageIDs { + registryPrefix, registryVersion, messageKey := getMessageDetails(messageID) + if strings.Contains(strings.ToLower(registryPrefix), "iloevents") { + objName := strings.ToLower(registryPrefix) + "." + registryVersion + messageRegistry := esu.commonRec.GetEventMessageRegistryObject(esu.ctx, constants.MetadataName, objName, esu.namespace) + if messageRegistry != nil { + if _, ok := messageRegistry.Spec.Messages[messageKey]; !ok { + l.LogWithFields(esu.ctx).Errorf("message ID %s not found in the message registry %s. Please enter a valid message ID", messageKey, messageRegistry.Spec.Name) + return fmt.Errorf("failed to create event subscription request") + } + } else { + return fmt.Errorf("message registry not found for message ID %s", messageID) + } + } + } + return nil +} + +func getMessageDetails(message string) (string, string, string) { + message = strings.Replace(message, ".", "-", 1) + index := strings.LastIndex(message, ".") + parsedMessage := message[:index] + strings.Replace(message[index:], ".", "-", 1) + messageDetails := strings.Split(parsedMessage, "-") + return messageDetails[0], messageDetails[1], messageDetails[2] +} + +func addSubscriptionToAllCollections(subscribingCollection map[string]bool) { + for collectionURI := range MapResourceURIToCollection { + subscribingCollection[collectionURI] = true + } +} + +func addSubscriptionToAllCollectionForBmc(subscribedCollection map[string]bool, bmcSystemID string) { + subscribedCollection["/redfish/v1/Systems/"+bmcSystemID] = true + subscribedCollection["/redfish/v1/Managers/"+bmcSystemID] = true + subscribedCollection["/redfish/v1/Chassis/"+bmcSystemID] = true +} + +func getOriginResourcesForFirmware(subscribedCollection map[string]bool, inventories string) { + listOfInventories := strings.Split(inventories, ",") + for _, item := range listOfInventories { + subscribedCollection[item] = true + } +} + +// SetupWithManager sets up the controller with the Manager. +func (r *EventsubscriptionReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&infraiov1.Eventsubscription{}). + WithEventFilter(utils.IgnoreStatusUpdate()). + Complete(r) +} + +// GetEventSubscriptionUtils will create and return an object of eventsubscriptionUtils +func GetEventSubscriptionUtils(ctx context.Context, restClient restclient.RestClientInterface, commonRec utils.ReconcilerInterface, eventSubObj *infraiov1.Eventsubscription, ns string) EventSubscriptionInterface { + return &eventsubscriptionUtils{ + ctx: ctx, + eventsubRestClient: restClient, + commonRec: commonRec, + eventSubObj: eventSubObj, + commonUtil: common.GetCommonUtils(restClient), + namespace: ns, + } +} diff --git a/controllers/eventsubscription/eventsubscription_controller_test.go b/controllers/eventsubscription/eventsubscription_controller_test.go new file mode 100644 index 0000000..c409ca5 --- /dev/null +++ b/controllers/eventsubscription/eventsubscription_controller_test.go @@ -0,0 +1,87 @@ +//(C) Copyright [2023] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +package controllers + +import ( + "context" + "reflect" + "testing" + + infraiov1 "github.com/ODIM-Project/BMCOperator/api/v1" +) + +var commonRecObj = MockCommonRec{variable: "eventSubscriptionObject"} +var mockRC = mockRestClient{} + +func Test_GetEventSubscriptionRequest(t *testing.T) { + eventSubObj := &infraiov1.Eventsubscription{ + Spec: infraiov1.EventsubscriptionSpec{ + Destination: "https://10.24.1.14:8093/Destination", + Context: "ODIMRA_Event", + EventTypes: []string{"Alert"}, + ResourceTypes: []string{"ComputerSystem"}, + OriginResources: []string{"systemCollection"}, + }, + } + eventSubUtils := &eventsubscriptionUtils{ + ctx: context.TODO(), + eventsubRestClient: &mockRC, + commonRec: &commonRecObj, + eventSubObj: eventSubObj, + commonUtil: nil, + namespace: "bmc-op", + } + + expectedResp := "{\"Destination\":\"https://10.24.1.14:8093/Destination\",\"EventTypes\":[\"Alert\"],\"ResourceTypes\":[\"ComputerSystem\"],\"Context\":\"ODIMRA_Event\",\"SubordinateResources\":false,\"OriginResources\":[{\"@odata.id\":\"/redfish/v1/Systems\"}],\"Protocol\":\"Redfish\",\"SubscriptionType\":\"RedfishEvent\",\"DeliveryRetryPolicy\":\"RetryForever\"}" + + eventSubscriptionRequest, _ := eventSubUtils.GetEventSubscriptionRequest() + + if !reflect.DeepEqual(string(eventSubscriptionRequest), expectedResp) { + t.Errorf("GetEventSubscriptionRequest(): expected: %s, recieved %s", expectedResp, string(eventSubscriptionRequest)) + } +} + +func Test_DeleteEventsubscription(t *testing.T) { + eventSubObj := &infraiov1.Eventsubscription{ + Spec: infraiov1.EventsubscriptionSpec{ + Destination: "https://10.24.1.14:8093/Destination", + Context: "ODIMRA_Event", + EventTypes: []string{"Alert"}, + ResourceTypes: []string{"ComputerSystem"}, + OriginResources: []string{"systemCollection"}, + }, + Status: infraiov1.EventsubscriptionStatus{ + ID: "1234", + Destination: "https://10.24.1.14:8093/Destination", + Context: "ODIMRA_Event", + EventTypes: []string{"Alert"}, + ResourceTypes: []string{"ComputerSystem"}, + OriginResources: []string{"systemCollection"}, + }, + } + eventSubUtils := &eventsubscriptionUtils{ + ctx: context.TODO(), + eventsubRestClient: &mockRC, + commonRec: &commonRecObj, + eventSubObj: eventSubObj, + commonUtil: nil, + namespace: "bmc-op", + } + + deleteResp := eventSubUtils.DeleteEventsubscription() + if !deleteResp { + t.Errorf("DeleteEventsubscription(): expected: %v, recieved %v", true, deleteResp) + } +} diff --git a/controllers/eventsubscription/mockUtils_test.go b/controllers/eventsubscription/mockUtils_test.go new file mode 100644 index 0000000..c506e51 --- /dev/null +++ b/controllers/eventsubscription/mockUtils_test.go @@ -0,0 +1,237 @@ +//(C) Copyright [2023] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +package controllers + +import ( + "context" + "net/http" + + infraiov1 "github.com/ODIM-Project/BMCOperator/api/v1" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type MockCommonRec struct { + variable string +} +type mockRestClient struct{ url string } + +// Mock Common Rec Methods +// get objects +func (m *MockCommonRec) GetBmcObject(ctx context.Context, field, value, ns string) *infraiov1.Bmc { + if m.variable == "sendBmcObj" { + return &infraiov1.Bmc{Status: infraiov1.BmcStatus{BmcSystemID: "fakeSystemID"}} + } else if m.variable == "doNotSendBmcObj" { + return nil + } + return nil +} + +func (m *MockCommonRec) GetAllBmcObject(ctx context.Context, ns string) *[]infraiov1.Bmc { + return nil +} + +func (m *MockCommonRec) GetBiosSchemaObject(ctx context.Context, field, value, ns string) *infraiov1.BiosSchemaRegistry { + return nil +} +func (m *MockCommonRec) GetBiosObject(ctx context.Context, field, value, ns string) *infraiov1.BiosSetting { + return nil +} + +func (m *MockCommonRec) GetBootObject(ctx context.Context, field, value, ns string) *infraiov1.BootOrderSetting { + return nil +} + +func (m *MockCommonRec) GetOdimObject(ctx context.Context, field, value, ns string) *infraiov1.Odim { + return nil +} + +func (m *MockCommonRec) GetVolumeObject(ctx context.Context, bmcIP, ns string) *infraiov1.Volume { + return nil +} + +func (m *MockCommonRec) GetFirmwareObject(ctx context.Context, field, value, ns string) *infraiov1.Firmware { + return nil +} + +// create objects +func (m *MockCommonRec) CreateBiosSettingObject(ctx context.Context, biosAttributes map[string]string, bmcObj *infraiov1.Bmc) bool { + return true +} + +func (m *MockCommonRec) CreateBootOrderSettingObject(ctx context.Context, bootAttributes *infraiov1.BootSetting, bmcObj *infraiov1.Bmc) bool { + return true +} + +func (m *MockCommonRec) CreateEventSubscriptionObject(ctx context.Context, subscriptionDetails map[string]interface{}, ns string, originResources []string) bool { + return true +} + +func (m *MockCommonRec) GetAllEventSubscriptionObjects(ctx context.Context, ns string) *[]infraiov1.Eventsubscription { + return nil +} + +func (m *MockCommonRec) GetEventsubscriptionObject(ctx context.Context, field, value, ns string) *infraiov1.Eventsubscription { + return nil +} + +func (m *MockCommonRec) GetEventMessageRegistryObject(ctx context.Context, registryPrefix, registryVersion, ns string) *infraiov1.EventsMessageRegistry { + return nil +} + +func (m *MockCommonRec) CheckAndCreateEventMessageObject(ctx context.Context, messageRegistryResp map[string]interface{}, bmcObj *infraiov1.Bmc) bool { + return true +} + +func (m *MockCommonRec) CheckAndCreateBiosSchemaObject(ctx context.Context, attributeResp map[string]interface{}, bmcObj *infraiov1.Bmc) bool { + return true +} + +// update objects +func (m *MockCommonRec) UpdateBmcStatus(ctx context.Context, bmcObj *infraiov1.Bmc) { +} + +func (m *MockCommonRec) UpdateOdimStatus(ctx context.Context, status string, odimObj *infraiov1.Odim) { +} + +func (m *MockCommonRec) UpdateBmcObjectOnReset(ctx context.Context, bmcObject *infraiov1.Bmc, status string) { +} + +func (m *MockCommonRec) UpdateVolumeStatus(ctx context.Context, volObject *infraiov1.Volume, volumeID, volumeName, capBytes, durableName, durableNameFormat string) { +} + +// get updated objects +func (m *MockCommonRec) GetUpdatedBmcObject(ctx context.Context, ns types.NamespacedName, bmcObj *infraiov1.Bmc) { +} + +func (m *MockCommonRec) GetUpdatedOdimObject(ctx context.Context, ns types.NamespacedName, odimObj *infraiov1.Odim) { +} + +func (m *MockCommonRec) GetUpdatedVolumeObject(ctx context.Context, ns types.NamespacedName, volObj *infraiov1.Volume) { +} + +func (m *MockCommonRec) GetUpdatedFirmwareObject(ctx context.Context, ns types.NamespacedName, firmObj *infraiov1.Firmware) { +} + +func (m *MockCommonRec) UpdateBiosSettingObject(ctx context.Context, biosAttributes map[string]string, biosObj *infraiov1.BiosSetting) bool { + return true +} + +func (m *MockCommonRec) GetAllVolumeObjectIds(ctx context.Context, bmc *infraiov1.Bmc, ns string) map[string][]string { + return nil +} + +func (m *MockCommonRec) DeleteVolumeObject(ctx context.Context, volObj *infraiov1.Volume) {} + +func (m *MockCommonRec) DeleteBmcObject(ctx context.Context, bmcObj *infraiov1.Bmc) {} + +func (m *MockCommonRec) UpdateEventsubscriptionStatus(ctx context.Context, eventsubObj *infraiov1.Eventsubscription, eventsubscriptionDetails map[string]interface{}, originResouces []string) { +} + +func (m *MockCommonRec) GetUpdatedEventsubscriptionObjects(ctx context.Context, ns types.NamespacedName, eventSubObj *infraiov1.Eventsubscription) { +} + +func (m *MockCommonRec) GetAllVolumeObjects(ctx context.Context, bmcIP, ns string) []*infraiov1.Volume { + return nil +} + +func (m *MockCommonRec) GetAllBiosSchemaRegistryObjects(ctx context.Context, ns string) *[]infraiov1.BiosSchemaRegistry { + return nil +} + +func (m *MockCommonRec) GetVolumeObjectByVolumeID(ctx context.Context, volumeName, ns string) *infraiov1.Volume { + return nil +} + +// common reconciler funcs +func (m *MockCommonRec) GetCommonReconcilerClient() client.Client { + return MockCommonRec{} +} + +func (m *MockCommonRec) GetCommonReconcilerScheme() *runtime.Scheme { + return nil +} + +// Client mocks +func (m MockCommonRec) Get(ctx context.Context, key client.ObjectKey, obj client.Object) error { + return nil +} + +func (m MockCommonRec) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { + return nil +} + +func (m MockCommonRec) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { + return nil +} + +func (m MockCommonRec) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error { + return nil +} + +func (m MockCommonRec) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil +} + +func (m MockCommonRec) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error { + return nil +} + +func (m MockCommonRec) DeleteAllOf(ctx context.Context, obj client.Object, opts ...client.DeleteAllOfOption) error { + return nil +} + +func (m MockCommonRec) Status() client.StatusWriter { + return MockCommonRec{} +} + +func (m MockCommonRec) RESTMapper() meta.RESTMapper { + return nil +} + +func (m MockCommonRec) Scheme() *runtime.Scheme { + return nil +} + +func (m MockCommonRec) GetRootCAFromSecret(ctx context.Context) []byte { + return []byte{} +} + +// mock Rest calls + +func (m *mockRestClient) Post(string, string, interface{}) (*http.Response, error) { + return nil, nil +} +func (m *mockRestClient) Get(string, string) (map[string]interface{}, int, error) { + return nil, 0, nil +} +func (m *mockRestClient) Patch(string, string, interface{}) (*http.Response, error) { + return nil, nil +} +func (m *mockRestClient) Delete(string, string) (*http.Response, error) { + resp := &http.Response{ + Status: "200 OK", + StatusCode: 200, + } + return resp, nil +} +func (m *mockRestClient) Put(string, string, interface{}) (*http.Response, error) { + return nil, nil +} +func (m *mockRestClient) RecallWithNewToken(url, reason, method string, body interface{}) (*http.Response, error) { + return nil, nil +} diff --git a/controllers/firmware/firmwareUtils.go b/controllers/firmware/firmwareUtils.go new file mode 100644 index 0000000..2fc0751 --- /dev/null +++ b/controllers/firmware/firmwareUtils.go @@ -0,0 +1,68 @@ +//(C) Copyright [2023] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +package controllers + +import ( + "context" + + infraiov1 "github.com/ODIM-Project/BMCOperator/api/v1" + bios "github.com/ODIM-Project/BMCOperator/controllers/bios" + common "github.com/ODIM-Project/BMCOperator/controllers/common" + restclient "github.com/ODIM-Project/BMCOperator/controllers/restclient" + utils "github.com/ODIM-Project/BMCOperator/controllers/utils" +) + +type firmwareUtils struct { + ctx context.Context + firmObj *infraiov1.Firmware + commonRec utils.ReconcilerInterface + firmRestClient restclient.RestClientInterface + commonUtil common.CommonInterface + biosUtil bios.BiosInterface + namespace string +} + +type firmwareInterface interface { + getFirmwareBody() ([]byte, error) + UpdateFirmwareStatus(string, string, string) + GetSystemID() string + GetFirmwareVersion() string + UpdateFirmwareLabels(string) + UpdateBmcObjectWithFirmwareVersionAndSchema(*infraiov1.Bmc, string, string) + CreateFirmwareInSystem([]byte) (bool, bool, error) + checkIfBiosSchemaRegistryChanged(bmcObj *infraiov1.Bmc) string + GetFirmwareInventoryDetails(systemID string) []string +} + +type firmPayload struct { + Image string `json:"ImageURI"` + Targets []string `json:"Targets"` + Username string `json:"Username"` + Password string `json:"Password"` + TransferProtocol string `json:"transferProtocol"` +} + +type firmPayloadWithTransferProtocol struct { + Image string `json:"ImageURI"` + Targets []string `json:"Targets"` + Username string `json:"Username"` + Password string `json:"Password"` + TransferProtocol string `json:"transferProtocol"` +} + +type FirmPayloadWithoutAuth struct { + Image string `json:"ImageURI"` + Targets []string `json:"Targets"` +} diff --git a/controllers/firmware/firmware_controller.go b/controllers/firmware/firmware_controller.go new file mode 100644 index 0000000..4dfbd9e --- /dev/null +++ b/controllers/firmware/firmware_controller.go @@ -0,0 +1,329 @@ +//(C) Copyright [2022] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +// Package controllers ... +package controllers + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "os" + "strings" + "time" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + infraiov1 "github.com/ODIM-Project/BMCOperator/api/v1" + "github.com/ODIM-Project/BMCOperator/config/constants" + bios "github.com/ODIM-Project/BMCOperator/controllers/bios" + common "github.com/ODIM-Project/BMCOperator/controllers/common" + restclient "github.com/ODIM-Project/BMCOperator/controllers/restclient" + utils "github.com/ODIM-Project/BMCOperator/controllers/utils" + l "github.com/ODIM-Project/BMCOperator/logs" + "github.com/google/uuid" + Error "k8s.io/apimachinery/pkg/api/errors" +) + +// FirmwareReconciler reconciles a Firmware object +type FirmwareReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +var podName = os.Getenv("POD_NAME") + +//+kubebuilder:rbac:groups=infra.io.odimra,resources=firmwares,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=infra.io.odimra,resources=firmwares/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=infra.io.odimra,resources=firmwares/finalizers,verbs=update + +// 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 Firmware 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.11.0/pkg/reconcile +func (r *FirmwareReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + //commonReconciler object creation + transactionId := uuid.New() + ctx = l.CreateContextForLogging(ctx, transactionId.String(), constants.BmcOperator, constants.ODIMObjectOperationActionID, constants.ODIMObjectOperationActionName, podName) + commonRec := utils.GetCommonReconciler(r.Client, r.Scheme) + firmObj := &infraiov1.Firmware{} + // Fetch the Odim object + err := r.Get(ctx, req.NamespacedName, firmObj) + if err != nil { + if Error.IsNotFound(err) { + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } + if firmObj.ObjectMeta.Labels == nil || (firmObj.ObjectMeta.Annotations["old_firmware"] != "" && firmObj.ObjectMeta.Annotations["old_firmware"] != firmObj.Spec.Image.ImageLocation) { + l.LogWithFields(ctx).Info("Firmware update starting...") + odimObject := commonRec.GetOdimObject(ctx, constants.MetadataName, "odim", req.Namespace) + firmRestClient, err := restclient.NewRestClient(ctx, odimObject, commonRec.(*utils.CommonReconciler), constants.BMCOPERATOR) + if err != nil { + l.LogWithFields(ctx).Error("Failed to get rest client for ODIM" + err.Error()) + return ctrl.Result{}, err + } + //get firmwareUtil object + firmUtil := GetFirmwareUtils(ctx, firmObj, commonRec, firmRestClient, req.Namespace) + //build body + firmwarePayload, err := firmUtil.getFirmwareBody() + if err != nil { + return ctrl.Result{}, nil + } + ok, isDelete, err := firmUtil.CreateFirmwareInSystem(firmwarePayload) + if !ok { + return ctrl.Result{}, nil + } else if err != nil { + return ctrl.Result{}, err + } else if isDelete { + l.LogWithFields(ctx).Info("Unable to update firmware, try again") + err = r.Delete(ctx, firmObj) + if err != nil { + l.LogWithFields(ctx).Error("error while deleting firmware object" + err.Error()) + return ctrl.Result{}, err + } + return ctrl.Result{}, nil + } + time.Sleep(time.Duration(40) * time.Second) // added sleep so that fimware version is updated in /redfish/v1/Managers/ + firmVersion := firmUtil.GetFirmwareVersion() + if firmVersion == "" { + l.LogWithFields(ctx).Info("Not able to fetch updated firmware version from ODIM,hence skipping updating of firmware and bmc object") + return ctrl.Result{}, nil + } + firmUtil.UpdateFirmwareLabels(firmVersion) + commonRec.GetUpdatedFirmwareObject(ctx, req.NamespacedName, firmObj) // getting updated firmware object after updating labels to avoid updating on old firmware object + firmUtil.UpdateFirmwareStatus("Success", firmVersion, firmObj.Spec.Image.ImageLocation) + bmcObj := commonRec.GetBmcObject(ctx, constants.MetadataName, firmObj.Name, req.Namespace) + newSchema := firmUtil.checkIfBiosSchemaRegistryChanged(bmcObj) + firmUtil.UpdateBmcObjectWithFirmwareVersionAndSchema(bmcObj, firmVersion, newSchema) + l.LogWithFields(ctx).Info("Firmware and Bmc object updation completed!") + return ctrl.Result{}, nil + } + return ctrl.Result{}, nil +} + +func (fu *firmwareUtils) checkIfBiosSchemaRegistryChanged(bmcObj *infraiov1.Bmc) string { + allBiosSchemaRegistryObjects := fu.commonRec.GetAllBiosSchemaRegistryObjects(fu.ctx, fu.namespace) + allBiosSchemaRegistryObjectsNames := getBiosSchemaRegistryObjectNames(allBiosSchemaRegistryObjects) + var biosAttributeID string + var registryResponse map[string]interface{} + systemDetails := fu.commonUtil.GetBmcSystemDetails(fu.ctx, bmcObj) + if biosVersion, ok := systemDetails["BiosVersion"].(string); ok { + biosAttributeID, registryResponse = fu.biosUtil.GetBiosAttributeID(biosVersion, bmcObj) + } + if biosAttributeID != "" { + formattedBiosAttributeID := utils.RemoveSpecialChar(biosAttributeID) + for _, schemaName := range allBiosSchemaRegistryObjectsNames { + if formattedBiosAttributeID != schemaName { + l.LogWithFields(fu.ctx).Info("Bios Schema Registry has changed, pulling the new schema...") + created := fu.commonRec.CheckAndCreateBiosSchemaObject(fu.ctx, registryResponse, bmcObj) + if created { + l.LogWithFields(fu.ctx).Info(fmt.Sprintf("New Bios Schema Registry object %s created", schemaName)) + return biosAttributeID + } + } else { + l.LogWithFields(fu.ctx).Info("Bios Schema Registry has not changed") + } + } + } else { + l.LogWithFields(fu.ctx).Info("Not able to find the Bios Version") + } + return "" +} + +func getBiosSchemaRegistryObjectNames(allBiosSchemaRegistryObjects *[]infraiov1.BiosSchemaRegistry) []string { + allBiosRegistryFormattedNames := []string{} + for _, biosRegistry := range *allBiosSchemaRegistryObjects { + allBiosRegistryFormattedNames = append(allBiosRegistryFormattedNames, biosRegistry.ObjectMeta.Name) + } + return allBiosRegistryFormattedNames +} + +func (fu *firmwareUtils) CreateFirmwareInSystem(firmwarePayload []byte) (bool, bool, error) { + firmwareResp, err := fu.firmRestClient.Post("/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate", "Posting firmware update..", firmwarePayload) + if err != nil { + l.LogWithFields(fu.ctx).Error("Error while firmware update" + err.Error()) + return false, false, err + } + if firmwareResp.StatusCode == http.StatusAccepted { + done, _ := fu.commonUtil.MoniteringTaskmon(firmwareResp.Header, fu.ctx, common.FIRMWARE, fu.firmObj.ObjectMeta.Name) + if done { + time.Sleep(time.Duration(40) * time.Second) + bmcObj := fu.commonRec.GetBmcObject(fu.ctx, constants.MetadataName, fu.firmObj.Name, fu.firmObj.Namespace) + if ok := common.MapOfFirmware[bmcObj.GetName()]; ok { + delete(common.MapOfFirmware, bmcObj.GetName()) + } + return true, false, nil + } + } + return false, true, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *FirmwareReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&infraiov1.Firmware{}). + WithEventFilter(utils.IgnoreStatusUpdate()). + Complete(r) +} + +// UpdateBmcObjectWithFirmwareVersion updates the bmc object with latest firmware version +func (fu *firmwareUtils) UpdateBmcObjectWithFirmwareVersionAndSchema(bmcObj *infraiov1.Bmc, firmVersion, biosSchema string) { + bmcObj.Status.FirmwareVersion = firmVersion + if biosSchema != "" { + bmcObj.Status.BiosAttributeRegistry = biosSchema + } + err := fu.commonRec.GetCommonReconcilerClient().Status().Update(fu.ctx, bmcObj) + if err != nil { + l.LogWithFields(fu.ctx).Error("Error: Updating firmware in bmc status " + err.Error()) + } + time.Sleep(time.Duration(40) * time.Second) // NOTE: Do not delete this, this helps in proper update of the object + firmwareVersion := formatForLabels(firmVersion) + bmcObj.ObjectMeta.Labels["firmwareVersion"] = firmwareVersion + err = fu.commonRec.GetCommonReconcilerClient().Update(fu.ctx, bmcObj) + if err != nil { + l.LogWithFields(fu.ctx).Error("Error: Updating firmware bmc labels " + err.Error()) + } + time.Sleep(time.Duration(40) * time.Second) // NOTE: Do not delete this, this helps in proper update of the object +} + +// UpdateFirmwareLabels updates the labels of firmware object +func (fu *firmwareUtils) UpdateFirmwareLabels(firmVersion string) { + fu.firmObj.ObjectMeta.Labels = map[string]string{} + firmwareVersion := formatForLabels(firmVersion) + fu.firmObj.ObjectMeta.Labels["firmwareVersion"] = firmwareVersion + fu.firmObj.ObjectMeta.Annotations["old_firmware"] = fu.firmObj.Spec.Image.ImageLocation + err := fu.commonRec.GetCommonReconcilerClient().Update(fu.ctx, fu.firmObj) + if err != nil { + l.LogWithFields(fu.ctx).Error("Error: Updating firmware operation " + err.Error()) + } + time.Sleep(time.Duration(40) * time.Second) // NOTE: Do not delete this, this helps in proper update of the object +} + +// formatForLabels will remove all the spaces and replace it with '-' since labels do not allow spaces +func formatForLabels(firmVersion string) string { + firmStrArr := strings.Split(firmVersion, " ") + firmwareVersion := strings.Join(firmStrArr, "-") + return firmwareVersion +} + +// UpdateFirmwareStatus updates the firmware status +func (fu *firmwareUtils) UpdateFirmwareStatus(status, firmVersion, imageLocation string) { + fu.firmObj.Status.Status = status + if firmVersion != "" { + fu.firmObj.Status.FirmwareVersion = firmVersion + fu.firmObj.Status.ImagePath = imageLocation + } + err := fu.commonRec.GetCommonReconcilerClient().Status().Update(fu.ctx, fu.firmObj) + if err != nil { + l.LogWithFields(fu.ctx).Error("Error: Updating firmware operation " + err.Error()) + } + time.Sleep(time.Duration(40) * time.Second) // NOTE: Do not delete this, this helps in proper update of the object +} + +func (fu *firmwareUtils) GetFirmwareVersion() string { + systemID := fu.GetSystemID() + managerResp, _, err := fu.firmRestClient.Get(fmt.Sprintf("/redfish/v1/Managers/%s", systemID), "Getting firmware version details..") + if err != nil { + l.LogWithFields(fu.ctx).Error("Error: Fetching firmware version details " + err.Error()) + return "" + } + inventories := fu.GetFirmwareInventoryDetails(systemID) + fu.firmObj.ObjectMeta.Annotations["odata.id"] = strings.Join(inventories, ",") + err = fu.commonRec.GetCommonReconcilerClient().Update(fu.ctx, fu.firmObj) + if err != nil { + l.LogWithFields(fu.ctx).Error(fmt.Sprintf("Error: Updating firmware object annotations: %s of bmc", err.Error())) + } + if _, ok := managerResp["FirmwareVersion"].(string); ok { + return managerResp["FirmwareVersion"].(string) + } else { + return "" + } +} + +// getFirmwareBody prepares the payload for firmware operation +func (fu *firmwareUtils) getFirmwareBody() ([]byte, error) { + var body interface{} + var systemsURI string + if systemID := fu.GetSystemID(); systemID != "" { + systemsURI = fmt.Sprintf("/redfish/v1/Systems/%s", systemID) + } else { + l.LogWithFields(fu.ctx).Info("BMC object doesn't exist for firmware update, Please check if it is added.") + fu.commonRec.GetCommonReconcilerClient().Delete(fu.ctx, fu.firmObj) + return nil, errors.New("bmc object doesnt exist") + } + if fu.firmObj.Spec.Image.Auth.Username == "" || fu.firmObj.Spec.Image.Auth.Password == "" && fu.firmObj.Spec.TransferProtocol == "" { + body = FirmPayloadWithoutAuth{Image: fu.firmObj.Spec.Image.ImageLocation, Targets: []string{systemsURI}} + } else if fu.firmObj.Spec.TransferProtocol == "" { + body = firmPayload{Image: fu.firmObj.Spec.Image.ImageLocation, Targets: []string{systemsURI}, Username: fu.firmObj.Spec.Image.Auth.Username, Password: fu.firmObj.Spec.Image.Auth.Password} + } else { + body = firmPayloadWithTransferProtocol{Image: fu.firmObj.Spec.Image.ImageLocation, Targets: []string{systemsURI}, Username: fu.firmObj.Spec.Image.Auth.Username, Password: fu.firmObj.Spec.Image.Auth.Password, TransferProtocol: fu.firmObj.Spec.TransferProtocol} + } + marshalInput, err := json.Marshal(body) + if err != nil { + l.LogWithFields(fu.ctx).Error("Failed to marshal firmware input" + err.Error()) + return nil, err + } + return marshalInput, nil +} + +// GetSystemID will get the systemID of the bmc whose firmware needs to be updated +func (fu *firmwareUtils) GetSystemID() string { + bmcObj := fu.commonRec.GetBmcObject(fu.ctx, constants.MetadataName, fu.firmObj.Name, fu.namespace) + if bmcObj != nil { + return bmcObj.Status.BmcSystemID + } + return "" +} + +func (fu *firmwareUtils) GetFirmwareInventoryDetails(systemID string) []string { + systemUUID := strings.Split(systemID, ".") + inventoryList := []string{} + firmwareInventoryResp, _, err := fu.firmRestClient.Get("/redfish/v1/UpdateService/FirmwareInventory", "Getting firmware inventory details..") + if err != nil { + l.LogWithFields(fu.ctx).Error("Error: Fetching firmware inventory details " + err.Error()) + return []string{} + } + + if inventories, ok := firmwareInventoryResp["Members"].([]interface{}); ok { + for _, member := range inventories { + odataid := member.(map[string]interface{})["@odata.id"].(string) + if strings.Contains(odataid, systemUUID[0]) { + inventoryList = append(inventoryList, odataid) + } + } + } + return inventoryList +} + +func GetFirmwareUtils(ctx context.Context, firmObj *infraiov1.Firmware, commonRec utils.ReconcilerInterface, firmRestClient restclient.RestClientInterface, ns string) firmwareInterface { + return &firmwareUtils{ + ctx: ctx, + firmObj: firmObj, + commonRec: commonRec, + biosUtil: bios.GetBiosUtils(ctx, nil, commonRec, firmRestClient, ns), + firmRestClient: firmRestClient, + commonUtil: common.GetCommonUtils(firmRestClient), + namespace: ns, + } +} diff --git a/controllers/firmware/firmware_controller_test.go b/controllers/firmware/firmware_controller_test.go new file mode 100644 index 0000000..3954cc7 --- /dev/null +++ b/controllers/firmware/firmware_controller_test.go @@ -0,0 +1,187 @@ +//(C) Copyright [2023] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License.. + +// Package controllers ... + +package controllers + +import ( + "context" + "encoding/json" + "reflect" + "testing" + + infraiov1 "github.com/ODIM-Project/BMCOperator/api/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var mockCR1 = mockCommonRec{variable: "sendBmcObj"} +var mockCR2 = mockCommonRec{variable: "doNotSendBmcObj"} +var mockRC = &mockRestClient{} + +func Test_firmwareUtils_getSystemID(t *testing.T) { + tests := []struct { + name string + fu *firmwareUtils + want string + }{ + { + name: "", + fu: &firmwareUtils{context.TODO(), &infraiov1.Firmware{}, &mockCR1, mockRC, nil, nil, "bmc-op"}, + want: "fakeSystemID.1", + }, + { + name: "", + fu: &firmwareUtils{context.TODO(), &infraiov1.Firmware{}, &mockCR2, mockRC, nil, nil, "bmc-op"}, + want: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.fu.GetSystemID(); got != tt.want { + t.Errorf("firmwareUtils.GetSystemID() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_firmwareUtils_getFirmwareBody(t *testing.T) { + + var body = FirmPayloadWithoutAuth{Image: "fakeimageloc/", Targets: []string{"/redfish/v1/Systems/fakeSystemID.1"}} + var out, _ = json.Marshal(body) + tests := []struct { + name string + fu *firmwareUtils + want []byte + wantErr bool + }{ + { + name: "bmcObj is none", + fu: &firmwareUtils{context.TODO(), &infraiov1.Firmware{}, &mockCR2, mockRC, nil, nil, "bmc-op"}, + want: nil, + wantErr: true, + }, + { + name: "success case", + fu: &firmwareUtils{context.TODO(), &infraiov1.Firmware{Spec: infraiov1.FirmwareSpec{Image: infraiov1.ImageDetails{ImageLocation: "fakeimageloc/"}}}, &mockCR1, mockRC, nil, nil, "bmc-op"}, + want: out, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.fu.getFirmwareBody() + if (err != nil) != tt.wantErr { + t.Errorf("firmwareUtils.getFirmwareBody() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("firmwareUtils.getFirmwareBody() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_firmwareUtils_updateFirmwareStatus(t *testing.T) { + // comment the sleep in function and then run testcase + type args struct { + status string + firmVersion string + imageLocation string + } + tests := []struct { + name string + fu *firmwareUtils + args args + }{ + { + name: "success case", + fu: &firmwareUtils{context.TODO(), &infraiov1.Firmware{}, &mockCR1, mockRC, nil, nil, "bmc-op"}, + args: args{status: "Success", firmVersion: "2.60"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.fu.UpdateFirmwareStatus(tt.args.status, tt.args.firmVersion, tt.args.imageLocation) + }) + } +} + +func Test_firmwareUtils_GetFirmwareVersion(t *testing.T) { + tests := []struct { + name string + fu *firmwareUtils + want string + }{ + { + name: "success case", + fu: &firmwareUtils{context.TODO(), &infraiov1.Firmware{ObjectMeta: v1.ObjectMeta{Annotations: map[string]string{}}}, &mockCR1, &mockRestClient{url: "/redfish/v1/Managers/fakeSystemID.1"}, nil, nil, "bmc-op"}, + want: "2.60", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.fu.GetFirmwareVersion(); got != tt.want { + t.Errorf("firmwareUtils.GetFirmwareVersion() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_formatForLabels(t *testing.T) { + type args struct { + firmVersion string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "success case", + args: args{"ILO v1 2.60"}, + want: "ILO-v1-2.60", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := formatForLabels(tt.args.firmVersion); got != tt.want { + t.Errorf("formatForLabels() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_firmwareUtils_UpdateFirmwareLabels(t *testing.T) { + // comment the sleep in function and then run testcase + type args struct { + firmVersion string + } + tests := []struct { + name string + fu *firmwareUtils + args args + }{ + { + name: "Success case", + fu: &firmwareUtils{context.TODO(), &infraiov1.Firmware{ObjectMeta: v1.ObjectMeta{Annotations: map[string]string{}}}, &mockCR1, mockRC, nil, nil, "bmc-op"}, + args: args{firmVersion: "ILO v1 2.60"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.fu.UpdateFirmwareLabels(tt.args.firmVersion) + }) + } +} diff --git a/controllers/firmware/mockUtils_test.go b/controllers/firmware/mockUtils_test.go new file mode 100644 index 0000000..a134e16 --- /dev/null +++ b/controllers/firmware/mockUtils_test.go @@ -0,0 +1,240 @@ +//(C) Copyright [2023] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +package controllers + +import ( + "context" + "net/http" + + infraiov1 "github.com/ODIM-Project/BMCOperator/api/v1" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type mockCommonRec struct { + variable string +} +type mockRestClient struct{ url string } + +// Mock Common Rec Methods +// get objects +func (m *mockCommonRec) GetBmcObject(ctx context.Context, field, value, ns string) *infraiov1.Bmc { + if m.variable == "sendBmcObj" { + return &infraiov1.Bmc{Status: infraiov1.BmcStatus{BmcSystemID: "fakeSystemID.1"}} + } else if m.variable == "doNotSendBmcObj" { + return nil + } + return nil +} + +func (m *mockCommonRec) GetAllBmcObject(ctx context.Context, ns string) *[]infraiov1.Bmc { + return nil +} + +func (m *mockCommonRec) GetBiosSchemaObject(ctx context.Context, field, value, ns string) *infraiov1.BiosSchemaRegistry { + return nil +} +func (m *mockCommonRec) GetBiosObject(ctx context.Context, field, value, ns string) *infraiov1.BiosSetting { + return nil +} + +func (m *mockCommonRec) GetBootObject(ctx context.Context, field, value, ns string) *infraiov1.BootOrderSetting { + return nil +} + +func (m *mockCommonRec) GetOdimObject(ctx context.Context, field, value, ns string) *infraiov1.Odim { + return nil +} + +func (m *mockCommonRec) GetVolumeObject(ctx context.Context, bmcIP, ns string) *infraiov1.Volume { + return nil +} + +func (m *mockCommonRec) GetFirmwareObject(ctx context.Context, field, value, ns string) *infraiov1.Firmware { + return nil +} + +// create objects +func (m *mockCommonRec) CreateBiosSettingObject(ctx context.Context, biosAttributes map[string]string, bmcObj *infraiov1.Bmc) bool { + return true +} + +func (m *mockCommonRec) CreateBootOrderSettingObject(ctx context.Context, bootAttributes *infraiov1.BootSetting, bmcObj *infraiov1.Bmc) bool { + return true +} + +func (m *mockCommonRec) CheckAndCreateBiosSchemaObject(ctx context.Context, attributeResp map[string]interface{}, bmcObj *infraiov1.Bmc) bool { + return true +} + +func (m *mockCommonRec) CreateEventSubscriptionObject(ctx context.Context, subscriptionDetails map[string]interface{}, ns string, originResources []string) bool { + return true +} + +func (m *mockCommonRec) GetAllEventSubscriptionObjects(ctx context.Context, ns string) *[]infraiov1.Eventsubscription { + return nil +} + +func (m *mockCommonRec) GetEventsubscriptionObject(ctx context.Context, field, value, ns string) *infraiov1.Eventsubscription { + return nil +} + +func (m *mockCommonRec) GetEventMessageRegistryObject(ctx context.Context, registryPrefix, registryVersion, ns string) *infraiov1.EventsMessageRegistry { + return nil +} + +func (m *mockCommonRec) CheckAndCreateEventMessageObject(ctx context.Context, messageRegistryResp map[string]interface{}, bmcObj *infraiov1.Bmc) bool { + return true +} + +// update objects +func (m *mockCommonRec) UpdateBmcStatus(ctx context.Context, bmcObj *infraiov1.Bmc) { +} + +func (m *mockCommonRec) UpdateOdimStatus(ctx context.Context, status string, odimObj *infraiov1.Odim) { +} + +func (m *mockCommonRec) UpdateBmcObjectOnReset(ctx context.Context, bmcObject *infraiov1.Bmc, status string) { +} + +func (m *mockCommonRec) UpdateVolumeStatus(ctx context.Context, volObject *infraiov1.Volume, volumeID, volumeName, capBytes, durableName, durableNameFormat string) { +} + +// get updated objects +func (m *mockCommonRec) GetUpdatedBmcObject(ctx context.Context, ns types.NamespacedName, bmcObj *infraiov1.Bmc) { +} + +func (m *mockCommonRec) GetUpdatedOdimObject(ctx context.Context, ns types.NamespacedName, odimObj *infraiov1.Odim) { +} + +func (m *mockCommonRec) GetUpdatedVolumeObject(ctx context.Context, ns types.NamespacedName, volObj *infraiov1.Volume) { +} + +func (m *mockCommonRec) GetUpdatedFirmwareObject(ctx context.Context, ns types.NamespacedName, firmObj *infraiov1.Firmware) { +} + +func (m *mockCommonRec) UpdateBiosSettingObject(ctx context.Context, biosAttributes map[string]string, biosObj *infraiov1.BiosSetting) bool { + return true +} + +func (m *mockCommonRec) GetAllVolumeObjectIds(ctx context.Context, bmc *infraiov1.Bmc, ns string) map[string][]string { + return nil +} + +func (m *mockCommonRec) GetAllBiosSchemaRegistryObjects(ctx context.Context, ns string) *[]infraiov1.BiosSchemaRegistry { + return nil +} + +func (m *mockCommonRec) DeleteVolumeObject(ctx context.Context, volObj *infraiov1.Volume) {} + +func (m *mockCommonRec) DeleteBmcObject(ctx context.Context, bmcObj *infraiov1.Bmc) {} + +func (m *mockCommonRec) UpdateEventsubscriptionStatus(ctx context.Context, eventsubObj *infraiov1.Eventsubscription, eventsubscriptionDetails map[string]interface{}, originResouces []string) { +} + +func (m *mockCommonRec) GetUpdatedEventsubscriptionObjects(ctx context.Context, ns types.NamespacedName, eventSubObj *infraiov1.Eventsubscription) { +} + +func (m *mockCommonRec) GetAllVolumeObjects(ctx context.Context, bmcIP, ns string) []*infraiov1.Volume { + return nil +} + +func (m *mockCommonRec) GetVolumeObjectByVolumeID(ctx context.Context, volumeName, ns string) *infraiov1.Volume { + return nil +} + +func (m *mockCommonRec) GetRootCAFromSecret(ctx context.Context) []byte { + return []byte{} +} + +// common reconciler funcs +func (m *mockCommonRec) GetCommonReconcilerClient() client.Client { + return mockCommonRec{} +} + +func (m *mockCommonRec) GetCommonReconcilerScheme() *runtime.Scheme { + return nil +} + +// Client mocks +func (m mockCommonRec) Get(ctx context.Context, key client.ObjectKey, obj client.Object) error { + return nil +} + +func (m mockCommonRec) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { + return nil +} + +func (m mockCommonRec) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { + return nil +} + +func (m mockCommonRec) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error { + return nil +} + +func (m mockCommonRec) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil +} + +func (m mockCommonRec) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error { + return nil +} + +func (m mockCommonRec) DeleteAllOf(ctx context.Context, obj client.Object, opts ...client.DeleteAllOfOption) error { + return nil +} + +func (m mockCommonRec) Status() client.StatusWriter { + return mockCommonRec{} +} + +func (m mockCommonRec) RESTMapper() meta.RESTMapper { + return nil +} + +func (m mockCommonRec) Scheme() *runtime.Scheme { + return nil +} + +// mock Rest calls + +func (m *mockRestClient) Post(string, string, interface{}) (*http.Response, error) { + return nil, nil +} +func (m *mockRestClient) Get(url string, reason string) (map[string]interface{}, int, error) { + // Test_firmwareUtils_getFirmwareVersion : success case + if m.url == "/redfish/v1/Managers/fakeSystemID.1" && url == "/redfish/v1/UpdateService/FirmwareInventory" { + return map[string]interface{}{"Members": []interface{}{map[string]interface{}{"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/fakeSystemID.5"}}}, 200, nil + } + if m.url == "/redfish/v1/Managers/fakeSystemID.1" { + return map[string]interface{}{"FirmwareVersion": "2.60"}, 200, nil + } + return nil, 0, nil +} +func (m *mockRestClient) Patch(string, string, interface{}) (*http.Response, error) { + return nil, nil +} +func (m *mockRestClient) Delete(string, string) (*http.Response, error) { + return nil, nil +} +func (m *mockRestClient) Put(string, string, interface{}) (*http.Response, error) { + return nil, nil +} +func (m *mockRestClient) RecallWithNewToken(url, reason, method string, body interface{}) (*http.Response, error) { + return nil, nil +} diff --git a/controllers/odim/odimUtils.go b/controllers/odim/odimUtils.go new file mode 100644 index 0000000..1563198 --- /dev/null +++ b/controllers/odim/odimUtils.go @@ -0,0 +1,91 @@ +//(C) Copyright [2023] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +package controllers + +import ( + "context" + "encoding/json" + + infraiov1 "github.com/ODIM-Project/BMCOperator/api/v1" + config "github.com/ODIM-Project/BMCOperator/controllers/config" + restclient "github.com/ODIM-Project/BMCOperator/controllers/restclient" + utils "github.com/ODIM-Project/BMCOperator/controllers/utils" +) + +const ( + // DefaultEventSubscriptionName is the event subscription name for bmc operator event listener + DefaultEventSubscriptionName = "BmcOperatorSubscription" + // DefaultEventSubscriptionContext is the context of event subscription for bmc operator event listener + DefaultEventSubscriptionContext = "Default Bmc-Operator EventSubscription" +) + +type odimInterface interface { + updateConnectionMethodVariantsStatus(connMethVarInfo map[string]string) + checkOdimConnection() bool + updateConnectionMethodVariants() +} + +type odimUtils struct { + ctx context.Context + odimRestClient restclient.RestClientInterface + commonRec utils.ReconcilerInterface + odimObj *infraiov1.Odim +} + +// EventSubscriptionPayload is the payload of event subscription for server operator event listener +type EventSubscriptionPayload struct { + Name string `json:"Name"` + Destination string `json:"Destination"` + EventTypes []string `json:"EventTypes"` + MessageIds []string `json:"MessageIds"` + ResourceTypes []string `json:"ResourceTypes"` + Context string `json:"Context"` + Protocol string `json:"Protocol"` + SubscriptionType string `json:"SubscriptionType"` + EventFormatType string `json:"EventFormatType"` + SubordinateResources bool `json:"SubordinateResources"` + DeliveryRetryPolicy string `json:"DeliveryRetryPolicy"` + OriginResources []Link `json:"OriginResources"` +} + +// Link is struct defined for OriginResources in EventSubscriptionPayload +type Link struct { + Oid string `json:"@odata.id"` +} + +// GetEventSubscriptionPayload return the payload of event subscription for bmc operator event listener +func GetEventSubscriptionPayload(destination string) []byte { + payload := EventSubscriptionPayload{ + Name: DefaultEventSubscriptionName, + Destination: destination + "/OdimEvents", + EventTypes: config.Data.OperatorEventSubscriptionEventTypes, + MessageIds: config.Data.OperatorEventSubscriptionMeesageIds, + ResourceTypes: config.Data.OperatorEventSubscriptionResourceTypes, + Context: DefaultEventSubscriptionContext, + Protocol: "Redfish", + SubscriptionType: "RedfishEvent", + EventFormatType: "Event", + SubordinateResources: true, + DeliveryRetryPolicy: "RetryForever", + OriginResources: []Link{ + { + Oid: "/redfish/v1/Systems", + }, + }, + } + + body, _ := json.Marshal(payload) + return body +} diff --git a/controllers/odim/odim_controller.go b/controllers/odim/odim_controller.go new file mode 100644 index 0000000..0c32bd7 --- /dev/null +++ b/controllers/odim/odim_controller.go @@ -0,0 +1,271 @@ +//(C) Copyright [2022] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +// Package controllers ... +package controllers + +import ( + "context" + "fmt" + "net/http" + "net/url" + "os" + "path" + + Error "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + infraiov1 "github.com/ODIM-Project/BMCOperator/api/v1" + "github.com/ODIM-Project/BMCOperator/config/constants" + common "github.com/ODIM-Project/BMCOperator/controllers/common" + eventsClient "github.com/ODIM-Project/BMCOperator/controllers/eventsClient" + restclient "github.com/ODIM-Project/BMCOperator/controllers/restclient" + utils "github.com/ODIM-Project/BMCOperator/controllers/utils" + l "github.com/ODIM-Project/BMCOperator/logs" + "github.com/google/uuid" +) + +// OdimReconciler reconciles a Odim object +type OdimReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +const odimFinalizer = "infra.io.odim/finalizer" + +var podName = os.Getenv("POD_NAME") + +//+kubebuilder:rbac:groups=infra.io.odimra,resources=odims,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=infra.io.odimra,resources=odims/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=infra.io.odimra,resources=odims/finalizers,verbs=update +//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch +//+kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;patch;update;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 Odim 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.11.0/pkg/reconcile +func (r *OdimReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + //commonReconciler object creation + transactionId := uuid.New() + ctx = l.CreateContextForLogging(ctx, transactionId.String(), constants.BmcOperator, constants.ODIMObjectOperationActionID, constants.ODIMObjectOperationActionName, podName) + commonRec := utils.GetCommonReconciler(r.Client, r.Scheme) + odimObj := &infraiov1.Odim{} + // Fetch the Odim object + err := r.Get(ctx, req.NamespacedName, odimObj) + if err != nil { + if Error.IsNotFound(err) { + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } + odimRestClient, err := restclient.NewRestClient(ctx, odimObj, commonRec.(*utils.CommonReconciler), constants.BMCOPERATOR) + if err != nil { + l.LogWithFields(ctx).Error("Failed to get rest client for ODIM" + err.Error()) + return ctrl.Result{}, err + } + //get odimUtil object + odimUtil := getOdimUtils(ctx, odimRestClient, commonRec, odimObj) + l.LogWithFields(ctx).Info("Checking odim presence") + //Check redfish uri to see if connected + //Checking for deletion + isOdimMarkedToBeDeleted := odimObj.GetDeletionTimestamp() != nil + if isOdimMarkedToBeDeleted { + if controllerutil.ContainsFinalizer(odimObj, odimFinalizer) { + isEventSubscriptionRemoved := removeEventsSubscription(ctx, odimRestClient) + if isEventSubscriptionRemoved { + controllerutil.RemoveFinalizer(odimObj, odimFinalizer) + err = r.Update(ctx, odimObj) + if err != nil { + return ctrl.Result{}, err + } + } else { + l.LogWithFields(ctx).Error("cannot remove subscription from odim", err) + return ctrl.Result{}, err + } + } + return ctrl.Result{}, nil + } + // Add finalizer for this CR + if !controllerutil.ContainsFinalizer(odimObj, odimFinalizer) { + controllerutil.AddFinalizer(odimObj, odimFinalizer) + err := r.Update(ctx, odimObj) + if err != nil { + l.LogWithFields(ctx).Error("Error while updating odim object after adding finalizer", err) + return ctrl.Result{}, err + } + } + connected := odimUtil.checkOdimConnection() + if connected { + l.LogWithFields(ctx).Info("Odim successfully registered!, updating connection methods") + odimUtil.updateConnectionMethodVariants() + l.LogWithFields(ctx).Info("Successfully added all connection methods!") + } else { + return ctrl.Result{}, nil + } + + commonRec.GetUpdatedOdimObject(ctx, req.NamespacedName, odimObj) + // starting event listener + go eventsClient.Start(r.Client, r.Scheme) + // Creating default event subscription so that odim will send the events to the server operator listener + err = CreateEventSubscription(ctx, odimRestClient, odimObj.Spec.EventListenerHost) + if err != nil { + l.LogWithFields(ctx).Error("error while creating the event subscription ", err.Error()) + return ctrl.Result{}, nil + } + l.LogWithFields(ctx).Debugf("event subscription is created with destination %s", odimObj.Spec.EventListenerHost) + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *OdimReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&infraiov1.Odim{}). + WithEventFilter(utils.IgnoreStatusUpdate()). + Complete(r) +} + +// updateConnectionMethodVariantsStatus updates the connection method variants in status of the odim object +func (ou *odimUtils) updateConnectionMethodVariantsStatus(connMethVarInfo map[string]string) { + ou.odimObj.Status.ConnMethVariants = connMethVarInfo //update printer column + err := ou.commonRec.GetCommonReconcilerClient().Status().Update(ou.ctx, ou.odimObj) + if err != nil { + l.LogWithFields(ou.ctx).Error("Error: Updating plugin status of ODIM" + err.Error()) + } +} + +// checkOdimConnection checks if we are able to connect odim +func (ou *odimUtils) checkOdimConnection() bool { + resp, _, err := ou.odimRestClient.Get("/redfish/v1/", "Fetching response on service root..") + if err != nil { + l.LogWithFields(ou.ctx).Error("Not able to connect to ODIM", err.Error()) + ou.commonRec.UpdateOdimStatus(ou.ctx, "Not Connected", ou.odimObj) + return false + } + if resp["@odata.id"] != nil { + ou.commonRec.UpdateOdimStatus(ou.ctx, "Connected", ou.odimObj) + return true + } else { + ou.commonRec.UpdateOdimStatus(ou.ctx, "Not Connected", ou.odimObj) + return false + } +} + +// updateConnectionMethodVariants updates the connection method variants in the object +func (ou *odimUtils) updateConnectionMethodVariants() { + connMethVarInfo := make(map[string]string) + ConnMeths, _, err := ou.odimRestClient.Get("/redfish/v1/AggregationService/ConnectionMethods", "Fetching connection methods for ODIM..") + if err != nil { + l.LogWithFields(ou.ctx).Debug("Unable to get connection methods " + err.Error()) + return + } + if ConnMeths["Members"] != nil || len(ConnMeths["Members"].([]interface{})) != 0 { + for _, memConnMethod := range ConnMeths["Members"].([]interface{}) { + connMethInfo, _, err := ou.odimRestClient.Get(memConnMethod.(map[string]interface{})["@odata.id"].(string), "Fetching connection method details..") + if err != nil { + l.LogWithFields(ou.ctx).Error("Unable to get connection method info", err.Error()) + return + } + connMethVarInfo[connMethInfo["ConnectionMethodVariant"].(string)] = connMethInfo["@odata.id"].(string) + } + } else { + l.LogWithFields(ou.ctx).Debug("No plugins present on ODIM") + return + } + ou.updateConnectionMethodVariantsStatus(connMethVarInfo) +} + +// getOdimUtils will return odimUtil struct with odim details +func getOdimUtils(ctx context.Context, restClient restclient.RestClientInterface, commonRec utils.ReconcilerInterface, odimObj *infraiov1.Odim) odimInterface { + return &odimUtils{ + ctx: ctx, + odimRestClient: restClient, + commonRec: commonRec, + odimObj: odimObj, + } +} + +// CreateEventSubscription creates event subscription for server operator event listener +func CreateEventSubscription(ctx context.Context, restClient restclient.RestClientInterface, destination string) error { + uri := "/redfish/v1/EventService/Subscriptions" + body := GetEventSubscriptionPayload(destination) + resp, err := restClient.Post(uri, "Creating default event subscription", body) + if err != nil { + return fmt.Errorf("error while adding default subscription for server operator: %s", err.Error()) + } + if resp.StatusCode == http.StatusAccepted { + commonUtil := common.GetCommonUtils(restClient) + ok, taskResp := commonUtil.MoniteringTaskmon(resp.Header, ctx, common.EVENTSUBSCRIPTION, destination) + if !ok { + return fmt.Errorf("failed to create event subscription: task response %v", taskResp) + } + } + return nil +} + +func removeEventsSubscription(ctx context.Context, restClient restclient.RestClientInterface) bool { + subscriptionsIds := []string{} + subscriptionsCollectionUri := "/redfish/v1/EventService/Subscriptions" + subscriptionsUrl := "/redfish/v1/EventService/Subscriptions/%s" + resp, statusCode, err := restClient.Get(subscriptionsCollectionUri, "Getting all event subscription collections") + if err != nil { + l.LogWithFields(ctx).Errorf("error while getting event subscription collections for bmc operator: %s", err.Error()) + return false + } + if statusCode == http.StatusOK { + for _, mem := range resp["Members"].([]interface{}) { + members := mem.(map[string]interface{}) + member, err := url.Parse(members["@odata.id"].(string)) + if err != nil { + l.LogWithFields(ctx).Errorf("error while parsing the url %v", err) + return false + } + subscriptionsIds = append(subscriptionsIds, path.Base(member.String())) + } + } + // Checking for the operator subscription + for _, subs := range subscriptionsIds { + subUri := fmt.Sprintf(subscriptionsUrl, subs) + resp, statusCode, err := restClient.Get(subUri, "Creating default event subscription") + if err != nil { + l.LogWithFields(ctx).Errorf("error while getting subscription for bmc operator: %s", err.Error()) + return false + } + if statusCode == http.StatusOK { + if resp["Name"] == "BmcOperatorSubscription" { + resp, err := restClient.Delete(subUri, "Deleting the operator events subscription") + if err != nil { + l.LogWithFields(ctx).Errorf("error while deleting subscription for bmc operator: %s", err.Error()) + return false + } + if resp.StatusCode == http.StatusOK { + l.LogWithFields(ctx).Infof("deleted events subscription with ID %s", subs) + return true + } + } + } + } + l.LogWithFields(ctx).Error("Received unexpected status code while deleting the events subscription") + return false +} diff --git a/controllers/pollData/accomodateDetails.go b/controllers/pollData/accomodateDetails.go new file mode 100644 index 0000000..188832b --- /dev/null +++ b/controllers/pollData/accomodateDetails.go @@ -0,0 +1,205 @@ +//(C) Copyright [2023] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +// Package controllers ... +package controllers + +import ( + "context" + "encoding/json" + "fmt" + "path" + "reflect" + "strings" + + infraiov1 "github.com/ODIM-Project/BMCOperator/api/v1" + "github.com/ODIM-Project/BMCOperator/config/constants" + + controllers "github.com/ODIM-Project/BMCOperator/controllers/bmc" + boot "github.com/ODIM-Project/BMCOperator/controllers/boot" + + common "github.com/ODIM-Project/BMCOperator/controllers/common" + firmware "github.com/ODIM-Project/BMCOperator/controllers/firmware" + restclient "github.com/ODIM-Project/BMCOperator/controllers/restclient" + l "github.com/ODIM-Project/BMCOperator/logs" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// AccommodatePollingDetails will get bmc details and update information accordingly +func (r PollingReconciler) AccommodatePollingDetails(ctx context.Context, restClient restclient.RestClientInterface, mapOfBmc map[string]common.BmcState) { + for urlpath, bmcState := range mapOfBmc { + r.AccommodateBMCInfo(ctx, restClient, urlpath, bmcState) + } +} + +// AccommodateBMCInfo will accommodate information for a single BMC +func (r PollingReconciler) AccommodateBMCInfo(ctx context.Context, restClient restclient.RestClientInterface, + urlpath string, bmcState common.BmcState) { + var objCreated bool + // get the system ID from URL path and replace it with aggregation URL to get hostName + systemID := path.Base(urlpath) + aggregationURL := strings.Replace(urlpath, "/Systems/", "/AggregationService/AggregationSources/", 1) + + if bmcState.IsAdded || bmcState.IsDeleted { + r.bmcObject = r.commonRec.GetBmcObject(ctx, constants.StatusBmcSystemID, systemID, r.namespace) + if r.bmcObject != nil { + if r.checkIfSystemIsUndergoingReset() { + return + } + + // TODO: move below functions to different file + if bmcState.IsObjCreated { + r.CheckAndAccomodateFirmware(ctx, restClient) + r.CheckAndAccommodateBoot(ctx, restClient) + } + bmcState := common.MapOfBmc[urlpath] + bmcState.IsObjCreated = true + common.MapOfBmc[urlpath] = bmcState + objCreated = true + } else if r.bmcObject == nil && bmcState.IsDeleted { + delete(common.MapOfBmc, urlpath) + } + } + + // Delete bmc object + if bmcState.IsDeleted && objCreated { + l.LogWithFields(ctx).Info("Remove Bmc object: ", r.bmcObject.Name) + err := r.Client.Delete(ctx, r.bmcObject) + if err != nil { + l.LogWithFields(ctx).Info("Object not deleted", err) + return + } + delete(common.MapOfBmc, urlpath) + return + } + + //create BMC object + if !bmcState.IsObjCreated && !objCreated && r.bmcObject == nil && bmcState.IsAdded && !bmcState.IsDeleted { + l.LogWithFields(ctx).Info("Creating Bmc object") + res, _, err := restClient.Get(aggregationURL, "Getting response on Aggregation Sources") + if err != nil { + l.LogWithFields(ctx).Errorf("Failed to get Aggregation Sources details") + return + } + if hostName, ok := res["HostName"].(string); ok { + links := res["Links"].(map[string]interface{}) + connMethodOdata := links["ConnectionMethod"].(map[string]interface{}) + connMethodID := connMethodOdata["@odata.id"].(string) + + connecRes, _, err := restClient.Get(connMethodID, "Getting response on Connection Method") + if err != nil { + l.LogWithFields(ctx).Errorf("Failed to get Connection Method details") + return + } + if connMethod, ok := connecRes["ConnectionMethodVariant"]; ok { + obj := r.createBmcObject(r.Client, "Bmc", hostName, systemID, res["UserName"].(string), connMethod.(string)) + err = r.Client.Create(ctx, obj) + if err != nil { + l.LogWithFields(ctx).Errorf("Failed to create object: %v", err) + return + } + common.MapOfBmc[urlpath] = common.BmcState{IsAdded: false, IsDeleted: false, IsObjCreated: true} + } + } + } +} + +// createBmcObject used for creating unstructured bmc object +func (r PollingReconciler) createBmcObject(c client.Client, kind, name, systemID, username, ConnMethVariant string) *infraiov1.Bmc { + bmcObj := &infraiov1.Bmc{} + bmcObj.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "infra.io.odimra", + Kind: kind, + Version: "v1", + }) + bmcObj.SetName(name) + if r.odimObj != nil { + bmcObj.SetNamespace(r.odimObj.Namespace) + } + bmcObj.Spec.BmcDetails.ConnMethVariant = ConnMethVariant + bmcObj.Status.BmcSystemID = systemID + bmcObj.Spec.BmcDetails.Address = name + bmcObj.Spec.Credentials.Username = username + bmcObj.Labels = map[string]string{"systemId": systemID, "name": name} + resourceBytes, err := json.Marshal(bmcObj) + if err != nil { + return nil + } + annotations := bmcObj.GetAnnotations() + if annotations == nil { + annotations = make(map[string]string) + } + annotations["kubectl.kubernetes.io/last-applied-configuration"] = string(resourceBytes) + annotations["odata.id"] = "/redfish/v1/Systems/" + systemID + bmcObj.SetAnnotations(annotations) + + return bmcObj +} + +// CheckAndAccomodateFirmware is used to check, update firmware version in bmc & firmware object +func (r PollingReconciler) CheckAndAccomodateFirmware(ctx context.Context, client restclient.RestClientInterface) { + bmcUtil := controllers.GetBmcUtils(ctx, r.bmcObject, r.bmcObject.Namespace, &r.commonRec, &client, common.GetCommonUtils(client), false) + mgrDetails := bmcUtil.GetManagerDetails() + if mgrDetails != nil { + if firmwareVersion, ok := mgrDetails["FirmwareVersion"].(string); ok { + firmwareObj := r.commonRec.GetFirmwareObject(ctx, constants.MetadataName, r.bmcObject.ObjectMeta.Name, r.bmcObject.Namespace) + firmUtil := firmware.GetFirmwareUtils(ctx, firmwareObj, r.commonRec, client, r.bmcObject.Namespace) + if firmwareVersion != r.bmcObject.Status.FirmwareVersion { + // Update firware version with latest value in bmc object and firmware object + if firmwareObj != nil { + firmUtil.UpdateBmcObjectWithFirmwareVersionAndSchema(r.bmcObject, firmwareVersion, "") + firmUtil.UpdateFirmwareLabels(firmwareVersion) + imagePath := getImagePath(firmwareVersion, firmwareObj.Status.ImagePath) + firmUtil.UpdateFirmwareStatus("Success", firmwareVersion, imagePath) + } + } + } + } +} + +// CheckAndAccommodateBoot is used to check, and update the boot object as per the changes made on bmc +func (r PollingReconciler) CheckAndAccommodateBoot(ctx context.Context, client restclient.RestClientInterface) { + bootObj := r.commonRec.GetBootObject(ctx, constants.MetadataName, r.bmcObject.GetName(), r.bmcObject.Namespace) + bootUtil := boot.GetBootUtils(ctx, bootObj, r.commonRec, client, common.GetCommonUtils(client), r.bmcObject.Namespace) + sysDetails := common.GetCommonUtils(client).GetBmcSystemDetails(ctx, r.bmcObject) + bootSetting := bootUtil.GetBootAttributes(sysDetails) + if !reflect.DeepEqual(bootSetting, &bootObj.Status.Boot) { + bootObj.Status.Boot = *bootSetting + err := r.commonRec.GetCommonReconcilerClient().Status().Update(ctx, bootObj) + if err != nil { + l.LogWithFields(ctx).Error(fmt.Sprintf("Error while updating boot order setting object for %s BMC: %s", bootObj.Name, err.Error())) + } + } +} + +// getImagePath is used to get the image path for saving it in object +func getImagePath(firmwareVersion, path string) string { + split := strings.SplitN(firmwareVersion, "v", 2) + lowerCaseVersion := strings.ToLower(split[0]) + lowerCaseVersion = strings.Replace(lowerCaseVersion, " ", "", -1) + version := strings.Replace(split[1], ".", "", -1) + finalpath := strings.Replace(lowerCaseVersion+" "+version, " ", "_", -1) + return replacePath(path, finalpath+".bin") +} + +// replacePath is used to get final path to be saved in object +func replacePath(path string, replacement string) string { + lastSlashIndex := strings.LastIndex(path, "/") + if lastSlashIndex == -1 { + return replacement + } + replacedPath := path[:lastSlashIndex+1] + replacement + return replacedPath +} diff --git a/controllers/pollData/accomodateDetails_test.go b/controllers/pollData/accomodateDetails_test.go new file mode 100644 index 0000000..9485948 --- /dev/null +++ b/controllers/pollData/accomodateDetails_test.go @@ -0,0 +1,43 @@ +//(C) Copyright [2023] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +package controllers + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCreateBmcObject(t *testing.T) { + client := &MockClient{} + reconciler := PollingReconciler{ + Client: client, + Scheme: nil, + odimObj: nil, + bmcObject: nil, + commonRec: nil, + } + bmcObj := reconciler.createBmcObject(client, "Bmc", "10.1.1.1", "1", "admin", "Compute:BasicAuth:ILO_v2.0.0") + assert.Equal(t, "infra.io.odimra/v1, Kind=Bmc", bmcObj.GroupVersionKind().String()) + assert.Equal(t, "10.1.1.1", bmcObj.GetName()) + assert.Equal(t, "", bmcObj.GetNamespace()) + assert.Equal(t, "Compute:BasicAuth:ILO_v2.0.0", bmcObj.Spec.BmcDetails.ConnMethVariant) + assert.Equal(t, "1", bmcObj.Status.BmcSystemID) + assert.Equal(t, "10.1.1.1", bmcObj.Spec.BmcDetails.Address) + assert.Equal(t, "admin", bmcObj.Spec.Credentials.Username) + assert.Equal(t, map[string]string{"systemId": "1", "name": "10.1.1.1"}, bmcObj.Labels) + annotations := bmcObj.GetAnnotations() + assert.NotNil(t, annotations) +} diff --git a/controllers/pollData/getPollingUpdates.go b/controllers/pollData/getPollingUpdates.go new file mode 100644 index 0000000..0104a21 --- /dev/null +++ b/controllers/pollData/getPollingUpdates.go @@ -0,0 +1,173 @@ +//(C) Copyright [2023] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +// Package controllers ... +package controllers + +import ( + "context" + "net/url" + "sync" + "time" + + v1 "github.com/ODIM-Project/BMCOperator/api/v1" + "github.com/ODIM-Project/BMCOperator/config/constants" + bmc "github.com/ODIM-Project/BMCOperator/controllers/bmc" + common "github.com/ODIM-Project/BMCOperator/controllers/common" + config "github.com/ODIM-Project/BMCOperator/controllers/config" + restclient "github.com/ODIM-Project/BMCOperator/controllers/restclient" + utils "github.com/ODIM-Project/BMCOperator/controllers/utils" + volume "github.com/ODIM-Project/BMCOperator/controllers/volume" + l "github.com/ODIM-Project/BMCOperator/logs" + "github.com/google/uuid" + "sigs.k8s.io/controller-runtime/pkg/manager" +) + +// PollDetails is used to get data a some time interval +func PollDetails(mgr manager.Manager) { + ctx := context.Background() + transactionID := uuid.New() + ctx = l.CreateContextForLogging(ctx, transactionID.String(), constants.BmcOperator, constants.PollingActionID, constants.PollingActionName, podName) + r := PollingReconciler{Client: mgr.GetClient(), Scheme: mgr.GetScheme()} //TODO: getPollingUtils + // Set time ticker + interval := time.Duration(config.TickerTime) * time.Hour + if config.TickerTime == 0 { + interval = time.Duration(constants.DefaultTimeTicker) * time.Hour + } + config.Ticker = time.NewTicker(interval) + defer config.Ticker.Stop() + var wg sync.WaitGroup + for range config.Ticker.C { + l.LogWithFields(ctx).Info("reconciling....") + + if config.Data.Reconciliation != "" || config.Data.EventSubReconciliation != "" { + if r.odimObj == nil { + r.commonRec = utils.GetCommonReconciler(r.Client, r.Scheme) + r.odimObj = r.commonRec.GetOdimObject(ctx, constants.MetadataName, "odim", config.Data.Namespace) + if r.odimObj == nil { + continue + } + } + client, err := restclient.NewRestClient(ctx, r.odimObj, r.commonRec.(*utils.CommonReconciler), constants.BMCOPERATOR) + if err != nil { + l.LogWithFields(ctx).Errorf("Failed to get rest client for BMC: %s", err.Error()) + continue + } + res, _, err := client.Get("/redfish/v1/Systems", "Getting response on systems") + if err != nil { + l.LogWithFields(ctx).Errorf("Failed to get system details") + continue + } + //TODO: getutils will handle it + r.ctx = ctx + r.namespace = config.Data.Namespace + r.pollRestClient = client + r.commonUtil = common.GetCommonUtils(client) + r.bmcUtil = bmc.GetBmcUtils(ctx, nil, r.namespace, &r.commonRec, &r.pollRestClient, r.commonUtil, true) + r.volUtil = volume.GetVolumeUtils(ctx, client, r.commonRec, &v1.Volume{}, r.namespace) + allBmcObjects := r.commonRec.GetAllBmcObject(r.ctx, r.namespace) + mapOfSystems := make(map[string]bool) + // Iterate to the systems + for _, mem := range res["Members"].([]interface{}) { + members := mem.(map[string]interface{}) + member, err := url.Parse(members["@odata.id"].(string)) + if err != nil { + l.LogWithFields(ctx).Errorf("error parsing URL: %v", err) + continue + } + /* If new member is added then set value for IsAdded as true + mapOfSystems = current added bmcs in ODIM' + MapofBmc = bmc objects in operator */ + mapOfSystems[member.Path] = true + if _, ok := common.MapOfBmc[member.Path]; !ok { + bmcState := common.MapOfBmc[member.Path] + bmcState.IsAdded = true + common.MapOfBmc[member.Path] = bmcState + } + } + if config.Data.Reconciliation == constants.Revert { + wg.Add(1) + go func() { + defer wg.Done() + r.RevertPollingDetails(ctx, client, mapOfSystems) + r.RevertBiosDetails(ctx, client) + r.RevertVolumeDetails() + if allBmcObjects != nil { + for _, bmc := range *allBmcObjects { + r.bmcObject = &bmc + if !r.checkIfSystemIsUndergoingReset() { + r.RevertState() + } + } + } + }() + wg.Wait() + + } + if config.Data.Reconciliation == constants.Accommodate { + // check if system is present, if not then set state IsDeleted to true + if len(mapOfSystems) != len(common.MapOfBmc) { + for urlKey := range common.MapOfBmc { + if _, ok := mapOfSystems[urlKey]; !ok { + bmcState := common.MapOfBmc[urlKey] + bmcState.IsDeleted = true + common.MapOfBmc[urlKey] = bmcState + } + } + } + l.LogWithFields(ctx).Info("map containing members: ", common.MapOfBmc) + r.AccommodatePollingDetails(ctx, client, common.MapOfBmc) + r.AccommodateBiosDetails(ctx, client, mapOfSystems) + r.AccommodateVolumeDetails() + if allBmcObjects != nil { + for _, bmc := range *allBmcObjects { + r.bmcObject = &bmc + if !r.checkIfSystemIsUndergoingReset() { + r.AccommodateState() + } + } + } + } + + defaultEventsubscriptionDestination := r.odimObj.Spec.EventListenerHost + "/OdimEvents" + if config.Data.EventSubReconciliation == constants.Revert { + wg.Add(1) + go func() { + defer wg.Done() + r.RevertEventSubscriptionDetails(ctx, client, defaultEventsubscriptionDestination) + }() + wg.Wait() + } + if config.Data.EventSubReconciliation == constants.Accommodate { + r.AccommodateEventSubscriptionDetails(ctx, client, defaultEventsubscriptionDestination) + } + } + } +} + +func (pr PollingReconciler) checkIfSystemIsUndergoingReset() bool { + systemDetails := pr.commonUtil.GetBmcSystemDetails(pr.ctx, pr.bmcObject) + if statusOfBMC, ok := systemDetails["Status"]; ok { + if stateOfBMC, ok := statusOfBMC.(map[string]interface{})["State"], ok; ok { + //checking if reset is triggered from operator or ODIM directly + if (pr.bmcObject.Status.SystemReset != "" && pr.bmcObject.Status.SystemReset != "Done") || stateOfBMC.(string) == "Starting" { + return true + } + } else { + l.LogWithFields(pr.ctx).Info("Could not access the state of BMC") + } + l.LogWithFields(pr.ctx).Info("Could not access the status of BMC") + } + return false +} diff --git a/controllers/pollData/mockUtils_test.go b/controllers/pollData/mockUtils_test.go new file mode 100644 index 0000000..0b281d3 --- /dev/null +++ b/controllers/pollData/mockUtils_test.go @@ -0,0 +1,456 @@ +//(C) Copyright [2023] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +package controllers + +import ( + "context" + // "fmt" + "net/http" + + infraiov1 "github.com/ODIM-Project/BMCOperator/api/v1" + v1 "github.com/ODIM-Project/BMCOperator/api/v1" + restclient "github.com/ODIM-Project/BMCOperator/controllers/restclient" + utils "github.com/ODIM-Project/BMCOperator/controllers/utils" + volume "github.com/ODIM-Project/BMCOperator/controllers/volume" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +var count = 0 + +type MockCommonRec struct { + variable string +} +type mockRestClient struct{ url string } + +type mockCommonUtil struct{} + +type mockVolumeUtil struct{} + +type mockEventSubscriptionUtil struct { + name string +} + +// --------------------------------COMMON RECONCILER MOCKS------------------------------------------ +func (m *MockCommonRec) GetBmcObject(ctx context.Context, field, value, ns string) *infraiov1.Bmc { + if m.variable == "sendBmcObj" { + return &infraiov1.Bmc{Status: infraiov1.BmcStatus{BmcSystemID: "fakeSystemID"}} + } else if m.variable == "doNotSendBmcObj" { + return nil + } + return nil +} + +func (m *MockCommonRec) GetAllBmcObject(ctx context.Context, ns string) *[]infraiov1.Bmc { + bmc1 := infraiov1.Bmc{ObjectMeta: metav1.ObjectMeta{Name: "10.10.10.10"}, Spec: infraiov1.BmcSpec{BmcDetails: infraiov1.BMC{Address: "10.10.10.10"}}, Status: infraiov1.BmcStatus{BmcSystemID: "systemID13"}} + // bmc2 := infraiov1.Bmc{ObjectMeta: metav1.ObjectMeta{Name: "10.10.10.10"}, Status: infraiov1.BmcStatus{BmcSystemID: "somefakeid2"}} + return &[]infraiov1.Bmc{bmc1} +} + +func (m *MockCommonRec) GetBiosSchemaObject(ctx context.Context, field, value, ns string) *infraiov1.BiosSchemaRegistry { + return nil +} +func (m *MockCommonRec) GetBiosObject(ctx context.Context, field, value, ns string) *infraiov1.BiosSetting { + return nil +} + +func (m *MockCommonRec) GetBootObject(ctx context.Context, field, value, ns string) *infraiov1.BootOrderSetting { + return nil +} + +func (m *MockCommonRec) GetOdimObject(ctx context.Context, field, value, ns string) *infraiov1.Odim { + return nil +} + +func (m *MockCommonRec) GetVolumeObject(ctx context.Context, bmcIP, ns string) *infraiov1.Volume { + return nil +} + +func (m *MockCommonRec) GetFirmwareObject(ctx context.Context, field, value, ns string) *infraiov1.Firmware { + return nil +} + +// create objects +func (m *MockCommonRec) CreateBiosSettingObject(ctx context.Context, biosAttributes map[string]string, bmcObj *infraiov1.Bmc) bool { + return true +} + +func (m *MockCommonRec) CreateBootOrderSettingObject(ctx context.Context, bootAttributes *infraiov1.BootSetting, bmcObj *infraiov1.Bmc) bool { + return true +} + +func (m *MockCommonRec) CreateEventSubscriptionObject(ctx context.Context, subscriptionDetails map[string]interface{}, ns string, originResources []string) bool { + return true +} + +func (m *MockCommonRec) GetAllEventSubscriptionObjects(ctx context.Context, ns string) *[]infraiov1.Eventsubscription { + return nil +} + +func (m *MockCommonRec) GetEventsubscriptionObject(ctx context.Context, field, value, ns string) *infraiov1.Eventsubscription { + return nil +} + +func (m *MockCommonRec) GetEventMessageRegistryObject(ctx context.Context, registryPrefix, registryVersion, ns string) *infraiov1.EventsMessageRegistry { + return nil +} + +func (m *MockCommonRec) CheckAndCreateEventMessageObject(ctx context.Context, messageRegistryResp map[string]interface{}, bmcObj *infraiov1.Bmc) bool { + return true +} + +func (m *MockCommonRec) CheckAndCreateBiosSchemaObject(ctx context.Context, attributeResp map[string]interface{}, bmcObj *infraiov1.Bmc) bool { + return true +} + +// update objects +func (m *MockCommonRec) UpdateBmcStatus(ctx context.Context, bmcObj *infraiov1.Bmc) { +} + +func (m *MockCommonRec) UpdateOdimStatus(ctx context.Context, status string, odimObj *infraiov1.Odim) { +} + +func (m *MockCommonRec) UpdateBmcObjectOnReset(ctx context.Context, bmcObject *infraiov1.Bmc, status string) { +} + +func (m *MockCommonRec) UpdateVolumeStatus(ctx context.Context, volObject *infraiov1.Volume, volumeID, volumeName, capBytes, durableName, durableNameFormat string) { +} + +// get updated objects +func (m *MockCommonRec) GetUpdatedBmcObject(ctx context.Context, ns types.NamespacedName, bmcObj *infraiov1.Bmc) { +} + +func (m *MockCommonRec) GetUpdatedOdimObject(ctx context.Context, ns types.NamespacedName, odimObj *infraiov1.Odim) { +} + +func (m *MockCommonRec) GetUpdatedVolumeObject(ctx context.Context, ns types.NamespacedName, volObj *infraiov1.Volume) { +} + +func (m *MockCommonRec) GetUpdatedFirmwareObject(ctx context.Context, ns types.NamespacedName, firmObj *infraiov1.Firmware) { +} + +func (m *MockCommonRec) UpdateBiosSettingObject(ctx context.Context, biosAttributes map[string]string, biosObj *infraiov1.BiosSetting) bool { + return true +} + +func (m *MockCommonRec) GetAllVolumeObjectIds(ctx context.Context, bmc *infraiov1.Bmc, ns string) map[string][]string { + if bmc.ObjectMeta.Name == "10.10.10.10" { + return map[string][]string{"ArrayControllers-0": []string{"1", "2"}, "ArrayControllers-1": []string{"4", "5", "6"}} + } + return nil +} + +func (m *MockCommonRec) DeleteVolumeObject(ctx context.Context, volObj *infraiov1.Volume) {} + +func (m *MockCommonRec) DeleteBmcObject(ctx context.Context, bmcObj *infraiov1.Bmc) {} + +func (m *MockCommonRec) UpdateEventsubscriptionStatus(ctx context.Context, eventsubObj *infraiov1.Eventsubscription, eventsubscriptionDetails map[string]interface{}, originResouces []string) { +} + +func (m *MockCommonRec) GetUpdatedEventsubscriptionObjects(ctx context.Context, ns types.NamespacedName, eventSubObj *infraiov1.Eventsubscription) { +} + +func (m *MockCommonRec) GetAllVolumeObjects(ctx context.Context, bmcIP, ns string) []*infraiov1.Volume { + if bmcIP == "10.10.10.10" { + vol1 := infraiov1.Volume{ObjectMeta: metav1.ObjectMeta{Name: "10.10.10.10/volume1"}, Status: infraiov1.VolumeStatus{VolumeID: "1", StorageControllerID: "ArrayControllers-0"}} + vol2 := infraiov1.Volume{ObjectMeta: metav1.ObjectMeta{Name: "10.10.10.10/volume2"}, Status: infraiov1.VolumeStatus{VolumeID: "2", StorageControllerID: "ArrayControllers-0"}} + vol3 := infraiov1.Volume{ObjectMeta: metav1.ObjectMeta{Name: "10.10.10.10/volume3"}, Status: infraiov1.VolumeStatus{VolumeID: "3", StorageControllerID: "ArrayControllers-0"}} + vol4 := infraiov1.Volume{ObjectMeta: metav1.ObjectMeta{Name: "10.10.10.10/volume4"}, Status: infraiov1.VolumeStatus{VolumeID: "4", StorageControllerID: "ArrayControllers-1"}} + vol5 := infraiov1.Volume{ObjectMeta: metav1.ObjectMeta{Name: "10.10.10.10/volume5"}, Status: infraiov1.VolumeStatus{VolumeID: "5", StorageControllerID: "ArrayControllers-1"}} + vol6 := infraiov1.Volume{ObjectMeta: metav1.ObjectMeta{Name: "10.10.10.10/volume6"}, Status: infraiov1.VolumeStatus{VolumeID: "6", StorageControllerID: "ArrayControllers-1"}} + controllerutil.AddFinalizer(&vol6, volume.VolFinalizer) + return []*infraiov1.Volume{&vol1, &vol2, &vol3, &vol4, &vol5, &vol6} + } + return nil +} + +func (m *MockCommonRec) GetAllBiosSchemaRegistryObjects(ctx context.Context, ns string) *[]infraiov1.BiosSchemaRegistry { + return nil +} + +func (m *MockCommonRec) GetVolumeObjectByVolumeID(ctx context.Context, volumeID, ns string) *infraiov1.Volume { + if volumeID == "6" { + volObj := &infraiov1.Volume{ObjectMeta: metav1.ObjectMeta{Name: "10.10.10.10.Volume6"}, + Spec: infraiov1.VolumeSpec{ + RAIDType: "RAID0", + Drives: []int{6}, + }} + return volObj + } + return nil +} + +// common reconciler funcs +func (m *MockCommonRec) GetCommonReconcilerClient() client.Client { + return MockCommonRec{} +} + +func (m *MockCommonRec) GetCommonReconcilerScheme() *runtime.Scheme { + return nil +} + +// ----------------------------CLIENT MOCKS-------------------------------- +// Client mocks +func (m MockCommonRec) Get(ctx context.Context, key client.ObjectKey, obj client.Object) error { + return nil +} + +func (m MockCommonRec) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { + return nil +} + +func (m MockCommonRec) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { + return nil +} + +func (m MockCommonRec) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error { + return nil +} + +func (m MockCommonRec) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil +} + +func (m MockCommonRec) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error { + return nil +} + +func (m MockCommonRec) DeleteAllOf(ctx context.Context, obj client.Object, opts ...client.DeleteAllOfOption) error { + return nil +} + +func (m MockCommonRec) Status() client.StatusWriter { + return MockCommonRec{} +} + +func (m MockCommonRec) RESTMapper() meta.RESTMapper { + return nil +} + +func (m MockCommonRec) Scheme() *runtime.Scheme { + return nil +} + +// --------------------------REST CALL MOCKS--------------------------------- +func (m *mockRestClient) Post(url string, reason string, body interface{}) (*http.Response, error) { + // Test_RevertState : success case + if url == "/redfish/v1/Systems/somefakeid/Actions/ComputerSystem.Reset" { + return &http.Response{StatusCode: 202, Header: http.Header{}}, nil + } + return nil, nil +} +func (m *mockRestClient) Get(url string, reason string) (map[string]interface{}, int, error) { + // Test_AccommodateVolumeDetails : success case + // Accomodate Addition for ArrayControllers-0 Accommodate Deletion for ArrayControllers-1 + // getAllVolumeObjectIdsFromODIM : getting all storage details + if url == "/redfish/v1/Systems/systemID13/Storage/" { + members := []interface{}{ + map[string]interface{}{ + "@odata.id": "/redfish/v1/Systems/systemID13/Storage/ArrayControllers-0", + }, map[string]interface{}{ + "@odata.id": "/redfish/v1/Systems/systemID13/Storage/ArrayControllers-1", + }} + return map[string]interface{}{"Members": members}, 200, nil + // return map[string]interface{}{"ArrayControllers-0": []string{"1", "2", "3"}, "ArrayControllers-1": []string{"4", "5"}}, 200, nil + } + // getVolumeDetailsAndCreateVolumeObject : creating volume object3, hence filling its details from ODIM + if url == "/redfish/v1/Systems/systemID13/Storage/ArrayControllers-0/Volumes/3" { + newVolume := map[string]interface{}{ + "Name": "Volume3", + "RAIDType": "RAID0", + "CapacityBytes": float64(10234567), + "Id": "3", + "Identifiers": []interface{}{map[string]interface{}{"DurableName": "someDurableName", "DurableNameFormat": "someDurableFormat"}}, + "Links": map[string]interface{}{ + "Drives": []interface{}{ + map[string]interface{}{"@odata.id": "someOdataIDDrive/3"}, + map[string]interface{}{"@odata.id": "someOdataIDDrive/2"}, + }, + }, + } + return newVolume, 200, nil + } + // getAllVolumeObjectIdsFromODIM : getting all volume details + if url == "/redfish/v1/Systems/systemID13/Storage/ArrayControllers-0/Volumes" { + members := []interface{}{ + map[string]interface{}{ + "@odata.id": "/redfish/v1/Systems/systemID13/Storage/ArrayControllers-0/Volumes/1", + }, map[string]interface{}{ + "@odata.id": "/redfish/v1/Systems/systemID13/Storage/ArrayControllers-0/Volumes/2", + }, map[string]interface{}{ + "@odata.id": "/redfish/v1/Systems/systemID13/Storage/ArrayControllers-0/Volumes/3", + }, + } + return map[string]interface{}{"Members": members}, 200, nil + } + // get volumes request from getNewVolumeIDForObject for revert case should send back volume 6 as well, since we have created it + if url == "/redfish/v1/Systems/systemID13/Storage/ArrayControllers-1/Volumes" && reason == "Fetching all volumes.." { + members := []interface{}{ + map[string]interface{}{ + "@odata.id": "/redfish/v1/Systems/systemID13/Storage/ArrayControllers-1/Volumes/4", + }, map[string]interface{}{ + "@odata.id": "/redfish/v1/Systems/systemID13/Storage/ArrayControllers-1/Volumes/5", + }, map[string]interface{}{ + "@odata.id": "/redfish/v1/Systems/systemID13/Storage/ArrayControllers-1/Volumes/6", + }, + } + return map[string]interface{}{"Members": members}, 200, nil + } + if url == "/redfish/v1/Systems/systemID13/Storage/ArrayControllers-1/Volumes" { + count++ + members := []interface{}{ + map[string]interface{}{ + "@odata.id": "/redfish/v1/Systems/systemID13/Storage/ArrayControllers-1/Volumes/4", + }, map[string]interface{}{ + "@odata.id": "/redfish/v1/Systems/systemID13/Storage/ArrayControllers-1/Volumes/5", + }, + } + return map[string]interface{}{"Members": members}, 200, nil + } + // Test_RevertVolumeDetails : success case + // getNewVolumeIDForObject : for creation of volume 6 + if url == "/redfish/v1/Systems/systemID13/Storage/ArrayControllers-1/Volumes/6" { + newVolume := map[string]interface{}{ + "Name": "Volume6", + "RAIDType": "RAID0", + "CapacityBytes": float64(10234567), + "Id": "6", + "Identifiers": []interface{}{map[string]interface{}{"DurableName": "someDurableName", "DurableNameFormat": "someDurableFormat"}}, + "Links": map[string]interface{}{ + "Drives": []interface{}{ + map[string]interface{}{"@odata.id": "someOdataIDDrive/6"}, + }, + }, + } + return newVolume, 200, nil + } + // getNewVolumeIDForObject : for looping + if url == "/redfish/v1/Systems/systemID13/Storage/ArrayControllers-1/Volumes/5" { + newVolume := map[string]interface{}{ + "Name": "Volume5", + "RAIDType": "RAID0", + "CapacityBytes": float64(10234567), + "Id": "5", + "Identifiers": []interface{}{map[string]interface{}{"DurableName": "someDurableName", "DurableNameFormat": "someDurableFormat"}}, + "Links": map[string]interface{}{ + "Drives": []interface{}{ + map[string]interface{}{"@odata.id": "someOdataIDDrive/1"}, + map[string]interface{}{"@odata.id": "someOdataIDDrive/2"}, + }, + }, + } + return newVolume, 200, nil + } + // getNewVolumeIDForObject : for looping + if url == "/redfish/v1/Systems/systemID13/Storage/ArrayControllers-1/Volumes/4" { + newVolume := map[string]interface{}{ + "Name": "Volume4", + "RAIDType": "RAID0", + "CapacityBytes": float64(10234567), + "Id": "4", + "Identifiers": []interface{}{map[string]interface{}{"DurableName": "someDurableName", "DurableNameFormat": "someDurableFormat"}}, + "Links": map[string]interface{}{ + "Drives": []interface{}{ + map[string]interface{}{"@odata.id": "someOdataIDDrive/3"}, + map[string]interface{}{"@odata.id": "someOdataIDDrive/4"}, + }, + }, + } + return newVolume, 200, nil + } + return nil, 0, nil +} +func (m *mockRestClient) Patch(string, string, interface{}) (*http.Response, error) { + return nil, nil +} +func (m *mockRestClient) Delete(string, string) (*http.Response, error) { + resp := &http.Response{ + Status: "200 OK", + StatusCode: 200, + } + return resp, nil +} +func (m *mockRestClient) Put(string, string, interface{}) (*http.Response, error) { + return nil, nil +} +func (m *mockRestClient) RecallWithNewToken(url, reason, method string, body interface{}) (*http.Response, error) { + return nil, nil +} + +// --------------------------------------EVENT SUBCRIPTION MOCKS------------------------------- +func (m *mockEventSubscriptionUtil) CreateEventsubscription(eventSubObj *infraiov1.Eventsubscription, namespacedName types.NamespacedName) (bool, error) { + return true, nil +} +func (m *mockEventSubscriptionUtil) UpdateEventsubscriptionStatus(eventSubscriptionID string) bool { + return true +} +func (m *mockEventSubscriptionUtil) GetEventSubscriptionRequest() ([]byte, error) { + return nil, nil +} +func (m *mockEventSubscriptionUtil) DeleteEventsubscription() bool { + return true +} +func (m *mockEventSubscriptionUtil) GetSystemIDFromURI(resourceURIArr []string, resourceOID string) string { + return "" +} +func (m *mockEventSubscriptionUtil) UpdateEventSubscriptionLabels() {} +func (m *mockEventSubscriptionUtil) MapOriginResources(subscriptionDetails map[string]interface{}) ([]string, error) { + return []string{}, nil +} + +func (m *MockCommonRec) GetRootCAFromSecret(ctx context.Context) []byte { + return []byte{} +} +func (m *mockEventSubscriptionUtil) GetMessageRegistryDetails(bmcObj *infraiov1.Bmc, registryName string) ([]string, []map[string]interface{}) { + return []string{}, nil +} +func (m *mockEventSubscriptionUtil) ValidateMessageIDs(messageIDs []string) error { + return nil +} + +// --------------------------COMMON UTIL MOCKS------------------------ +func (m mockCommonUtil) GetBmcSystemDetails(context.Context, *infraiov1.Bmc) map[string]interface{} { + return map[string]interface{}{"PowerState": "Off"} +} +func (m mockCommonUtil) MoniteringTaskmon(headerInfo http.Header, ctx context.Context, operation, resourceName string) (bool, map[string]interface{}) { + return true, nil +} +func (m mockCommonUtil) BmcAddition(ctx context.Context, bmcObject *v1.Bmc, body []byte, restClient restclient.RestClientInterface) (bool, map[string]interface{}) { + return false, nil +} +func (m mockCommonUtil) BmcDeleteOperation(ctx context.Context, aggregationURL string, restClient restclient.RestClientInterface, resource string) bool { + return false +} + +// -----------------------------------VOLUME UTIL MOCKS-------------------------------------------------- +func (m mockVolumeUtil) CreateVolume(bmcObj *infraiov1.Bmc, dispName string, namespacedName types.NamespacedName, updateVolumeObject bool, commonRec utils.ReconcilerInterface) (bool, error) { + //successfully created volume + return true, nil +} +func (m mockVolumeUtil) GetVolumeRequestPayload(systemID, dispName string) []byte { + return nil +} +func (m mockVolumeUtil) UpdateVolumeStatusAndClearSpec(bmcObj *infraiov1.Bmc, dispName string) (bool, string) { + return false, "" +} +func (m mockVolumeUtil) DeleteVolume(bmcObj *infraiov1.Bmc) bool { + //successfully deleted volume + return true +} +func (m mockVolumeUtil) UpdateVolumeObject(volObj *infraiov1.Volume) {} diff --git a/controllers/pollData/pollingUtils.go b/controllers/pollData/pollingUtils.go new file mode 100644 index 0000000..d632c58 --- /dev/null +++ b/controllers/pollData/pollingUtils.go @@ -0,0 +1,92 @@ +//(C) Copyright [2023] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +// Package controllers ... +package controllers + +import ( + "context" + "fmt" + "os" + + infraiov1 "github.com/ODIM-Project/BMCOperator/api/v1" + "github.com/ODIM-Project/BMCOperator/config/constants" + bmc "github.com/ODIM-Project/BMCOperator/controllers/bmc" + common "github.com/ODIM-Project/BMCOperator/controllers/common" + restclient "github.com/ODIM-Project/BMCOperator/controllers/restclient" + utils "github.com/ODIM-Project/BMCOperator/controllers/utils" + volume "github.com/ODIM-Project/BMCOperator/controllers/volume" + l "github.com/ODIM-Project/BMCOperator/logs" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +var ( + podName = os.Getenv("POD_NAME") +) + +// PollingReconciler defines a struct of object and interfaces +type PollingReconciler struct { + client.Client + Scheme *runtime.Scheme + odimObj *infraiov1.Odim + bmcObject *infraiov1.Bmc + ctx context.Context + commonRec utils.ReconcilerInterface + pollRestClient restclient.RestClientInterface + commonUtil common.CommonInterface + volUtil volume.VolumeInterface + bmcUtil bmc.BmcInterface + namespace string +} + +// links struct is used in add bmc body +type links struct { + ConnectionMethod map[string]string +} + +// addBmcPayloadBody struct is used for creating add bmc payload +type addBmcPayloadBody struct { + HostName string + UserName string + Password string + Links links +} + +// getKeysSecret retreives the keys from secret +func (r *PollingReconciler) getEncryptedPemKeysFromSecret(ctx context.Context, secret *corev1.Secret, secretName, namespace string) (string, string) { + ns := types.NamespacedName{Namespace: namespace, Name: secretName} + err := r.Get(context.TODO(), ns, secret) + if err != nil { + l.LogWithFields(ctx).Error(fmt.Sprintf("Error fetching %s secret", secretName), err.Error()) + } + publicKey := string(secret.Data["publicKey"]) + privateKey := string(secret.Data["privateKey"]) + return publicKey, privateKey +} + +func GetPollingReconciler(ctx context.Context, client client.Client, Scheme *runtime.Scheme) (*PollingReconciler, error) { + r := &PollingReconciler{Client: client, Scheme: Scheme} + + if r.odimObj == nil { + r.commonRec = utils.GetCommonReconciler(r.Client, r.Scheme) + r.odimObj = r.commonRec.GetOdimObject(ctx, constants.MetadataName, "odim", r.namespace) + if r.odimObj == nil { + return nil, fmt.Errorf("odim Object is not present") + } + } + return r, nil +} diff --git a/controllers/pollData/processEvents.go b/controllers/pollData/processEvents.go new file mode 100644 index 0000000..3f9acd6 --- /dev/null +++ b/controllers/pollData/processEvents.go @@ -0,0 +1,139 @@ +//(C) Copyright [2023] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +// Package controllers ... +package controllers + +import ( + "context" + "path" + "regexp" + "strings" + + infraiov1 "github.com/ODIM-Project/BMCOperator/api/v1" + "github.com/ODIM-Project/BMCOperator/config/constants" + bios "github.com/ODIM-Project/BMCOperator/controllers/bios" + common "github.com/ODIM-Project/BMCOperator/controllers/common" + config "github.com/ODIM-Project/BMCOperator/controllers/config" + restclient "github.com/ODIM-Project/BMCOperator/controllers/restclient" + utils "github.com/ODIM-Project/BMCOperator/controllers/utils" + l "github.com/ODIM-Project/BMCOperator/logs" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +var resourceReconcilerMap map[string]string = map[string]string{ + "bmc": "/redfish/v1/Systems/[a-zA-Z0-9._-]+[/]*$", +} + +// OdimEventMessage contains information of Events and message details including arguments +type OdimEventMessage struct { + OdataType string `json:"@odata.type"` + Name string `json:"Name"` + Context string `json:"@odata.context"` + Events []OdimEvent `json:"Events"` +} + +// OdimEvent contains the details of the event subscribed from PMB +type OdimEvent struct { + EventType string `json:"EventType"` + EventID string `json:"EventId"` + Severity string `json:"Severity"` + EventTimestamp string `json:"EventTimestamp"` + Message string `json:"Message"` + MessageID string `json:"MessageId"` + OriginOfCondition *Link `json:"OriginOfCondition,omitempty"` +} + +// Link property shall contain a link to the resource or object that originated the condition that caused the event to be generated +type Link struct { + Oid string `json:"@odata.id"` +} + +// ProcessOdimEvent receives the event from ODIM, validate it +// and trigger appropriate reconciler to sync the changes to server operator +func ProcessOdimEvent(ctx context.Context, client client.Client, + Scheme *runtime.Scheme, eventName string, event OdimEvent) { + + for resource, regexString := range resourceReconcilerMap { + regExp := regexp.MustCompile(regexString) + if ok := regExp.MatchString(event.OriginOfCondition.Oid); ok { + switch resource { + case "bmc": + r, err := GetPollingReconciler(ctx, client, Scheme) + if err != nil { + l.LogWithFields(ctx).Warnf("Event can not be processed: %s", err.Error()) + return + } + r.processEventsForSystemsResource(ctx, event.MessageID, event.OriginOfCondition.Oid) + } + } + } +} + +func (r *PollingReconciler) processEventsForSystemsResource(ctx context.Context, messageID string, originOfCondition string) { + client, err := restclient.NewRestClient(ctx, r.odimObj, r.commonRec.(*utils.CommonReconciler), constants.BMCOPERATOR) + if err != nil { + l.LogWithFields(ctx).Errorf("Failed to get rest client for BMC: %s", err.Error()) + } + r.pollRestClient = client + r.commonUtil = common.GetCommonUtils(client) + r.ctx = ctx + r.namespace = config.Data.Namespace + l.LogWithFields(ctx).Debugf("Processing events for system resources with messageID %s originOfCondition %s", messageID, originOfCondition) + switch { + case strings.Contains(messageID, "ResourceAdded"): + var bs common.BmcState + bs.IsAdded = true + if config.Data.Reconciliation == constants.Accommodate { + r.AccommodateBMCInfo(ctx, client, originOfCondition, bs) + } else if config.Data.Reconciliation == constants.Revert { + r.RevertResourceAdded(ctx, originOfCondition) + } + case strings.Contains(messageID, "ResourceRemoved"): + var bs common.BmcState + bs.IsDeleted = true + if config.Data.Reconciliation == constants.Accommodate { + r.AccommodateBMCInfo(ctx, client, originOfCondition, bs) + } else if config.Data.Reconciliation == constants.Revert { + r.RevertResourceRemoved(ctx, originOfCondition) + } + case strings.Contains(messageID, "ServerPostDiscoveryComplete"): + systemID := path.Base(originOfCondition) + r.bmcObject = r.commonRec.GetBmcObject(ctx, constants.StatusBmcSystemID, systemID, r.namespace) + biosObj := &infraiov1.BiosSetting{} + biosUtil := bios.GetBiosUtils(ctx, biosObj, r.commonRec, client, r.namespace) + volumeObjectsForStorageControllerInOperator := r.commonRec.GetAllVolumeObjectIds(ctx, r.bmcObject, r.namespace) + volumeObjectsForStorageControllerFromODIM := r.getAllVolumeObjectIdsFromODIM(r.bmcObject, ctx) + l.LogWithFields(ctx).Debug("Volumes from operator:", volumeObjectsForStorageControllerInOperator) + l.LogWithFields(ctx).Debug("Volumes from ODIM:", volumeObjectsForStorageControllerFromODIM) + if config.Data.Reconciliation == constants.Accommodate { + r.CheckAndAccomodateFirmware(ctx, client) + r.CheckAndAccommodateBoot(ctx, client) + if volumeObjectsForStorageControllerInOperator != nil && volumeObjectsForStorageControllerFromODIM != nil { + r.checkVolumeCreatedAndAccommodate(r.bmcObject, volumeObjectsForStorageControllerFromODIM, volumeObjectsForStorageControllerInOperator) + r.checkVolumeDeletedAndAccommodate(r.bmcObject, volumeObjectsForStorageControllerFromODIM, volumeObjectsForStorageControllerInOperator) + } + r.AccommodateBiosInfo(ctx, biosUtil, systemID) + } else if config.Data.Reconciliation == constants.Revert { + r.CheckAndRevertFirmwareVersion(ctx, *r.bmcObject, client) + r.CheckAndRevertBoot(ctx, *r.bmcObject, client) + r.CheckAndRevertBios(ctx, *r.bmcObject, client) + if volumeObjectsForStorageControllerInOperator != nil && volumeObjectsForStorageControllerFromODIM != nil { + r.checkVolumeCreatedAndRevert(r.bmcObject, volumeObjectsForStorageControllerFromODIM, volumeObjectsForStorageControllerInOperator) + r.checkVolumeDeletedAndRevert(r.bmcObject, volumeObjectsForStorageControllerFromODIM, volumeObjectsForStorageControllerInOperator) + } + } + } +} diff --git a/controllers/pollData/reconcileBios.go b/controllers/pollData/reconcileBios.go new file mode 100644 index 0000000..a9e849a --- /dev/null +++ b/controllers/pollData/reconcileBios.go @@ -0,0 +1,101 @@ +//(C) Copyright [2023] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +package controllers + +import ( + "context" + "path" + + infraiov1 "github.com/ODIM-Project/BMCOperator/api/v1" + "github.com/ODIM-Project/BMCOperator/config/constants" + bios "github.com/ODIM-Project/BMCOperator/controllers/bios" + controllers "github.com/ODIM-Project/BMCOperator/controllers/bmc" + common "github.com/ODIM-Project/BMCOperator/controllers/common" + restclient "github.com/ODIM-Project/BMCOperator/controllers/restclient" + utils "github.com/ODIM-Project/BMCOperator/controllers/utils" + l "github.com/ODIM-Project/BMCOperator/logs" +) + +// AccommodateBiosDetails functions accommodates bios setting in object as per the changes in ODIM +func (r PollingReconciler) AccommodateBiosDetails(ctx context.Context, restClient restclient.RestClientInterface, mapOfSystems map[string]bool) { + biosObj := &infraiov1.BiosSetting{} + biosUtil := bios.GetBiosUtils(ctx, biosObj, r.commonRec, restClient, r.namespace) + for system := range mapOfSystems { + systemID := path.Base(system) + r.AccommodateBiosInfo(ctx, biosUtil, systemID) + } +} + +func (r PollingReconciler) AccommodateBiosInfo(ctx context.Context, biosUtil bios.BiosInterface, systemID string) { + r.bmcObject = r.commonRec.GetBmcObject(ctx, constants.StatusBmcSystemID, systemID, r.namespace) + if r.bmcObject == nil { + l.LogWithFields(ctx).Info("bmc object not found: ", systemID) + return + } + if r.bmcObject.Status.BmcSystemID != "" { + mapOfattributes := biosUtil.GetBiosAttributes(r.bmcObject) + biosObject := r.commonRec.GetBiosObject(ctx, constants.MetadataName, r.bmcObject.ObjectMeta.Name, r.odimObj.Namespace) + isequal, _ := utils.CompareMaps(mapOfattributes, biosObject.Status.BiosAttributes) + if !isequal { + r.commonRec.UpdateBiosSettingObject(ctx, mapOfattributes, biosObject) + } + } +} + +// RevertBiosDetails function reverts back bios setting as per the object present in operator +func (r PollingReconciler) RevertBiosDetails(ctx context.Context, restClient restclient.RestClientInterface) { + bmcObjs := r.commonRec.GetAllBmcObject(ctx, r.namespace) + if bmcObjs != nil { + for _, bmc := range *bmcObjs { + r.CheckAndRevertBios(ctx, bmc, restClient) + } + } +} + +func (r PollingReconciler) CheckAndRevertBios(ctx context.Context, bmc infraiov1.Bmc, restClient restclient.RestClientInterface) { + if !common.RestartRequired[bmc.Status.BmcSystemID] { + r.bmcObject = &bmc + biosObj := r.commonRec.GetBiosObject(ctx, constants.MetadataName, r.bmcObject.Name, r.odimObj.Namespace) + biosUtil := bios.GetBiosUtils(ctx, biosObj, r.commonRec, restClient, r.namespace) + if bmc.Status.BiosAttributeRegistry != "" { + mapOfattributes := biosUtil.GetBiosAttributes(r.bmcObject) + if mapOfattributes != nil { + isequal, mapOfBiosAttribute := utils.CompareMaps(biosObj.Status.BiosAttributes, mapOfattributes) + if !isequal { + biosObj.Spec.Bios = mapOfBiosAttribute + ok, body := biosUtil.ValidateBiosAttributes(bmc.Status.BiosAttributeRegistry) + if ok { + if isUpdated := common.BiosAttributeUpdation(ctx, body, bmc.Status.BmcSystemID, restClient); isUpdated { + common.RestartRequired[bmc.Status.BmcSystemID] = true + go func() { + bmc.Spec.BmcDetails.ResetType = "ForceRestart" + bmcUtil := controllers.GetBmcUtils(ctx, &bmc, r.namespace, &r.commonRec, &restClient, common.GetCommonUtils(restClient), false) + resetdone := bmcUtil.ResetSystem(true, true) + if resetdone { + attributes := biosUtil.GetBiosAttributes(r.bmcObject) + biosObj.Spec.Bios = map[string]string{} + biosObj.Status = infraiov1.BiosSettingStatus{ + BiosAttributes: attributes, + } + r.Client.Status().Update(ctx, biosObj) + } + }() + } + } + } + } + } + } +} diff --git a/controllers/pollData/reconcileEventSubscription.go b/controllers/pollData/reconcileEventSubscription.go new file mode 100644 index 0000000..b9683d8 --- /dev/null +++ b/controllers/pollData/reconcileEventSubscription.go @@ -0,0 +1,263 @@ +//(C) Copyright [2023] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +package controllers + +import ( + "context" + "fmt" + "net/http" + "reflect" + + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + infraiov1 "github.com/ODIM-Project/BMCOperator/api/v1" + "github.com/ODIM-Project/BMCOperator/config/constants" + common "github.com/ODIM-Project/BMCOperator/controllers/common" + eventsubscription "github.com/ODIM-Project/BMCOperator/controllers/eventsubscription" + restclient "github.com/ODIM-Project/BMCOperator/controllers/restclient" + utils "github.com/ODIM-Project/BMCOperator/controllers/utils" + l "github.com/ODIM-Project/BMCOperator/logs" +) + +// AccommodateEventSubscriptionDetails will accomodate the newly created event subscriptions on ODIM into BMC operator +func (r PollingReconciler) AccommodateEventSubscriptionDetails(ctx context.Context, restClient restclient.RestClientInterface, defaultEventsubscriptionDestination string) { + mapOfEventSubscriptions := make(map[string]bool) //mapOfEventSubscriptions contains a eventsubscriptionIDs of the eventsubscriptions present in the operator + + res, _, err := restClient.Get("/redfish/v1/EventService/Subscriptions", "Fetching all the event subscriptions present on ODIM") + if err != nil { + l.LogWithFields(ctx).Errorf("Failed to get event subscriptions") + } + + eventsubscriptionObject := &infraiov1.Eventsubscription{} + eventsubUtils := eventsubscription.GetEventSubscriptionUtils(ctx, r.pollRestClient, r.commonRec, eventsubscriptionObject, r.namespace) + + eventsubObjs := r.commonRec.GetAllEventSubscriptionObjects(ctx, r.namespace) + createEventsubscriptionsMap(eventsubObjs, mapOfEventSubscriptions) + + if subscriptions, ok := res["Members"].([]interface{}); ok { + for _, subscription := range subscriptions { + reqURL := subscription.(map[string]interface{})["@odata.id"].(string) + + subscriptionDetails, statusCode, err := restClient.Get(reqURL, fmt.Sprintf("Fetching %s eventsubscription details..", reqURL[len(reqURL)-1:])) + + if err != nil { + l.LogWithFields(ctx).Error(fmt.Sprintf("error while fetching %s eventsubscription details:", reqURL[len(reqURL)-1:]) + err.Error()) + } else if statusCode != http.StatusOK { + l.LogWithFields(ctx).Info(fmt.Sprintf("Failed to get %s eventsubscription details, try again.", reqURL[len(reqURL)-1:])) + } else { + //scenario: If eventsubscription object is present on operator then check and update the status according to the object present on ODIM + if subscriptionDetails["Destination"].(string) != defaultEventsubscriptionDestination { + if _, ok := mapOfEventSubscriptions[subscriptionDetails["Id"].(string)]; ok { + mapOfEventSubscriptions[subscriptionDetails["Id"].(string)] = true + err := r.checkAndUpdateEventSubscriptionObject(ctx, subscriptionDetails, subscriptionDetails["Id"].(string), r.namespace, eventsubUtils) + if err != nil { + l.LogWithFields(ctx).Error(err) + } + } else { + // scenario: If the object does not exist on operator then create one + isCreated := r.CreateEventsubscriptionObject(ctx, subscriptionDetails, r.namespace, eventsubUtils) + if !isCreated { + l.LogWithFields(ctx).Error("Failed to create event subscription object") + } else { + mapOfEventSubscriptions[subscriptionDetails["Id"].(string)] = true + l.LogWithFields(ctx).Infof("Successfully created eventsubscription object %s", subscriptionDetails["Name"].(string)) + } + } + } + } + } + } + r.RemoveSubscriptions(mapOfEventSubscriptions) +} + +// RemoveSubscriptions deletes eventsubscription object which are not present on ODIM from operator +func (r PollingReconciler) RemoveSubscriptions(mapOfEventSubscriptions map[string]bool) { + for eventsubID, isAdded := range mapOfEventSubscriptions { + if !isAdded { + existingEventsubObj := r.commonRec.GetEventsubscriptionObject(r.ctx, constants.StatusEventsubscriptionID, eventsubID, r.namespace) + if existingEventsubObj != nil { + controllerutil.RemoveFinalizer(existingEventsubObj, constants.EventsubscriptionFinalizer) + err := r.Client.Update(r.ctx, existingEventsubObj) + if err != nil { + l.LogWithFields(r.ctx).Errorf("error while updating eventsubscription object for deletion with ID %s: %s", eventsubID, err.Error()) + return + } + err = r.Delete(r.ctx, existingEventsubObj) + if err != nil { + l.LogWithFields(r.ctx).Errorf("error while deleting eventsubscription object with ID %s: %s", eventsubID, err.Error()) + return + } + } + delete(mapOfEventSubscriptions, eventsubID) + } + } + +} + +// RevertEventSubscriptionDetails will revert the event subscriptions on ODIM which does not exist in BMC operator +func (r PollingReconciler) RevertEventSubscriptionDetails(ctx context.Context, restClient restclient.RestClientInterface, defaultEventsubscriptionDestination string) { + res, _, err := restClient.Get("/redfish/v1/EventService/Subscriptions", "Fetching all the event subscriptions present on ODIM") + if err != nil { + l.LogWithFields(ctx).Errorf("Failed to get event subscriptions") + } + mapOfEventSubscriptions := make(map[string]bool) + eventsubObjs := r.commonRec.GetAllEventSubscriptionObjects(ctx, r.namespace) + createEventsubscriptionsMap(eventsubObjs, mapOfEventSubscriptions) + + if subscriptions, ok := res["Members"].([]interface{}); ok { + for _, subscription := range subscriptions { + reqURL := subscription.(map[string]interface{})["@odata.id"].(string) + + subscriptionDetails, statusCode, err := restClient.Get(reqURL, fmt.Sprintf("Fetching %s eventsubscription details..", reqURL[len(reqURL)-1:])) + + if err != nil { + l.LogWithFields(ctx).Error(fmt.Sprintf("error while fetching %s eventsubscription details:", reqURL[len(reqURL)-1:]) + err.Error()) + } else if statusCode != http.StatusOK { + l.LogWithFields(ctx).Info(fmt.Sprintf("Failed to get %s eventsubscription details, try again.", reqURL[len(reqURL)-1:])) + } else { + if subscriptionDetails["Destination"].(string) != defaultEventsubscriptionDestination { + subscriptionID := subscriptionDetails["Id"].(string) + if _, ok := mapOfEventSubscriptions[subscriptionID]; ok { + mapOfEventSubscriptions[subscriptionID] = true + } else { + isDeleted := r.DeleteEventsubscriptionFromODIM(ctx, subscriptionID, restClient) + if !isDeleted { + l.LogWithFields(ctx).Infof("Could not delete event subscription from ODIM with ID: %s", subscriptionID) + } + } + } + } + } + } + if eventsubObjs != nil { + for subscriptionID, isAdded := range mapOfEventSubscriptions { + if !isAdded { + r.AddEventSubscription(subscriptionID) + } + } + } +} + +// AddEventSubscription sends create request to ODIM for creating event subscription +func (r PollingReconciler) AddEventSubscription(subscriptionID string) { + eventsubObj := r.commonRec.GetEventsubscriptionObject(r.ctx, constants.StatusEventsubscriptionID, subscriptionID, r.namespace) + if eventsubObj != nil { + eventsubUtils := eventsubscription.GetEventSubscriptionUtils(r.ctx, r.pollRestClient, r.commonRec, eventsubObj, r.namespace) + req, err := eventsubUtils.GetEventSubscriptionRequest() + if err != nil { + l.LogWithFields(r.ctx).Errorf("could not create eventsubscription request for object %s: %s", eventsubObj.ObjectMeta.Name, err.Error()) + return + } + resp, err := r.pollRestClient.Post("/redfish/v1/EventService/Subscriptions", "Posting eventsubscription creation payload...", req) + if err != nil { + l.LogWithFields(r.ctx).Errorf("error while creating eventsubscription for object %s: %s", eventsubObj.ObjectMeta.Name, err.Error()) + return + } + if resp.StatusCode == http.StatusAccepted { + done, _ := r.commonUtil.MoniteringTaskmon(resp.Header, r.ctx, common.EVENTSUBSCRIPTION, eventsubObj.ObjectMeta.Name) + if done { + l.LogWithFields(r.ctx).Info("\nEventsubscription created successfully!\n") + l.LogWithFields(r.ctx).Info("Updating Eventsubscription status...") + statusUpdated := eventsubUtils.UpdateEventsubscriptionStatus("") + if !statusUpdated { + l.LogWithFields(r.ctx).Info("Eventsubscription created successfully but failed to update the status") + } else { + l.LogWithFields(r.ctx).Info("Eventsubscription status updated successfully!") + } + return + } + } + l.LogWithFields(r.ctx).Errorf("Could not create event subscription %s", eventsubObj.ObjectMeta.Name) + } + +} + +func (r PollingReconciler) checkAndUpdateEventSubscriptionObject(ctx context.Context, eventsubscriptionResp map[string]interface{}, eventsubscriptionID, ns string, eventsubUtils eventsubscription.EventSubscriptionInterface) error { + existingEventsubObj := r.commonRec.GetEventsubscriptionObject(ctx, constants.StatusEventsubscriptionID, eventsubscriptionID, ns) + + eventsubscriptionStatus := infraiov1.EventsubscriptionStatus{ + ID: eventsubscriptionResp["Id"].(string), + Destination: eventsubscriptionResp["Destination"].(string), + Context: eventsubscriptionResp["Context"].(string), + Protocol: eventsubscriptionResp["Protocol"].(string), + SubscriptionType: eventsubscriptionResp["SubscriptionType"].(string), + } + if name, ok := eventsubscriptionResp["Name"].(string); ok { + eventsubscriptionStatus.Name = name + } + + if eventTypes, ok := eventsubscriptionResp["EventTypes"].([]interface{}); ok { + eventsubscriptionStatus.EventTypes = utils.ConvertInterfaceToStringArray(eventTypes) + } + if messageIDs, ok := eventsubscriptionResp["MessageIds"].([]interface{}); ok { + eventsubscriptionStatus.MessageIds = utils.ConvertInterfaceToStringArray(messageIDs) + } + + if resourceTypes, ok := eventsubscriptionResp["ResourceTypes"].([]interface{}); ok { + eventsubscriptionStatus.ResourceTypes = utils.ConvertInterfaceToStringArray(resourceTypes) + } + originResources, err := eventsubUtils.MapOriginResources(eventsubscriptionResp) + if err != nil { + return fmt.Errorf("Failed to update eventsubscription object: %s", err.Error()) + } + if len(originResources) > 0 { + eventsubscriptionStatus.OriginResources = originResources + } + + if existingEventsubObj != nil { + if !reflect.DeepEqual(existingEventsubObj.Status, eventsubscriptionStatus) { + existingEventsubObj.Status = eventsubscriptionStatus + err := r.Client.Status().Update(ctx, existingEventsubObj) + if err != nil { + l.LogWithFields(ctx).Errorf("Error: Updating eventsubscription status for %s: %s", existingEventsubObj.ObjectMeta.Name, err.Error()) + } + } + } + return nil +} + +// CreateEventsubscriptionObject will create a new event subscription object in BMC operator +func (r PollingReconciler) CreateEventsubscriptionObject(ctx context.Context, eventsubscriptionResp map[string]interface{}, ns string, eventsubUtils eventsubscription.EventSubscriptionInterface) bool { + originResources, err := eventsubUtils.MapOriginResources(eventsubscriptionResp) + if err != nil { + l.LogWithFields(r.ctx).Error(err.Error()) + return false + } + objectCreated := r.commonRec.CreateEventSubscriptionObject(ctx, eventsubscriptionResp, ns, originResources) + return objectCreated +} + +// DeleteEventsubscriptionFromODIM will delete the event subscription from ODIM +func (r PollingReconciler) DeleteEventsubscriptionFromODIM(ctx context.Context, subscriptionID string, restClient restclient.RestClientInterface) bool { + deleteResp, err := restClient.Delete(fmt.Sprintf("/redfish/v1/EventService/Subscriptions/%s", subscriptionID), fmt.Sprintf("Deleting event subscription with ID %s", subscriptionID)) + if err != nil { + l.LogWithFields(ctx).Error(fmt.Sprintf("error while deleting event subscription with ID %s from ODIM", subscriptionID)) + } + if deleteResp.StatusCode == http.StatusOK { + l.LogWithFields(ctx).Info(fmt.Sprintf("Successfully deleted event subscription with ID %s from ODIM", subscriptionID)) + return true + } + return false +} + +func createEventsubscriptionsMap(eventsubObjs *[]infraiov1.Eventsubscription, mapOfEventSubscriptions map[string]bool) { + if eventsubObjs != nil { + for _, eventsubscription := range *eventsubObjs { + if eventsubscription.Status.ID != "" { + mapOfEventSubscriptions[eventsubscription.Status.ID] = false + } + } + } +} diff --git a/controllers/pollData/reconcileEventSubscription_test.go b/controllers/pollData/reconcileEventSubscription_test.go new file mode 100644 index 0000000..a5633cc --- /dev/null +++ b/controllers/pollData/reconcileEventSubscription_test.go @@ -0,0 +1,100 @@ +//(C) Copyright [2023] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +package controllers + +import ( + "context" + "reflect" + "testing" + + infraiov1 "github.com/ODIM-Project/BMCOperator/api/v1" +) + +func Test_createEventsubscriptionsMap(t *testing.T) { + mapOfEventSubscriptions := make(map[string]bool) + eventsubObjs := &[]infraiov1.Eventsubscription{ + infraiov1.Eventsubscription{ + Status: infraiov1.EventsubscriptionStatus{ + ID: "123", + Destination: "https://10.24.1.14:8000/Destination", + Name: "EventSub1", + Context: "Events", + Protocol: "Redfish", + SubscriptionType: "RedfishEvent", + }, + }, + infraiov1.Eventsubscription{ + Status: infraiov1.EventsubscriptionStatus{ + ID: "1234", + Destination: "https://10.24.1.14:8001/Destination", + Name: "EventSub2", + Context: "TestEvent", + Protocol: "Redfish", + SubscriptionType: "RedfishEvent", + }, + }, + } + expectedMapOfEventSubscriptions := map[string]bool{ + "123": false, + "1234": false, + } + createEventsubscriptionsMap(eventsubObjs, mapOfEventSubscriptions) + if !reflect.DeepEqual(mapOfEventSubscriptions, expectedMapOfEventSubscriptions) { + t.Errorf("createEventsubscriptionsMap(): Expected :%v but got: %v", eventsubObjs, mapOfEventSubscriptions) + } +} + +func Test_CreateEventsubscriptionObject(t *testing.T) { + mockUtil := mockEventSubscriptionUtil{name: "eventsubscription"} + reconciler := getPollingReconcilerObject() + + eventsubResp := map[string]interface{}{ + "ID": "1234", + "Destination": "https://10.24.1.14:8001/Destination", + "Name": "EventSub2", + "Context": "TestEvent", + "Protocol": "Redfish", + "SubscriptionType": "RedfishEvent", + } + + iscreated := reconciler.CreateEventsubscriptionObject(context.Background(), eventsubResp, "bmc-op", &mockUtil) + if !iscreated { + t.Errorf("CreateEventsubscriptionObject(): Expected true but got: %v", iscreated) + } +} + +func Test_DeleteEventsubscriptionFromODIM(t *testing.T) { + reconciler := getPollingReconcilerObject() + + isDeleted := reconciler.DeleteEventsubscriptionFromODIM(context.Background(), "1234", reconciler.pollRestClient) + if !isDeleted { + t.Errorf("DeleteEventsubscriptionFromODIM(): Expected true but got: %v", isDeleted) + } +} + +func getPollingReconcilerObject() PollingReconciler { + commonRecObj := MockCommonRec{variable: "eventSubscriptionObject"} + + reconciler := PollingReconciler{ + Client: &MockClient{}, + Scheme: nil, + odimObj: nil, + bmcObject: nil, + commonRec: &commonRecObj, + pollRestClient: &mockRestClient{}, + ctx: context.Background(), + } + return reconciler +} diff --git a/controllers/pollData/reconcileState.go b/controllers/pollData/reconcileState.go new file mode 100644 index 0000000..7b36eac --- /dev/null +++ b/controllers/pollData/reconcileState.go @@ -0,0 +1,82 @@ +//(C) Copyright [2023] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +// Package controllers ... +package controllers + +import ( + "encoding/json" + "fmt" + "net/http" + + common "github.com/ODIM-Project/BMCOperator/controllers/common" + l "github.com/ODIM-Project/BMCOperator/logs" +) + +func (pr *PollingReconciler) AccommodateState() { + powerStateOfBMC, powerStateInBMCObject := pr.getPowerStateOnBMCAndObject() + l.LogWithFields(pr.ctx).Info(fmt.Sprintf("Configured power state for %s BMC in object: `%s`", pr.bmcObject.Spec.BmcDetails.Address, powerStateInBMCObject)) + if powerStateOfBMC != powerStateInBMCObject { + l.LogWithFields(pr.ctx).Info(fmt.Sprintf("Accommodating %s power state for %s BMC", powerStateOfBMC, pr.bmcObject.Spec.BmcDetails.Address)) + pr.bmcObject.Status.PowerState = powerStateOfBMC + err := pr.commonRec.GetCommonReconcilerClient().Status().Update(pr.ctx, pr.bmcObject) + if err != nil { + l.LogWithFields(pr.ctx).Errorf("Failed to update %s BMC: %s", pr.bmcObject.ObjectMeta.Name, err.Error()) + } + l.LogWithFields(pr.ctx).Info(fmt.Sprintf("Successfully accommodated power state for %s BMC to %s", pr.bmcObject.ObjectMeta.Name, powerStateOfBMC)) + } +} + +func (pr *PollingReconciler) RevertState() { + var request common.ComputerSystemReset + powerStateOfBMC, powerStateInBMCObject := pr.getPowerStateOnBMCAndObject() + l.LogWithFields(pr.ctx).Info(fmt.Sprintf("Configured power state for %s BMC in object: `%s`", pr.bmcObject.Spec.BmcDetails.Address, powerStateInBMCObject)) + if powerStateOfBMC != powerStateInBMCObject { + l.LogWithFields(pr.ctx).Info(fmt.Sprintf("Reverting %s power state for %s BMC", powerStateInBMCObject, pr.bmcObject.Spec.BmcDetails.Address)) + resetPowerStateURI := fmt.Sprintf("/redfish/v1/Systems/%s/Actions/ComputerSystem.Reset", pr.bmcObject.Status.BmcSystemID) + if powerStateInBMCObject == "Off" { + request = common.ComputerSystemReset{ResetType: "ForceOff"} + } else { + request = common.ComputerSystemReset{ResetType: powerStateInBMCObject} + } + resetReq, err := json.Marshal(request) + if err != nil { + l.LogWithFields(pr.ctx).Error(fmt.Sprintf("error marshalling reset request for %s BMC", pr.bmcObject.ObjectMeta.Name), err) + } + resp, err := pr.pollRestClient.Post(resetPowerStateURI, fmt.Sprintf("Posting reset request to %s BMC", pr.bmcObject.ObjectMeta.Name), resetReq) + if err != nil { + l.LogWithFields(pr.ctx).Error(fmt.Sprintf("error while posting reset request to %s BMC", pr.bmcObject.ObjectMeta.Name), err) + return + } + if resp.StatusCode == http.StatusAccepted { + done, _ := pr.commonUtil.MoniteringTaskmon(resp.Header, pr.ctx, common.RESETBMC, pr.bmcObject.ObjectMeta.Name) + if done { + l.LogWithFields(pr.ctx).Info(fmt.Sprintf("Successfully reverted power state for %s BMC to %s", pr.bmcObject.ObjectMeta.Name, powerStateInBMCObject)) + } + } else { + l.LogWithFields(pr.ctx).Info(fmt.Sprintf("Unable to revert power state for %s BMC", pr.bmcObject.ObjectMeta.Name)) + } + } +} + +func (pr *PollingReconciler) getPowerStateOnBMCAndObject() (string, string) { + var powerStateOfBMC string + sysRes := pr.commonUtil.GetBmcSystemDetails(pr.ctx, pr.bmcObject) + if val, ok := sysRes["PowerState"]; ok { + powerStateOfBMC = val.(string) + l.LogWithFields(pr.ctx).Info(fmt.Sprintf("Current power state for %s BMC: `%s`", pr.bmcObject.Spec.BmcDetails.Address, powerStateOfBMC)) + } + powerStateInBMCObject := pr.bmcObject.Status.PowerState + return powerStateOfBMC, powerStateInBMCObject +} diff --git a/controllers/pollData/reconcileState_test.go b/controllers/pollData/reconcileState_test.go new file mode 100644 index 0000000..d4eca21 --- /dev/null +++ b/controllers/pollData/reconcileState_test.go @@ -0,0 +1,80 @@ +//(C) Copyright [2023] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +// Package controllers ... +package controllers + +import ( + "context" + "testing" + + infraiov1 "github.com/ODIM-Project/BMCOperator/api/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestPollingReconciler_AccommodateState(t *testing.T) { + var bmcObj = infraiov1.Bmc{ObjectMeta: metav1.ObjectMeta{Name: "10.10.10.10"}, Spec: infraiov1.BmcSpec{BmcDetails: infraiov1.BMC{Address: "10.10.10.10"}}, Status: infraiov1.BmcStatus{BmcSystemID: "somefakeid", PowerState: "On"}} + tests := []struct { + name string + pr *PollingReconciler + }{ + { + name: "Success case", + pr: &PollingReconciler{ + Client: &MockClient{}, + Scheme: nil, + odimObj: nil, + bmcObject: &bmcObj, + ctx: context.TODO(), + commonRec: &MockCommonRec{}, + pollRestClient: &mockRestClient{}, + commonUtil: mockCommonUtil{}, + namespace: "bmc-op", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.pr.AccommodateState() + }) + } +} + +func TestPollingReconciler_RevertState(t *testing.T) { + var bmcObj = infraiov1.Bmc{ObjectMeta: metav1.ObjectMeta{Name: "10.10.10.10"}, Spec: infraiov1.BmcSpec{BmcDetails: infraiov1.BMC{Address: "10.10.10.10"}}, Status: infraiov1.BmcStatus{BmcSystemID: "somefakeid", PowerState: "On"}} + tests := []struct { + name string + pr *PollingReconciler + }{ + { + name: "Success case", + pr: &PollingReconciler{ + Client: &MockClient{}, + Scheme: nil, + odimObj: nil, + bmcObject: &bmcObj, + ctx: context.TODO(), + commonRec: &MockCommonRec{}, + pollRestClient: &mockRestClient{}, + commonUtil: mockCommonUtil{}, + namespace: "bmc-op", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.pr.RevertState() + }) + } +} diff --git a/controllers/pollData/reconcileVolume.go b/controllers/pollData/reconcileVolume.go new file mode 100644 index 0000000..477d3cc --- /dev/null +++ b/controllers/pollData/reconcileVolume.go @@ -0,0 +1,442 @@ +//(C) Copyright [2023] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +package controllers + +import ( + "context" + "fmt" + "net/http" + "reflect" + "sort" + "strconv" + "strings" + + infraiov1 "github.com/ODIM-Project/BMCOperator/api/v1" + common "github.com/ODIM-Project/BMCOperator/controllers/common" + volume "github.com/ODIM-Project/BMCOperator/controllers/volume" + l "github.com/ODIM-Project/BMCOperator/logs" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +// -------------------------------ACCOMMODATE/REVERT GO FUNCS------------------------------------------------- +// AccommodateVolumeDetails will accommodate volumes created/deleted as part of Restclient +// ODIM's state is given priority over Operator +// volumeObjectsForStorageControllerInOperator : Existing volume objects in operator +// volumeObjectsForStorageControllerFromODIM : all volume objects present in ODIM +func (pr PollingReconciler) AccommodateVolumeDetails() { + allBmcObjects := pr.commonRec.GetAllBmcObject(pr.ctx, pr.namespace) + if allBmcObjects != nil { + for _, bmc := range *allBmcObjects { + l.LogWithFields(pr.ctx).Info(fmt.Sprintf("Reconciling volumes for %s bmc", bmc.ObjectMeta.Name)) + volumeObjectsForStorageControllerInOperator := pr.commonRec.GetAllVolumeObjectIds(pr.ctx, &bmc, pr.namespace) + volumeObjectsForStorageControllerFromODIM := pr.getAllVolumeObjectIdsFromODIM(&bmc, pr.ctx) + l.LogWithFields(pr.ctx).Debug("Volumes from operator:", volumeObjectsForStorageControllerInOperator) + l.LogWithFields(pr.ctx).Debug("Volumes from ODIM:", volumeObjectsForStorageControllerFromODIM) + if volumeObjectsForStorageControllerInOperator != nil && volumeObjectsForStorageControllerFromODIM != nil { + go pr.checkVolumeCreatedAndAccommodate(&bmc, volumeObjectsForStorageControllerFromODIM, volumeObjectsForStorageControllerInOperator) + go pr.checkVolumeDeletedAndAccommodate(&bmc, volumeObjectsForStorageControllerFromODIM, volumeObjectsForStorageControllerInOperator) + } else { + l.LogWithFields(pr.ctx).Info(fmt.Sprintf("Could not successfully retrieve volume IDs for %s Bmc", bmc.ObjectMeta.Name)) + continue + } + } + } +} + +// AccommodateVolumeDetails will accommodate volumes created/deleted as part of Restclient +// Operator's state is given priority over ODIM +// volumeObjectsForStorageControllerInOperator : Existing volume objects in operator +// volumeObjectsForStorageControllerFromODIM : all volume objects present in ODIM +func (pr PollingReconciler) RevertVolumeDetails() { + allBmcObjects := pr.commonRec.GetAllBmcObject(pr.ctx, pr.namespace) + if allBmcObjects != nil { + for _, bmc := range *allBmcObjects { + l.LogWithFields(pr.ctx).Info(fmt.Sprintf("Reconciling volumes for %s bmc", bmc.ObjectMeta.Name)) + volumeObjectsForStorageControllerInOperator := pr.commonRec.GetAllVolumeObjectIds(pr.ctx, &bmc, pr.namespace) + volumeObjectsForStorageControllerFromODIM := pr.getAllVolumeObjectIdsFromODIM(&bmc, pr.ctx) + l.LogWithFields(pr.ctx).Debug("Volumes from operator:", volumeObjectsForStorageControllerInOperator) + l.LogWithFields(pr.ctx).Debug("Volumes from ODIM:", volumeObjectsForStorageControllerFromODIM) + if volumeObjectsForStorageControllerInOperator != nil && volumeObjectsForStorageControllerFromODIM != nil { + go pr.checkVolumeCreatedAndRevert(&bmc, volumeObjectsForStorageControllerFromODIM, volumeObjectsForStorageControllerInOperator) + go pr.checkVolumeDeletedAndRevert(&bmc, volumeObjectsForStorageControllerFromODIM, volumeObjectsForStorageControllerInOperator) + } else { + l.LogWithFields(pr.ctx).Info(fmt.Sprintf("Could not successfully retrieve volume IDs for %s Bmc", bmc.ObjectMeta.Name)) + continue + } + } + } +} + +// ---------------------------------------------ACCOMMODATE USE CASE------------------------------------------------------------- +// checkVolumeCreatedAndAccomodate will create volume objects in operator when new volumes are added to ODIM +// Example: +// operator: {"ArrayController-0":[]string{"1","2","3"},"ArrayController-1":{"5","7"},"ArrayController-2":{"8"}} +// ODIM: {"ArrayController-0":[]string{"1","2","3","4"},"ArrayController-1":{"5","7"},"ArrayController-2":{"8"}} +// newCreatedVolumes : {"ArrayController-0":[]string{"4"},"ArrayController-2":{"8"}} +// filteredOutVolumesAlreadyInProgress will filter out all volumes from newCreatedVolumes whose object is being created, i.e operation is inprogress +// volume objects for volumes in filteredOutVolumesAlreadyInProgress will be created by operator +func (pr PollingReconciler) checkVolumeCreatedAndAccommodate(bmc *infraiov1.Bmc, volumeObjectsForStorageControllerFromODIM, volumeObjectsForStorageControllerInOperator map[string][]string) { + // do not change the sequence in which maps are sent to getVolumes + newCreatedVolumes := pr.getVolumes(volumeObjectsForStorageControllerFromODIM, volumeObjectsForStorageControllerInOperator) + l.LogWithFields(pr.ctx).Debug("Difference in volumes from ODIM & Operator:", newCreatedVolumes) + for storageController, volumeIds := range newCreatedVolumes { + filteredOutVolumesAlreadyInProgress := pr.filterOutVolumesInProgress(volumeIds, bmc.ObjectMeta.Name, bmc.Status.BmcSystemID, storageController, "IsGettingCreated") + l.LogWithFields(pr.ctx).Info(fmt.Sprintf("Creating volume objects for %s Storage Controller having %s volumes(filtered out inprogress volumes):", storageController, filteredOutVolumesAlreadyInProgress)) + pr.getVolumeDetailsAndCreateVolumeObject(bmc, storageController, filteredOutVolumesAlreadyInProgress) + } +} + +// checkVolumeDeletedAndAccomodate will delete volumes in operator when volumes are deleted in ODIM +// Example: +// operator: {"ArrayController-0":[]string{"1","2","3"},"ArrayController-1":{"5","7"},"ArrayController-2":{"8"}} +// ODIM: {"ArrayController-0":[]string{"1","2"},"ArrayController-1":{"5"}} +// deletedVolumesPerStorageController : {"ArrayController-0":[]string{"3"},"ArrayController-1":{"7"},"ArrayController-2":{"8"}} +// filteredOutVolumesAlreadyInProgress will filter out all volumes from deletedVolumesPerStorageController whose object is being deleted, i.e operation is inprogress +// volume objects for volumes in filteredOutVolumesAlreadyInProgress will be deleted by operator +func (pr PollingReconciler) checkVolumeDeletedAndAccommodate(bmc *infraiov1.Bmc, volumeObjectsForStorageControllerFromODIM, volumeObjectsForStorageControllerInOperator map[string][]string) { + // do not change the sequence in which maps are sent to getVolumes + deletedVolumesPerStorageController := pr.getVolumes(volumeObjectsForStorageControllerInOperator, volumeObjectsForStorageControllerFromODIM) + l.LogWithFields(pr.ctx).Debug("Difference in volumes from ODIM & Operator:", deletedVolumesPerStorageController) + for storageController, volumeIds := range deletedVolumesPerStorageController { + filteredOutVolumesAlreadyInProgress := pr.filterOutVolumesInProgress(volumeIds, bmc.ObjectMeta.Name, bmc.Status.BmcSystemID, storageController, "IsGettingDeleted") + l.LogWithFields(pr.ctx).Debug(fmt.Sprintf("Deleting volume objects for %s Storage Controller having %s volumes(filtered out inprogress volumes):", storageController, filteredOutVolumesAlreadyInProgress)) + pr.deleteVolumeObjects(bmc, storageController, filteredOutVolumesAlreadyInProgress) + } +} + +// --------------------------------------------REVERT USE CASE------------------------------------------------------- +// checkVolumeCreatedAndRevert will check for new volumes created in ODIM and delete them +// Example: +// operator: {"ArrayController-0":[]string{"1","2","3"},"ArrayController-1":{"5","7"}} +// ODIM: {"ArrayController-0":[]string{"1","2","3","4"},"ArrayController-1":{"5","7"},"ArrayController-2":{"8"}} +// newCreatedVolumes : {"ArrayController-0":[]string{"4"},"ArrayController-2":{"8"}} +// filteredOutVolumesAlreadyInProgress will filter out all volumes from newCreatedVolumes which is currently being deleted from ODIM, i.e operation is inprogress +// volumes in filteredOutVolumesAlreadyInProgress will be deleted by operator in ODIM +func (pr PollingReconciler) checkVolumeCreatedAndRevert(bmc *infraiov1.Bmc, volumeObjectsForStorageControllerFromODIM, volumeObjectsForStorageControllerInOperator map[string][]string) { + // do not change the sequence in which maps are sent to getVolumes + newCreatedVolumes := pr.getVolumes(volumeObjectsForStorageControllerFromODIM, volumeObjectsForStorageControllerInOperator) + l.LogWithFields(pr.ctx).Debug("Difference in volumes from ODIM & Operator:", newCreatedVolumes) + for storageController, volumeIds := range newCreatedVolumes { + filteredOutVolumesAlreadyInProgress := pr.filterOutVolumesInProgress(volumeIds, bmc.ObjectMeta.Name, bmc.Status.BmcSystemID, storageController, "IsGettingDeleted") + l.LogWithFields(pr.ctx).Debug(fmt.Sprintf("Deleting volumes from ODIM for %s Storage Controller having %s volumes(filtered out inprogress volumes):", storageController, filteredOutVolumesAlreadyInProgress)) + pr.getVolumeDetailsAndDeleteVolumeInODIM(bmc, storageController, filteredOutVolumesAlreadyInProgress) + } +} + +// checkVolumeDeletedAndRevert will check for volumes deleted in ODIM and create them back +// Example: +// operator: {"ArrayController-0":[]string{"1","2","3"},"ArrayController-1":{"5","7"},"ArrayController-2":{"8"}} +// ODIM: {"ArrayController-0":[]string{"1","2"},"ArrayController-1":{"5"}} +// deletedVolumesPerStorageController : {"ArrayController-0":[]string{"3"},"ArrayController-1":{"7"},"ArrayController-2":{"8"}} +// filteredOutVolumesAlreadyInProgress will filter out all volumes from deletedVolumesPerStorageController which is being created in ODIM, i.e operation is inprogress +// volumes in filteredOutVolumesAlreadyInProgress will be created by operator in ODIM directly taking details from volume object already present +func (pr PollingReconciler) checkVolumeDeletedAndRevert(bmc *infraiov1.Bmc, volumeObjectsForStorageControllerFromODIM, volumeObjectsForStorageControllerInOperator map[string][]string) { + // do not change the sequence in which maps are sent to getVolumes + deletedVolumesPerStorageController := pr.getVolumes(volumeObjectsForStorageControllerInOperator, volumeObjectsForStorageControllerFromODIM) + l.LogWithFields(pr.ctx).Debug("Difference in volumes from ODIM & Operator:", deletedVolumesPerStorageController) + for storageController, volumeIds := range deletedVolumesPerStorageController { + filteredOutVolumesAlreadyInProgress := pr.filterOutVolumesInProgress(volumeIds, bmc.ObjectMeta.Name, bmc.Status.BmcSystemID, storageController, "IsGettingCreated") + l.LogWithFields(pr.ctx).Debug(fmt.Sprintf("Creating volumes in ODIM for %s Storage Controller having %s volumes(filtered out inprogress volumes):", storageController, filteredOutVolumesAlreadyInProgress)) + pr.getVolumeObjectDetailsAndCreateVolumeInODIM(bmc, storageController, filteredOutVolumesAlreadyInProgress) + } +} + +//------------------------------------------------POLL VOLUME UTILS------------------------------------------------- + +// getDifferenceInVolumesBetweenOdimAndOperator will get the difference between volumes in ODIM and Operator +func (pr PollingReconciler) getDifferenceInVolumesBetweenOdimAndOperator(volumeObjectIds1 []string, volumeObjectIds2 []string) (bool, []string) { + var diffVolumes []string + for _, odimVolId := range volumeObjectIds2 { // 1 2 3 4 5 + present := false + for _, operatorVolID := range volumeObjectIds1 { // 1 2 3 + if odimVolId == operatorVolID { + present = true + break + } + } + if !present { + diffVolumes = append(diffVolumes, odimVolId) + } + } + if diffVolumes != nil { + return true, diffVolumes + } + return false, nil +} + +// getAllVolumeObjectIdsFromODIM will get all the volume object ids currently present in ODIM +func (pr PollingReconciler) getAllVolumeObjectIdsFromODIM(bmc *infraiov1.Bmc, ctx context.Context) map[string][]string { + var volumesFromODIM = map[string][]string{} + storageDetails, _, err := pr.pollRestClient.Get(fmt.Sprintf("/redfish/v1/Systems/%s/Storage/", bmc.Status.BmcSystemID), fmt.Sprintf("Fetching Storage details for %s BMC..", bmc.Spec.BmcDetails.Address)) + if err != nil { + l.LogWithFields(ctx).Error(err, "Error getting storage details") + return nil + } + if storageDetails["Members"] == nil || len(storageDetails["Members"].([]interface{})) == 0 || storageDetails["Members"].([]interface{}) == nil { + l.LogWithFields(ctx).Info("No storage controllers present..") + return map[string][]string{} + } else { + arrContrDetails := storageDetails["Members"].([]interface{}) + for _, arrayControllerPath := range arrContrDetails { // arrayControllerPath : "@odata.id": "/redfish/v1/Systems/e19e1a0c-32a9-45e9-83d1-53824085df99.1/Storage/ArrayControllers-0" + path := arrayControllerPath.(map[string]interface{}) + arrayController := strings.Split(path["@odata.id"].(string), "/") //arrayController : [ redfish v1 Systems e19e1a0c-32a9-45e9-83d1-53824085df99.1 Storage ArrayControllers-0] + volumeResponse, _, err := pr.pollRestClient.Get(fmt.Sprintf("/redfish/v1/Systems/%s/Storage/%s/Volumes", bmc.Status.BmcSystemID, arrayController[len(arrayController)-1]), fmt.Sprintf("Fetching Volume details for %s BMC..", bmc.Spec.BmcDetails.Address)) + if err != nil { + l.LogWithFields(ctx).Error(err, fmt.Sprintf("Error getting volume details for %s", arrayController[len(arrayController)-1])) + } + if volumeResponse["Members"] == nil || len(volumeResponse["Members"].([]interface{})) == 0 || volumeResponse["Members"].([]interface{}) == nil { + volumesFromODIM[arrayController[len(arrayController)-1]] = []string{} + continue + } + volumeDetails := volumeResponse["Members"].([]interface{}) + var volumeIds []string + for _, eachVolPath := range volumeDetails { + path := eachVolPath.(map[string]interface{}) + eachVol := path["@odata.id"].(string) //eachVol : redfish/v1/Systems/e19e1a0c-32a9-45e9-83d1-53824085df99.1/Storage/ArrayControllers-0/Volumes/1 + volumeIds = append(volumeIds, string(eachVol[len(eachVol)-1])) + } + volumesFromODIM[arrayController[len(arrayController)-1]] = volumeIds + } + return volumesFromODIM + } +} + +// getVolumeDetailsAndCreateVolumeObject will get newly created volume details from ODIM and create new objects for it in operator +func (pr PollingReconciler) getVolumeDetailsAndCreateVolumeObject(bmc *infraiov1.Bmc, storageController string, volumeIds []string) { + for _, volId := range volumeIds { + volumeURL := fmt.Sprintf("/redfish/v1/Systems/%s/Storage/%s/Volumes/%s", bmc.Status.BmcSystemID, storageController, volId) + newVolumeDetails, _, err := pr.pollRestClient.Get(volumeURL, fmt.Sprintf("Fetching newly created Volume %s details", volId)) + if err != nil { + l.LogWithFields(pr.ctx).Error(err, fmt.Sprintf("Error getting new volume %s details", volId)) + } + var newVolume = infraiov1.Volume{} + newVolume.ObjectMeta.Namespace = pr.namespace + volName := newVolumeDetails["Name"].(string) + if volName != "" { + volName = strings.ToLower(strings.ReplaceAll(newVolumeDetails["Name"].(string), " ", "")) + } else { + l.LogWithFields(pr.ctx).Info("Volume name is not present, hence using the storageID and volume ID as volume name") + volName = storageController + "_vol" + volId + } + newVolume.ObjectMeta.Name = bmc.ObjectMeta.Name + "." + volName + newVolume.ObjectMeta.Annotations = map[string]string{} + newVolume.ObjectMeta.Annotations["odata.id"] = volumeURL + controllerutil.AddFinalizer(&newVolume, volume.VolFinalizer) + var drives = []int{} + var identifier infraiov1.Identifier + l.LogWithFields(pr.ctx).Info(fmt.Sprintf("Creating volume object %s...", newVolume.ObjectMeta.Name)) + err = pr.Client.Create(pr.ctx, &newVolume) + if err != nil { + l.LogWithFields(pr.ctx).Errorf("Failed to create volume object: %v", err) + continue + } + newVolume.Status.RAIDType = newVolumeDetails["RAIDType"].(string) + newVolume.Status.StorageControllerID = storageController + driveLinks := newVolumeDetails["Links"].(map[string]interface{})["Drives"].([]interface{}) + for _, dl := range driveLinks { + drive := dl.(map[string]interface{})["@odata.id"].(string) + driveID, err := strconv.Atoi(drive[len(drive)-1:]) + if err != nil { + l.Log.Info("Could not convert drive id to Integer") + } + drives = append(drives, driveID) + } + newVolume.Status.Drives = drives + newVolume.Status.VolumeID = newVolumeDetails["Id"].(string) + newVolume.Status.CapacityBytes = fmt.Sprintf("%f", newVolumeDetails["CapacityBytes"].(float64)) + identifierDetails := newVolumeDetails["Identifiers"].([]interface{}) + for _, idenDet := range identifierDetails { + identifier.DurableName = idenDet.(map[string]interface{})["DurableName"].(string) + identifier.DurableNameFormat = idenDet.(map[string]interface{})["DurableNameFormat"].(string) + } + newVolume.Status.Identifiers = identifier + newVolume.Status.VolumeName = newVolumeDetails["Name"].(string) + err = pr.commonRec.GetCommonReconcilerClient().Status().Update(pr.ctx, &newVolume) + if err != nil { + l.LogWithFields(pr.ctx).Errorf("Error: Updating Status of %s Volume : %s", newVolume.Status.VolumeName, err.Error()) + continue + } + l.LogWithFields(pr.ctx).Info(fmt.Sprintf("Successfully created volume object %s", newVolume.ObjectMeta.Name)) + } +} + +// deleteVolumeObjects will delete all the volumes objects in operator for each storage controller +func (pr PollingReconciler) deleteVolumeObjects(bmc *infraiov1.Bmc, storageController string, volumeIds []string) { + allVolumeObjects := pr.commonRec.GetAllVolumeObjects(pr.ctx, bmc.ObjectMeta.Name, pr.namespace) + for _, volObj := range allVolumeObjects { + if volObj.Status.StorageControllerID == storageController { + for _, volId := range volumeIds { + if volObj.Status.VolumeID == volId { + controllerutil.RemoveFinalizer(volObj, volume.VolFinalizer) + err := pr.commonRec.GetCommonReconcilerClient().Update(pr.ctx, volObj) + if err != nil { + l.LogWithFields(pr.ctx).Error(fmt.Sprintf("error while deleting volume object for volume %s:", volObj.Status.VolumeID) + err.Error()) + continue + } + l.LogWithFields(pr.ctx).Info(fmt.Sprintf("Deleting %s volume Object", volObj.ObjectMeta.Name)) + pr.commonRec.DeleteVolumeObject(pr.ctx, volObj) + } + } + } + } +} + +// getVolumeObjectDetailsAndCreateVolumeInODIM will get deleted volume details from existing volume objects and create volumes +func (pr PollingReconciler) getVolumeObjectDetailsAndCreateVolumeInODIM(bmcObj *infraiov1.Bmc, storageController string, volumeIds []string) { + for _, volId := range volumeIds { + existingVolObj := pr.commonRec.GetVolumeObjectByVolumeID(pr.ctx, volId, pr.namespace) + pr.volUtil.UpdateVolumeObject(existingVolObj) + // setting updateVolumeObject = false because we are not creating new volume object ,since we already have an existing one. + l.LogWithFields(pr.ctx).Info(fmt.Sprintf("Creating %s volume in ODIM", existingVolObj.ObjectMeta.Name)) + isCreated, err := pr.volUtil.CreateVolume(bmcObj, existingVolObj.Status.VolumeName, types.NamespacedName{Namespace: pr.namespace}, false, pr.commonRec) + if err != nil { + l.LogWithFields(pr.ctx).Error(fmt.Sprintf("error while creating volume for %s:", existingVolObj.Name) + err.Error()) + continue + } + //reset here + bmcObj.Spec.BmcDetails.ResetType = "ForceRestart" + pr.bmcUtil.UpdateBmcObject(bmcObj) + resetdone := pr.bmcUtil.ResetSystem(false, false) // setting updateBmcDependents = false, since we do not want those actions to take place, we just need to reset for our volume to be created again + if resetdone { + if isCreated { + newVolumeID, err := pr.getNewVolumeIDForObject(bmcObj, storageController, existingVolObj) + if err != nil || newVolumeID == "" { + l.LogWithFields(pr.ctx).Error(fmt.Sprintf("error while getting new volume ID for exsisting volume object %s:", existingVolObj.Name) + err.Error()) + continue + } + l.LogWithFields(pr.ctx).Debug(fmt.Sprintf("New volume ID for exsisting object %s is : %s", existingVolObj.ObjectMeta.Name, newVolumeID)) + existingVolObj.Status.VolumeID = newVolumeID + err = pr.commonRec.GetCommonReconcilerClient().Status().Update(pr.ctx, existingVolObj) + if err != nil { + l.LogWithFields(pr.ctx).Errorf("Error: Updating Status of %s Volume : %s", existingVolObj.Status.VolumeName, err.Error()) + } + l.LogWithFields(pr.ctx).Info(fmt.Sprintf("Updated volume %s in ODIM successfully", existingVolObj.ObjectMeta.Name)) + } else { + l.LogWithFields(pr.ctx).Info(fmt.Sprintf("Could not update volume %s in ODIM", existingVolObj.ObjectMeta.Name)) + } + } else { + l.LogWithFields(pr.ctx).Info("Reset operation not successful, volume is not visible on ODIM, hence unable to update new volume ID") + } + } +} + +// getNewVolumeIDForObject will get the new VolumeID for the volume object whose Volume was deleted from ODIM, and had to be added back as part of Revert use case +func (pr PollingReconciler) getNewVolumeIDForObject(bmcObj *infraiov1.Bmc, storageController string, volObj *infraiov1.Volume) (string, error) { + getAllVolumesResp, sCode, err := pr.pollRestClient.Get(fmt.Sprintf("/redfish/v1/Systems/%s/Storage/%s/Volumes", bmcObj.Status.BmcSystemID, storageController), "Fetching all volumes..") + if err != nil { + l.LogWithFields(pr.ctx).Error("error while fetching all volumes:" + err.Error()) + return "", err + } else if sCode != http.StatusOK { + l.LogWithFields(pr.ctx).Info("Failed to get all volumes, try again.") + return "", nil + } + volumesList := getAllVolumesResp["Members"].([]interface{}) + for _, vol := range volumesList { + volURL := vol.(map[string]interface{})["@odata.id"].(string) + getEachVolResp, sCode, err := pr.pollRestClient.Get(volURL, fmt.Sprintf("Fetching %s volume details..", volURL[len(volURL)-1:])) + if err != nil { + l.LogWithFields(pr.ctx).Error(fmt.Sprintf("error while fetching %s volume details:", volURL[len(volURL)-1:]) + err.Error()) + return "", err + } else if sCode != http.StatusOK { + l.LogWithFields(pr.ctx).Info(fmt.Sprintf("Failed to get %s volume details, try again.", volURL[len(volURL)-1:])) + return "", nil + } else { + //getting all drives in the volume + drives := []int{} + driveLinks := getEachVolResp["Links"].(map[string]interface{})["Drives"].([]interface{}) + for _, dl := range driveLinks { + drive := dl.(map[string]interface{})["@odata.id"].(string) + driveID, err := strconv.Atoi(drive[len(drive)-1:]) + if err != nil { + l.Log.Info("Could not convert drive id to Integer") + } + drives = append(drives, driveID) + } + sort.Ints(drives) + sort.Ints(volObj.Status.Drives) + if getEachVolResp["Name"].(string) == volObj.Status.VolumeName && getEachVolResp["RAIDType"] == volObj.Status.RAIDType && reflect.DeepEqual(drives, volObj.Status.Drives) { + return getEachVolResp["Id"].(string), nil + } + } + } + return "", nil +} + +// getVolumeDetailsAndDeleteVolumeInODIM will delete the volumes for each storage controller on ODIM directly +func (pr PollingReconciler) getVolumeDetailsAndDeleteVolumeInODIM(bmcObj *infraiov1.Bmc, storageController string, volumeIds []string) { + var volObj = infraiov1.Volume{} + for _, volID := range volumeIds { + volObj.Status.VolumeID = volID + volObj.Status.VolumeName = volID // setting volumeName same as volumeID as could not get name, because we are directly using volumeids to get the difference + volObj.Status.StorageControllerID = storageController + pr.volUtil.UpdateVolumeObject(&volObj) + l.LogWithFields(pr.ctx).Info(fmt.Sprintf("Deleting %s volume in ODIM...", volID)) + pr.volUtil.DeleteVolume(bmcObj) + } +} + +// getVolumes will get the difference between volumeObjectsMap1 and volumeObjectsMap2 +// where volumeObjectsMap1 can be either Operator/ODIM's volumes +// and volumeObjectsMap2 can be either Operator/ODIM's volumes +func (pr PollingReconciler) getVolumes(volumeObjectsMap1, volumeObjectsMap2 map[string][]string) map[string][]string { + var diffVolumesPerStorageController = map[string][]string{} // ArrayController: []string{1,2,...} + for storageController, volumeIds := range volumeObjectsMap1 { + if volumeObjectsMap2[storageController] != nil { + isDifferencePresent, diffVolumes := pr.getDifferenceInVolumesBetweenOdimAndOperator(volumeObjectsMap2[storageController], volumeIds) + if isDifferencePresent { + diffVolumesPerStorageController[storageController] = diffVolumes + } + } else { + diffVolumesPerStorageController[storageController] = volumeIds + } + } + return diffVolumesPerStorageController +} + +// filterOutVolumesInProgress will filter out all the volumes whose next set of operations based on Accommodate/Revert are in progress, from the difference of volumes in Operator & ODIM +func (pr PollingReconciler) filterOutVolumesInProgress(volumeIds []string, bmcName, bmcSystemID, storageController, operation string) []string { + filteredVolumes := []string{} + for _, volID := range volumeIds { + if operation == "IsGettingDeleted" { + bmcStorageVolume := common.BmcStorageControllerVolumesStatus{Bmc: bmcName, StorageController: storageController, VolumeID: volID} + if !common.TrackVolumeStatus[bmcStorageVolume].IsGettingDeleted { + filteredVolumes = append(filteredVolumes, volID) + } + + } else if operation == "IsGettingCreated" { + volumeObjName := bmcName + "." + pr.getVolumeName(bmcSystemID, storageController, volID) + bmcStorageVolume := common.BmcStorageControllerVolumesStatus{Bmc: bmcName, StorageController: storageController, VolumeID: volumeObjName} + if volumeObjName != "" && !common.TrackVolumeStatus[bmcStorageVolume].IsGettingCreated { + filteredVolumes = append(filteredVolumes, volID) + } + } + } + return filteredVolumes +} + +// getVolumeName will return the volume name as present in ODIM based on Volume ID +func (pr PollingReconciler) getVolumeName(bmcSystemID, storageController, volID string) string { + volResp, sCode, err := pr.pollRestClient.Get(fmt.Sprintf("/redfish/v1/Systems/%s/Storage/%s/Volumes/%s", bmcSystemID, storageController, volID), "Getting volume details..") + if err != nil { + l.LogWithFields(pr.ctx).Error(fmt.Sprintf("error while getting %s volume details:", volID) + err.Error()) + return "" + } + if sCode != http.StatusOK { + l.LogWithFields(pr.ctx).Info(fmt.Sprintf("Could not retrieve volume %s details..", volID)) + return "" + } + return volResp["Name"].(string) +} diff --git a/controllers/pollData/reconcileVolume_test.go b/controllers/pollData/reconcileVolume_test.go new file mode 100644 index 0000000..06d5231 --- /dev/null +++ b/controllers/pollData/reconcileVolume_test.go @@ -0,0 +1,152 @@ +//(C) Copyright [2023] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +package controllers + +import ( + "context" + "reflect" + "testing" + + common "github.com/ODIM-Project/BMCOperator/controllers/common" +) + +func TestPollingReconciler_filterOutVolumesInProgress(t *testing.T) { + volStatus := common.VolumeStatus{IsGettingCreated: false, IsGettingDeleted: true} + common.TrackVolumeStatus = map[common.BmcStorageControllerVolumesStatus]common.VolumeStatus{{Bmc: "10.10.10.10", StorageController: "ArrayController-0", VolumeID: "1"}: volStatus} + type args struct { + volumeIds []string + bmcName string + bmcSystemID string + storageController string + operation string + } + tests := []struct { + name string + pr PollingReconciler + args args + want []string + }{ + { + name: "Success case", + pr: PollingReconciler{Client: nil, Scheme: nil, odimObj: nil, bmcObject: nil, ctx: context.TODO(), commonRec: nil, pollRestClient: nil, namespace: "bmc-op"}, + args: args{volumeIds: []string{"1", "2", "3", "4"}, bmcName: "10.10.10.10", bmcSystemID: "somefakesystemid", storageController: "ArrayController-0", operation: "IsGettingDeleted"}, + want: []string{"2", "3", "4"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.pr.filterOutVolumesInProgress(tt.args.volumeIds, tt.args.bmcName, tt.args.bmcSystemID, tt.args.storageController, tt.args.operation); !reflect.DeepEqual(got, tt.want) { + t.Errorf("PollingReconciler.filterOutVolumesInProgress() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestPollingReconciler_getVolumes(t *testing.T) { + type args struct { + volumeObjectsMap1 map[string][]string + volumeObjectsMap2 map[string][]string + } + tests := []struct { + name string + pr PollingReconciler + args args + want map[string][]string + }{ + { + name: "Success case", + pr: PollingReconciler{Client: nil, Scheme: nil, odimObj: nil, bmcObject: nil, ctx: context.TODO(), commonRec: nil, pollRestClient: nil, namespace: "bmc-op"}, + args: args{volumeObjectsMap1: map[string][]string{"ArrayController-0": {"1", "2", "3", "4"}}, volumeObjectsMap2: map[string][]string{"ArrayController-0": {"1", "2"}}}, + want: map[string][]string{"ArrayController-0": {"3", "4"}}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.pr.getVolumes(tt.args.volumeObjectsMap1, tt.args.volumeObjectsMap2); !reflect.DeepEqual(got, tt.want) { + t.Errorf("PollingReconciler.getVolumes() = %v, want %v", got, tt.want) + } + }) + } +} + +// Volumes in Operator = {"ArrayController-0":{"1","2"},"ArrayController-1":{"4","5","6"}} +// Volumes in ODIM = {"ArrayController-0":{"1","2","3"},"ArrayController-1":{"4","5"}} +// Difference: {"ArrayController-0":{"3"}} : Accommodate Add (checkVolumeCreatedAndAccommodate) +// +// {"ArrayController-1":{"6"}} : Accommodate Delete (checkVolumeDeletedAndAccommodate) +func TestPollingReconciler_AccommodateVolumeDetails(t *testing.T) { + // TrackVolumeStatus used in filterOutVolumesInProgress + common.TrackVolumeStatus = map[common.BmcStorageControllerVolumesStatus]common.VolumeStatus{} + tests := []struct { + name string + pr PollingReconciler + }{ + { + name: "Success case", + pr: PollingReconciler{ + Client: &MockCommonRec{}, //same struct implements client functions + Scheme: nil, + odimObj: nil, + bmcObject: nil, + ctx: context.TODO(), + commonRec: &MockCommonRec{}, + pollRestClient: &mockRestClient{}, + commonUtil: mockCommonUtil{}, + volUtil: mockVolumeUtil{}, + namespace: "bmc-op", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.pr.AccommodateVolumeDetails() + }) + } +} + +// Volumes in Operator = {"ArrayController-0":{"1","2"},"ArrayController-1":{"4","5","6"}} +// Volumes in ODIM = {"ArrayController-0":{"1","2","3"},"ArrayController-1":{"4","5"}} +// Difference: {"ArrayController-0":{"3"}} : Revert Add (checkVolumeCreatedAndRevert) +// +// {"ArrayController-1":{"6"}} : Revert Delete (checkVolumeDeletedAndRevert) +func TestPollingReconciler_RevertVolumeDetails(t *testing.T) { + // TrackVolumeStatus used in filterOutVolumesInProgress + common.TrackVolumeStatus = map[common.BmcStorageControllerVolumesStatus]common.VolumeStatus{} + tests := []struct { + name string + pr PollingReconciler + }{ + { + name: "Success case", + pr: PollingReconciler{ + Client: &MockCommonRec{}, //same struct implements client functions + Scheme: nil, + odimObj: nil, + bmcObject: nil, + ctx: context.TODO(), + commonRec: &MockCommonRec{}, + pollRestClient: &mockRestClient{}, + commonUtil: mockCommonUtil{}, + volUtil: mockVolumeUtil{}, + namespace: "bmc-op", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.pr.RevertVolumeDetails() + }) + } +} diff --git a/controllers/pollData/revertDetails.go b/controllers/pollData/revertDetails.go new file mode 100644 index 0000000..98e7c77 --- /dev/null +++ b/controllers/pollData/revertDetails.go @@ -0,0 +1,257 @@ +//(C) Copyright [2023] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +// Package controllers ... +package controllers + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "path" + "reflect" + "strings" + "time" + + v1 "github.com/ODIM-Project/BMCOperator/api/v1" + "github.com/ODIM-Project/BMCOperator/config/constants" + controllers "github.com/ODIM-Project/BMCOperator/controllers/bmc" + boot "github.com/ODIM-Project/BMCOperator/controllers/boot" + common "github.com/ODIM-Project/BMCOperator/controllers/common" + config "github.com/ODIM-Project/BMCOperator/controllers/config" + firmware "github.com/ODIM-Project/BMCOperator/controllers/firmware" + restclient "github.com/ODIM-Project/BMCOperator/controllers/restclient" + utils "github.com/ODIM-Project/BMCOperator/controllers/utils" + l "github.com/ODIM-Project/BMCOperator/logs" +) + +func (r PollingReconciler) RevertPollingDetails(ctx context.Context, restClient restclient.RestClientInterface, mapOfSystems map[string]bool) { + mapOfBmcObj := make(map[string]bool) + isMarkedDeleted := make(map[string]bool) + // get all bmc object + bmcObjs := r.commonRec.GetAllBmcObject(ctx, config.Data.Namespace) + // if bmcOjbects are not present then skip + if bmcObjs != nil { + // loop over bmc objects present in operator + for _, bmc := range *bmcObjs { + r.bmcObject = &bmc + if r.checkIfSystemIsUndergoingReset() { + continue + } + // if deletion process is in progress then mark the device as isMarkedDeleted + if bmc.GetDeletionTimestamp() != nil { + isMarkedDeleted[constants.SystemURI+bmc.Status.BmcSystemID] = true + } + // add all the bmc's present in operator to mapOfBmcObj + mapOfBmcObj[constants.SystemURI+bmc.Status.BmcSystemID] = true + if !isMarkedDeleted[constants.SystemURI+bmc.Status.BmcSystemID] { + if ok := mapOfSystems[constants.SystemURI+bmc.Status.BmcSystemID]; !ok { + r.bmcObject = &bmc + isAdded, id := r.bmcAdditionOperation(ctx, restClient, bmc.Status.BmcSystemID) + if isAdded && id != "" { + r.bmcObject.Labels["systemId"] = id + err := r.Client.Update(ctx, r.bmcObject) + if err != nil { + l.LogWithFields(ctx).Errorf("error while updating bmc object %s", err) + } + r.bmcObject.Status.BmcSystemID = id + // update latest bmc id in bmc object + updateErr := r.commonRec.GetCommonReconcilerClient().Status().Update(ctx, r.bmcObject) + if updateErr != nil { + l.LogWithFields(ctx).Errorf("error while updating object status %s", updateErr) + } + mapOfSystems[constants.SystemURI+id] = true + // delete the key from map since new id gets generated once bmc is added to odim + delete(mapOfBmcObj, constants.SystemURI+bmc.Status.BmcSystemID) + mapOfBmcObj[constants.SystemURI+id] = true + } + } + } + // TODO: move the below functions to different file + if ok := common.MapOfFirmware[bmc.GetName()]; !ok { + r.CheckAndRevertFirmwareVersion(ctx, bmc, restClient) + r.CheckAndRevertBoot(ctx, bmc, restClient) + } + + } + } + //delete systems from odim if no object present + for key := range mapOfSystems { + systemID := path.Base(key) + r.bmcObject = r.commonRec.GetBmcObject(ctx, constants.StatusBmcSystemID, systemID, r.namespace) + if r.bmcObject != nil && r.checkIfSystemIsUndergoingReset() { + continue + } + if !mapOfBmcObj[key] && !isMarkedDeleted[key] && !mapOfBmcObj[constants.SystemURI] { + aggregationURL := strings.Replace(key, "/Systems/", "/AggregationService/AggregationSources/", 1) + r.commonUtil.BmcDeleteOperation(ctx, aggregationURL, restClient, r.bmcObject.ObjectMeta.Name) + } + delete(mapOfBmcObj, constants.SystemURI) + } +} + +// RevertResourceRemoved is called when event resource removed is arrived +func (r *PollingReconciler) RevertResourceRemoved(ctx context.Context, systemUrl string) { + isMarkedDeleted := make(map[string]bool) + // get all bmc object + systemID := path.Base(systemUrl) + r.bmcObject = r.commonRec.GetBmcObject(ctx, constants.StatusBmcSystemID, systemID, r.namespace) + // if bmcOjbects are not present then skip + if r.bmcObject != nil { + if r.checkIfSystemIsUndergoingReset() { + return + } + // if deletion process is in progress then mark the device as isMarkedDeleted + if r.bmcObject.GetDeletionTimestamp() != nil { + isMarkedDeleted[constants.SystemURI+r.bmcObject.Status.BmcSystemID] = true + } + // add all the bmc's present in operator to mapOfBmcObj + if !isMarkedDeleted[constants.SystemURI+r.bmcObject.Status.BmcSystemID] { + if systemID == r.bmcObject.Status.BmcSystemID { + isAdded, id := r.bmcAdditionOperation(ctx, r.pollRestClient, r.bmcObject.Status.BmcSystemID) + if isAdded && id != "" { + r.bmcObject.Labels["systemId"] = id + err := r.Client.Update(ctx, r.bmcObject) + if err != nil { + l.LogWithFields(ctx).Errorf("error while updating bmc object %s", err) + } + r.bmcObject.Status.BmcSystemID = id + // update latest bmc id in bmc object + updateErr := r.commonRec.GetCommonReconcilerClient().Status().Update(ctx, r.bmcObject) + if updateErr != nil { + l.LogWithFields(ctx).Errorf("error while updating object status %s", updateErr) + } + } + } + + } + // TODO: move the below functions to different file + if ok := common.MapOfFirmware[r.bmcObject.GetName()]; !ok { + r.CheckAndRevertFirmwareVersion(ctx, *r.bmcObject, r.pollRestClient) + r.CheckAndRevertBoot(ctx, *r.bmcObject, r.pollRestClient) + } + } +} + +// RevertResourceAdded is called when event resource removed is arrived +func (r *PollingReconciler) RevertResourceAdded(ctx context.Context, systemUrl string) { + + aggregationURL := strings.Replace(systemUrl, "/Systems/", "/AggregationService/AggregationSources/", 1) + systemID := path.Base(systemUrl) + var resp map[string]interface{} + // checking if the aggregation source has been completely added so that it does find the object on operator + for i := 0; i < 3; i++ { + var statusCode int + resp, statusCode, _ = r.pollRestClient.Get(aggregationURL, "Checking BMC addition") + if statusCode == http.StatusOK { + break + } + + l.LogWithFields(ctx).Debugf("Aggregation source has not been added successfully waiting ...") + + time.Sleep(20 * time.Second) + } + if Hostname, ok := resp["HostName"]; ok { + r.bmcObject = r.commonRec.GetBmcObject(ctx, constants.MetadataName, Hostname.(string), r.namespace) + } + if r.bmcObject != nil && r.checkIfSystemIsUndergoingReset() { + l.LogWithFields(ctx).Debugf("Resource %s is already added or undergoing reset so skipping deletion", systemID) + return + } + if r.bmcObject == nil { + l.LogWithFields(ctx).Debugf("bmc object with systemID %s could not be found hence proceeding to delete", systemID) + + r.commonUtil.BmcDeleteOperation(ctx, aggregationURL, r.pollRestClient, systemID) + } +} + +// bmcAdditionOperation will add bmc in ODIM +func (r PollingReconciler) bmcAdditionOperation(ctx context.Context, restClient restclient.RestClientInterface, sysID string) (bool, string) { + l.LogWithFields(ctx).Info("adding bmc...") + bmcUtil := controllers.GetBmcUtils(ctx, r.bmcObject, config.Data.Namespace, &r.commonRec, &restClient, common.GetCommonUtils(restClient), false) + connMeth := bmcUtil.GetConnectionMethod(r.odimObj) + if connMeth != "" { + decryptedPass := utils.DecryptWithPrivateKey(r.ctx, r.bmcObject.Spec.Credentials.Password, *utils.PrivateKey, true) //doBase64Decode = true, because bmc password is encrypted n encoded + // Prepare Bmc Payload + body, err := r.prepareBmcPayload(ctx, connMeth, decryptedPass) + if err != nil { + l.LogWithFields(ctx).Error(fmt.Sprintf("error while creating bmc payload %s: ", err.Error())) + return false, "" + } + + ok, taskResp := common.GetCommonUtils(restClient).BmcAddition(ctx, r.bmcObject, body, restClient) + if ok && taskResp["Name"].(string) == "Aggregation Source" { + if task, ok := taskResp["Id"]; ok { + return true, task.(string) + } + } + } + return false, "" +} + +// prepareBody will prepare the body for add bmc // TODO: reused, change it +func (r PollingReconciler) prepareBmcPayload(ctx context.Context, connectionMeth, password string) ([]byte, error) { + connMeth := map[string]string{"@odata.id": connectionMeth} + link := links{ConnectionMethod: connMeth} + details := addBmcPayloadBody{HostName: r.bmcObject.Spec.BmcDetails.Address, UserName: r.bmcObject.Spec.Credentials.Username, Password: password, Links: link} + body, err := json.Marshal(details) + if err != nil { + l.LogWithFields(ctx).Error(fmt.Sprintf("Error marshalling add request body for %s BMC: %s", r.bmcObject.Spec.BmcDetails.Address, err.Error())) + return nil, err + } + return body, nil +} + +// CheckAndRevertFirmwareVersion function verify the firmwareversion and update it as per the version present in object +func (r PollingReconciler) CheckAndRevertFirmwareVersion(ctx context.Context, bmc v1.Bmc, client restclient.RestClientInterface) { + var body interface{} + firmwareObj := r.commonRec.GetFirmwareObject(ctx, constants.MetadataName, bmc.ObjectMeta.Name, bmc.Namespace) + firmUtil := firmware.GetFirmwareUtils(ctx, firmwareObj, r.commonRec, client, bmc.Namespace) + if firmwareObj == nil { + l.LogWithFields(ctx).Info(fmt.Sprintf("firmware object not present %s: ", bmc.Status.FirmwareVersion)) + return + } + firmwareVersion := firmUtil.GetFirmwareVersion() + if bmc.Status.FirmwareVersion != firmwareVersion { + body = firmware.FirmPayloadWithoutAuth{Image: firmwareObj.Status.ImagePath, Targets: []string{"/redfish/v1/Systems/" + bmc.Status.BmcSystemID}} + marshalInput, err := json.Marshal(body) + if err != nil { + l.LogWithFields(ctx).Error("Failed to marshal firmware input" + err.Error()) + return + } + common.MapOfFirmware[bmc.GetName()] = true + go firmUtil.CreateFirmwareInSystem(marshalInput) + } +} + +// CheckAndRevertBoot is used to check and revert boot details as per the details present in boot object +func (r PollingReconciler) CheckAndRevertBoot(ctx context.Context, bmc v1.Bmc, client restclient.RestClientInterface) { + bootObj := r.commonRec.GetBootObject(ctx, constants.MetadataName, bmc.GetName(), bmc.Namespace) + if bootObj == nil { + l.LogWithFields(ctx).Info(fmt.Sprintf("no boot object present for bmc %s : ", bmc.Name)) + return + } + bootUtil := boot.GetBootUtils(ctx, bootObj, r.commonRec, client, common.GetCommonUtils(client), bmc.Namespace) + sysDetails := common.GetCommonUtils(client).GetBmcSystemDetails(ctx, &bmc) + bootSetting := bootUtil.GetBootAttributes(sysDetails) + if !reflect.DeepEqual(&bootObj.Status.Boot, bootSetting) { + bootObj.Spec.Boot = &v1.BootSetting{} + bootObj.Spec.Boot.BootOrder = bootObj.Status.Boot.BootOrder + ok := bootUtil.UpdateBootDetails(ctx, bmc.Status.BmcSystemID, bmc.Name, bootObj, &bmc) + if ok { + l.LogWithFields(ctx).Info(fmt.Sprintf("Boot order setting configured for %s BMC", bmc.Name)) + } + } +} diff --git a/controllers/pollData/revertDetails_test.go b/controllers/pollData/revertDetails_test.go new file mode 100644 index 0000000..2aa6a1b --- /dev/null +++ b/controllers/pollData/revertDetails_test.go @@ -0,0 +1,140 @@ +//(C) Copyright [2023] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +package controllers + +import ( + "context" + "net/http" + "testing" + + infraiov1 "github.com/ODIM-Project/BMCOperator/api/v1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// MockRestClient is a mock implementation of the RestClientInterface interface +type MockRestClient struct{ mock.Mock } + +func (m *MockRestClient) Delete(url string, description string) (*http.Response, error) { + return &http.Response{StatusCode: http.StatusAccepted, Header: http.Header{"Location": []string{"/redfish/v1/TaskMon/1"}}}, nil +} + +func (m *MockRestClient) Get(url string, description string) (map[string]interface{}, int, error) { + return nil, http.StatusNoContent, nil +} + +func (m *MockRestClient) Post(url string, description string, body interface{}) (*http.Response, error) { + return &http.Response{StatusCode: http.StatusOK}, nil +} + +func (m *MockRestClient) Patch(url string, description string, body interface{}) (*http.Response, error) { + return &http.Response{StatusCode: http.StatusOK}, nil +} + +func (m *MockRestClient) Put(url string, description string, body interface{}) (*http.Response, error) { + return &http.Response{StatusCode: http.StatusOK}, nil +} + +func (m *MockRestClient) RecallWithNewToken(url, reason, method string, body interface{}) (*http.Response, error) { + return &http.Response{StatusCode: http.StatusOK}, nil +} + +func TestPrepareAddBmcPayload(t *testing.T) { + ctx := context.Background() + + reconciler := PollingReconciler{ + bmcObject: &infraiov1.Bmc{ + Spec: infraiov1.BmcSpec{ + BmcDetails: infraiov1.BMC{ + Address: "example.com", + }, + Credentials: infraiov1.Credential{ + Username: "admin", + }, + }, + }, + } + + connectionMethod := "/redfish/v1/ConnectionMethods/1" + password := "password123" + + expectedPayload := []byte(`{"HostName":"example.com","UserName":"admin","Password":"password123","Links":{"ConnectionMethod":{"@odata.id":"/redfish/v1/ConnectionMethods/1"}}}`) + + payload, err := reconciler.prepareBmcPayload(ctx, connectionMethod, password) + assert.NoError(t, err) + + assert.Equal(t, expectedPayload, payload) +} + +type MockClient struct { + mock.Mock +} + +// Implement the Create method for the mock client +func (m *MockClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { + args := m.Called(ctx, obj, opts) + return args.Error(0) +} + +// Implement the Update method for the mock client +func (m *MockClient) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + args := m.Called(ctx, obj, opts) + return args.Error(0) +} + +// Implement the Update method for the mock client +func (m *MockClient) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error { + args := m.Called(ctx, obj, opts) + return args.Error(0) +} + +func (m *MockClient) DeleteAllOf(ctx context.Context, obj client.Object, opts ...client.DeleteAllOfOption) error { + args := m.Called(ctx, obj, opts) + return args.Error(0) +} + +func (m *MockClient) Get(ctx context.Context, key types.NamespacedName, obj client.Object) error { + args := m.Called(ctx, key, obj) + return args.Error(0) +} + +func (m *MockClient) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { + args := m.Called(ctx, list, opts) + return args.Error(0) +} + +func (m *MockClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error { + args := m.Called(ctx, obj, opts) + return args.Error(0) +} + +func (m *MockClient) RESTMapper() meta.RESTMapper { + //args := m.Called(ctx, obj, opts) + return nil +} + +func (m *MockClient) Scheme() *runtime.Scheme { + args := m.Called() + return args.Get(0).(*runtime.Scheme) +} + +func (m *MockClient) Status() client.StatusWriter { + args := m.Called() + return args.Get(0).(client.StatusWriter) +} diff --git a/controllers/restclient/restClient.go b/controllers/restclient/restClient.go new file mode 100644 index 0000000..9f0812c --- /dev/null +++ b/controllers/restclient/restClient.go @@ -0,0 +1,260 @@ +//(C) Copyright [2022] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +// Package controllers ... +package controllers + +import ( + "bytes" + "context" + "crypto/tls" + "crypto/x509" + b64 "encoding/base64" + "encoding/json" + "fmt" + "io/ioutil" + "net" + "net/http" + "strings" + + infraiov1 "github.com/ODIM-Project/BMCOperator/api/v1" + utils "github.com/ODIM-Project/BMCOperator/controllers/utils" + l "github.com/ODIM-Project/BMCOperator/logs" + corev1 "k8s.io/api/core/v1" +) + +var ( + httpConn HTTPClientInterface + err error +) + +// -------------------------------NEW CLIENT CALLS--------------------------------- +// NewRestClient creates rest client for odim +func NewRestClient(ctx context.Context, odimObj *infraiov1.Odim, commonRec *utils.CommonReconciler, rootdir string) (RestClientInterface, error) { + //getting secrets + secret := corev1.Secret{} + var secretName string + for field, val := range odimObj.Annotations { + if strings.Contains(field, "auth") { + secretName = val + } + } + username, password, authType := commonRec.GetObjectSecret(ctx, &secret, secretName, rootdir) + //get password in base64 form + pass := utils.DecryptWithPrivateKey(ctx, password, *utils.PrivateKey, false) //doBase64Decode = false, because odim password fetched from secret is already decoded + l.LogWithFields(ctx).Info("Fetching Odim address") + //get host,port + host, port, err := utils.GetHostPort(ctx, odimObj.Spec.URL) + if err != nil { + l.LogWithFields(ctx).Error("Error fetching host/port of ODIM" + err.Error()) + return nil, err + } + rootCA := utils.RootCA + //Create rc object + restClient, err := getNewRestClient(ctx, username, pass, authType, host, port, rootCA) + if err != nil { + l.LogWithFields(ctx).Error("Error in creating odim rest client." + err.Error()) + return nil, err + } + return restClient, nil +} + +// getNewRestClient returns Rest client +func getNewRestClient(ctx context.Context, username, password, authType, host, port string, rootCA []byte) (RestClientInterface, error) { + l.LogWithFields(ctx).Info("Creating new rest client for ODIM") + userPass := b64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", strings.TrimSpace(username), strings.TrimSpace(password)))) + var auth map[string]string + if strings.Contains(authType, "Basic") { // authType = BasicAuth + auth = map[string]string{"Authorization": fmt.Sprintf("Basic %s", userPass)} + } else if strings.Contains(authType, "Session") { // authType = RedfishSessionAuth + session := getSessionObject(username, password, host, port) + xAuthToken, err := session.getXAuthToken(rootCA) + if err != nil { + return nil, err + } + auth = map[string]string{"X-Auth-Token": xAuthToken} + } + headers := map[string]string{"Content-Type": "application/json", + "Accept": "application/json"} + //adding authentication details to headers + for key, val := range auth { + headers[key] = val + } + return &RestClient{ + reqHeaderAuth: NewHeaderAuth(headers, authType), + host: host, + port: port, + username: username, + password: password, + rootCA: rootCA, + }, nil +} + +func NewHeaderAuth(headers map[string]string, authType string) requestInterface { + return &requestDetails{ + Headers: headers, + AuthType: authType, + } +} + +// -----------------------------REST CALLS---------------------------------- +// Post rest call +func (rc *RestClient) Post(uri, reason string, body interface{}) (*http.Response, error) { + url := fmt.Sprintf("https://%s:%s%s", rc.host, rc.port, uri) + resp, err := rc.reqHeaderAuth.sendRequest(http.MethodPost, reason, url, body, rc.rootCA) + if err != nil { + return nil, err + } else if resp.StatusCode == http.StatusUnauthorized && strings.Contains(rc.reqHeaderAuth.getAuthType(), "Session") { + return rc.RecallWithNewToken(url, http.MethodPost, reason, body) + } else if resp.StatusCode == http.StatusUnauthorized { + return resp, nil + } + return resp, nil +} + +// Patch rest call +func (rc *RestClient) Patch(uri, reason string, body interface{}) (*http.Response, error) { + url := fmt.Sprintf("https://%s:%s%s", rc.host, rc.port, uri) + resp, err := rc.reqHeaderAuth.sendRequest(http.MethodPatch, reason, url, body, rc.rootCA) + if err != nil { + return nil, err + } else if resp.StatusCode == http.StatusUnauthorized && strings.Contains(rc.reqHeaderAuth.getAuthType(), "Session") { // TODO: what if password given is wrong // handle that case + return rc.RecallWithNewToken(url, http.MethodPatch, reason, body) + } else if resp.StatusCode == http.StatusUnauthorized { + return resp, nil + } + return resp, nil +} + +// Put rest call +func (rc *RestClient) Put(uri, reason string, body interface{}) (*http.Response, error) { + url := fmt.Sprintf("https://%s:%s%s", rc.host, rc.port, uri) + resp, err := rc.reqHeaderAuth.sendRequest(http.MethodPut, reason, url, body, rc.rootCA) + if err != nil { + return nil, err + } else if resp.StatusCode == http.StatusUnauthorized && strings.Contains(rc.reqHeaderAuth.getAuthType(), "Session") { + return rc.RecallWithNewToken(url, http.MethodPut, reason, body) + } else if resp.StatusCode == http.StatusUnauthorized { + return resp, nil + } + return resp, nil +} + +// Delete rest call +func (rc *RestClient) Delete(uri, reason string) (*http.Response, error) { + url := fmt.Sprintf("https://%s:%s%s", rc.host, rc.port, uri) + resp, err := rc.reqHeaderAuth.sendRequest(http.MethodDelete, reason, url, "", rc.rootCA) + if err != nil { + return nil, err + } else if resp.StatusCode == http.StatusUnauthorized { + return rc.RecallWithNewToken(url, http.MethodDelete, reason, "") + } + return resp, nil +} + +// Get rest call +func (rc *RestClient) Get(uri, reason string) (map[string]interface{}, int, error) { + url := fmt.Sprintf("https://%s:%s%s", rc.host, rc.port, uri) + var data map[string]interface{} + resp, err := rc.reqHeaderAuth.sendRequest(http.MethodGet, reason, url, "", rc.rootCA) + if err != nil { + return nil, 0, err + } + if resp.StatusCode == 308 { + data, statusCode, err := rc.Get((uri + "/"), reason) + if err != nil { + return nil, statusCode, err + } + return data, resp.StatusCode, err + } else if resp.StatusCode == http.StatusUnauthorized && strings.Contains(rc.reqHeaderAuth.getAuthType(), "Session") { + resp, err = rc.RecallWithNewToken(url, http.MethodGet, reason, "") + if err != nil { + return nil, 0, err + } + } // handle 401 + resbody, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, resp.StatusCode, err + } + if len(resbody) == 0 { // for DELETE query's taskmon (doesnt contain Response Body) + return nil, resp.StatusCode, nil + } + err = json.Unmarshal([]byte(resbody), &data) + if err != nil { + return nil, resp.StatusCode, err + } + return data, resp.StatusCode, nil +} + +// -----------------------------------REQUEST CALLS------------------------------------ +// sendRequest sends the request +func (request *requestDetails) sendRequest(method, reason, uri string, body interface{}, rootCA []byte) (*http.Response, error) { + //creating new req + var req *http.Request + //if method = GET/DELETE , body is empty + if method == "GET" || method == "DELETE" { + req, err = http.NewRequest(method, uri, nil) + if err != nil { + errMsg := fmt.Errorf("Error: Creating HTTP request %s on %s to query ODIM: %s", method, uri, err.Error()) + return nil, errMsg + } + } else { + req, err = http.NewRequest(method, uri, bytes.NewBuffer(body.([]byte))) + if err != nil { + errMsg := fmt.Errorf("Error: Creating HTTP request %s on %s to query ODIM: %s", method, uri, err.Error()) + return nil, errMsg + } + } + // setting header + for header, val := range request.Headers { + req.Header.Set(header, val) + } + if httpConn == nil { + httpConn = getConn(rootCA) + } + res, err := httpConn.Do(req) + if err != nil { + errMsg := fmt.Errorf("Error: Sending HTTP request %s on %s to query ODIM: %s", method, uri, err.Error()) + return nil, errMsg + } + return res, nil +} + +// getAuthType returns authentication type +func (request *requestDetails) getAuthType() string { + return request.AuthType +} + +func (request *requestDetails) getHeaderDetails() map[string]string { + return request.Headers +} + +// ------------------------HTTP CONNECTION-------------------------- +// genConn establishes connection +func getConn(rootCA []byte) HTTPClientInterface { + capool := x509.NewCertPool() + capool.AppendCertsFromPEM(rootCA) + + connection := &http.Client{ + Transport: &http.Transport{ + DialTLS: func(network, addr string) (net.Conn, error) { + conn, err := tls.Dial(network, addr, &tls.Config{ + RootCAs: capool, + }) + return conn, err + }, + }, + } + return connection +} diff --git a/controllers/restclient/restUtils.go b/controllers/restclient/restUtils.go new file mode 100644 index 0000000..b419635 --- /dev/null +++ b/controllers/restclient/restUtils.go @@ -0,0 +1,69 @@ +//(C) Copyright [2023] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +package controllers + +import ( + "net/http" +) + +// --------------REST CLIENT-------------------------- +type requestInterface interface { + sendRequest(string, string, string, interface{}, []byte) (*http.Response, error) + getAuthType() string + getHeaderDetails() map[string]string +} + +type RestClientInterface interface { + Post(string, string, interface{}) (*http.Response, error) + Get(string, string) (map[string]interface{}, int, error) + Patch(string, string, interface{}) (*http.Response, error) + Delete(string, string) (*http.Response, error) + Put(string, string, interface{}) (*http.Response, error) + RecallWithNewToken(url, reason, method string, body interface{}) (*http.Response, error) +} + +type RestClient struct { + reqHeaderAuth requestInterface + host string + port string + username string + password string + rootCA []byte +} + +type requestDetails struct { + Headers map[string]string + AuthType string +} + +//--------------SESSION---------------- + +type sessionInterface interface { + getXAuthToken([]byte) (string, error) +} + +type createTokenDetails struct { + username string + password string + host string + port string +} + +//------------ HTTP CONNECTION---------------- + +// HTTPClient interface +type HTTPClientInterface interface { + Do(req *http.Request) (*http.Response, error) +} diff --git a/controllers/restclient/session.go b/controllers/restclient/session.go new file mode 100644 index 0000000..37448b1 --- /dev/null +++ b/controllers/restclient/session.go @@ -0,0 +1,79 @@ +//(C) Copyright [2022] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +// Package controllers ... +package controllers + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "net/http" +) + +func (rc *RestClient) getNewTokenAndUpdateOdimClient() error { + session := getSessionObject(rc.username, rc.password, rc.host, rc.port) + token, err := session.getXAuthToken(rc.rootCA) + if err != nil { + return fmt.Errorf("Error: Generating new token for ODIM" + err.Error()) + } + headerDetails := rc.reqHeaderAuth.getHeaderDetails() + headerDetails["X-Auth-Token"] = token + // rc.reqHeaderAuth.(*requestDetails).Auth["X-Auth-Token"] = token + // odimRcs.(*RestClient).headerAuthDetails.(*headerAuth).Auth["X-Auth-Token"] = token + return nil +} + +func (token createTokenDetails) getXAuthToken(rootCA []byte) (string, error) { + sessionUrl := fmt.Sprintf("https://%s:%s/redfish/v1/SessionService/Sessions", token.host, token.port) + creds := map[string]string{"UserName": token.username, "Password": token.password} + body, err := json.Marshal(creds) + if err != nil { + return "", err + } + conn := getConn(rootCA) + // Create a HTTP post request + req, err := http.NewRequest("POST", sessionUrl, bytes.NewBuffer(body)) + if err != nil { + return "Unable to create request for session!", err + } + response, err := conn.Do(req) + if err != nil { + return "Error in sending Request", err + } + if response.StatusCode == http.StatusCreated { + return response.Header["X-Auth-Token"][0], nil + } else if response.StatusCode == http.StatusUnauthorized { + return "", errors.New("Username/Password is wrong for session creation!, Check again") + } + return response.Status, nil +} + +// RecallWithNewToken calls the same Rest call with new token +func (rc *RestClient) RecallWithNewToken(url, reason, method string, body interface{}) (*http.Response, error) { + err := rc.getNewTokenAndUpdateOdimClient() + if err != nil { + return nil, err + } + resp, err := rc.reqHeaderAuth.sendRequest(method, reason, url, body, rc.rootCA) + if err != nil { + return nil, err + } + return resp, nil +} + +func getSessionObject(username, password, host, port string) sessionInterface { + return createTokenDetails{username: username, password: password, host: host, port: port} +} diff --git a/controllers/suite_test.go b/controllers/suite_test.go new file mode 100644 index 0000000..aaf8f78 --- /dev/null +++ b/controllers/suite_test.go @@ -0,0 +1,78 @@ +//(C) Copyright [2022] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +package controllers + +import ( + "path/filepath" + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + "sigs.k8s.io/controller-runtime/pkg/envtest/printer" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + + infraiov1 "github.com/ODIM-Project/BMCOperator/api/v1" + //+kubebuilder:scaffold:imports +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +var cfg *rest.Config +var k8sClient client.Client +var testEnv *envtest.Environment + +func TestAPIs(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecsWithDefaultAndCustomReporters(t, + "Controller Suite", + []Reporter{printer.NewlineReporter{}}) +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: true, + } + + cfg, err := testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + err = infraiov1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + + //+kubebuilder:scaffold:scheme + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + +}, 60) + +var _ = AfterSuite(func() { + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +}) diff --git a/controllers/utils/encryption.go b/controllers/utils/encryption.go new file mode 100644 index 0000000..5e992c6 --- /dev/null +++ b/controllers/utils/encryption.go @@ -0,0 +1,65 @@ +//(C) Copyright [2022] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +package controllers + +import ( + "context" + "crypto/rand" + "crypto/rsa" + "crypto/sha512" + "encoding/base64" + + l "github.com/ODIM-Project/BMCOperator/logs" +) + +// Encryption with OAEP padding +func EncryptWithPublicKey(ctx context.Context, secretMessage string, + key rsa.PublicKey) string { + + rng := rand.Reader + ciphertext, err := rsa.EncryptOAEP(sha512.New(), + rng, &key, []byte(secretMessage), nil) + + if err != nil { + l.LogWithFields(ctx).Errorf("Unable to encrypt password: %s", err.Error()) + return "" + } + + return base64.StdEncoding.EncodeToString(ciphertext) +} + +// Decryption +func DecryptWithPrivateKey(ctx context.Context, cipherText string, + privKey rsa.PrivateKey, doBase64Decode bool) string { + ct := []byte(cipherText) + if doBase64Decode { + //Decode the Cipher text + var err error + ct, err = base64.StdEncoding.DecodeString(cipherText) + if err != nil { + l.LogWithFields(ctx).Errorf("Unable to decode password: %s", err.Error()) + return "" + } + } + rng := rand.Reader + secrettext, err := rsa.DecryptOAEP(sha512.New(), + rng, &privKey, ct, nil) + + if err != nil { + l.LogWithFields(ctx).Errorf("Unable to decrypt password: %s", err.Error()) + return "" + } + return string(secrettext) +} diff --git a/controllers/utils/objectUtils.go b/controllers/utils/objectUtils.go new file mode 100644 index 0000000..2eb03a2 --- /dev/null +++ b/controllers/utils/objectUtils.go @@ -0,0 +1,824 @@ +// (C) Copyright [2022] Hewlett Packard Enterprise Development LP +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +package controllers + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net" + "net/url" + "strings" + "time" + + infraiov1 "github.com/ODIM-Project/BMCOperator/api/v1" + "github.com/ODIM-Project/BMCOperator/config/constants" + config "github.com/ODIM-Project/BMCOperator/controllers/config" + l "github.com/ODIM-Project/BMCOperator/logs" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + types "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/predicate" +) + +var ( + updateFunc = func(e event.UpdateEvent) bool { + return e.ObjectOld.GetGeneration() != e.ObjectNew.GetGeneration() + } +) + +type CommonReconciler struct { + Client client.Client + Scheme *runtime.Scheme +} + +type ReconcilerInterface interface { + //get objects + GetBmcObject(ctx context.Context, field, value, ns string) *infraiov1.Bmc + GetAllBmcObject(ctx context.Context, ns string) *[]infraiov1.Bmc + GetBiosSchemaObject(ctx context.Context, field, value, ns string) *infraiov1.BiosSchemaRegistry + GetBiosObject(ctx context.Context, field, value, ns string) *infraiov1.BiosSetting + GetBootObject(ctx context.Context, field, value, ns string) *infraiov1.BootOrderSetting + GetOdimObject(ctx context.Context, field, value, ns string) *infraiov1.Odim + GetVolumeObject(ctx context.Context, bmcIP, ns string) *infraiov1.Volume + GetAllVolumeObjects(ctx context.Context, bmcIP, ns string) []*infraiov1.Volume + GetVolumeObjectByVolumeID(ctx context.Context, volumeID, ns string) *infraiov1.Volume + GetEventsubscriptionObject(ctx context.Context, field, value, ns string) *infraiov1.Eventsubscription + GetAllEventSubscriptionObjects(ctx context.Context, ns string) *[]infraiov1.Eventsubscription + GetAllBiosSchemaRegistryObjects(ctx context.Context, ns string) *[]infraiov1.BiosSchemaRegistry + //get attributes of objects + GetAllVolumeObjectIds(ctx context.Context, bmc *infraiov1.Bmc, ns string) map[string][]string + GetFirmwareObject(ctx context.Context, field, value, ns string) *infraiov1.Firmware + GetEventMessageRegistryObject(ctx context.Context, field, value, ns string) *infraiov1.EventsMessageRegistry + //create objects + CreateBiosSettingObject(ctx context.Context, biosAttributes map[string]string, bmcObj *infraiov1.Bmc) bool + CreateBootOrderSettingObject(ctx context.Context, bootAttributes *infraiov1.BootSetting, bmcObj *infraiov1.Bmc) bool + CheckAndCreateBiosSchemaObject(ctx context.Context, attributeResp map[string]interface{}, bmcObj *infraiov1.Bmc) bool + CreateEventSubscriptionObject(ctx context.Context, subscriptionDetails map[string]interface{}, ns string, originResources []string) bool + CheckAndCreateEventMessageObject(ctx context.Context, messageRegistryResp map[string]interface{}, bmcObj *infraiov1.Bmc) bool + //update objects + UpdateBiosSettingObject(ctx context.Context, biosAttributes map[string]string, bmcObj *infraiov1.BiosSetting) bool + UpdateBmcStatus(ctx context.Context, bmcObj *infraiov1.Bmc) + UpdateOdimStatus(ctx context.Context, status string, odimObj *infraiov1.Odim) + UpdateBmcObjectOnReset(ctx context.Context, bmcObject *infraiov1.Bmc, status string) + UpdateVolumeStatus(ctx context.Context, volObject *infraiov1.Volume, volumeID, volumeName, capBytes, durableName, durableNameFormat string) + UpdateEventsubscriptionStatus(ctx context.Context, eventsubObj *infraiov1.Eventsubscription, eventsubscriptionDetails map[string]interface{}, originResouces []string) + //get updated objects + GetUpdatedBmcObject(ctx context.Context, ns types.NamespacedName, bmcObj *infraiov1.Bmc) + GetUpdatedOdimObject(ctx context.Context, ns types.NamespacedName, odimObj *infraiov1.Odim) + GetUpdatedVolumeObject(ctx context.Context, ns types.NamespacedName, volObj *infraiov1.Volume) + //delete objects + DeleteBmcObject(ctx context.Context, bmcObj *infraiov1.Bmc) + DeleteVolumeObject(ctx context.Context, volObj *infraiov1.Volume) + GetUpdatedFirmwareObject(ctx context.Context, ns types.NamespacedName, firmObj *infraiov1.Firmware) + GetUpdatedEventsubscriptionObjects(ctx context.Context, ns types.NamespacedName, eventSubObj *infraiov1.Eventsubscription) + //common reconciler funcs + GetCommonReconcilerClient() client.Client + GetCommonReconcilerScheme() *runtime.Scheme +} + +// GetCommonReconciler will return common Reconciler object +func GetCommonReconciler(c client.Client, s *runtime.Scheme) ReconcilerInterface { + return &CommonReconciler{Client: c, Scheme: s} +} +func (c *CommonReconciler) GetCommonReconcilerClient() client.Client { + return c.Client +} +func (c *CommonReconciler) GetCommonReconcilerScheme() *runtime.Scheme { + return c.Scheme +} + +// ignoreStatusUpdate ignores reconcile when status/metadata is updated +func IgnoreStatusUpdate() predicate.Predicate { + return predicate.Funcs{ + UpdateFunc: updateFunc, + } +} + +// ----------------------------GET OBJECTS--------------------------------- +// GetBmcObject is used to get bmc object details based on given field and value +func (r *CommonReconciler) GetBmcObject(ctx context.Context, field, value, ns string) *infraiov1.Bmc { + list := &infraiov1.BmcList{} + opts := []client.ListOption{ + client.InNamespace(ns), + client.MatchingFields{field: value}, + } + err := r.Client.List(ctx, list, opts...) + if len(list.Items) == 0 { + l.LogWithFields(ctx).Error(fmt.Sprintf("Couldn't find any BMC object for value %s and field %s", value, field)) + return nil + } + if err != nil { + l.LogWithFields(ctx).Error(err, (fmt.Sprintf("Error fetching the BMC object for given field %s", field))) + return nil + } + return &list.Items[0] +} + +// GetAllBmcObject is used to get all bmc object details based on given namespace +func (r *CommonReconciler) GetAllBmcObject(ctx context.Context, ns string) *[]infraiov1.Bmc { + list := &infraiov1.BmcList{} + opts := []client.ListOption{ + client.InNamespace(ns), + } + err := r.Client.List(ctx, list, opts...) + if len(list.Items) == 0 { + l.LogWithFields(ctx).Error("Couldn't find any BMC object") + return nil + } + if err != nil { + l.LogWithFields(ctx).Error(err, "Error fetching the BMC object details") + return nil + } + return &list.Items +} + +// GetBiosSchemaObject is used to get bios schema object details based on given field and value +func (r *CommonReconciler) GetBiosSchemaObject(ctx context.Context, field, value, ns string) *infraiov1.BiosSchemaRegistry { + list := &infraiov1.BiosSchemaRegistryList{} + opts := []client.ListOption{ + client.InNamespace(ns), + client.MatchingFields{field: value}, + } + err := r.Client.List(ctx, list, opts...) + if len(list.Items) == 0 { + l.LogWithFields(ctx).Error(fmt.Sprintf("Couldn't find any BIOS-SCHEMA object for value %s and field %s", value, field)) + return nil + } + if err != nil { + l.LogWithFields(ctx).Error(fmt.Sprintf("Error fetching the BIOS-SCHEMA object for given field %s: %s", field, err.Error())) + return nil + } + return &list.Items[0] +} + +// GetBiosObject is used to get bios object details based on given field and value +func (r *CommonReconciler) GetBiosObject(ctx context.Context, field, value, ns string) *infraiov1.BiosSetting { + list := &infraiov1.BiosSettingList{} + opts := []client.ListOption{ + client.InNamespace(ns), + client.MatchingFields{field: value}, + } + err := r.Client.List(ctx, list, opts...) + if len(list.Items) == 0 { + l.LogWithFields(ctx).Info(fmt.Sprintf("Couldn't find any BIOS object for value %s and field %s", value, field)) + return nil + } + if err != nil { + l.LogWithFields(ctx).Error(fmt.Sprintf("Error fetching the BIOS object for given field %s: %s", field, err.Error())) + return nil + } + return &list.Items[0] +} + +// GetBootObject is used to get bios object details based on given field and value +func (r *CommonReconciler) GetBootObject(ctx context.Context, field, value, ns string) *infraiov1.BootOrderSetting { + list := &infraiov1.BootOrderSettingList{} + opts := []client.ListOption{ + client.InNamespace(ns), + client.MatchingFields{field: value}, + } + err := r.Client.List(ctx, list, opts...) + if len(list.Items) == 0 { + l.LogWithFields(ctx).Info(fmt.Sprintf("Couldn't find any BOOT object for value %s and field %s", value, field)) + return nil + } + if err != nil { + l.LogWithFields(ctx).Error(fmt.Sprintf("Error fetching the BOOT object for given field %s: %s", field, err.Error())) + return nil + } + return &list.Items[0] +} + +func (r *CommonReconciler) GetOdimObject(ctx context.Context, field, value, ns string) *infraiov1.Odim { + list := &infraiov1.OdimList{} + opts := []client.ListOption{ + client.InNamespace(ns), + client.MatchingFields{field: value}, + } + err := r.Client.List(ctx, list, opts...) + if len(list.Items) == 0 { + l.LogWithFields(ctx).Info(fmt.Sprintf("Couldn't find any ODIM object for value %s and field %s", value, field)) + return nil + } + if err != nil { + l.LogWithFields(ctx).Error(fmt.Sprintf("Error fetching the ODIM object for given field %s: %s", field, err.Error())) + return nil + } + return &list.Items[0] +} + +func (r *CommonReconciler) GetVolumeObject(ctx context.Context, bmcIP, ns string) *infraiov1.Volume { + list := &infraiov1.VolumeList{} + opts := []client.ListOption{ + client.InNamespace(ns), + } + err := r.Client.List(ctx, list, opts...) + if len(list.Items) == 0 { + l.LogWithFields(ctx).Error(fmt.Sprintf("Couldn't find any Volume object for bmc: %s", bmcIP)) + return nil + } + if err != nil { + l.LogWithFields(ctx).Error(err, (fmt.Sprintf("Error fetching the Volume object for bmc: %s", bmcIP))) + return nil + } + for _, volObj := range list.Items { + if strings.Contains(volObj.ObjectMeta.Name, bmcIP) { + return &volObj + } + } + return &list.Items[0] +} + +func (r *CommonReconciler) GetAllVolumeObjects(ctx context.Context, bmcIP, ns string) []*infraiov1.Volume { + list := &infraiov1.VolumeList{} + volList := []*infraiov1.Volume{} + opts := []client.ListOption{ + client.InNamespace(ns), + } + err := r.Client.List(ctx, list, opts...) + if len(list.Items) == 0 { + l.LogWithFields(ctx).Error(fmt.Sprintf("Couldn't find any Volume object for bmc: %s", bmcIP)) + return nil + } + if err != nil { + l.LogWithFields(ctx).Error(err, (fmt.Sprintf("Error fetching the Volume object for bmc: %s", bmcIP))) + return nil + } + for _, volObj := range list.Items { + if strings.Contains(volObj.ObjectMeta.Name, bmcIP) { + volList = append(volList, &volObj) + } + } + return volList +} + +// GetVolumeObjectByVolumeID will fetch and return volume object based on volumeID +func (r *CommonReconciler) GetVolumeObjectByVolumeID(ctx context.Context, volumeID, ns string) *infraiov1.Volume { + list := &infraiov1.VolumeList{} + opts := []client.ListOption{ + client.InNamespace(ns), + } + err := r.Client.List(ctx, list, opts...) + if len(list.Items) == 0 { + l.LogWithFields(ctx).Error(fmt.Sprintf("Couldn't find any Volume object by volumeName %s:", volumeID), err.Error()) + return nil + } + if err != nil { + l.LogWithFields(ctx).Error(err, (fmt.Sprintf("Error fetching the Volume object %s: ", volumeID)), err.Error()) + return nil + } + for _, volObj := range list.Items { + if volObj.Status.VolumeID == volumeID { + return &volObj + } + } + return &list.Items[0] +} + +// -----------------------------------------GET OBJECT'S ATTRIBUTES---------------------------------------------- + +// GetAllVolumeObjectIds will get all the volume ids of specific bmc volume objects +func (r *CommonReconciler) GetAllVolumeObjectIds(ctx context.Context, bmc *infraiov1.Bmc, ns string) map[string][]string { + list := &infraiov1.VolumeList{} + volumeIds := map[string][]string{} + opts := []client.ListOption{ + client.InNamespace(ns), + } + err := r.Client.List(ctx, list, opts...) + if len(list.Items) == 0 { + l.LogWithFields(ctx).Error(fmt.Sprintf("Couldn't find any Volume objects in %s namespace", ns)) + return map[string][]string{} + } + if err != nil { + l.LogWithFields(ctx).Error(err, (fmt.Sprintf("Error fetching Volume objects in %s namespace", ns))) + return nil + } + for _, volume := range list.Items { + if strings.Contains(volume.ObjectMeta.Name, bmc.ObjectMeta.Name) { + if volume.Status.StorageControllerID == "" { + continue + } + if volumeIds[volume.Status.StorageControllerID] == nil { + var volIds []string + volIds = append(volIds, volume.Status.VolumeID) + volumeIds[volume.Status.StorageControllerID] = volIds + } else { + volIds := volumeIds[volume.Status.StorageControllerID] + volIds = append(volIds, volume.Status.VolumeID) + volumeIds[volume.Status.StorageControllerID] = volIds + } + } + } + return volumeIds +} + +// GetFirmwareObject will fetch and return a firmware object based on the field and value +func (r *CommonReconciler) GetFirmwareObject(ctx context.Context, field, value, ns string) *infraiov1.Firmware { + list := &infraiov1.FirmwareList{} + opts := []client.ListOption{ + client.InNamespace(ns), + client.MatchingFields{field: value}, + } + err := r.Client.List(ctx, list, opts...) + if len(list.Items) == 0 { + l.LogWithFields(ctx).Info(fmt.Sprintf("Couldn't find any Firmware object for value %s and field %s", value, field)) + return nil + } + if err != nil { + l.LogWithFields(ctx).Error(fmt.Sprintf("Error fetching the Firmware object for given field %s: %s", field, err.Error())) + return nil + } + return &list.Items[0] +} + +// GetEventsubscriptionObject will fetch the event subscription present in BMC operator based on the field and value and return the first object +func (r *CommonReconciler) GetEventsubscriptionObject(ctx context.Context, field, value, ns string) *infraiov1.Eventsubscription { + list := &infraiov1.EventsubscriptionList{} + opts := []client.ListOption{ + client.InNamespace(ns), + client.MatchingFields{field: value}, + } + err := r.Client.List(ctx, list, opts...) + if len(list.Items) == 0 { + l.LogWithFields(ctx).Error(fmt.Sprintf("Couldn't find any eventsubscription object for value %s and field %s", value, field)) + return nil + } + if err != nil { + l.LogWithFields(ctx).Error(err, (fmt.Sprintf("Error fetching the BMC object for given field %s", field))) + return nil + } + return &list.Items[0] +} + +// GetAllEventSubscriptionObjects will return all the event subscription objects present in BMC operator under the requested namespace +func (r *CommonReconciler) GetAllEventSubscriptionObjects(ctx context.Context, ns string) *[]infraiov1.Eventsubscription { + list := &infraiov1.EventsubscriptionList{} + opts := []client.ListOption{ + client.InNamespace(ns), + } + err := r.Client.List(ctx, list, opts...) + if len(list.Items) == 0 { + l.LogWithFields(ctx).Error("Couldn't find any Event subscription object") + return nil + } + + if err != nil { + l.LogWithFields(ctx).Error(err, "Error fetching the Event subscription details") + return nil + } + return &list.Items +} + +// GetAllBiosSchemaRegistryObjects will get all the bios schema registry objects +func (r *CommonReconciler) GetAllBiosSchemaRegistryObjects(ctx context.Context, ns string) *[]infraiov1.BiosSchemaRegistry { + list := &infraiov1.BiosSchemaRegistryList{} + opts := []client.ListOption{ + client.InNamespace(ns), + } + err := r.Client.List(ctx, list, opts...) + if len(list.Items) == 0 { + l.LogWithFields(ctx).Error("Couldn't find any Bios Schema Registry object") + return nil + } + if err != nil { + l.LogWithFields(ctx).Error(err, "Error fetching Bios Schema Registry object") + return nil + } + return &list.Items +} + +// GetEventMessageRegistryObject fetch eventsmessageregistry object based on fields and values passed +func (r *CommonReconciler) GetEventMessageRegistryObject(ctx context.Context, field, value, ns string) *infraiov1.EventsMessageRegistry { + list := &infraiov1.EventsMessageRegistryList{} + opts := []client.ListOption{ + client.InNamespace(ns), + client.MatchingFields{field: value}, + } + err := r.Client.List(ctx, list, opts...) + if len(list.Items) == 0 { + l.LogWithFields(ctx).Error(fmt.Sprintf("Couldn't find any event registry object value %s and field %s", value, field)) + return nil + } + if err != nil { + l.LogWithFields(ctx).Error(fmt.Sprintf("Error fetching the event registry object value %s and field %s : %s", value, field, err.Error())) + return nil + } + return &list.Items[0] +} + +// -----------------------------------------CREATE OBJECTS---------------------------------------------- + +// createBiosSettingObject is used for creating bios setting object with same name and namespace as of bmc +func (r *CommonReconciler) CreateBiosSettingObject(ctx context.Context, biosAttributes map[string]string, bmcObj *infraiov1.Bmc) bool { + bios := infraiov1.BiosSetting{} + bios.ObjectMeta.Name = bmcObj.ObjectMeta.Name + bios.ObjectMeta.Namespace = bmcObj.ObjectMeta.Namespace + bios.Spec.Bios = map[string]string{} + bios.ObjectMeta.Annotations = map[string]string{} + bios.ObjectMeta.Annotations["odata.id"] = "/redfish/v1/Systems/" + bmcObj.Status.BmcSystemID + "/Bios" + err := r.Client.Create(context.Background(), &bios) + if err != nil { + l.LogWithFields(ctx).Error(fmt.Sprintf("Error while creating biosSetting object for %s BMC: %s", bmcObj.Spec.BmcDetails.Address, err.Error())) + return false + } + bios.Status = infraiov1.BiosSettingStatus{ + BiosAttributes: biosAttributes, + } + err = r.Client.Status().Update(context.Background(), &bios) + if err != nil { + l.LogWithFields(ctx).Error(fmt.Sprintf("Error: Updating status with bios attributes for %s BMC: %s", bmcObj.Spec.BmcDetails.Address, err.Error())) + } + return true +} + +// CreateBootOrderSettingObject is used for creating boot order object with same name and namespace as of bmc +func (r *CommonReconciler) CreateBootOrderSettingObject(ctx context.Context, bootAttributes *infraiov1.BootSetting, bmcObj *infraiov1.Bmc) bool { + boot := infraiov1.BootOrderSetting{} + boot.ObjectMeta.Name = bmcObj.ObjectMeta.Name + boot.ObjectMeta.Namespace = bmcObj.ObjectMeta.Namespace + boot.ObjectMeta.Annotations = map[string]string{} + boot.ObjectMeta.Annotations["odata.id"] = "/redfish/v1/Systems/" + bmcObj.Status.BmcSystemID + "/BootOptions" + err := r.Client.Create(context.Background(), &boot) + if err != nil { + l.LogWithFields(ctx).Error(fmt.Sprintf("Error while creating bootOrderSetting object for %s BMC: %s", bmcObj.Spec.BmcDetails.Address, err.Error())) + return false + } + boot.Status = infraiov1.BootOrderSettingsStatus{ + Boot: *bootAttributes, + } + err = r.Client.Status().Update(context.Background(), &boot) + if err != nil { + l.LogWithFields(ctx).Error(fmt.Sprintf("Error: Updating status with boot attributes for %s BMC: %s", bmcObj.Spec.BmcDetails.Address, err.Error())) + } + return true +} + +// CheckAndCreateBiosSchemaObject verifies if bios schema object is available else create it +func (r *CommonReconciler) CheckAndCreateBiosSchemaObject(ctx context.Context, attributeResp map[string]interface{}, bmcObj *infraiov1.Bmc) bool { + biosID := RemoveSpecialChar(attributeResp["Id"].(string)) + var attribute = make([]map[string]string, 0) + attributes := attributeResp["RegistryEntries"].(map[string]interface{})["Attributes"] + for _, val := range attributes.([]interface{}) { + var attributeDetails = make(map[string]string) + for k, v := range val.(map[string]interface{}) { + if k == "Value" { + res, _ := json.Marshal(v) + attributeDetails[k] = string(res) + } else { + s := fmt.Sprintf("%v", v) + attributeDetails[k] = s + } + + } + attribute = append(attribute, attributeDetails) + } + biosSchema := infraiov1.BiosSchemaRegistry{} + biosSchema.ObjectMeta.Name = biosID + biosSchema.ObjectMeta.Namespace = bmcObj.Namespace + biosSchema.Spec.Name = attributeResp["Name"].(string) + biosSchema.Spec.ID = attributeResp["Id"].(string) + biosSchema.Spec.OwningEntity = attributeResp["OwningEntity"].(string) + biosSchema.Spec.Attributes = attribute + var supportedSystems []infraiov1.SupportedSystems + supportedSystemsFromResp := attributeResp["SupportedSystems"].([]interface{}) + for _, system := range supportedSystemsFromResp { + sys := system.(map[string]interface{}) + supportedSystem := infraiov1.SupportedSystems{} + if sys["ProductName"] != nil { + supportedSystem.ProductName = sys["ProductName"].(string) + } + if sys["SystemID"] != nil { + supportedSystem.SystemID = sys["SystemID"].(string) + } + if sys["FirmwareVersion"] != nil { + supportedSystem.FirmwareVersion = sys["FirmwareVersion"].(string) + } + supportedSystems = append(supportedSystems, supportedSystem) + } + biosSchema.Spec.SupportedSystems = supportedSystems + key := client.ObjectKey{Namespace: bmcObj.Namespace, Name: biosID} + err := r.Client.Get(ctx, key, &biosSchema) + if err != nil { + err := r.Client.Create(ctx, &biosSchema) + if err != nil { + l.LogWithFields(ctx).Error(fmt.Sprintf("Error while creating BIOS-SCHEMA object for %s BMC!: %s", bmcObj.Spec.BmcDetails.Address, err.Error())) + return false + } + l.LogWithFields(ctx).Info(fmt.Sprintf("BIOS-SCHEMA object for %s BMC created with name: %s", bmcObj.Spec.BmcDetails.Address, biosID)) + } + return true +} + +// CreateEventSubscriptionObject creates an eventsubscription object in system +func (r *CommonReconciler) CreateEventSubscriptionObject(ctx context.Context, subscriptionDetails map[string]interface{}, ns string, originResources []string) bool { + eventsubscriptionObj := infraiov1.Eventsubscription{} + subscriptionName := r.GetEventSubscriptionObjectName(ctx, subscriptionDetails["Destination"].(string), subscriptionDetails["Name"].(string), ns) + eventsubscriptionObj.ObjectMeta.Name = subscriptionName + eventsubscriptionObj.ObjectMeta.Namespace = ns + err := r.Client.Create(context.Background(), &eventsubscriptionObj) + if err != nil { + l.LogWithFields(ctx).Error(fmt.Sprintf("Error while creating eventsubscription object %s: %s", eventsubscriptionObj.ObjectMeta.Name, err.Error())) + return false + } + + eventsubscriptionObj.Status.ID = subscriptionDetails["Id"].(string) + eventsubscriptionObj.Status.Destination = subscriptionDetails["Destination"].(string) + eventsubscriptionObj.Status.Context = subscriptionDetails["Context"].(string) + eventsubscriptionObj.Status.Protocol = subscriptionDetails["Protocol"].(string) + eventsubscriptionObj.Status.SubscriptionType = subscriptionDetails["SubscriptionType"].(string) + eventsubscriptionObj.Status.Name = subscriptionName + + if eventTypes, ok := subscriptionDetails["EventTypes"].([]interface{}); ok { + eventsubscriptionObj.Status.EventTypes = ConvertInterfaceToStringArray(eventTypes) + } + if messageIDs, ok := subscriptionDetails["MessageIds"].([]interface{}); ok { + eventsubscriptionObj.Status.MessageIds = ConvertInterfaceToStringArray(messageIDs) + } + + if resourceTypes, ok := subscriptionDetails["ResourceTypes"].([]interface{}); ok { + eventsubscriptionObj.Status.ResourceTypes = ConvertInterfaceToStringArray(resourceTypes) + } + if len(originResources) > 0 { + eventsubscriptionObj.Status.OriginResources = originResources + } + err = r.Client.Status().Update(context.Background(), &eventsubscriptionObj) + if err != nil { + l.LogWithFields(ctx).Error(fmt.Sprintf("Error: Updating eventsubscription object status %s BMC: %s", eventsubscriptionObj.ObjectMeta.Name, err.Error())) + } + + return true +} + +// CheckAndCreateEventMessageObject checks if the message registry object already exist, if not then the object is created +func (r *CommonReconciler) CheckAndCreateEventMessageObject(ctx context.Context, messageRegistryResp map[string]interface{}, bmcObj *infraiov1.Bmc) bool { + messages := map[string]infraiov1.EventMessage{} + registryID := RemoveSpecialChar(messageRegistryResp["Id"].(string)) + messageEntries := messageRegistryResp["Messages"].(map[string]interface{}) + for key, value := range messageEntries { + messageDetails := value.(map[string]interface{}) + eventMessage := infraiov1.EventMessage{ + Description: messageDetails["Description"].(string), + Message: messageDetails["Message"].(string), + NumberOfArgs: fmt.Sprintf("%f", messageDetails["NumberOfArgs"].(float64)), + Resolution: messageDetails["Resolution"].(string), + Severity: messageDetails["Severity"].(string), + ParamTypes: ConvertInterfaceToStringArray(messageDetails["ParamTypes"].([]interface{})), + Oem: map[string]infraiov1.Oem{}, + } + var dataType, healthCatagory, eventType string + if oemDetails, ok := messageDetails["Oem"].(map[string]interface{}); ok { + for oem, oemData := range oemDetails { + oemProperties := oemData.(map[string]interface{}) + eventMessage.Oem[oem] = infraiov1.Oem{} + if value, ok := oemProperties["@odata.type"]; ok { + dataType = value.(string) + } + if value, ok := oemProperties["HealthCategory"]; ok { + healthCatagory = value.(string) + } + if value, ok := oemProperties["Type"]; ok { + eventType = value.(string) + } + eventMessage.Oem[oem] = infraiov1.Oem{ + OdataType: dataType, + HealthCategory: healthCatagory, + Type: eventType, + } + } + } + messages[key] = eventMessage + } + + objName := strings.ToLower(messageRegistryResp["RegistryPrefix"].(string)) + "." + messageRegistryResp["RegistryVersion"].(string) + messageRegistryObj := infraiov1.EventsMessageRegistry{} + messageRegistryObj.ObjectMeta.Name = objName + messageRegistryObj.ObjectMeta.Namespace = bmcObj.Namespace + messageRegistryObj.Spec.Name = messageRegistryResp["Name"].(string) + messageRegistryObj.Spec.ID = messageRegistryResp["Id"].(string) + messageRegistryObj.Spec.OwningEntity = messageRegistryResp["OwningEntity"].(string) + messageRegistryObj.Spec.RegistryPrefix = messageRegistryResp["RegistryPrefix"].(string) + messageRegistryObj.Spec.RegistryVersion = messageRegistryResp["RegistryVersion"].(string) + messageRegistryObj.Spec.Messages = messages + + key := client.ObjectKey{Namespace: bmcObj.Namespace, Name: objName} + err := r.Client.Get(ctx, key, &messageRegistryObj) + if err != nil { + err := r.Client.Create(ctx, &messageRegistryObj) + if err != nil { + l.LogWithFields(ctx).Error(fmt.Sprintf("Error while creating EventMessageRegistry object for %s BMC!: %s", bmcObj.Spec.BmcDetails.Address, err.Error())) + return false + } + l.LogWithFields(ctx).Info(fmt.Sprintf("EventMessageRegistry object for %s BMC created with name: %s", bmcObj.Spec.BmcDetails.Address, registryID)) + } + return true +} + +// ----------------------------------------UPDATE OBJECT STATUS--------------------------------------- +// UpdateBiosSettingObject used to update the bios object +func (r *CommonReconciler) UpdateBiosSettingObject(ctx context.Context, biosAttributes map[string]string, biosObj *infraiov1.BiosSetting) bool { + biosObj.Spec.Bios = map[string]string{} + biosObj.Status = infraiov1.BiosSettingStatus{ + BiosAttributes: biosAttributes, + } + err := r.Client.Status().Update(ctx, biosObj) + if err != nil { + l.LogWithFields(ctx).Error(fmt.Sprintf("Error: Updating status with bios attributes for %s BMC: %s", biosObj.Name, err.Error())) + } + return true +} + +// UpdateBmcStatus used to update the bmc object +func (r *CommonReconciler) UpdateBmcStatus(ctx context.Context, bmcObj *infraiov1.Bmc) { + err := r.Client.Status().Update(ctx, bmcObj) + if err != nil { + l.Log.Error(fmt.Sprintf("Error: Updating status of %s BMC: %s", bmcObj.Spec.BmcDetails.Address, err.Error())) + } + time.Sleep(time.Duration(40) * time.Second) // NOTE: Do not delete this, this helps in proper update of the object +} + +// UpdateOdimStatus updates the status of odim object under status field +func (r *CommonReconciler) UpdateOdimStatus(ctx context.Context, status string, odimObj *infraiov1.Odim) { + odimObj.Status.Status = status //update printer column + err := r.Client.Status().Update(ctx, odimObj) + if err != nil { + l.LogWithFields(ctx).Error("Error: Updating status of ODIM" + err.Error()) + } + time.Sleep(time.Duration(10) * time.Second) // NOTE: Do not delete this, this helps in proper update of the object +} + +// UpdateBmcObjectOnReset updates the systemReset field in bmc object +func (r *CommonReconciler) UpdateBmcObjectOnReset(ctx context.Context, bmcObject *infraiov1.Bmc, status string) { + bmcObject.Status.SystemReset = status + err := r.Client.Status().Update(ctx, bmcObject) + if err != nil { + l.LogWithFields(ctx).Errorf("Error: Updating Status of %s BMC after Reset: %s", bmcObject.Spec.BmcDetails.Address, err.Error()) + } +} + +// UpdateVolumeStatus updates the volume object status +func (r *CommonReconciler) UpdateVolumeStatus(ctx context.Context, volObject *infraiov1.Volume, volumeID, volumeName, capBytes, durableName, durableNameFormat string) { + volObject.Status.VolumeID = volumeID + volObject.Status.VolumeName = volumeName + volObject.Status.RAIDType = volObject.Spec.RAIDType + volObject.Status.StorageControllerID = volObject.Spec.StorageControllerID + volObject.Status.CapacityBytes = capBytes + volObject.Status.Drives = volObject.Spec.Drives + volObject.Status.Identifiers.DurableName = durableName + volObject.Status.Identifiers.DurableNameFormat = durableNameFormat + err := r.Client.Status().Update(ctx, volObject) + if err != nil { + l.LogWithFields(ctx).Errorf("Error: Updating Status of %s Volume : %s", volObject.Status.VolumeName, err.Error()) + } + time.Sleep(time.Duration(30) * time.Second) +} + +// UpdateEventsubscriptionStatus will update the status of event subscription object +func (r *CommonReconciler) UpdateEventsubscriptionStatus(ctx context.Context, eventsubObj *infraiov1.Eventsubscription, eventsubscriptionDetails map[string]interface{}, originResources []string) { + eventsubObj.Status.ID = eventsubscriptionDetails["Id"].(string) + eventsubObj.Status.Destination = eventsubscriptionDetails["Destination"].(string) + eventsubObj.Status.Protocol = eventsubscriptionDetails["Protocol"].(string) + eventsubObj.Status.SubscriptionType = eventsubscriptionDetails["SubscriptionType"].(string) + eventsubObj.Status.Context = eventsubscriptionDetails["Context"].(string) + + if name, ok := eventsubscriptionDetails["Name"].(string); ok { + eventsubObj.Status.Name = name + } + if eventTypes, ok := eventsubscriptionDetails["EventTypes"].([]interface{}); ok { + eventsubObj.Status.EventTypes = ConvertInterfaceToStringArray(eventTypes) + } + if messageIDs, ok := eventsubscriptionDetails["MessageIds"].([]interface{}); ok { + eventsubObj.Status.MessageIds = ConvertInterfaceToStringArray(messageIDs) + } + + if resourceTypes, ok := eventsubscriptionDetails["ResourceTypes"].([]interface{}); ok { + eventsubObj.Status.ResourceTypes = ConvertInterfaceToStringArray(resourceTypes) + } + if len(originResources) > 0 { + eventsubObj.Status.OriginResources = originResources + } + + err := r.Client.Status().Update(ctx, eventsubObj) + if err != nil { + l.LogWithFields(ctx).Errorf("Error: Updating eventsubscription status for %s: %s", eventsubObj.Status.Name, err.Error()) + } +} + +// ---------------------------------GET UPDATED OBJECT----------------------------------- +// getUpdatedBmcObject used to get updated BMC object +func (r *CommonReconciler) GetUpdatedBmcObject(ctx context.Context, ns types.NamespacedName, bmcObj *infraiov1.Bmc) { + err := r.Client.Get(ctx, ns, bmcObj) + if err != nil { + l.LogWithFields(ctx).Error(fmt.Sprintf("Error fetching updated %s BMC Object: %s", bmcObj.Spec.BmcDetails.Address, err.Error())) + } +} + +// GetUpdatedOdimObject gets the updated Odim object +func (r *CommonReconciler) GetUpdatedOdimObject(ctx context.Context, ns types.NamespacedName, odimObj *infraiov1.Odim) { + err := r.Client.Get(ctx, ns, odimObj) + if err != nil { + l.LogWithFields(ctx).Error("Error fetching updated Object of ODIM" + err.Error()) + } +} + +// GetUpdatedVolumeObject used to get updated BMC object +func (r *CommonReconciler) GetUpdatedVolumeObject(ctx context.Context, ns types.NamespacedName, volObj *infraiov1.Volume) { + err := r.Client.Get(ctx, ns, volObj) + if err != nil { + l.LogWithFields(ctx).Error("Error fetching updated Volume Object:", err.Error()) + } +} + +// ----------------------------- DELETE OBJECT -------------------------- + +// DeleteBmcObject will delete the bmc object from operator +func (r *CommonReconciler) DeleteBmcObject(ctx context.Context, bmcObj *infraiov1.Bmc) { + err := r.Client.Delete(ctx, bmcObj) + if err != nil { + l.LogWithFields(ctx).Error(fmt.Sprintf("BMC object %s not deleted", bmcObj.ObjectMeta.Name), err) + } + l.LogWithFields(ctx).Info(fmt.Sprintf("BMC object %s deleted successfully", bmcObj.ObjectMeta.Name)) +} + +// DeleteVolumeObject will delete the volume object from operator +func (r *CommonReconciler) DeleteVolumeObject(ctx context.Context, volObj *infraiov1.Volume) { + err := r.Client.Delete(ctx, volObj) + if err != nil { + l.LogWithFields(ctx).Error(fmt.Sprintf("Volume object %s not deleted", volObj.Status.VolumeID), err) + } + l.LogWithFields(ctx).Info(fmt.Sprintf("Volume object %s deleted successfully", volObj.Status.VolumeID)) +} + +// GetUpdatedFirmwareObject used to get updated BMC object +func (r *CommonReconciler) GetUpdatedFirmwareObject(ctx context.Context, ns types.NamespacedName, firmObj *infraiov1.Firmware) { + err := r.Client.Get(ctx, ns, firmObj) + if err != nil { + l.LogWithFields(ctx).Error("Error fetching updated Firmware Object:", err.Error()) + } + +} + +// GetUpdatedEventsubscriptionObjects used to get updated BMC object +func (r *CommonReconciler) GetUpdatedEventsubscriptionObjects(ctx context.Context, ns types.NamespacedName, eventSubObj *infraiov1.Eventsubscription) { + err := r.Client.Get(ctx, ns, eventSubObj) + if err != nil { + l.LogWithFields(ctx).Error("Error fetching updated Eventsubscription Object:", err.Error()) + } +} + +// -----------------------------GET OBJECT DETAILS-------------------------- +// getSecret returns the username,password,authenticationType for a particular secret +func (r *CommonReconciler) GetObjectSecret(ctx context.Context, secret *corev1.Secret, secretName, rootdir string) (string, string, string) { + + ns := types.NamespacedName{Namespace: config.Data.Namespace, Name: secretName} + err := r.Client.Get(ctx, ns, secret) + if err != nil { + l.LogWithFields(ctx).Error(fmt.Sprintf("Error fetching %s secret", secretName) + err.Error()) + } + authType := string(secret.Type) + user := string(secret.Data["username"]) + pass := string(secret.Data["password"]) + return user, pass, authType +} + +// getHostPort returns host,port of odim +func GetHostPort(ctx context.Context, uri string) (string, string, error) { + hostport, err := url.Parse(uri) + if err != nil { + l.LogWithFields(ctx).Error("Error: Getting host and port of ODIM" + err.Error()) + return "", "", errors.New("error: Retriving host/port of ODIM") + } + host, port, _ := net.SplitHostPort(hostport.Host) + return host, port, nil +} + +// GetEventSubscriptionObjectName will parse and return eventsubscription object name which will be supported by metadata.name +func (r *CommonReconciler) GetEventSubscriptionObjectName(ctx context.Context, destination, name, ns string) string { + destinationIP := strings.Split(destination, "/") + var subscriptionObjName string + if name != "" { + subscriptionObj := r.GetEventsubscriptionObject(ctx, constants.MetadataName, strings.ToLower(name), ns) + if subscriptionObj != nil { + subscriptionObjName = strings.Replace(destinationIP[2], ":", ".", 1) + } else { + subscriptionObjName = strings.ToLower(name) + } + } else { + subscriptionObjName = strings.Replace(destinationIP[2], ":", ".", 1) + } + return subscriptionObjName +} diff --git a/controllers/utils/pem.go b/controllers/utils/pem.go new file mode 100644 index 0000000..c805657 --- /dev/null +++ b/controllers/utils/pem.go @@ -0,0 +1,108 @@ +//(C) Copyright [2022] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +package controllers + +import ( + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "errors" + "fmt" + "sync" +) + +var ( + // MuxLock is used for avoiding race conditions + MuxLock = &sync.Mutex{} +) + +func ExportRsaPrivateKeyAsPemStr(privkey *rsa.PrivateKey) string { + + privkey_bytes, err := x509.MarshalPKCS8PrivateKey(privkey) + if err != nil { + privkey_bytes = x509.MarshalPKCS1PrivateKey(privkey) + } + privkey_pem := pem.EncodeToMemory( + &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: privkey_bytes, + }, + ) + return string(privkey_pem) +} + +func ExportRsaPublicKeyAsPemStr(pubkey *rsa.PublicKey) (string, error) { + pubkey_bytes, err := x509.MarshalPKIXPublicKey(pubkey) + if err != nil { + return "", err + } + pubkey_pem := pem.EncodeToMemory( + &pem.Block{ + Type: "RSA PUBLIC KEY", + Bytes: pubkey_bytes, + }, + ) + + return string(pubkey_pem), nil +} + +func ParseRsaPublicKeyFromPemStr(pubPEM string) (*rsa.PublicKey, error) { + block, _ := pem.Decode([]byte(pubPEM)) + if block == nil { + return nil, errors.New("failed to parse PEM block containing the key") + } + + pub, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return nil, err + } + + switch pub := pub.(type) { + case *rsa.PublicKey: + return pub, nil + default: + break // fall through + } + return nil, errors.New("Key type is not RSA") +} + +func ParseRsaPrivateKeyFromPemStr(privPEM string) (*rsa.PrivateKey, error) { + MuxLock.Lock() + defer MuxLock.Unlock() + block, _ := pem.Decode([]byte(privPEM)) + if block == nil { + return nil, errors.New("failed to parse PEM block containing the key") + } + enc := x509.IsEncryptedPEMBlock(block) + b := block.Bytes + var priv *rsa.PrivateKey + if enc { + bb, err := x509.DecryptPEMBlock(block, nil) + if err != nil { + return nil, fmt.Errorf("error while trying to decrypt pem block: %v", err) + } + b = bb + } + priv, err := x509.ParsePKCS1PrivateKey(b) + if err != nil { + pkcs8Key, err := x509.ParsePKCS8PrivateKey(b) + if err != nil { + return nil, fmt.Errorf("error while parsing private key %v", err) + } + priv = pkcs8Key.(*rsa.PrivateKey) + } + + return priv, nil +} diff --git a/controllers/utils/utils.go b/controllers/utils/utils.go new file mode 100644 index 0000000..dc9711a --- /dev/null +++ b/controllers/utils/utils.go @@ -0,0 +1,115 @@ +//(C) Copyright [2023] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +package controllers + +import ( + "crypto/rsa" + "reflect" + "regexp" + "sort" + "strings" + + infraiov1 "github.com/ODIM-Project/BMCOperator/api/v1" +) + +var ( + // PrivateKey holds the value for PrivateKey + PrivateKey *rsa.PrivateKey + // PublicKey holds the value for PublicKey + PublicKey *rsa.PublicKey + // RootCA contains the RootCA value + RootCA []byte +) + +// ConvertToArray is used to convert interface to array of string +func ConvertToArray(data []interface{}) []string { + aString := make([]string, len(data)) + for i, v := range data { + aString[i] = v.(string) + } + return aString +} + +// CompareArray is used to compare two arrays +func CompareArray(arr1, arr2 []string) bool { + if len(arr1) != len(arr2) { + return false + } + + arr1Copy := make([]string, len(arr1)) + arr2Copy := make([]string, len(arr2)) + + copy(arr1Copy, arr1) + copy(arr2Copy, arr2) + + sort.Strings(arr1Copy) + sort.Strings(arr2Copy) + + return reflect.DeepEqual(arr1Copy, arr2Copy) +} + +// ContainsValue checks if array contains the string value +func ContainsValue(elems []string, v string) bool { + for _, s := range elems { + if v == s { + return true + } + } + return false +} + +// RemoveSpecialChar used to remove special char from ID to get bios object name +func RemoveSpecialChar(str string) string { + re, _ := regexp.Compile(`[^a-zA-Z0-9 ]+`) + str = re.ReplaceAllString(str, " ") + biosName := strings.ReplaceAll(str, " ", "") + biosName = strings.ToLower(biosName) + return biosName +} + +// CaseInsensitiveContains used to check if vendor is available +func CaseInsensitiveContains(s string) (string, bool) { + for _, substr := range infraiov1.Vendor { + s, substr = strings.ToUpper(s), strings.ToUpper(substr) + if strings.Contains(s, substr) { + return substr, true + } + } + return "", false +} + +// CompareMaps function compares values of two maps and return true if equal else returns false +func CompareMaps(m1, m2 map[string]string) (bool, map[string]string) { + distinctMap := make(map[string]string) + if reflect.DeepEqual(m1, m2) { + return true, nil + } else { + for k, v := range m1 { + if m2[k] != v { + distinctMap[k] = v + } + } + } + return false, distinctMap +} + +// ConvertInterfaceToStringArray will convert and return an array of interface into array of string +func ConvertInterfaceToStringArray(items []interface{}) []string { + var values []string + for _, item := range items { + values = append(values, item.(string)) + } + return values +} diff --git a/controllers/volume/volumeUtils.go b/controllers/volume/volumeUtils.go new file mode 100644 index 0000000..ee23f94 --- /dev/null +++ b/controllers/volume/volumeUtils.go @@ -0,0 +1,58 @@ +//(C) Copyright [2023] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +package controllers + +import ( + "context" + + infraiov1 "github.com/ODIM-Project/BMCOperator/api/v1" + common "github.com/ODIM-Project/BMCOperator/controllers/common" + restclient "github.com/ODIM-Project/BMCOperator/controllers/restclient" + utils "github.com/ODIM-Project/BMCOperator/controllers/utils" + "k8s.io/apimachinery/pkg/types" +) + +// VolFinalizer: finalizer for volume object +const VolFinalizer = "infra.io.volume/finalizer" + +// VolumeInterface declares method signatures to be defined by Volume utils +type VolumeInterface interface { + CreateVolume(bmcObj *infraiov1.Bmc, dispName string, namespacedName types.NamespacedName, updateVolumeObject bool, commonRec utils.ReconcilerInterface) (bool, error) + GetVolumeRequestPayload(systemID, dispName string) []byte + UpdateVolumeStatusAndClearSpec(bmcObj *infraiov1.Bmc, dispName string) (bool, string) + DeleteVolume(bmcObj *infraiov1.Bmc) bool + UpdateVolumeObject(volObj *infraiov1.Volume) +} + +type volumeUtils struct { + ctx context.Context + volumeRestClient restclient.RestClientInterface + commonRec utils.ReconcilerInterface + commonUtil common.CommonInterface + volObj *infraiov1.Volume + namespace string +} + +// volume request payload struct +type requestPayload struct { + RAIDType string `json:"RAIDType"` + Links link `json:"Links"` + DisplayName string `json:"DisplayName"` + ApplyTime string `json:"@Redfish.OperationApplyTime"` +} + +type link struct { + Drives []map[string]string `json:"Drives"` +} diff --git a/controllers/volume/volume_controller.go b/controllers/volume/volume_controller.go new file mode 100644 index 0000000..4a88582 --- /dev/null +++ b/controllers/volume/volume_controller.go @@ -0,0 +1,343 @@ +//(C) Copyright [2022] Hewlett Packard Enterprise Development LP +// +//Licensed under the Apache License, Version 2.0 (the "License"); you may +//not use this file except in compliance with the License. You may obtain +//a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +//WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +//License for the specific language governing permissions and limitations +// under the License. + +package controllers + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "os" + "reflect" + "sort" + "strconv" + "strings" + "time" + + Error "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + infraiov1 "github.com/ODIM-Project/BMCOperator/api/v1" + "github.com/ODIM-Project/BMCOperator/config/constants" + common "github.com/ODIM-Project/BMCOperator/controllers/common" + restclient "github.com/ODIM-Project/BMCOperator/controllers/restclient" + utils "github.com/ODIM-Project/BMCOperator/controllers/utils" + l "github.com/ODIM-Project/BMCOperator/logs" + "github.com/google/uuid" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +var podName = os.Getenv("POD_NAME") + +// VolumeReconciler reconciles a Volume object +type VolumeReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=infra.io.odimra,resources=volumes,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=infra.io.odimra,resources=volumes/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=infra.io.odimra,resources=volumes/finalizers,verbs=update + +// 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 Volume 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.11.0/pkg/reconcile +func (r *VolumeReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + //common reconciler ddeclaration + transactionId := uuid.New() + ctx = l.CreateContextForLogging(ctx, transactionId.String(), constants.BmcOperator, constants.VolumeActionID, constants.VolumeActionName, podName) + commonRec := utils.GetCommonReconciler(r.Client, r.Scheme) + //create rest client + odimObj := commonRec.GetOdimObject(ctx, constants.MetadataName, "odim", req.Namespace) + volRestClient, err := restclient.NewRestClient(ctx, odimObj, commonRec.(*utils.CommonReconciler), constants.BMCOPERATOR) + if err != nil { + l.LogWithFields(ctx).Errorf("Failed to get rest client for Volume: %s", err.Error()) + return ctrl.Result{}, err + } + volObj := &infraiov1.Volume{} + // Fetch the Volume instance. + err = r.Get(ctx, req.NamespacedName, volObj) + if err != nil { + if Error.IsNotFound(err) { + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } + //get volumeUtil object + volUtil := GetVolumeUtils(ctx, volRestClient, commonRec, volObj, req.Namespace) + // get bmcname and volume name + var bmcName, dispName string + // ex: 10.24.0.14.volume1 + strArr := strings.Split(volObj.ObjectMeta.Name, ".") + dispName = strArr[len(strArr)-1] // last name is volume name : volume1 + bmcName = strings.Join(strArr[:len(strArr)-1], ".") //first name is bmc name : 10.24.0.14 + bmcObj := commonRec.GetBmcObject(ctx, constants.MetadataName, bmcName, req.Namespace) + if bmcObj == nil { + l.LogWithFields(ctx).Info(fmt.Sprintf("Could not find %s BMC object, hence cannot create volume", bmcName)) + err = r.Delete(ctx, volObj) + if err != nil { + l.LogWithFields(ctx).Error(fmt.Sprintf("Error: Deleting %s Volume object: %s", dispName, err.Error())) + return ctrl.Result{}, err + } + return ctrl.Result{}, nil + } + //Checking for deletion + isVolumeMarkedToBeDeleted := volObj.GetDeletionTimestamp() != nil + if isVolumeMarkedToBeDeleted { + if controllerutil.ContainsFinalizer(volObj, VolFinalizer) { + isVolumeDeleted := volUtil.DeleteVolume(bmcObj) + if isVolumeDeleted { + controllerutil.RemoveFinalizer(volObj, VolFinalizer) + err := r.Update(ctx, volObj) + if err != nil { + return ctrl.Result{}, err + } + } else { + l.LogWithFields(ctx).Info(fmt.Sprintf("Volume %s not deleted! Retrying..", volObj.Status.VolumeName)) + return ctrl.Result{Requeue: true}, nil // Calling Reconcile again for delete to execute + } + } + return ctrl.Result{}, nil + } + + if reflect.DeepEqual(volObj.Spec, infraiov1.VolumeSpec{}) { + return ctrl.Result{}, nil + } + if volObj.Spec.StorageControllerID != "" && volObj.Spec.RAIDType != "" && len(volObj.Spec.Drives) != 0 { + // Add finalizer for this CR + if !controllerutil.ContainsFinalizer(volObj, VolFinalizer) { + controllerutil.AddFinalizer(volObj, VolFinalizer) + err := r.Client.Update(ctx, volObj) + if err != nil { + l.LogWithFields(ctx).Error(fmt.Sprintf("Error: Updating %s Volume object: %s", volObj.Status.VolumeName, err.Error())) + return ctrl.Result{}, err + } + time.Sleep(time.Duration(constants.SleepTime) * time.Second) + } + //create volume + volCreated, volErr := volUtil.CreateVolume(bmcObj, dispName, req.NamespacedName, true, commonRec) // setting updateVolumeObject = true since we are creating volume in ODIM and creating object first time + if !volCreated { + return ctrl.Result{}, volErr + } + } else { + l.LogWithFields(ctx).Info("Please enter all the details required for volume creation") + } + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *VolumeReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&infraiov1.Volume{}). + WithEventFilter(utils.IgnoreStatusUpdate()). + Complete(r) +} + +// CreateVolume will create volume in ODIM +func (vu *volumeUtils) CreateVolume(bmcObj *infraiov1.Bmc, dispName string, namespacedName types.NamespacedName, updateVolumeObject bool, commonRec utils.ReconcilerInterface) (bool, error) { + if updateVolumeObject { + vu.commonRec.GetUpdatedVolumeObject(vu.ctx, namespacedName, vu.volObj) + } + //update track status + volumeStatus := common.VolumeStatus{IsGettingCreated: true, IsGettingDeleted: false} + vu.updateTrackVolume(bmcObj, volumeStatus) + //create vol req body + volReqBody := vu.GetVolumeRequestPayload(bmcObj.Status.BmcSystemID, dispName) + var resp *http.Response + var err error + //send request for vol creation + if vu.volObj.Spec.StorageControllerID == "" { // check added for reconcile volumes case + resp, err = vu.volumeRestClient.Post(fmt.Sprintf("/redfish/v1/Systems/%s/Storage/%s/Volumes", bmcObj.Status.BmcSystemID, vu.volObj.Status.StorageControllerID), "Posting volume creation payload..", volReqBody) + } else { + resp, err = vu.volumeRestClient.Post(fmt.Sprintf("/redfish/v1/Systems/%s/Storage/%s/Volumes", bmcObj.Status.BmcSystemID, vu.volObj.Spec.StorageControllerID), "Posting volume creation payload..", volReqBody) + } + if err != nil { + l.LogWithFields(vu.ctx).Error("error while creating volume:" + err.Error()) + return false, err + } + if resp.StatusCode == http.StatusAccepted { + // BmcObj name is sent as "" + done, _ := vu.commonUtil.MoniteringTaskmon(resp.Header, vu.ctx, common.CREATEVOLUME, vu.volObj.ObjectMeta.Name) + if done { + commonRec.UpdateBmcObjectOnReset(vu.ctx, bmcObj, fmt.Sprintf("%s %s Volume", constants.PendingForResetEvent, dispName)) + return true, nil + } + } + l.LogWithFields(vu.ctx).Info(fmt.Sprintf("Could not create %s volume, try again", vu.volObj.ObjectMeta.Name)) + if updateVolumeObject { + controllerutil.RemoveFinalizer(vu.volObj, VolFinalizer) + err = vu.commonRec.GetCommonReconcilerClient().Update(vu.ctx, vu.volObj) + if err != nil { + l.LogWithFields(vu.ctx).Error("error while updating volume object:" + err.Error()) + } + err = vu.commonRec.GetCommonReconcilerClient().Delete(vu.ctx, vu.volObj) + if err != nil { + l.LogWithFields(vu.ctx).Error("error while deleting volume object:" + err.Error()) + } + } + return false, err +} + +// updateVolumeStatusAndClearSpec will clear the spec and upidate the status fields of volume object +func (vu *volumeUtils) UpdateVolumeStatusAndClearSpec(bmcObj *infraiov1.Bmc, dispName string) (bool, string) { + getAllVolumesResp, sCode, err := vu.volumeRestClient.Get(fmt.Sprintf("/redfish/v1/Systems/%s/Storage/%s/Volumes", bmcObj.Status.BmcSystemID, vu.volObj.Spec.StorageControllerID), "Fetching all volumes..") + volumeNotUpdatedMsg := "Could not update volume status, but volume is created" + if err != nil { + l.LogWithFields(vu.ctx).Error("error while fetching all volumes:" + err.Error()) + return false, volumeNotUpdatedMsg + } else if sCode != http.StatusOK { + l.LogWithFields(vu.ctx).Info("Failed to get all volumes, try again.") + return false, volumeNotUpdatedMsg + } + volumesList := getAllVolumesResp["Members"].([]interface{}) + for _, vol := range volumesList { + volURL := vol.(map[string]interface{})["@odata.id"].(string) + getEachVolResp, sCode, err := vu.volumeRestClient.Get(volURL, fmt.Sprintf("Fetching %s volume details..", volURL[len(volURL)-1:])) + if err != nil { + l.LogWithFields(vu.ctx).Error(fmt.Sprintf("error while fetching %s volume details:", volURL[len(volURL)-1:]) + err.Error()) + return false, volumeNotUpdatedMsg + } else if sCode != http.StatusOK { + l.LogWithFields(vu.ctx).Info(fmt.Sprintf("Failed to get %s volume details, try again.", volURL[len(volURL)-1:])) + return false, volumeNotUpdatedMsg + } else { + //getting all drives in the volume + var found = false + drives := []int{} + driveLinks := getEachVolResp["Links"].(map[string]interface{})["Drives"].([]interface{}) + for _, dl := range driveLinks { + drive := dl.(map[string]interface{})["@odata.id"].(string) + driveID, err := strconv.Atoi(drive[len(drive)-1:]) + if err != nil { + l.Log.Info("Could not convert drive id to Integer") + } + drives = append(drives, driveID) + } + sort.Ints(drives) + sort.Ints(vu.volObj.Spec.Drives) + if getEachVolResp["Name"].(string) == dispName && getEachVolResp["RAIDType"] == vu.volObj.Spec.RAIDType && reflect.DeepEqual(drives, vu.volObj.Spec.Drives) { + vu.volObj.ObjectMeta.Annotations["odata.id"] = getEachVolResp["@odata.id"].(string) + err = vu.commonRec.GetCommonReconcilerClient().Update(vu.ctx, vu.volObj) + if err != nil { + l.LogWithFields(vu.ctx).Error(fmt.Sprintf("Error: Updating volume object %s annotations of bmc: %s", dispName, err.Error())) + } + identifiers := getEachVolResp["Identifiers"].([]interface{}) + var durableName, durableNameFormat string + for _, id := range identifiers { + durableName = id.(map[string]interface{})["DurableName"].(string) + durableNameFormat = id.(map[string]interface{})["DurableNameFormat"].(string) + } + vu.commonRec.UpdateVolumeStatus(vu.ctx, vu.volObj, getEachVolResp["Id"].(string), dispName, fmt.Sprintf("%f", getEachVolResp["CapacityBytes"].(float64)), durableName, durableNameFormat) + vu.volObj.Spec = infraiov1.VolumeSpec{} + err := vu.commonRec.GetCommonReconcilerClient().Update(vu.ctx, vu.volObj) + if err != nil { + l.LogWithFields(vu.ctx).Error(fmt.Sprintf("Error: Updating %s Volume object: %s", vu.volObj.Status.VolumeName, err.Error())) + } + found = true + return true, "Successfully updated volume object status" + } + if !found { + vu.commonRec.DeleteVolumeObject(vu.ctx, vu.volObj) + return false, "Could not find the volume created in ODIM after reset, deleting the volume object, try creating again" + } + } + } + return false, volumeNotUpdatedMsg +} + +// getVolumeRequestPayload prepares payload for volume creation +func (vu *volumeUtils) GetVolumeRequestPayload(systemID, dispName string) []byte { + l.Log.Info("Creating volume request payload..") + linkBody := link{} + var reqBody requestPayload + //check added to see if volume creation is triggered from reconcile, where volume object will already be present and we have to fetch details from status + if vu.volObj.Spec.RAIDType == "" && vu.volObj.Spec.Drives == nil && vu.volObj.Spec.StorageControllerID == "" { + for _, driveId := range vu.volObj.Status.Drives { + // TODO : check if the drive is present and then proceed + driveDet := map[string]string{"@odata.id": fmt.Sprintf("/redfish/v1/Systems/%s/Storage/%s/Drives/%s", systemID, vu.volObj.Status.StorageControllerID, strconv.Itoa(driveId))} + linkBody.Drives = append(linkBody.Drives, driveDet) + } + reqBody = requestPayload{RAIDType: vu.volObj.Status.RAIDType, Links: linkBody, DisplayName: dispName, ApplyTime: constants.ApplyTime} + } else { // when volume is created via operator directly, it will pass through this case + for _, driveId := range vu.volObj.Spec.Drives { + // TODO : check if the drive is present and then proceed + driveDet := map[string]string{"@odata.id": fmt.Sprintf("/redfish/v1/Systems/%s/Storage/%s/Drives/%s", systemID, vu.volObj.Spec.StorageControllerID, strconv.Itoa(driveId))} + linkBody.Drives = append(linkBody.Drives, driveDet) + } + reqBody = requestPayload{RAIDType: vu.volObj.Spec.RAIDType, Links: linkBody, DisplayName: dispName, ApplyTime: constants.ApplyTime} + } + marshalBody, err := json.Marshal(reqBody) + if err != nil { + l.LogWithFields(vu.ctx).Error("Failed to marshal volume request payload" + err.Error()) + } + return marshalBody +} + +// DeleteVolume will delete volume in ODIM +func (vu *volumeUtils) DeleteVolume(bmcObj *infraiov1.Bmc) bool { + //update volume status + volumeStatus := common.VolumeStatus{IsGettingCreated: false, IsGettingDeleted: true} + vu.updateTrackVolume(bmcObj, volumeStatus) + deleteResp, err := vu.volumeRestClient.Delete(fmt.Sprintf("/redfish/v1/Systems/%s/Storage/%s/Volumes/%s", bmcObj.Status.BmcSystemID, vu.volObj.Status.StorageControllerID, vu.volObj.Status.VolumeID), fmt.Sprintf("Deleting %s volume..", vu.volObj.Status.VolumeName)) + if err != nil { + l.LogWithFields(vu.ctx).Error(fmt.Sprintf("error while deleting %s volume:", vu.volObj.Status.VolumeName) + err.Error()) + return false + } + if deleteResp.StatusCode == http.StatusAccepted { + //monitering taskmon + done, _ := vu.commonUtil.MoniteringTaskmon(deleteResp.Header, vu.ctx, common.DELETEVOLUME, vu.volObj.ObjectMeta.Name) + if done { + return true + } + } + l.LogWithFields(vu.ctx).Info(fmt.Sprintf("Could not delete %s volume, try again", vu.volObj.ObjectMeta.Name)) + return false +} + +// updateTrackVolume will update the TrackVolume map for polling +func (vu *volumeUtils) updateTrackVolume(bmcObj *infraiov1.Bmc, volumeStatus common.VolumeStatus) { + var bmcStorageVolume common.BmcStorageControllerVolumesStatus + if vu.volObj.Spec.StorageControllerID != "" { // While creation of volume,spec will be present,hence taking storageController from it,volume id is not present,hence using volObj name + bmcStorageVolume = common.BmcStorageControllerVolumesStatus{Bmc: bmcObj.ObjectMeta.Name, StorageController: vu.volObj.Spec.StorageControllerID, VolumeID: vu.volObj.ObjectMeta.Name} + } else { // when spec is empty, it means volume is already present,might be for deletion step,hence taking storageController and volume ID from status + bmcStorageVolume = common.BmcStorageControllerVolumesStatus{Bmc: bmcObj.ObjectMeta.Name, StorageController: vu.volObj.Status.StorageControllerID, VolumeID: vu.volObj.Status.VolumeID} + } + common.TrackVolumeStatus[bmcStorageVolume] = volumeStatus + l.LogWithFields(vu.ctx).Info("Track volume status: ", common.TrackVolumeStatus) +} + +func (vu *volumeUtils) UpdateVolumeObject(volObject *infraiov1.Volume) { + vu.volObj = volObject +} + +// GetVolumeUtils will return volume utils object +func GetVolumeUtils(ctx context.Context, restClient restclient.RestClientInterface, commonRec utils.ReconcilerInterface, volObj *infraiov1.Volume, ns string) VolumeInterface { + return &volumeUtils{ + ctx: ctx, + volumeRestClient: restClient, + commonRec: commonRec, + volObj: volObj, + commonUtil: common.GetCommonUtils(restClient), + namespace: ns, + } +} diff --git a/dockerfiles/Dockerfile.bmcoperator b/dockerfiles/Dockerfile.bmcoperator new file mode 100644 index 0000000..bcc5d07 --- /dev/null +++ b/dockerfiles/Dockerfile.bmcoperator @@ -0,0 +1,39 @@ +# Build the manager binary +FROM golang:1.19.5 as builder + +ENV GO111MODULE=auto +WORKDIR /bmc-operator +# Copy the Go Modules manifests +COPY go.mod . +COPY go.sum . +# cache deps before building and copying source so that we don't need to re-download as much +# and so that source changes don't invalidate our downloaded layer +RUN go mod download + +# Copy the go source +COPY main/main.go main/main.go +COPY api/v1 api/v1 +COPY controllers/ controllers/ +COPY logs/ logs/ +COPY config/ config/ +COPY dockerfiles/scripts/ dockerfiles/scripts/ +# Build +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o manager main/main.go + +# Use distroless as minimal base image to package the manager binary +# Refer to https://github.com/GoogleContainerTools/distroless for more details +FROM ubuntu:22.04 + +WORKDIR / +COPY --from=builder /bmc-operator/manager . +COPY --from=builder /bmc-operator/config/keys /bmc-operator/config/keys +COPY --from=builder /bmc-operator/dockerfiles/scripts/start_bmc_operator.sh . +RUN mkdir /var/log/operator_logs +RUN chown -R 2021:2021 /var/log/operator_logs +RUN groupadd -r -g 2021 odimra && /usr/sbin/useradd -s /bin/bash -u 2021 -m -d /home/odimra -r -g odimra odimra +RUN mkdir /etc/config && chown 2021:2021 /etc/config +RUN chown -R 2021:2021 /bmc-operator/config/keys +RUN chown 2021:2021 start_bmc_operator.sh +RUN chmod 755 start_bmc_operator.sh +USER 2021:2021 + # Entrypoint is taken from manager.yaml file diff --git a/dockerfiles/scripts/start_bmc_operator.sh b/dockerfiles/scripts/start_bmc_operator.sh new file mode 100644 index 0000000..b188281 --- /dev/null +++ b/dockerfiles/scripts/start_bmc_operator.sh @@ -0,0 +1,74 @@ +#!/bin/bash +# (C) Copyright [2020] Hewlett Packard Enterprise Development LP +# +# 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. + +declare PID=0 +declare OWN_PID=$$ + +sigterm_handler() +{ + if [[ $PID -ne 0 ]]; then + sleep 1 + kill -9 $PID + wait "$PID" 2>/dev/null + fi + exit 0 +} + +# create a signal trap +create_signal_trap() +{ + trap 'echo "[$(date)] -- INFO -- SIGTERM received for BMC Operator, initiating shut down"; sigterm_handler' SIGTERM +} + +# keep the script running till SIGTERM is received +run_forever() +{ + wait +} + +start_bmc_operator() +{ + nohup /manager >> /var/log/operator_logs/bmc_operator.log 2>&1 & + PID=$! + sleep 3 + +} + +monitor_process() +{ + while true; do + pid=$(pgrep -fc manager 2> /dev/null) + if [[ $? -ne 0 ]] || [[ $pid -gt 1 ]]; then + echo "[$(date)] -- ERROR -- BMC Operator not found running, exiting" + kill -15 ${OWN_PID} + exit 1 + fi + sleep 5 + done & +} + +############################################## +############### MAIN ####################### +############################################## + +start_bmc_operator + +create_signal_trap + +monitor_process + +run_forever + +exit 0 diff --git a/docs/images/bmc_op_architecture.png b/docs/images/bmc_op_architecture.png new file mode 100644 index 0000000..2907782 Binary files /dev/null and b/docs/images/bmc_op_architecture.png differ diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..e1f5517 --- /dev/null +++ b/go.mod @@ -0,0 +1,113 @@ +module github.com/ODIM-Project/BMCOperator + +go 1.19 + +require ( + github.com/kataras/iris/v12 v12.1.8 + github.com/onsi/ginkgo v1.16.5 + github.com/onsi/gomega v1.17.0 + github.com/sirupsen/logrus v1.8.1 + gopkg.in/yaml.v2 v2.4.0 + k8s.io/api v0.23.0 + k8s.io/apimachinery v0.23.0 + k8s.io/client-go v0.23.0 + sigs.k8s.io/controller-runtime v0.11.0 +) + +require ( + github.com/BurntSushi/toml v0.3.1 // indirect + github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 // indirect + github.com/CloudyKit/jet/v3 v3.0.0 // indirect + github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398 // indirect + github.com/ajg/form v1.5.1 // indirect + github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible // indirect + github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 // indirect + github.com/fatih/structs v1.1.0 // indirect + github.com/google/go-querystring v1.1.0 // indirect + github.com/imkira/go-interpol v1.1.0 // indirect + github.com/iris-contrib/blackfriday v2.0.0+incompatible // indirect + github.com/iris-contrib/jade v1.1.3 // indirect + github.com/iris-contrib/pongo2 v0.0.1 // indirect + github.com/iris-contrib/schema v0.0.1 // indirect + github.com/kataras/golog v0.0.10 // indirect + github.com/kataras/pio v0.0.2 // indirect + github.com/kataras/sitemap v0.0.5 // indirect + github.com/klauspost/compress v1.16.3 // indirect + github.com/microcosm-cc/bluemonday v1.0.2 // indirect + github.com/moul/http2curl v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/ryanuber/columnize v2.1.0+incompatible // indirect + github.com/schollz/closestmatch v2.1.0+incompatible // indirect + github.com/sergi/go-diff v1.3.1 // indirect + github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect + github.com/stretchr/objx v0.5.0 // indirect + github.com/valyala/fasthttp v1.48.0 // indirect + github.com/xeipuuv/gojsonschema v1.2.0 // indirect + github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 // indirect + github.com/yudai/gojsondiff v1.0.0 // indirect + github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect + gopkg.in/ini.v1 v1.62.0 // indirect +) + +require ( + cloud.google.com/go v0.81.0 // indirect + github.com/Azure/go-autorest v14.2.0+incompatible // indirect + github.com/Azure/go-autorest/autorest v0.11.18 // indirect + github.com/Azure/go-autorest/autorest/adal v0.9.13 // indirect + github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect + github.com/Azure/go-autorest/logger v0.2.1 // indirect + github.com/Azure/go-autorest/tracing v0.6.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.1.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/evanphx/json-patch v4.12.0+incompatible // indirect + github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect + github.com/fsnotify/fsnotify v1.5.1 + github.com/go-logr/logr v1.2.0 // indirect + github.com/go-logr/zapr v1.2.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/google/go-cmp v0.5.9 // indirect + github.com/google/gofuzz v1.1.0 // indirect + github.com/google/uuid v1.3.0 + github.com/googleapis/gnostic v0.5.5 // indirect + github.com/imdario/mergo v0.3.12 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/nxadm/tail v1.4.8 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/prometheus/client_golang v1.11.0 // indirect + github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/common v0.28.0 // indirect + github.com/prometheus/procfs v0.6.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/testify v1.8.3 + go.uber.org/atomic v1.7.0 // indirect + go.uber.org/multierr v1.6.0 // indirect + go.uber.org/zap v1.19.1 // indirect + golang.org/x/crypto v0.7.0 // indirect + golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df + golang.org/x/net v0.8.0 // indirect + golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect + golang.org/x/sys v0.6.0 // indirect + golang.org/x/term v0.6.0 // indirect + golang.org/x/text v0.8.0 // indirect + golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect + gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/protobuf v1.28.1 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/apiextensions-apiserver v0.23.0 // indirect + k8s.io/component-base v0.23.0 // indirect + k8s.io/klog/v2 v2.30.0 // indirect + k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect + k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b // indirect + sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.0 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..39a6d68 --- /dev/null +++ b/go.sum @@ -0,0 +1,1095 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0 h1:at8Tk2zUz63cLPR0JPWm5vp77pEZmzxEQBEfRKn1VV8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= +github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.11.18 h1:90Y4srNYrwOtAgVo3ndrQkTYn6kf1Eg/AjTFJ8Is2aM= +github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= +github.com/Azure/go-autorest/autorest/adal v0.9.13 h1:Mp5hbtOePIzM8pJVRa3YLrWWmZtoxRXqUEzCfJt3+/Q= +github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= +github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= +github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 h1:sR+/8Yb4slttB4vD+b9btVEnWgL3Q00OBTzVT8B9C0c= +github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= +github.com/CloudyKit/jet/v3 v3.0.0 h1:1PwO5w5VCtlUUl+KTOBsTGZlhjWkcybsGaAau52tOy8= +github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo= +github.com/Joker/hpp v1.0.0 h1:65+iuJYdRXv/XyN62C1uEmmOx3432rNG/rKlX6V7Kkc= +github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398 h1:WDC6ySpJzbxGWFh4aMxFFC28wwGp5pEuoTtvA4q/qQ4= +github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= +github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= +github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210826220005-b48c857c3a0e/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible h1:Ppm0npCCsmuR9oQaBtRuZcmILVE74aXE+AmrJj8L2ns= +github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= +github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +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/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= +github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= +github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= +github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 h1:clC1lXBpe2kTj2VHdaIu9ajZQe4kcEY9j0NsnDDBZ3o= +github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= +github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= +github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= +github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c= +github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= +github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= +github.com/gavv/httpexpect v2.0.0+incompatible h1:1X9kcRshkSKEjNJJxX9Y9mQ5BRfbxU5kORdjhlA1yX8= +github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= +github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= +github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-check/check v0.0.0-20180628173108-788fd7840127 h1:0gkP6mzaMqkmpcJYCFOLkIBwI7xFExG03bbkOkCvUPI= +github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v1.2.0 h1:QK40JKJyMdUDz+h+xvCsru/bJhvG0UxvePV0ufL/AcE= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/zapr v1.2.0 h1:n4JnPI1T3Qq1SFEi/F8rwLrZERp2bso19PJZDB9dayk= +github.com/go-logr/zapr v1.2.0/go.mod h1:Qa4Bsj2Vb+FAVeAKsLD8RLQ+YRJB8YDmOAKxaBQf7Ro= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= +github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/cel-go v0.9.0/go.mod h1:U7ayypeSkw23szu4GaQTPJGx66c20mx8JklMSxrmI1w= +github.com/google/cel-spec v0.6.0/go.mod h1:Nwjgxy5CbjlPrtCWjeDjUyKMl8w41YBYGjsyDdqk0xA= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= +github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= +github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk= +github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/iris-contrib/blackfriday v2.0.0+incompatible h1:o5sHQHHm0ToHUlAJSTjW9UWicjJSDDauOOQ2AHuIVp4= +github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI= +github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= +github.com/iris-contrib/jade v1.1.3 h1:p7J/50I0cjo0wq/VWVCDFd8taPJbuFC+bq23SniRFX0= +github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk= +github.com/iris-contrib/pongo2 v0.0.1 h1:zGP7pW51oi5eQZMIlGA3I+FHY9/HOQWDB+572yin0to= +github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g= +github.com/iris-contrib/schema v0.0.1 h1:10g/WnoRR+U+XXHWKBHeNy/+tZmM2kcAVGLOsz+yaDA= +github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kataras/golog v0.0.10 h1:vRDRUmwacco/pmBAm8geLn8rHEdc+9Z4NAr5Sh7TG/4= +github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8= +github.com/kataras/iris/v12 v12.1.8 h1:O3gJasjm7ZxpxwTH8tApZsvf274scSGQAUpNe47c37U= +github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE= +github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE= +github.com/kataras/pio v0.0.2 h1:6NAi+uPJ/Zuid6mrAKlgpbI11/zK/lV4B2rxWaJN98Y= +github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro= +github.com/kataras/sitemap v0.0.5 h1:4HCONX5RLgVy6G4RkYOV3vKNcma9p236LdGOipJsaFE= +github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0PcNQY= +github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8= +github.com/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s= +github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A= +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= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs= +github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.28.0 h1:vGVfV9KrDTvWt5boZO0I19g2E3CsWfpPPKZM9dt3mEw= +github.com/prometheus/common v0.28.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/columnize v2.1.0+incompatible h1:j1Wcmh8OrK4Q7GXY+V7SVSY8nUWQxHW5TkBe7YUl+2s= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/schollz/closestmatch v2.1.0+incompatible h1:Uel2GXEpJqOWBrlyI+oY9LTiyyjYS17cCYRqP13/SHk= +github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= +github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.48.0 h1:oJWvHb9BIZToTQS3MuQ2R3bJZiNSa2KiNdeI8A+79Tc= +github.com/valyala/fasthttp v1.48.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= +github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= +go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= +go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE= +go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc= +go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= +go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= +go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= +go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= +go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= +go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= +go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= +go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= +go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI= +go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME= +golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 h1:nt+Q6cXKz4MosCSpnbMtqiQ8Oz0pxTef2B4Vca2lvfk= +golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.6-0.20210820212750-d4cc65f0b2ff/go.mod h1:YD9qOF0M9xpSpdWTBbzEl5e/RnCefISl8E5Noe10jFM= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY= +gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201102152239-715cce707fb0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.23.0 h1:WrL1gb73VSC8obi8cuYETJGXEoFNEh3LU0Pt+Sokgro= +k8s.io/api v0.23.0/go.mod h1:8wmDdLBHBNxtOIytwLstXt5E9PddnZb0GaMcqsvDBpg= +k8s.io/apiextensions-apiserver v0.23.0 h1:uii8BYmHYiT2ZTAJxmvc3X8UhNYMxl2A0z0Xq3Pm+WY= +k8s.io/apiextensions-apiserver v0.23.0/go.mod h1:xIFAEEDlAZgpVBl/1VSjGDmLoXAWRG40+GsWhKhAxY4= +k8s.io/apimachinery v0.23.0 h1:mIfWRMjBuMdolAWJ3Fd+aPTMv3X9z+waiARMpvvb0HQ= +k8s.io/apimachinery v0.23.0/go.mod h1:fFCTTBKvKcwTPFzjlcxp91uPFZr+JA0FubU4fLzzFYc= +k8s.io/apiserver v0.23.0/go.mod h1:Cec35u/9zAepDPPFyT+UMrgqOCjgJ5qtfVJDxjZYmt4= +k8s.io/client-go v0.23.0 h1:vcsOqyPq7XV3QmQRCBH/t9BICJM9Q1M18qahjv+rebY= +k8s.io/client-go v0.23.0/go.mod h1:hrDnpnK1mSr65lHHcUuIZIXDgEbzc7/683c6hyG4jTA= +k8s.io/code-generator v0.23.0/go.mod h1:vQvOhDXhuzqiVfM/YHp+dmg10WDZCchJVObc9MvowsE= +k8s.io/component-base v0.23.0 h1:UAnyzjvVZ2ZR1lF35YwtNY6VMN94WtOnArcXBu34es8= +k8s.io/component-base v0.23.0/go.mod h1:DHH5uiFvLC1edCpvcTDV++NKULdYYU6pR9Tt3HIKMKI= +k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.30.0 h1:bUO6drIvCIsvZ/XFgfxoGFQU/a4Qkh0iAlvUR7vlHJw= +k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 h1:E3J9oCLlaobFUqsjG9DfKbP2BmgwBL2p7pn0A3dG9W4= +k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= +k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b h1:wxEMGetGMur3J1xuGLQY7GEQYg9bZxKn3tKo5k/eYcs= +k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.25/go.mod h1:Mlj9PNLmG9bZ6BHFwFKDo5afkpWyUISkb9Me0GnK66I= +sigs.k8s.io/controller-runtime v0.11.0 h1:DqO+c8mywcZLFJWILq4iktoECTyn30Bkj0CwgqMpZWQ= +sigs.k8s.io/controller-runtime v0.11.0/go.mod h1:KKwLiTooNGu+JmLZGn9Sl3Gjmfj66eMbCQznLP5zcqA= +sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 h1:fD1pz4yfdADVNfFmcP2aBEtudwUQ1AlLnRBALr33v3s= +sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= +sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= +sigs.k8s.io/structured-merge-diff/v4 v4.2.0 h1:kDvPBbnPk+qYmkHmSo8vKGp438IASWofnbbUKDE/bv0= +sigs.k8s.io/structured-merge-diff/v4 v4.2.0/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/hack/boilerplate.go.txt b/hack/boilerplate.go.txt new file mode 100644 index 0000000..29c55ec --- /dev/null +++ b/hack/boilerplate.go.txt @@ -0,0 +1,15 @@ +/* +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. +*/ \ No newline at end of file diff --git a/logs/logger.go b/logs/logger.go new file mode 100644 index 0000000..3c41799 --- /dev/null +++ b/logs/logger.go @@ -0,0 +1,291 @@ +//(C) Copyright [2022] Hewlett Packard Enterprise Development LP +// +//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 logs + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/sirupsen/logrus" +) + +// LogFormat is custom type created for the log formats supported by ODIM +type LogFormat uint32 + +// log formats supported by ODIM +const ( + SyslogFormat LogFormat = iota + JSONFormat +) + +var priorityLogFields = []string{ + "host", + "threadname", + "procid", + "messageid", +} + +const ( + actionid = "actionid" + actionname = "actionname" + defaultThreadId = "0" + processname = "processname" + threadid = "threadid" + threadname = "threadname" + transactionid = "transactionid" +) + +var syslogPriorityNumerics = map[string]int8{ + "panic": 8, + "fatal": 9, + "error": 11, + "warn": 12, + "warning": 12, + "info": 14, + "debug": 15, + "trace": 15, +} + +var logFields = map[string][]string{ + "account": { + "user", + "roleID", + }, + "request": { + "method", + "resource", + "requestBody", + }, + "response": { + "responseCode", + }, +} + +// SysLogFormatter implements logrus Format interface. It provides a formatter for odim in syslog format +type SysLogFormatter struct{} + +var Log *logrus.Entry + +func init() { + Log = logrus.NewEntry(logrus.New()) +} + +// Adorn adds the fields to Log variable +func Adorn(m logrus.Fields) { + Log = Log.WithFields(m) +} + +// LogWithFields add fields to log +func LogWithFields(ctx context.Context) *logrus.Entry { + transID := ctx.Value("transactionid") + processName := ctx.Value("processname") + threadID := ctx.Value("threadid") + actionName := ctx.Value("actionname") + threadName := ctx.Value("threadname") + actionID := ctx.Value("actionid") + fields := logrus.Fields{ + "processname": processName, + "transactionid": transID, + "actionid": actionID, + "actionname": actionName, + "threadid": threadID, + "threadname": threadName, + "messageid": actionName, + } + return Log.WithFields(fields) +} + +// Format renders a log in syslog format +func (f *SysLogFormatter) Format(entry *logrus.Entry) ([]byte, error) { + level := entry.Level.String() + priorityNumber := findSysLogPriorityNumeric(level) + sysLogMsg := fmt.Sprintf("<%d>%s %s ", priorityNumber, "1", entry.Time.UTC().Format(time.RFC3339)) + sysLogMsg = formatPriorityFields(entry, sysLogMsg) + sysLogMsg = formatStructuredFields(entry, sysLogMsg) + for k, v := range logFields { + if accountLog, present := formatSyslog(k, v, entry); present { + sysLogMsg = fmt.Sprintf("%s %s", sysLogMsg, accountLog) + } + } + + sysLogMsg = fmt.Sprintf("%s %s", sysLogMsg, entry.Message) + return append([]byte(sysLogMsg), '\n'), nil +} + +func findSysLogPriorityNumeric(level string) int8 { + return syslogPriorityNumerics[level] +} + +// formatStructuredFields is used to create structured fields for log +func formatStructuredFields(entry *logrus.Entry, msg string) string { + var transID, processName, actionID, actionName, threadID, threadName string + if val, ok := entry.Data["processname"]; ok { + if val != nil { + processName = val.(string) + } + } + if val, ok := entry.Data["transactionid"]; ok { + if val != nil { + transID = val.(string) + } + } + if val, ok := entry.Data["actionid"]; ok { + if val != nil { + actionID = val.(string) + } + } + if val, ok := entry.Data["actionname"]; ok { + if val != nil { + actionName = val.(string) + } + } + if val, ok := entry.Data["threadid"]; ok { + if val != nil { + threadID = val.(string) + } + } + if val, ok := entry.Data["threadname"]; ok { + if val != nil { + threadName = val.(string) + } + } + if transID != "" { + msg = fmt.Sprintf("%s [process@1 processName=\"%s\" transactionID=\"%s\" actionID=\"%s\" actionName=\"%s\" threadID=\"%s\" threadName=\"%s\"]", msg, processName, transID, actionID, actionName, threadID, threadName) + } + return msg +} +func formatPriorityFields(entry *logrus.Entry, msg string) string { + present := true + for _, v := range priorityLogFields { + if val, ok := entry.Data[v]; ok { + present = false + msg = fmt.Sprintf("%s %v ", msg, val) + } + } + if !present { + msg = msg[:len(msg)-1] + } + return msg +} + +func formatSyslog(logType string, logFields []string, entry *logrus.Entry) (string, bool) { + isPresent := false + msg := fmt.Sprintf("[%s@1 ", logType) + for _, v := range logFields { + if val, ok := entry.Data[v]; ok { + isPresent = true + msg = fmt.Sprintf("%s %s=\"%v\" ", msg, v, val) + } + } + msg = msg[:len(msg)-1] + return fmt.Sprintf("%s]", msg), isPresent +} + +// SetLogLevel sets the given input as log level +func SetLogLevel(level string) { + switch strings.ToLower(level) { + case "panic": + Log.Logger.SetLevel(logrus.PanicLevel) + case "fatal": + Log.Logger.SetLevel(logrus.FatalLevel) + case "error": + Log.Logger.SetLevel(logrus.ErrorLevel) + case "warn": + Log.Logger.SetLevel(logrus.WarnLevel) + case "info": + Log.Logger.SetLevel(logrus.InfoLevel) + case "debug": + Log.Logger.SetLevel(logrus.DebugLevel) + case "trace": + Log.Logger.SetLevel(logrus.TraceLevel) + default: + Log.Logger.SetLevel(logrus.WarnLevel) + Log.Warn("Configured invalid log level. Setting warn as the log level.") + } +} + +// SetLogFormat sets the giving input as logging format +func SetLogFormat(format LogFormat) { + switch format { + case JSONFormat: + Log.Logger.SetFormatter(&logrus.JSONFormatter{}) + case SyslogFormat: + Log.Logger.SetFormatter(&SysLogFormatter{}) + default: + Log.Logger.SetFormatter(&SysLogFormatter{}) + Log.Warn("Configured invalid log format. Setting syslog as the log format.") + } +} + +// Convert the log format to a string. +func (format LogFormat) String() string { + if b, err := format.MarshalText(); err == nil { + return string(b) + } + return "unknown_log_format" +} + +// ParseLogFormat takes a string level and returns the log format. +func ParseLogFormat(format string) (LogFormat, error) { + switch strings.ToLower(format) { + case "syslog": + return SyslogFormat, nil + case "json": + return JSONFormat, nil + } + + var lf LogFormat + return lf, fmt.Errorf("invalid log format : %s", format) +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (format *LogFormat) UnmarshalText(text []byte) error { + l, err := ParseLogFormat(string(text)) + if err != nil { + return err + } + + *format = l + return nil +} + +// MarshalText will validate the log format and return the corresponding string +func (format LogFormat) MarshalText() ([]byte, error) { + switch format { + case SyslogFormat: + return []byte("syslog"), nil + case JSONFormat: + return []byte("json"), nil + } + + return nil, fmt.Errorf("invalid log format %d", format) +} + +// CreateContextForLogging is to get the context with all the parameters those needed for log +func CreateContextForLogging(ctx context.Context, transactionId string, threadName string, actionID string, actionName string, podName string) context.Context { + // Add Action ID and Action Name in logs + ctx = context.WithValue(ctx, actionid, actionID) + ctx = context.WithValue(ctx, actionname, actionName) + + // Add values in context (TransactionID, ThreadName, ThreadID) + ctx = context.WithValue(ctx, transactionid, transactionId) + ctx = context.WithValue(ctx, threadname, threadName) + ctx = context.WithValue(ctx, threadid, defaultThreadId) + ctx = context.WithValue(ctx, processname, podName) + + return ctx +} diff --git a/main/main.go b/main/main.go new file mode 100644 index 0000000..233302e --- /dev/null +++ b/main/main.go @@ -0,0 +1,288 @@ +//(C) Copyright [2022] Hewlett Packard Enterprise Development LP +// +//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 main + +import ( + "context" + "errors" + "flag" + "fmt" + "os" + + // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) + // to ensure that exec-entrypoint and run can make use of them. + + corev1 "k8s.io/api/core/v1" + _ "k8s.io/client-go/plugin/pkg/client/auth" + + "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/config" + "sigs.k8s.io/controller-runtime/pkg/healthz" + "sigs.k8s.io/controller-runtime/pkg/manager" + + infraiov1 "github.com/ODIM-Project/BMCOperator/api/v1" + bios "github.com/ODIM-Project/BMCOperator/controllers/bios" + bmc "github.com/ODIM-Project/BMCOperator/controllers/bmc" + boot "github.com/ODIM-Project/BMCOperator/controllers/boot" + configuration "github.com/ODIM-Project/BMCOperator/controllers/config" + eventsubscription "github.com/ODIM-Project/BMCOperator/controllers/eventsubscription" + firmware "github.com/ODIM-Project/BMCOperator/controllers/firmware" + odim "github.com/ODIM-Project/BMCOperator/controllers/odim" + pollData "github.com/ODIM-Project/BMCOperator/controllers/pollData" + utils "github.com/ODIM-Project/BMCOperator/controllers/utils" + volume "github.com/ODIM-Project/BMCOperator/controllers/volume" + "github.com/ODIM-Project/BMCOperator/logs" + + "github.com/sirupsen/logrus" + //+kubebuilder:scaffold:imports +) + +var ( + scheme = runtime.NewScheme() +) + +func init() { + utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + + utilruntime.Must(infraiov1.AddToScheme(scheme)) + //+kubebuilder:scaffold:scheme +} + +func main() { + err := configuration.SetConfiguration() + if err != nil { + logs.Log.Fatal("unable to get config values" + err.Error()) + } + hostName := os.Getenv("HOST_NAME") + podName := os.Getenv("POD_NAME") + pid := os.Getpid() + logs.Adorn(logrus.Fields{ + "host": hostName, + "procid": podName + fmt.Sprintf("_%d", pid), + }) + log := logs.Log + log.Logger.SetLevel(configuration.Data.LogLevel) + log.Logger.SetOutput(os.Stdout) + logs.SetLogFormat(configuration.Data.LogFormat) + var metricsAddr string + var enableLeaderElection bool + var probeAddr string + flag.StringVar(&metricsAddr, "metrics-bind-address", fmt.Sprintf(":%s", configuration.Data.MetricPort), "The address the metric endpoint binds to.") + flag.StringVar(&probeAddr, "health-probe-bind-address", fmt.Sprintf(":%s", configuration.Data.HealthPort), "The address the probe endpoint binds to.") + flag.BoolVar(&enableLeaderElection, "leader-elect", false, + "Enable leader election for controller manager. "+ + "Enabling this will ensure there is only one active controller manager.") + + flag.Parse() + + // ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) + + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ + Scheme: scheme, + MetricsBindAddress: metricsAddr, + Port: 9443, + HealthProbeBindAddress: probeAddr, + LeaderElection: enableLeaderElection, + LeaderElectionID: "a247721e.odimra", + }) + + cfg, _ := config.GetConfig() + c, _ := client.New(cfg, client.Options{}) + //getting public private keys for encryption/decryption of passwords + err = updateKeys(configuration.Data.SecretName, configuration.Data.Namespace, c) + if err != nil { + logs.Log.Fatal("unable to get public private keys" + err.Error()) + } + if err = (&odim.OdimReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + logs.Log.Fatal("unable to create odim controller" + err.Error()) + } + if err = (&bmc.BmcReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + logs.Log.Fatal("unable to create controller" + err.Error()) + } + if err = (&bios.BiosSettingReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + logs.Log.Fatal("unable to create controller" + err.Error()) + } + if err = (&boot.BootOrderSettingsReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + logs.Log.Fatal("unable to create controller" + err.Error()) + } + if err = (&volume.VolumeReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + log.Fatal("unable to create controller" + err.Error()) + } + if err = (&firmware.FirmwareReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + log.Fatal("unable to create controller" + err.Error()) + } + if err = (&eventsubscription.EventsubscriptionReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + log.Fatal("unable to create controller" + err.Error()) + } + //+kubebuilder:scaffold:builder + + addIndex(mgr) + + if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { + logs.Log.Fatal("unable to set up health check" + err.Error()) + } + if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { + logs.Log.Fatal("unable to set up ready check" + err.Error()) + } + errChan := make(chan error) + go configuration.TrackConfigListener(errChan) + logs.Log.Info("starting manager") + go pollData.PollDetails(mgr) + + if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { + logs.Log.Fatal("problem running manager" + err.Error()) + } +} + +func updateKeys(secretName, namespace string, client client.Client) error { + keysSecret := &corev1.Secret{} + encryptedPublicKey, encryptedPrivateKey := bmc.GetEncryptedPemKeysFromSecret(context.TODO(), keysSecret, secretName, namespace, client) + privateKey, publicKey := bmc.GetPemKeys(context.TODO(), encryptedPublicKey, encryptedPrivateKey) + if privateKey == nil || publicKey == nil { + return errors.New("could not find public/private keys") + } + utils.PrivateKey = privateKey + utils.PublicKey = publicKey + utils.RootCA = keysSecret.Data["rootCACert"] + return nil +} + +// addIndex is used to create a index to search object based on given field +// r.Client.List() will work when we add the index here before using it +func addIndex(mgr manager.Manager) { + cache := mgr.GetCache() + bmcIpFunc := func(obj client.Object) []string { + return []string{obj.(*infraiov1.Bmc).Spec.BmcDetails.Address} + } + + if err := cache.IndexField(context.Background(), &infraiov1.Bmc{}, "spec.bmc.address", bmcIpFunc); err != nil { + panic(err) + } + serialNoFunc := func(obj client.Object) []string { + return []string{obj.(*infraiov1.Bmc).Status.SerialNumber} + } + + if err := cache.IndexField(context.Background(), &infraiov1.Bmc{}, "status.serialNumber", serialNoFunc); err != nil { + panic(err) + } + + systemIDFunc := func(obj client.Object) []string { + return []string{obj.(*infraiov1.Bmc).Status.BmcSystemID} + } + + if err := cache.IndexField(context.Background(), &infraiov1.Bmc{}, "status.bmcSystemId", systemIDFunc); err != nil { + panic(err) + } + + metadataFunc := func(obj client.Object) []string { + return []string{obj.(*infraiov1.Bmc).ObjectMeta.Name} + } + + if err := cache.IndexField(context.Background(), &infraiov1.Bmc{}, "metadata.name", metadataFunc); err != nil { + panic(err) + } + + biosSchemaFunc := func(obj client.Object) []string { + return []string{obj.(*infraiov1.BiosSchemaRegistry).Name} + } + + if err := cache.IndexField(context.Background(), &infraiov1.BiosSchemaRegistry{}, "metadata.name", biosSchemaFunc); err != nil { + panic(err) + } + + biosFunc := func(obj client.Object) []string { + return []string{obj.(*infraiov1.BiosSetting).ObjectMeta.Name} + } + + if err := cache.IndexField(context.Background(), &infraiov1.BiosSetting{}, "metadata.name", biosFunc); err != nil { + panic(err) + } + + bootFunc := func(obj client.Object) []string { + return []string{obj.(*infraiov1.BootOrderSetting).ObjectMeta.Name} + } + + if err := cache.IndexField(context.Background(), &infraiov1.BootOrderSetting{}, "metadata.name", bootFunc); err != nil { + panic(err) + } + + odimNameFunc := func(obj client.Object) []string { + return []string{obj.(*infraiov1.Odim).ObjectMeta.Name} + } + + if err := cache.IndexField(context.Background(), &infraiov1.Odim{}, "metadata.name", odimNameFunc); err != nil { + panic(err) + } + + firmwareNameFunc := func(obj client.Object) []string { + + return []string{obj.(*infraiov1.Firmware).ObjectMeta.Name} + + } + + if err := cache.IndexField(context.Background(), &infraiov1.Firmware{}, "metadata.name", firmwareNameFunc); err != nil { + + panic(err) + + } + + eventSubscriptionIDFunc := func(obj client.Object) []string { + return []string{obj.(*infraiov1.Eventsubscription).Status.ID} + } + + if err := cache.IndexField(context.Background(), &infraiov1.Eventsubscription{}, "status.eventSubscriptionID", eventSubscriptionIDFunc); err != nil { + panic(err) + } + + eventSubscriptionObjNameFunc := func(obj client.Object) []string { + return []string{obj.(*infraiov1.Eventsubscription).ObjectMeta.Name} + } + if err := cache.IndexField(context.Background(), &infraiov1.Eventsubscription{}, "metadata.name", eventSubscriptionObjNameFunc); err != nil { + panic(err) + } + + eventsMessageFunc := func(obj client.Object) []string { + return []string{obj.(*infraiov1.EventsMessageRegistry).ObjectMeta.Name} + } + + if err := cache.IndexField(context.Background(), &infraiov1.EventsMessageRegistry{}, "metadata.name", eventsMessageFunc); err != nil { + panic(err) + } +}