diff options
45 files changed, 1197 insertions, 511 deletions
diff --git a/deployments/build.sh b/deployments/build.sh index 05a076e2..c9053657 100755 --- a/deployments/build.sh +++ b/deployments/build.sh @@ -13,7 +13,7 @@ set -o pipefail k8s_path="$(git rev-parse --show-toplevel)" -VERSION="0.9.1-SNAPSHOT" +VERSION="0.9.2-SNAPSHOT" export IMAGE_NAME="nexus3.onap.org:10003/onap/multicloud/k8s" function _compile_src { diff --git a/docs/conf.py b/docs/conf.py index 8f40e8b8..3b28eb74 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -12,4 +12,4 @@ intersphinx_mapping = {} html_last_updated_fmt = '%d-%b-%y %H:%M' def setup(app): - app.add_stylesheet("css/ribbon_onap.css") + app.add_css_file("css/ribbon_onap.css") diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index b3188ddd..74a3b7a3 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,15 +1 @@ -tox -Sphinx -doc8 -docutils -setuptools -six -sphinx_rtd_theme>=0.4.3 -sphinxcontrib-blockdiag -sphinxcontrib-needs>=0.2.3 -sphinxcontrib-nwdiag -sphinxcontrib-seqdiag -sphinxcontrib-swaggerdoc -sphinxcontrib-plantuml -sphinx_bootstrap_theme lfdocs-conf diff --git a/docs/tox.ini b/docs/tox.ini index edac8c35..42ffa687 100644 --- a/docs/tox.ini +++ b/docs/tox.ini @@ -5,7 +5,10 @@ skipsdist = true [testenv:docs] basepython = python3 -deps = -r{toxinidir}/requirements-docs.txt +deps = + -r{toxinidir}/requirements-docs.txt + -chttps://git.onap.org/doc/plain/etc/upper-constraints.os.txt + -chttps://git.onap.org/doc/plain/etc/upper-constraints.onap.txt commands = sphinx-build -b html -n -d {envtmpdir}/doctrees ./ {toxinidir}/_build/html echo "Generated docs available in {toxinidir}/_build/html" diff --git a/kud/deployment_infra/emco/examples/01-cluster.yaml b/kud/deployment_infra/emco/examples/01-cluster.yaml index 6f7ce4ba..18d05f73 100644 --- a/kud/deployment_infra/emco/examples/01-cluster.yaml +++ b/kud/deployment_infra/emco/examples/01-cluster.yaml @@ -9,14 +9,14 @@ resourceContext: metadata : name: {{ .ClusterProvider }} -{{- range $index, $cluster := .Clusters }} +{{- range $clusterName, $cluster := .Clusters }} --- #creating cluster version: emco/v2 resourceContext: anchor: cluster-providers/{{ $.ClusterProvider }}/clusters metadata : - name: {{ $cluster.Name }} + name: {{ $clusterName }} file: {{ $cluster.KubeConfig }} @@ -24,6 +24,6 @@ file: #Add label cluster version: emco/v2 resourceContext: - anchor: cluster-providers/{{ $.ClusterProvider }}/clusters/{{ $cluster.Name }}/labels + anchor: cluster-providers/{{ $.ClusterProvider }}/clusters/{{ $clusterName }}/labels label-name: {{ $.ClustersLabel }} -{{- end }}
\ No newline at end of file +{{- end }} diff --git a/kud/deployment_infra/emco/examples/02-project.yaml b/kud/deployment_infra/emco/examples/02-project.yaml index d62a4f65..224126ce 100644 --- a/kud/deployment_infra/emco/examples/02-project.yaml +++ b/kud/deployment_infra/emco/examples/02-project.yaml @@ -65,17 +65,17 @@ spec: verbs: - "*" -{{- range $index, $cluster := .Clusters }} +{{- range $clusterName, $cluster := .Clusters }} --- #add cluster reference to logical cloud version: emco/v2 resourceContext: anchor: projects/{{ $.ProjectName }}/logical-clouds/{{ $.LogicalCloud }}/cluster-references metadata: - name: {{ $cluster.Name }} + name: {{ $clusterName }} spec: cluster-provider: {{ $.ClusterProvider }} - cluster-name: {{ $cluster.Name }} + cluster-name: {{ $clusterName }} loadbalancer-ip: "0.0.0.0" {{- end }} diff --git a/kud/deployment_infra/emco/examples/03-addons-app.yaml b/kud/deployment_infra/emco/examples/03-addons-app.yaml index 0fd15e0f..c5fb7ea5 100644 --- a/kud/deployment_infra/emco/examples/03-addons-app.yaml +++ b/kud/deployment_infra/emco/examples/03-addons-app.yaml @@ -1,96 +1,95 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright (c) 2020 Intel Corporation +{{- range $compositeAppName, $compositeApp := .CompositeApps }} --- #creating composite app entry version: emco/v2 resourceContext: - anchor: projects/{{ .ProjectName }}/composite-apps + anchor: projects/{{ $.ProjectName }}/composite-apps metadata : - name: {{ .AddonsApp }} - description: "KUD addons" + name: {{ $compositeAppName }} spec: version: v1 -{{- range $index, $addon := .Addons }} +{{- range $index, $app := $compositeApp.Apps }} --- #adding app to the composite app version: emco/v2 resourceContext: - anchor: projects/{{ $.ProjectName }}/composite-apps/{{ $.AddonsApp }}/v1/apps + anchor: projects/{{ $.ProjectName }}/composite-apps/{{ $compositeAppName }}/v1/apps metadata : - name: {{ $addon }} + name: {{ $app }} file: - {{ $.PackagesPath }}/{{ $addon }}.tar.gz + {{ $.PackagesPath }}/{{ $app }}.tar.gz {{- end }} --- #creating composite profile entry version: emco/v2 resourceContext: - anchor: projects/{{ .ProjectName }}/composite-apps/{{ .AddonsApp }}/v1/composite-profiles + anchor: projects/{{ $.ProjectName }}/composite-apps/{{ $compositeAppName }}/v1/composite-profiles metadata : - name: {{ .AddonsProfile }} + name: {{ $compositeAppName }} -{{- range $index, $addon := .Addons }} +{{- range $index, $app := $compositeApp.Apps }} --- #adding app profiles to the composite profile version: emco/v2 resourceContext: - anchor: projects/{{ $.ProjectName }}/composite-apps/{{ $.AddonsApp }}/v1/composite-profiles/{{ $.AddonsProfile }}/profiles + anchor: projects/{{ $.ProjectName }}/composite-apps/{{ $compositeAppName }}/v1/composite-profiles/{{ $compositeAppName }}/profiles metadata : - name: {{ $addon }}-profile + name: {{ $app }}-profile spec: - app-name: {{ $addon }} + app-name: {{ $app }} file: - {{ $.PackagesPath }}/{{ $addon }}_profile.tar.gz + {{ $.PackagesPath }}/{{ $app }}_profile.tar.gz {{- end }} --- #create deployment intent group version: emco/v2 resourceContext: - anchor: projects/{{ .ProjectName }}/composite-apps/{{ .AddonsApp }}/v1/deployment-intent-groups + anchor: projects/{{ $.ProjectName }}/composite-apps/{{ $compositeAppName }}/v1/deployment-intent-groups metadata : - name: {{ .AddonsDeploymentIntentGroup }} - description: "description" + name: deployment spec: - profile: {{ .AddonsProfile }} + profile: {{ $compositeAppName }} version: r1 - logical-cloud: {{ .LogicalCloud }} + logical-cloud: {{ $.LogicalCloud }} override-values: [] --- #create intent in deployment intent group version: emco/v2 resourceContext: - anchor: projects/{{ .ProjectName }}/composite-apps/{{ .AddonsApp }}/v1/deployment-intent-groups/{{ .AddonsDeploymentIntentGroup }}/intents + anchor: projects/{{ $.ProjectName }}/composite-apps/{{ $compositeAppName }}/v1/deployment-intent-groups/deployment/intents metadata : - name: {{ .AddonsDeploymentIntent }} + name: deployment-intent spec: intent: - genericPlacementIntent: {{ .AddonsPlacementIntent }} + genericPlacementIntent: placement-intent --- #create the generic placement intent version: emco/v2 resourceContext: - anchor: projects/{{ .ProjectName }}/composite-apps/{{ .AddonsApp }}/v1/deployment-intent-groups/{{ .AddonsDeploymentIntentGroup }}/generic-placement-intents + anchor: projects/{{ $.ProjectName }}/composite-apps/{{ $compositeAppName }}/v1/deployment-intent-groups/deployment/generic-placement-intents metadata : - name: {{ .AddonsPlacementIntent }} + name: placement-intent spec: - logical-cloud: {{ .LogicalCloud }} + logical-cloud: {{ $.LogicalCloud }} -{{- range $index, $addon := .Addons }} +{{- range $index, $app := $compositeApp.Apps }} --- #add the app placement intent to the generic placement intent version: emco/v2 resourceContext: - anchor: projects/{{ $.ProjectName }}/composite-apps/{{ $.AddonsApp }}/v1/deployment-intent-groups/{{ $.AddonsDeploymentIntentGroup }}/generic-placement-intents/{{ $.AddonsPlacementIntent }}/app-intents + anchor: projects/{{ $.ProjectName }}/composite-apps/{{ $compositeAppName }}/v1/deployment-intent-groups/deployment/generic-placement-intents/placement-intent/app-intents metadata: - name: {{ $addon }}-placement-intent + name: {{ $app }}-placement-intent spec: - app-name: {{ $addon }} + app-name: {{ $app }} intent: allOf: - provider-name: {{ $.ClusterProvider }} @@ -98,13 +97,8 @@ spec: {{- end }} --- -#Approve +#approve version: emco/v2 resourceContext: - anchor: projects/{{ .ProjectName }}/composite-apps/{{ .AddonsApp }}/v1/deployment-intent-groups/{{ .AddonsDeploymentIntentGroup }}/approve - ---- -#Instantiate -version: emco/v2 -resourceContext: - anchor: projects/{{ .ProjectName }}/composite-apps/{{ .AddonsApp }}/v1/deployment-intent-groups/{{ .AddonsDeploymentIntentGroup }}/instantiate + anchor: projects/{{ $.ProjectName }}/composite-apps/{{ $compositeAppName }}/v1/deployment-intent-groups/deployment/approve +{{- end }} diff --git a/kud/deployment_infra/emco/examples/04-addon-resources-app.yaml b/kud/deployment_infra/emco/examples/04-addon-resources-app.yaml deleted file mode 100644 index 92fd9539..00000000 --- a/kud/deployment_infra/emco/examples/04-addon-resources-app.yaml +++ /dev/null @@ -1,110 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# Copyright (c) 2020 Intel Corporation - ---- -#creating composite app entry -version: emco/v2 -resourceContext: - anchor: projects/{{ .ProjectName }}/composite-apps -metadata : - name: {{ .AddonResourcesApp }} - description: "KUD addons" -spec: - version: v1 - -{{- range $index, $addon := .AddonResources }} ---- -#adding app to the composite app -version: emco/v2 -resourceContext: - anchor: projects/{{ $.ProjectName }}/composite-apps/{{ $.AddonResourcesApp }}/v1/apps -metadata : - name: {{ $addon }} -file: - {{ $.PackagesPath }}/{{ $addon }}.tar.gz -{{- end }} - ---- -#creating composite profile entry -version: emco/v2 -resourceContext: - anchor: projects/{{ .ProjectName }}/composite-apps/{{ .AddonResourcesApp }}/v1/composite-profiles -metadata : - name: {{ .AddonResourcesProfile }} - -{{- range $index, $addon := .AddonResources }} ---- -#adding app profiles to the composite profile -version: emco/v2 -resourceContext: - anchor: projects/{{ $.ProjectName }}/composite-apps/{{ $.AddonResourcesApp }}/v1/composite-profiles/{{ $.AddonResourcesProfile }}/profiles -metadata : - name: {{ $addon }}-profile -spec: - app-name: {{ $addon }} -file: - {{ $.PackagesPath }}/{{ $addon }}_profile.tar.gz -{{- end }} - ---- -#create deployment intent group -version: emco/v2 -resourceContext: - anchor: projects/{{ .ProjectName }}/composite-apps/{{ .AddonResourcesApp }}/v1/deployment-intent-groups -metadata : - name: {{ .AddonResourcesDeploymentIntentGroup }} - description: "description" -spec: - profile: {{ .AddonResourcesProfile }} - version: r1 - logical-cloud: {{ .LogicalCloud }} - override-values: [] - ---- -#create intent in deployment intent group -version: emco/v2 -resourceContext: - anchor: projects/{{ .ProjectName }}/composite-apps/{{ .AddonResourcesApp }}/v1/deployment-intent-groups/{{ .AddonResourcesDeploymentIntentGroup }}/intents -metadata : - name: {{ .AddonResourcesDeploymentIntent }} -spec: - intent: - genericPlacementIntent: {{ .AddonResourcesPlacementIntent }} - ---- -#create the generic placement intent -version: emco/v2 -resourceContext: - anchor: projects/{{ .ProjectName }}/composite-apps/{{ .AddonResourcesApp }}/v1/deployment-intent-groups/{{ .AddonResourcesDeploymentIntentGroup }}/generic-placement-intents -metadata : - name: {{ .AddonResourcesPlacementIntent }} -spec: - logical-cloud: {{ .LogicalCloud }} - -{{- range $index, $addon := .AddonResources }} ---- -#add the app placement intent to the generic placement intent -version: emco/v2 -resourceContext: - anchor: projects/{{ $.ProjectName }}/composite-apps/{{ $.AddonResourcesApp }}/v1/deployment-intent-groups/{{ $.AddonResourcesDeploymentIntentGroup }}/generic-placement-intents/{{ $.AddonResourcesPlacementIntent }}/app-intents -metadata: - name: {{ $addon }}-placement-intent -spec: - app-name: {{ $addon }} - intent: - allOf: - - provider-name: {{ $.ClusterProvider }} - cluster-label-name: {{ $.ClustersLabel }} -{{- end }} - ---- -#Approve -version: emco/v2 -resourceContext: - anchor: projects/{{ .ProjectName }}/composite-apps/{{ .AddonResourcesApp }}/v1/deployment-intent-groups/{{ .AddonResourcesDeploymentIntentGroup }}/approve - ---- -#Instantiate -version: emco/v2 -resourceContext: - anchor: projects/{{ .ProjectName }}/composite-apps/{{ .AddonResourcesApp }}/v1/deployment-intent-groups/{{ .AddonResourcesDeploymentIntentGroup }}/instantiate diff --git a/kud/deployment_infra/emco/examples/README.md b/kud/deployment_infra/emco/examples/README.md index 203b83fd..dcf9edde 100644 --- a/kud/deployment_infra/emco/examples/README.md +++ b/kud/deployment_infra/emco/examples/README.md @@ -15,6 +15,8 @@ needs to be installed and configured for the edge cluster. 5. SR-IOV Network 6. QuickAssist Technology (QAT) Device Plugin 7. CPU Manager for Kubernetes +8. KubeVirt and CDI Operators +9. KubeVirt and CDI Instances ## Setup environment to deploy addons @@ -38,22 +40,40 @@ required to be done only once. `$ emcoctl apply -f 01-cluster.yaml -v values.yaml` `$ emcoctl apply -f 02-project.yaml -v values.yaml` -## Deploying addons +## Create addons project -This deploys the applications listed in the `Addons` and -`AddonResources` values. +This creates the project with the addons listed `CompositeApps` value. `$ emcoctl apply -f 03-addons-app.yaml -v values.yaml` - `$ emcoctl apply -f 04-addon-resources-app.yaml -v values.yaml` + +## Instantiate the addons + +This instantiates each composite app listed in the `CompositeApps` +value. + +NOTE: The ordering is important when both the sriov-network and +kubevirt addons are enabled. The sriov-network addon will trigger a +drain of the nodes and kubevirt will prevent the drain from +completing, so kubevirt must be instantiated after sriov-network has +completed the drain. + + `$ emcoctl apply projects/kud/composite-apps/addons/v1/deployment-intent-groups/deployment/instantiate` + `$ emcoctl apply projects/kud/composite-apps/networks/v1/deployment-intent-groups/deployment/instantiate` + `$ emcoctl apply projects/kud/composite-apps/kubevirt/v1/deployment-intent-groups/deployment/instantiate` ## Cleanup -1. Delete addons. +1. Terminate addons. + + `$ emcoctl apply projects/kud/composite-apps/kubevirt/v1/deployment-intent-groups/deployment/terminate` + `$ emcoctl apply projects/kud/composite-apps/networks/v1/deployment-intent-groups/deployment/terminate` + `$ emcoctl apply projects/kud/composite-apps/addons/v1/deployment-intent-groups/deployment/terminate` + +2. Delete addons. - `$ emcoctl delete -f 04-addon-resources-app.yaml -v values.yaml` `$ emcoctl delete -f 03-addons-app.yaml -v values.yaml` -2. Cleanup prerequisites. +3. Cleanup prerequisites. `$ emcoctl delete -f 02-project.yaml -v values.yaml` `$ emcoctl delete -f 01-cluster.yaml -v values.yaml` diff --git a/kud/deployment_infra/emco/examples/values.yaml.example b/kud/deployment_infra/emco/examples/values.yaml.example index 67944eb8..41e3cc82 100644 --- a/kud/deployment_infra/emco/examples/values.yaml.example +++ b/kud/deployment_infra/emco/examples/values.yaml.example @@ -7,31 +7,33 @@ DtcPort: 30483 ClusterProvider: kud ClustersLabel: kud-cluster Clusters: -- KubeConfig: $KUBE_PATH - Name: cluster + cluster: + KubeConfig: $KUBE_PATH ProjectName: kud LogicalCloud: kud PackagesPath: $PWD/../output/packages -AddonsApp: addons -AddonsProfile: addons-profile -AddonsDeploymentIntentGroup: addons-deployment-intent-group -AddonsDeploymentIntent: addons-deployment-intent -AddonsPlacementIntent: addons-placement-intent -Addons: -- multus-cni -- ovn4nfv -- node-feature-discovery -- sriov-network-operator -- qat-device-plugin -- cpu-manager -AddonResourcesApp: addon-resources -AddonResourcesProfile: addon-resources-profile -AddonResourcesDeploymentIntentGroup: addon-resources-deployment-intent-group -AddonResourcesDeploymentIntent: addon-resources-deployment-intent -AddonResourcesPlacementIntent: addon-resources-placement-intent -AddonResources: -- ovn4nfv-network -- sriov-network +# Each composite app will be contained in its own deployment intent +# group. This is to enable instantiating the addons in a specified +# order. +CompositeApps: + addons: + Apps: + - kubevirt-operator + - cdi-operator + - multus-cni + - ovn4nfv + - node-feature-discovery + - sriov-network-operator + - qat-device-plugin + - cpu-manager + networks: + Apps: + - ovn4nfv-network + - sriov-network + kubevirt: + Apps: + - kubevirt + - cdi diff --git a/kud/deployment_infra/installers/Dockerfile.iavf-driver-installer b/kud/deployment_infra/installers/Dockerfile.iavf-driver-installer index 9882c2f7..8d4593a6 100644 --- a/kud/deployment_infra/installers/Dockerfile.iavf-driver-installer +++ b/kud/deployment_infra/installers/Dockerfile.iavf-driver-installer @@ -19,6 +19,7 @@ RUN apt-get update && \ rm -rf /var/lib/apt/lists/* COPY _common.sh / +COPY skb-frag-off.patch / COPY entrypoint-iavf-driver-installer.sh /entrypoint.sh CMD /entrypoint.sh diff --git a/kud/deployment_infra/installers/entrypoint-iavf-driver-installer.sh b/kud/deployment_infra/installers/entrypoint-iavf-driver-installer.sh index 6cfc373d..d47e3b97 100755 --- a/kud/deployment_infra/installers/entrypoint-iavf-driver-installer.sh +++ b/kud/deployment_infra/installers/entrypoint-iavf-driver-installer.sh @@ -2,6 +2,7 @@ #set -x source _common.sh +SCRIPT_DIR=$(readlink -f "$(dirname "${BASH_SOURCE[0]}")") IAVF_DRIVER_VERSION="${IAVF_DRIVER_VERSION:-4.0.2}" IAVF_DRIVER_DOWNLOAD_URL_DEFAULT="https://downloadmirror.intel.com/30305/eng/iavf-${IAVF_DRIVER_VERSION}.tar.gz" @@ -27,10 +28,15 @@ download_iavf_src() { pushd "${IAVF_INSTALL_DIR_CONTAINER}" > /dev/null curl -L -sS "${IAVF_DRIVER_DOWNLOAD_URL}" -o "${IAVF_DRIVER_ARCHIVE}" tar xf "${IAVF_DRIVER_ARCHIVE}" --strip-components=1 + info "Patching IAVF source ... " + # Ubuntu 18.04 added the skb_frag_off definitions to the kernel + # headers beginning with 4.15.0-159 + patch -p1 < "${SCRIPT_DIR}/skb-frag-off.patch" popd > /dev/null } build_iavf_src() { + info "Building IAVF source ... " pushd "${IAVF_INSTALL_DIR_CONTAINER}/src" > /dev/null KSRC=${KERNEL_SRC_DIR} SYSTEM_MAP_FILE="${ROOT_MOUNT_DIR}/boot/System.map-$(uname -r)" INSTALL_MOD_PATH="${ROOT_MOUNT_DIR}" make install diff --git a/kud/deployment_infra/installers/skb-frag-off.patch b/kud/deployment_infra/installers/skb-frag-off.patch new file mode 100644 index 00000000..935828ee --- /dev/null +++ b/kud/deployment_infra/installers/skb-frag-off.patch @@ -0,0 +1,14 @@ +diff --git a/src/kcompat.h b/src/kcompat.h +index 21e9818..97abc2f 100644 +--- a/src/kcompat.h ++++ b/src/kcompat.h +@@ -7074,7 +7074,8 @@ devlink_flash_update_status_notify(struct devlink __always_unused *devlink, + /*****************************************************************************/ + #if (LINUX_VERSION_CODE < KERNEL_VERSION(5,4,0)) + #if (!(RHEL_RELEASE_CODE >= RHEL_RELEASE_VERSION(8,2)) && \ +- !(SLE_VERSION_CODE >= SLE_VERSION(15,2,0))) ++ !(SLE_VERSION_CODE >= SLE_VERSION(15,2,0)) && \ ++ !(UBUNTU_VERSION_CODE >= UBUNTU_VERSION(4,15,0,159))) + static inline unsigned int skb_frag_off(const skb_frag_t *frag) + { + return frag->page_offset; diff --git a/kud/hosting_providers/containerized/addons/README.md.tmpl b/kud/hosting_providers/containerized/addons/README.md.tmpl index 4ed4610a..eed30b43 100644 --- a/kud/hosting_providers/containerized/addons/README.md.tmpl +++ b/kud/hosting_providers/containerized/addons/README.md.tmpl @@ -22,28 +22,40 @@ cloud. \`$ /opt/kud/multi-cluster/${CLUSTER_NAME}/artifacts/emcoctl.sh apply -f 01-cluster.yaml -v values.yaml\` \`$ /opt/kud/multi-cluster/${CLUSTER_NAME}/artifacts/emcoctl.sh apply -f 02-project.yaml -v values.yaml\` -3. Deploy addons +3. Create addons project -This deploys the addons listed in the \`Addons\` and -\`AddonResources\` values in values.yaml. - -NOTE: On a single node cluster, the SRIOV addon resource will trigger -a drain of the worker node. KubeVirt will prevent the drain from -completing due to its PodDisruptionBudget. The workaround is to scale -down the KubeVirt operator before applying 04-addon-resources-app.yaml -then scaling it back up after the node is ready again. +This creates the project with the addons listed `CompositeApps` value. \`$ /opt/kud/multi-cluster/${CLUSTER_NAME}/artifacts/emcoctl.sh apply -f 03-addons-app.yaml -v values.yaml\` - \`$ /opt/kud/multi-cluster/${CLUSTER_NAME}/artifacts/emcoctl.sh apply -f 04-addon-resources-app.yaml -v values.yaml\` + +4. Instantiate the addons + +This instantiates each composite app listed in the `CompositeApps` +value. + +NOTE: The ordering is important when both the sriov-network and +kubevirt addons are enabled. The sriov-network addon will trigger a +drain of the nodes and kubevirt will prevent the drain from +completing, so kubevirt must be instantiated after sriov-network has +completed the drain. + + \`$ /opt/kud/multi-cluster/${CLUSTER_NAME}/artifacts/emcoctl.sh apply projects/kud/composite-apps/addons/v1/deployment-intent-groups/deployment/instantiate\` + \`$ /opt/kud/multi-cluster/${CLUSTER_NAME}/artifacts/emcoctl.sh apply projects/kud/composite-apps/networks/v1/deployment-intent-groups/deployment/instantiate\` + \`$ /opt/kud/multi-cluster/${CLUSTER_NAME}/artifacts/emcoctl.sh apply projects/kud/composite-apps/kubevirt/v1/deployment-intent-groups/deployment/instantiate\` # Uninstalling KUD addons with emcoctl -1. Delete addons +1. Terminate the addons + + \`$ /opt/kud/multi-cluster/${CLUSTER_NAME}/artifacts/emcoctl.sh apply projects/kud/composite-apps/kubevirt/v1/deployment-intent-groups/deployment/terminate\` + \`$ /opt/kud/multi-cluster/${CLUSTER_NAME}/artifacts/emcoctl.sh apply projects/kud/composite-apps/networks/v1/deployment-intent-groups/deployment/terminate\` + \`$ /opt/kud/multi-cluster/${CLUSTER_NAME}/artifacts/emcoctl.sh apply projects/kud/composite-apps/addons/v1/deployment-intent-groups/deployment/terminate\` + +2. Delete addons - \`$ /opt/kud/multi-cluster/${CLUSTER_NAME}/artifacts/emcoctl.sh delete -f 04-addon-resources-app.yaml -v values.yaml\` \`$ /opt/kud/multi-cluster/${CLUSTER_NAME}/artifacts/emcoctl.sh delete -f 03-addons-app.yaml -v values.yaml\` -2. Cleanup prerequisites +3. Cleanup prerequisites \`$ /opt/kud/multi-cluster/${CLUSTER_NAME}/artifacts/emcoctl.sh delete -f 02-project.yaml -v values.yaml\` \`$ /opt/kud/multi-cluster/${CLUSTER_NAME}/artifacts/emcoctl.sh delete -f 01-cluster.yaml -v values.yaml\` diff --git a/kud/hosting_providers/containerized/addons/values.yaml.tmpl b/kud/hosting_providers/containerized/addons/values.yaml.tmpl index b3e5845c..25e4cc48 100644 --- a/kud/hosting_providers/containerized/addons/values.yaml.tmpl +++ b/kud/hosting_providers/containerized/addons/values.yaml.tmpl @@ -7,35 +7,33 @@ DtcPort: 30483 ClusterProvider: kud ClustersLabel: kud-cluster Clusters: -- KubeConfig: ${KUBE_PATH} - Name: cluster + cluster: + KubeConfig: ${KUBE_PATH} ProjectName: kud LogicalCloud: kud PackagesPath: ${PACKAGES_PATH} -AddonsApp: addons -AddonsProfile: addons-profile -AddonsDeploymentIntentGroup: addons-deployment-intent-group -AddonsDeploymentIntent: addons-deployment-intent -AddonsPlacementIntent: addons-placement-intent -Addons: -- kubevirt-operator -- cdi-operator -- multus-cni -- ovn4nfv -- node-feature-discovery -- sriov-network-operator -- qat-device-plugin -- cpu-manager -AddonResourcesApp: addon-resources -AddonResourcesProfile: addon-resources-profile -AddonResourcesDeploymentIntentGroup: addon-resources-deployment-intent-group -AddonResourcesDeploymentIntent: addon-resources-deployment-intent -AddonResourcesPlacementIntent: addon-resources-placement-intent -AddonResources: -- ovn4nfv-network -- sriov-network -- kubevirt -- cdi +# Each composite app will be contained in its own deployment intent +# group. This is to enable instantiating the addons in a specified +# order. +CompositeApps: + addons: + Apps: + - kubevirt-operator + - cdi-operator + - multus-cni + - ovn4nfv + - node-feature-discovery + - sriov-network-operator + - qat-device-plugin + - cpu-manager + networks: + Apps: + - ovn4nfv-network + - sriov-network + kubevirt: + Apps: + - kubevirt + - cdi diff --git a/kud/tests/plugin_fw_v2.sh b/kud/tests/plugin_fw_v2.sh index 86ef97f9..6c8d8424 100755 --- a/kud/tests/plugin_fw_v2.sh +++ b/kud/tests/plugin_fw_v2.sh @@ -222,6 +222,10 @@ else KUBECONFIG=$file kubectl get providernetwork emco-private-net -o name KUBECONFIG=$file kubectl get providernetwork unprotected-private-net -o name done + # Give some time for the Pods to show up on the clusters. kubectl + # wait may return with "error: no matching resources found" if the + # Pods have not started yet. + sleep 30s for name in $(cluster_names); do print_msg "Wait for all pods to start on cluster $name" file=$(cluster_file "$name") diff --git a/releases/0.9.1-container.yaml b/releases/0.9.1-container.yaml new file mode 100644 index 00000000..dd0b031e --- /dev/null +++ b/releases/0.9.1-container.yaml @@ -0,0 +1,8 @@ +distribution_type: 'container' +container_release_tag: '0.9.1' +project: 'multicloud-k8s' +log_dir: 'multicloud-k8s-master-docker-golang-shell-daily/1104/' +ref: 258e59bf8563021f4eded42b33c6cc61a6ffebd8 +containers: + - name: 'multicloud/k8s' + version: '0.9.1-SNAPSHOT' diff --git a/src/k8splugin/api/api.go b/src/k8splugin/api/api.go index 94fb9b34..a3e53dc8 100644 --- a/src/k8splugin/api/api.go +++ b/src/k8splugin/api/api.go @@ -104,6 +104,7 @@ func NewRouter(defClient rb.DefinitionManager, resRouter.HandleFunc("/definition/{rbname}", defHandler.listVersionsHandler).Methods("GET") resRouter.HandleFunc("/definition", defHandler.listAllHandler).Methods("GET") resRouter.HandleFunc("/definition/{rbname}/{rbversion}", defHandler.getHandler).Methods("GET") + resRouter.HandleFunc("/definition/{rbname}/{rbversion}", defHandler.updateHandler).Methods("PUT") resRouter.HandleFunc("/definition/{rbname}/{rbversion}", defHandler.deleteHandler).Methods("DELETE") //Setup resource bundle profile routes @@ -115,6 +116,7 @@ func NewRouter(defClient rb.DefinitionManager, resRouter.HandleFunc("/definition/{rbname}/{rbversion}/profile", profileHandler.listHandler).Methods("GET") resRouter.HandleFunc("/definition/{rbname}/{rbversion}/profile/{prname}/content", profileHandler.uploadHandler).Methods("POST") resRouter.HandleFunc("/definition/{rbname}/{rbversion}/profile/{prname}", profileHandler.getHandler).Methods("GET") + resRouter.HandleFunc("/definition/{rbname}/{rbversion}/profile/{prname}", profileHandler.updateHandler).Methods("PUT") resRouter.HandleFunc("/definition/{rbname}/{rbversion}/profile/{prname}", profileHandler.deleteHandler).Methods("DELETE") // Config Template @@ -123,8 +125,10 @@ func NewRouter(defClient rb.DefinitionManager, } templateHandler := rbTemplateHandler{client: templateClient} resRouter.HandleFunc("/definition/{rbname}/{rbversion}/config-template", templateHandler.createHandler).Methods("POST") + resRouter.HandleFunc("/definition/{rbname}/{rbversion}/config-template", templateHandler.listHandler).Methods("GET") resRouter.HandleFunc("/definition/{rbname}/{rbversion}/config-template/{tname}/content", templateHandler.uploadHandler).Methods("POST") resRouter.HandleFunc("/definition/{rbname}/{rbversion}/config-template/{tname}", templateHandler.getHandler).Methods("GET") + resRouter.HandleFunc("/definition/{rbname}/{rbversion}/config-template/{tname}", templateHandler.updateHandler).Methods("PUT") resRouter.HandleFunc("/definition/{rbname}/{rbversion}/config-template/{tname}", templateHandler.deleteHandler).Methods("DELETE") // Config value @@ -136,9 +140,10 @@ func NewRouter(defClient rb.DefinitionManager, instRouter.HandleFunc("/instance/{instID}/config", configHandler.listHandler).Methods("GET") instRouter.HandleFunc("/instance/{instID}/config/{cfgname}", configHandler.getHandler).Methods("GET") instRouter.HandleFunc("/instance/{instID}/config/{cfgname}", configHandler.updateHandler).Methods("PUT") - instRouter.HandleFunc("/instance/{instID}/config/{cfgname}", configHandler.deleteHandler).Methods("DELETE") - instRouter.HandleFunc("/instance/{instID}/config/rollback", configHandler.rollbackHandler).Methods("POST") - instRouter.HandleFunc("/instance/{instID}/config/tagit", configHandler.tagitHandler).Methods("POST") + instRouter.HandleFunc("/instance/{instID}/config/{cfgname}", configHandler.deleteAllHandler).Methods("DELETE") + instRouter.HandleFunc("/instance/{instID}/config/{cfgname}/delete", configHandler.deleteHandler).Methods("POST") + instRouter.HandleFunc("/instance/{instID}/config/{cfgname}/rollback", configHandler.rollbackHandler).Methods("POST") + instRouter.HandleFunc("/instance/{instID}/config/{cfgname}/tagit", configHandler.tagitHandler).Methods("POST") // Instance Healthcheck API if healthcheckClient == nil { diff --git a/src/k8splugin/api/confighandler.go b/src/k8splugin/api/confighandler.go index c2236378..a4f08131 100644 --- a/src/k8splugin/api/confighandler.go +++ b/src/k8splugin/api/confighandler.go @@ -117,6 +117,22 @@ func (h rbConfigHandler) listHandler(w http.ResponseWriter, r *http.Request) { } // deleteHandler handles DELETE operations on a config +func (h rbConfigHandler) deleteAllHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + instanceID := vars["instID"] + cfgName := vars["cfgname"] + + err := h.client.DeleteAll(instanceID, cfgName) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusAccepted) +} + +// deleteHandler handles delete operations on a config creating its delete version func (h rbConfigHandler) deleteHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) instanceID := vars["instID"] @@ -176,6 +192,7 @@ func (h rbConfigHandler) updateHandler(w http.ResponseWriter, r *http.Request) { func (h rbConfigHandler) rollbackHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) instanceID := vars["instID"] + cfgName := vars["cfgname"] if r.Body == nil { http.Error(w, "Empty body", http.StatusBadRequest) @@ -183,12 +200,13 @@ func (h rbConfigHandler) rollbackHandler(w http.ResponseWriter, r *http.Request) } var p app.ConfigRollback - err := json.NewDecoder(r.Body).Decode(&p) + err := json.NewDecoder(r.Body).Decode(&p.AnyOf) if err != nil { http.Error(w, err.Error(), http.StatusUnprocessableEntity) return } - err = h.client.Rollback(instanceID, p) + err = h.client.Rollback(instanceID, cfgName, p) + //err = h.client.Cleanup(instanceID) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -200,6 +218,7 @@ func (h rbConfigHandler) rollbackHandler(w http.ResponseWriter, r *http.Request) func (h rbConfigHandler) tagitHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) instanceID := vars["instID"] + cfgName := vars["cfgname"] if r.Body == nil { http.Error(w, "Empty body", http.StatusBadRequest) @@ -213,7 +232,7 @@ func (h rbConfigHandler) tagitHandler(w http.ResponseWriter, r *http.Request) { return } - err = h.client.Tagit(instanceID, p) + err = h.client.Tagit(instanceID, cfgName, p) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return diff --git a/src/k8splugin/api/configtemplatehandler.go b/src/k8splugin/api/configtemplatehandler.go index bd7c2db9..e8750fd8 100644 --- a/src/k8splugin/api/configtemplatehandler.go +++ b/src/k8splugin/api/configtemplatehandler.go @@ -20,9 +20,10 @@ import ( "encoding/json" "io" "io/ioutil" - "github.com/onap/multicloud-k8s/src/k8splugin/internal/rb" "net/http" + "github.com/onap/multicloud-k8s/src/k8splugin/internal/rb" + "github.com/gorilla/mux" ) @@ -58,7 +59,7 @@ func (h rbTemplateHandler) createHandler(w http.ResponseWriter, r *http.Request) return } - err = h.client.Create(rbName, rbVersion, p) + err = h.client.CreateOrUpdate(rbName, rbVersion, p, false) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -122,6 +123,78 @@ func (h rbTemplateHandler) getHandler(w http.ResponseWriter, r *http.Request) { } } +// createHandler handles creation of the template entry in the database +func (h rbTemplateHandler) updateHandler(w http.ResponseWriter, r *http.Request) { + var p rb.ConfigTemplate + + vars := mux.Vars(r) + rbName := vars["rbname"] + rbVersion := vars["rbversion"] + templateName := vars["tname"] + + err := json.NewDecoder(r.Body).Decode(&p) + switch { + case err == io.EOF: + http.Error(w, "Empty body", http.StatusBadRequest) + return + case err != nil: + http.Error(w, err.Error(), http.StatusUnprocessableEntity) + return + } + + // Name is required. + if p.TemplateName == "" { + http.Error(w, "Missing name in POST request", http.StatusBadRequest) + return + } + + ret, err := h.client.Get(rbName, rbVersion, templateName) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if p.TemplateName != "" && p.TemplateName != ret.TemplateName { + http.Error(w, "Template name mismatch", http.StatusBadRequest) + return + } + + err = h.client.CreateOrUpdate(rbName, rbVersion, p, true) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + err = json.NewEncoder(w).Encode(p) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } +} + +// getHandler handles GET operations on a particular template +func (h rbTemplateHandler) listHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + rbName := vars["rbname"] + rbVersion := vars["rbversion"] + + ret, err := h.client.List(rbName, rbVersion) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + err = json.NewEncoder(w).Encode(ret) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } +} + // deleteHandler handles DELETE operations on a template func (h rbTemplateHandler) deleteHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) diff --git a/src/k8splugin/api/defhandler.go b/src/k8splugin/api/defhandler.go index 480d4be5..3dea8ade 100644 --- a/src/k8splugin/api/defhandler.go +++ b/src/k8splugin/api/defhandler.go @@ -35,7 +35,7 @@ type rbDefinitionHandler struct { client rb.DefinitionManager } -// createHandler handles creation of the definition entry in the database +// createOrUpdateHandler handles creation of the definition entry in the database func (h rbDefinitionHandler) createHandler(w http.ResponseWriter, r *http.Request) { var v rb.Definition @@ -48,20 +48,64 @@ func (h rbDefinitionHandler) createHandler(w http.ResponseWriter, r *http.Reques http.Error(w, err.Error(), http.StatusUnprocessableEntity) return } + h.createOrUpdateHandler(v, w, false) +} + +// createOrUpdateHandler handles creation of the definition entry in the database +func (h rbDefinitionHandler) updateHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + name := vars["rbname"] + version := vars["rbversion"] + + var v rb.Definition + err := json.NewDecoder(r.Body).Decode(&v) + switch { + case err == io.EOF: + http.Error(w, "Empty body", http.StatusBadRequest) + return + case err != nil: + http.Error(w, err.Error(), http.StatusUnprocessableEntity) + return + } + + if v.RBVersion != "" && v.RBVersion != version { + http.Error(w, "RB version mismatch", http.StatusBadRequest) + return + } + + if v.RBName != "" && v.RBName != name { + http.Error(w, "RB name mismatch", http.StatusBadRequest) + return + } + + v.RBVersion = version + v.RBName = name + + h.createOrUpdateHandler(v, w, true) +} + +// createOrUpdateHandler handles creation of the definition entry in the database +func (h rbDefinitionHandler) createOrUpdateHandler(v rb.Definition, w http.ResponseWriter, update bool) { // Name is required. if v.RBName == "" { - http.Error(w, "Missing name in POST request", http.StatusBadRequest) + http.Error(w, "Missing name in request", http.StatusBadRequest) return } // Version is required. if v.RBVersion == "" { - http.Error(w, "Missing version in POST request", http.StatusBadRequest) + http.Error(w, "Missing version in request", http.StatusBadRequest) return } - ret, err := h.client.Create(v) + var ret rb.Definition + var err error + if update { + ret, err = h.client.Update(v) + } else { + ret, err = h.client.Create(v) + } if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return diff --git a/src/k8splugin/api/profilehandler.go b/src/k8splugin/api/profilehandler.go index acd23060..1babc4ae 100644 --- a/src/k8splugin/api/profilehandler.go +++ b/src/k8splugin/api/profilehandler.go @@ -56,7 +56,7 @@ func (h rbProfileHandler) createHandler(w http.ResponseWriter, r *http.Request) return } - ret, err := h.client.Create(p) + ret, err := h.client.CreateOrUpdate(p, false) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -127,6 +127,68 @@ func (h rbProfileHandler) getHandler(w http.ResponseWriter, r *http.Request) { } } +// updateHandler updates Profile Key in the database +// Returns an rb.Profile +func (h rbProfileHandler) updateHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + rbName := vars["rbname"] + rbVersion := vars["rbversion"] + prName := vars["prname"] + + ret, err := h.client.Get(rbName, rbVersion, prName) + if err != nil { + // Separate "Not found" from generic DB errors + if strings.Contains(err.Error(), "Error finding") { + http.Error(w, err.Error(), http.StatusNotFound) + return + } else { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } + + var p rb.Profile + + err = json.NewDecoder(r.Body).Decode(&p) + switch { + case err == io.EOF: + http.Error(w, "Empty body", http.StatusBadRequest) + return + case err != nil: + http.Error(w, err.Error(), http.StatusUnprocessableEntity) + return + } + + if p.ProfileName != "" && p.ProfileName != ret.ProfileName { + http.Error(w, "Profile name mismatch", http.StatusBadRequest) + return + } + + if p.RBVersion != "" && p.RBVersion != ret.RBVersion { + http.Error(w, "RB version mismatch", http.StatusBadRequest) + return + } + + if p.RBName != "" && p.RBName != ret.RBName { + http.Error(w, "RB name mismatch", http.StatusBadRequest) + return + } + + p.ProfileName = ret.ProfileName + p.RBVersion = ret.RBVersion + p.RBName = ret.RBName + + ret, err = h.client.CreateOrUpdate(p, true) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + err = json.NewEncoder(w).Encode(ret) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } +} + // getHandler gets all profiles of a Resource Bundle Key in the database // Returns a list of rb.Profile func (h rbProfileHandler) listHandler(w http.ResponseWriter, r *http.Request) { diff --git a/src/k8splugin/api/profilehandler_test.go b/src/k8splugin/api/profilehandler_test.go index 32d0061f..181b775b 100644 --- a/src/k8splugin/api/profilehandler_test.go +++ b/src/k8splugin/api/profilehandler_test.go @@ -42,7 +42,7 @@ type mockRBProfile struct { Err error } -func (m *mockRBProfile) Create(inp rb.Profile) (rb.Profile, error) { +func (m *mockRBProfile) CreateOrUpdate(inp rb.Profile, update bool) (rb.Profile, error) { if m.Err != nil { return rb.Profile{}, m.Err } diff --git a/src/k8splugin/api/queryhandler.go b/src/k8splugin/api/queryhandler.go index 9c11954c..f5950cdf 100644 --- a/src/k8splugin/api/queryhandler.go +++ b/src/k8splugin/api/queryhandler.go @@ -52,7 +52,7 @@ func (i queryHandler) queryHandler(w http.ResponseWriter, r *http.Request) { return } // instance id is irrelevant here - resp, err := i.client.Query(namespace, cloudRegion, apiVersion, kind, name, labels, "query") + resp, err := i.client.Query(namespace, cloudRegion, apiVersion, kind, name, labels) if err != nil { log.Error("Error getting Query results", log.Fields{ "error": err, diff --git a/src/k8splugin/internal/app/client.go b/src/k8splugin/internal/app/client.go index 9813333e..a2868cd5 100644 --- a/src/k8splugin/internal/app/client.go +++ b/src/k8splugin/internal/app/client.go @@ -20,25 +20,30 @@ package app import ( "context" "io/ioutil" + appsv1 "k8s.io/api/apps/v1" + //appsv1beta1 "k8s.io/api/apps/v1beta1" //appsv1beta2 "k8s.io/api/apps/v1beta2" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" + //extensionsv1beta1 "k8s.io/api/extensions/v1beta1" //apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" //apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "os" "strings" "time" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + logger "log" + "github.com/onap/multicloud-k8s/src/k8splugin/internal/config" "github.com/onap/multicloud-k8s/src/k8splugin/internal/connection" "github.com/onap/multicloud-k8s/src/k8splugin/internal/helm" log "github.com/onap/multicloud-k8s/src/k8splugin/internal/logutils" "github.com/onap/multicloud-k8s/src/k8splugin/internal/plugin" - logger "log" pkgerrors "github.com/pkg/errors" "k8s.io/apimachinery/pkg/api/meta" @@ -251,6 +256,7 @@ func (k *KubernetesClient) queryResources(apiVersion, kind, labelSelector, names LabelSelector: labelSelector, } var unstrList *unstructured.UnstructuredList + dynClient.Resource(gvr).Namespace(namespace).List(context.TODO(), opts) switch mapping.Scope.Name() { case meta.RESTScopeNameNamespace: unstrList, err = dynClient.Resource(gvr).Namespace(namespace).List(context.TODO(), opts) @@ -546,8 +552,16 @@ func (k *KubernetesClient) DeleteKind(resource helm.KubernetesResource, namespac } err = pluginImpl.Delete(resource, namespace, k) + if err != nil { - return pkgerrors.Wrap(err, "Error deleting "+resource.Name) + if strings.Contains(err.Error(), "not found") == false { + return pkgerrors.Wrap(err, "Error deleting "+resource.Name) + } else { + log.Warn("Resource already does not exist", log.Fields{ + "gvk": resource.GVK, + "resource": resource.Name, + }) + } } return nil diff --git a/src/k8splugin/internal/app/client_test.go b/src/k8splugin/internal/app/client_test.go index 0ba244d2..f51c15fc 100644 --- a/src/k8splugin/internal/app/client_test.go +++ b/src/k8splugin/internal/app/client_test.go @@ -15,13 +15,14 @@ package app import ( "encoding/base64" - "github.com/onap/multicloud-k8s/src/k8splugin/internal/utils" "io/ioutil" "os" "plugin" "reflect" "testing" + "github.com/onap/multicloud-k8s/src/k8splugin/internal/utils" + "github.com/onap/multicloud-k8s/src/k8splugin/internal/connection" "github.com/onap/multicloud-k8s/src/k8splugin/internal/db" "github.com/onap/multicloud-k8s/src/k8splugin/internal/helm" diff --git a/src/k8splugin/internal/app/config.go b/src/k8splugin/internal/app/config.go index 94acadcc..8952c16d 100644 --- a/src/k8splugin/internal/app/config.go +++ b/src/k8splugin/internal/app/config.go @@ -18,10 +18,11 @@ package app import ( + "log" "strconv" "strings" - "github.com/onap/multicloud-k8s/src/k8splugin/internal/db" + "github.com/onap/multicloud-k8s/src/k8splugin/internal/helm" pkgerrors "github.com/pkg/errors" ) @@ -66,8 +67,10 @@ type ConfigManager interface { Help() map[string]string Update(instanceID, configName string, p Config) (ConfigResult, error) Delete(instanceID, configName string) (ConfigResult, error) - Rollback(instanceID string, p ConfigRollback) error - Tagit(instanceID string, p ConfigTagit) error + DeleteAll(instanceID, configName string) error + Rollback(instanceID string, configName string, p ConfigRollback) error + Cleanup(instanceID string) error + Tagit(instanceID string, configName string, p ConfigTagit) error } // ConfigClient implements the ConfigManager @@ -94,7 +97,7 @@ func (v *ConfigClient) Help() map[string]string { // Create an entry for the config in the database func (v *ConfigClient) Create(instanceID string, p Config) (ConfigResult, error) { - + log.Printf("[Config Create] Instance %s", instanceID) // Check required fields if p.ConfigName == "" || p.TemplateName == "" || len(p.Values) == 0 { return ConfigResult{}, pkgerrors.New("Incomplete Configuration Provided") @@ -120,10 +123,12 @@ func (v *ConfigClient) Create(instanceID string, p Config) (ConfigResult, error) // Acquire per profile Mutex lock.Lock() defer lock.Unlock() - err = applyConfig(instanceID, p, profileChannel, "POST") + var appliedResources ([]helm.KubernetesResource) + appliedResources, err = applyConfig(instanceID, p, profileChannel, "POST", nil) if err != nil { return ConfigResult{}, pkgerrors.Wrap(err, "Apply Config failed") } + log.Printf("POST result: %s", appliedResources) // Create Config DB Entry err = cs.createConfig(p) if err != nil { @@ -132,8 +137,9 @@ func (v *ConfigClient) Create(instanceID string, p Config) (ConfigResult, error) // Create Version Entry in DB for Config cvs := ConfigVersionStore{ instanceID: instanceID, + configName: p.ConfigName, } - version, err := cvs.createConfigVersion(p, Config{}, "POST") + version, err := cvs.createConfigVersion(p, Config{}, "POST", appliedResources) if err != nil { return ConfigResult{}, pkgerrors.Wrap(err, "Create Config Version DB Entry") } @@ -153,7 +159,7 @@ func (v *ConfigClient) Create(instanceID string, p Config) (ConfigResult, error) // Update an entry for the config in the database func (v *ConfigClient) Update(instanceID, configName string, p Config) (ConfigResult, error) { - + log.Printf("[Config Update] Instance %s Config %s", instanceID, configName) // Check required fields if len(p.Values) == 0 { return ConfigResult{}, pkgerrors.New("Incomplete Configuration Provided") @@ -176,10 +182,12 @@ func (v *ConfigClient) Update(instanceID, configName string, p Config) (ConfigRe // Acquire per profile Mutex lock.Lock() defer lock.Unlock() - err = applyConfig(instanceID, p, profileChannel, "PUT") + var appliedResources ([]helm.KubernetesResource) + appliedResources, err = applyConfig(instanceID, p, profileChannel, "PUT", nil) if err != nil { return ConfigResult{}, pkgerrors.Wrap(err, "Apply Config failed") } + log.Printf("PUT result: %s", appliedResources) // Update Config DB Entry configPrev, err := cs.updateConfig(p) if err != nil { @@ -188,8 +196,9 @@ func (v *ConfigClient) Update(instanceID, configName string, p Config) (ConfigRe // Create Version Entry in DB for Config cvs := ConfigVersionStore{ instanceID: instanceID, + configName: configName, } - version, err := cvs.createConfigVersion(p, configPrev, "PUT") + version, err := cvs.createConfigVersion(p, configPrev, "PUT", appliedResources) if err != nil { return ConfigResult{}, pkgerrors.Wrap(err, "Create Config Version DB Entry") } @@ -245,8 +254,49 @@ func (v *ConfigClient) List(instanceID string) ([]Config, error) { } // Delete the Config from database -func (v *ConfigClient) Delete(instanceID, configName string) (ConfigResult, error) { +func (v *ConfigClient) DeleteAll(instanceID, configName string) error { + log.Printf("[Config Delete All] Instance %s Config %s", instanceID, configName) + // Check if Config exists + cs := ConfigStore{ + instanceID: instanceID, + configName: configName, + } + _, err := cs.getConfig() + if err != nil { + return pkgerrors.Wrap(err, "Update Error - Config doesn't exist") + } + // Get Version Entry in DB for Config + cvs := ConfigVersionStore{ + instanceID: instanceID, + configName: configName, + } + currentVersion, err := cvs.getCurrentVersion(configName) + if err != nil { + return pkgerrors.Wrap(err, "Current version get failed") + } + _, _, action, _, err := cvs.getConfigVersion(configName, currentVersion) + if err != nil { + return pkgerrors.Wrap(err, "Config version get failed") + } + + if action != "DELETE" { + _, err = v.Delete(instanceID, configName) + if err != nil { + return pkgerrors.Wrap(err, "Config DELETE version failed") + } + } + // Delete Config from DB + _, err = cs.deleteConfig() + if err != nil { + return pkgerrors.Wrap(err, "Delete Config DB Entry") + } + cvs.cleanupIstanceTags(configName) + return nil +} +// Apply update with delete operation +func (v *ConfigClient) Delete(instanceID, configName string) (ConfigResult, error) { + log.Printf("[Config Delete] Instance %s Config %s", instanceID, configName) // Resolving rbName, Version, etc. not to break response rbName, rbVersion, profileName, _, err := resolveModelFromInstance(instanceID) if err != nil { @@ -259,28 +309,39 @@ func (v *ConfigClient) Delete(instanceID, configName string) (ConfigResult, erro } p, err := cs.getConfig() if err != nil { - return ConfigResult{}, pkgerrors.Wrap(err, "Update Error - Config doesn't exist") + return ConfigResult{}, pkgerrors.Wrap(err, "Delete Error - Config doesn't exist") } lock, profileChannel := getProfileData(instanceID) // Acquire per profile Mutex lock.Lock() defer lock.Unlock() - err = applyConfig(instanceID, p, profileChannel, "DELETE") + // Create Version Entry in DB for Config + cvs := ConfigVersionStore{ + instanceID: instanceID, + configName: configName, + } + currentVersion, err := cvs.getCurrentVersion(configName) if err != nil { - return ConfigResult{}, pkgerrors.Wrap(err, "Apply Config failed") + return ConfigResult{}, pkgerrors.Wrap(err, "Current version get failed") } - // Delete Config from DB - configPrev, err := cs.deleteConfig() + _, _, _, resources, err := cvs.getConfigVersion(configName, currentVersion) if err != nil { - return ConfigResult{}, pkgerrors.Wrap(err, "Delete Config DB Entry") + return ConfigResult{}, pkgerrors.Wrap(err, "Config version get failed") } - // Create Version Entry in DB for Config - cvs := ConfigVersionStore{ - instanceID: instanceID, + + _, err = applyConfig(instanceID, p, profileChannel, "DELETE", resources) + if err != nil { + return ConfigResult{}, pkgerrors.Wrap(err, "Apply Config failed") + } + log.Printf("DELETE resources: [%s]", resources) + // Update Config from DB + configPrev, err := cs.updateConfig(p) + if err != nil { + return ConfigResult{}, pkgerrors.Wrap(err, "Update Config DB Entry") } - version, err := cvs.createConfigVersion(Config{}, configPrev, "DELETE") + version, err := cvs.createConfigVersion(p, configPrev, "DELETE", []helm.KubernetesResource{}) if err != nil { - return ConfigResult{}, pkgerrors.Wrap(err, "Delete Config Version DB Entry") + return ConfigResult{}, pkgerrors.Wrap(err, "Create Delete Config Version DB Entry") } // Create Result structure @@ -297,13 +358,13 @@ func (v *ConfigClient) Delete(instanceID, configName string) (ConfigResult, erro } // Rollback starts from current version and rollbacks to the version desired -func (v *ConfigClient) Rollback(instanceID string, rback ConfigRollback) error { - +func (v *ConfigClient) Rollback(instanceID string, configName string, rback ConfigRollback) error { + log.Printf("[Config Rollback] Instance %s Config %s", instanceID, configName) var reqVersion string var err error if rback.AnyOf.ConfigTag != "" { - reqVersion, err = v.GetTagVersion(instanceID, rback.AnyOf.ConfigTag) + reqVersion, err = v.GetTagVersion(instanceID, configName, rback.AnyOf.ConfigTag) if err != nil { return pkgerrors.Wrap(err, "Rollback Invalid tag") } @@ -326,8 +387,9 @@ func (v *ConfigClient) Rollback(instanceID string, rback ConfigRollback) error { cvs := ConfigVersionStore{ instanceID: instanceID, + configName: configName, } - currentVersion, err := cvs.getCurrentVersion() + currentVersion, err := cvs.getCurrentVersion(configName) if err != nil { return pkgerrors.Wrap(err, "Rollback Get Current Config Version ") } @@ -338,40 +400,35 @@ func (v *ConfigClient) Rollback(instanceID string, rback ConfigRollback) error { //Rollback all the intermettinent configurations for i := currentVersion; i > rollbackIndex; i-- { - configNew, configPrev, action, err := cvs.getConfigVersion(i) + configNew, configPrev, _, resources, err := cvs.getConfigVersion(configName, i) if err != nil { return pkgerrors.Wrap(err, "Rollback Get Config Version") } + _, _, prevAction, _, err := cvs.getConfigVersion(configName, i-1) + if err != nil { + return pkgerrors.Wrap(err, "Rollback Get Prev Config Version") + } cs := ConfigStore{ instanceID: instanceID, configName: configNew.ConfigName, } - if action == "PUT" { - // PUT is proceeded by PUT or POST - err = applyConfig(instanceID, configPrev, profileChannel, "PUT") + if prevAction != "DELETE" { + appliedResources, err := applyConfig(instanceID, configPrev, profileChannel, prevAction, nil) if err != nil { return pkgerrors.Wrap(err, "Apply Config failed") } + log.Printf("%s result: %s", prevAction, appliedResources) _, err = cs.updateConfig(configPrev) if err != nil { return pkgerrors.Wrap(err, "Update Config DB Entry") } - } else if action == "POST" { + } else { // POST is always preceeded by Config not existing - err = applyConfig(instanceID, configNew, profileChannel, "DELETE") - if err != nil { - return pkgerrors.Wrap(err, "Delete Config failed") - } - _, err = cs.deleteConfig() - if err != nil { - return pkgerrors.Wrap(err, "Delete Config DB Entry") - } - } else if action == "DELETE" { - // DELETE is proceeded by PUT or POST - err = applyConfig(instanceID, configPrev, profileChannel, "PUT") + _, err := applyConfig(instanceID, configNew, profileChannel, prevAction, resources) if err != nil { return pkgerrors.Wrap(err, "Delete Config failed") } + log.Printf("DELETE resources: %s", resources) _, err = cs.updateConfig(configPrev) if err != nil { return pkgerrors.Wrap(err, "Update Config DB Entry") @@ -380,7 +437,7 @@ func (v *ConfigClient) Rollback(instanceID string, rback ConfigRollback) error { } for i := currentVersion; i > rollbackIndex; i-- { // Delete rolled back items - err = cvs.deleteConfigVersion() + err = cvs.deleteConfigVersion(configName) if err != nil { return pkgerrors.Wrap(err, "Delete Config Version ") } @@ -389,12 +446,8 @@ func (v *ConfigClient) Rollback(instanceID string, rback ConfigRollback) error { } // Tagit tags the current version with the tag provided -func (v *ConfigClient) Tagit(instanceID string, tag ConfigTagit) error { - - rbName, rbVersion, profileName, _, err := resolveModelFromInstance(instanceID) - if err != nil { - return pkgerrors.Wrap(err, "Retrieving model info") - } +func (v *ConfigClient) Tagit(instanceID string, configName string, tag ConfigTagit) error { + log.Printf("[Config Tag It] Instance %s Config %s", instanceID, configName) lock, _ := getProfileData(instanceID) // Acquire per profile Mutex lock.Lock() @@ -402,39 +455,54 @@ func (v *ConfigClient) Tagit(instanceID string, tag ConfigTagit) error { cvs := ConfigVersionStore{ instanceID: instanceID, + configName: configName, } - currentVersion, err := cvs.getCurrentVersion() + err := cvs.tagCurrentVersion(configName, tag.TagName) if err != nil { - return pkgerrors.Wrap(err, "Get Current Config Version ") + return pkgerrors.Wrap(err, "Tag of current version failed") } - tagKey := constructKey(rbName, rbVersion, profileName, instanceID, v.tagTag, tag.TagName) + return nil +} - err = db.Etcd.Put(tagKey, strconv.Itoa(int(currentVersion))) +// GetTagVersion returns the version associated with the tag +func (v *ConfigClient) GetTagVersion(instanceID, configName string, tagName string) (string, error) { + log.Printf("[Config Get Tag Version] Instance %s Config %s", instanceID, configName) + cvs := ConfigVersionStore{ + instanceID: instanceID, + configName: configName, + } + value, err := cvs.getTagVersion(configName, tagName) if err != nil { - return pkgerrors.Wrap(err, "TagIt store DB") + return "", pkgerrors.Wrap(err, "Tag of current version failed") } - return nil + + return value, nil } -// GetTagVersion returns the version associated with the tag -func (v *ConfigClient) GetTagVersion(instanceID, tagName string) (string, error) { +// Cleanup version used only when instance is being deleted. We do not pass errors and we try to delete data +func (v *ConfigClient) Cleanup(instanceID string) error { + log.Printf("[Config Cleanup] Instance %s", instanceID) + configs, err := v.List(instanceID) - rbName, rbVersion, profileName, _, err := resolveModelFromInstance(instanceID) if err != nil { - return "", pkgerrors.Wrap(err, "Retrieving model info") + return pkgerrors.Wrap(err, "Retrieving active config list info") } - tagKey := constructKey(rbName, rbVersion, profileName, instanceID, v.tagTag, tagName) - value, err := db.Etcd.Get(tagKey) - if err != nil { - return "", pkgerrors.Wrap(err, "Config DB Entry Not found") + for _, config := range configs { + err = v.DeleteAll(instanceID, config.ConfigName) + if err != nil { + log.Printf("Config %s delete failed: %s", config.ConfigName, err.Error()) + } } - return string(value), nil + + removeProfileData(instanceID) + + return nil } // ApplyAllConfig starts from first configuration version and applies all versions in sequence -func (v *ConfigClient) ApplyAllConfig(instanceID string) error { - +func (v *ConfigClient) ApplyAllConfig(instanceID string, configName string) error { + log.Printf("[Config Apply All] Instance %s Config %s", instanceID, configName) lock, profileChannel := getProfileData(instanceID) // Acquire per profile Mutex lock.Lock() @@ -442,8 +510,9 @@ func (v *ConfigClient) ApplyAllConfig(instanceID string) error { cvs := ConfigVersionStore{ instanceID: instanceID, + configName: configName, } - currentVersion, err := cvs.getCurrentVersion() + currentVersion, err := cvs.getCurrentVersion(configName) if err != nil { return pkgerrors.Wrap(err, "Get Current Config Version ") } @@ -453,14 +522,19 @@ func (v *ConfigClient) ApplyAllConfig(instanceID string) error { //Apply all configurations var i uint for i = 1; i <= currentVersion; i++ { - configNew, _, action, err := cvs.getConfigVersion(i) + configNew, _, action, resources, err := cvs.getConfigVersion(configName, i) if err != nil { return pkgerrors.Wrap(err, "Get Config Version") } - err = applyConfig(instanceID, configNew, profileChannel, action) + if action != "DELETE" { + resources = nil + } + var appliedResources ([]helm.KubernetesResource) + appliedResources, err = applyConfig(instanceID, configNew, profileChannel, action, resources) if err != nil { return pkgerrors.Wrap(err, "Apply Config failed") } + log.Printf("%s result: %s", action, appliedResources) } return nil } diff --git a/src/k8splugin/internal/app/config_backend.go b/src/k8splugin/internal/app/config_backend.go index 30a480df..1f22922a 100644 --- a/src/k8splugin/internal/app/config_backend.go +++ b/src/k8splugin/internal/app/config_backend.go @@ -38,9 +38,10 @@ import ( //ConfigStore contains the values that will be stored in the database type configVersionDBContent struct { - ConfigNew Config `json:"config-new"` - ConfigPrev Config `json:"config-prev"` - Action string `json:"action"` // CRUD opration for this config + ConfigNew Config `json:"config-new"` + ConfigPrev Config `json:"config-prev"` + Action string `json:"action"` // CRUD opration for this config + Resources []helm.KubernetesResource `json:"resources"` } //ConfigStore to Store the Config @@ -52,11 +53,13 @@ type ConfigStore struct { //ConfigVersionStore to Store the Versions of the Config type ConfigVersionStore struct { instanceID string + configName string } type configResourceList struct { resourceTemplates []helm.KubernetesResourceTemplate - createdResources []helm.KubernetesResource + resources []helm.KubernetesResource + updatedResources chan []helm.KubernetesResource profile rb.Profile action string } @@ -71,6 +74,7 @@ const ( storeName = "config" tagCounter = "counter" tagVersion = "configversion" + tagName = "configtag" tagConfig = "configdata" ) @@ -222,10 +226,46 @@ func (c ConfigStore) deleteConfig() (Config, error) { return configPrev, nil } +//Cleanup stored data in etcd before instance is being deleted +func (c ConfigVersionStore) cleanupIstanceTags(configName string) error { + + rbName, rbVersion, profileName, _, err := resolveModelFromInstance(c.instanceID) + if err != nil { + return pkgerrors.Wrap(err, "Retrieving model info") + } + + versionKey := constructKey(rbName, rbVersion, profileName, c.instanceID, tagVersion, configName) + err = db.Etcd.DeletePrefix(versionKey) + if err != nil { + log.Printf("Deleting versions of instance failed: %s", err.Error()) + } + + counterKey := constructKey(rbName, rbVersion, profileName, c.instanceID, tagCounter, configName) + err = db.Etcd.DeletePrefix(counterKey) + if err != nil { + log.Printf("Deleting counters of instance failed: %s", err.Error()) + } + + nameKey := constructKey(rbName, rbVersion, profileName, c.instanceID, tagName, configName) + err = db.Etcd.DeletePrefix(nameKey) + if err != nil { + log.Printf("Deleting counters of instance failed: %s", err.Error()) + } + + return nil +} + // Create a version for the configuration. If previous config provided that is also stored -func (c ConfigVersionStore) createConfigVersion(configNew, configPrev Config, action string) (uint, error) { +func (c ConfigVersionStore) createConfigVersion(configNew, configPrev Config, action string, resources []helm.KubernetesResource) (uint, error) { - version, err := c.incrementVersion() + configName := "" + if configNew.ConfigName != "" { + configName = configNew.ConfigName + } else { + configName = configPrev.ConfigName + } + + version, err := c.incrementVersion(configName) if err != nil { return 0, pkgerrors.Wrap(err, "Get Next Version") @@ -234,12 +274,14 @@ func (c ConfigVersionStore) createConfigVersion(configNew, configPrev Config, ac if err != nil { return 0, pkgerrors.Wrap(err, "Retrieving model info") } - versionKey := constructKey(rbName, rbVersion, profileName, c.instanceID, tagVersion, strconv.Itoa(int(version))) + + versionKey := constructKey(rbName, rbVersion, profileName, c.instanceID, tagVersion, configName, strconv.Itoa(int(version))) var cs configVersionDBContent cs.Action = action cs.ConfigNew = configNew cs.ConfigPrev = configPrev + cs.Resources = resources //[]helm.KubernetesResource{} configValue, err := db.Serialize(cs) if err != nil { @@ -253,9 +295,9 @@ func (c ConfigVersionStore) createConfigVersion(configNew, configPrev Config, ac } // Delete current version of the configuration. Configuration always deleted from top -func (c ConfigVersionStore) deleteConfigVersion() error { +func (c ConfigVersionStore) deleteConfigVersion(configName string) error { - counter, err := c.getCurrentVersion() + counter, err := c.getCurrentVersion(configName) if err != nil { return pkgerrors.Wrap(err, "Get Next Version") @@ -264,13 +306,13 @@ func (c ConfigVersionStore) deleteConfigVersion() error { if err != nil { return pkgerrors.Wrap(err, "Retrieving model info") } - versionKey := constructKey(rbName, rbVersion, profileName, c.instanceID, tagVersion, strconv.Itoa(int(counter))) + versionKey := constructKey(rbName, rbVersion, profileName, c.instanceID, tagVersion, configName, strconv.Itoa(int(counter))) err = db.Etcd.Delete(versionKey) if err != nil { return pkgerrors.Wrap(err, "Delete Config DB Entry") } - err = c.decrementVersion() + err = c.decrementVersion(configName) if err != nil { return pkgerrors.Wrap(err, "Decrement Version") } @@ -279,37 +321,37 @@ func (c ConfigVersionStore) deleteConfigVersion() error { // Read the specified version of the configuration and return its prev and current value. // Also returns the action for the config version -func (c ConfigVersionStore) getConfigVersion(version uint) (Config, Config, string, error) { +func (c ConfigVersionStore) getConfigVersion(configName string, version uint) (Config, Config, string, []helm.KubernetesResource, error) { rbName, rbVersion, profileName, _, err := resolveModelFromInstance(c.instanceID) if err != nil { - return Config{}, Config{}, "", pkgerrors.Wrap(err, "Retrieving model info") + return Config{}, Config{}, "", []helm.KubernetesResource{}, pkgerrors.Wrap(err, "Retrieving model info") } - versionKey := constructKey(rbName, rbVersion, profileName, c.instanceID, tagVersion, strconv.Itoa(int(version))) + versionKey := constructKey(rbName, rbVersion, profileName, c.instanceID, tagVersion, configName, strconv.Itoa(int(version))) configBytes, err := db.Etcd.Get(versionKey) if err != nil { - return Config{}, Config{}, "", pkgerrors.Wrap(err, "Get Config Version ") + return Config{}, Config{}, "", []helm.KubernetesResource{}, pkgerrors.Wrap(err, "Get Config Version ") } if configBytes != nil { pr := configVersionDBContent{} err = db.DeSerialize(string(configBytes), &pr) if err != nil { - return Config{}, Config{}, "", pkgerrors.Wrap(err, "DeSerialize Config Version") + return Config{}, Config{}, "", []helm.KubernetesResource{}, pkgerrors.Wrap(err, "DeSerialize Config Version") } - return pr.ConfigNew, pr.ConfigPrev, pr.Action, nil + return pr.ConfigNew, pr.ConfigPrev, pr.Action, pr.Resources, nil } - return Config{}, Config{}, "", pkgerrors.Wrap(err, "Invalid data ") + return Config{}, Config{}, "", []helm.KubernetesResource{}, pkgerrors.Wrap(err, "Invalid data ") } // Get the counter for the version -func (c ConfigVersionStore) getCurrentVersion() (uint, error) { +func (c ConfigVersionStore) getCurrentVersion(configName string) (uint, error) { rbName, rbVersion, profileName, _, err := resolveModelFromInstance(c.instanceID) if err != nil { return 0, pkgerrors.Wrap(err, "Retrieving model info") } - cfgKey := constructKey(rbName, rbVersion, profileName, c.instanceID, tagCounter) + cfgKey := constructKey(rbName, rbVersion, profileName, c.instanceID, tagCounter, configName) value, err := db.Etcd.Get(cfgKey) if err != nil { @@ -329,13 +371,13 @@ func (c ConfigVersionStore) getCurrentVersion() (uint, error) { } // Update the counter for the version -func (c ConfigVersionStore) updateVersion(counter uint) error { +func (c ConfigVersionStore) updateVersion(configName string, counter uint) error { rbName, rbVersion, profileName, _, err := resolveModelFromInstance(c.instanceID) if err != nil { return pkgerrors.Wrap(err, "Retrieving model info") } - cfgKey := constructKey(rbName, rbVersion, profileName, c.instanceID, tagCounter) + cfgKey := constructKey(rbName, rbVersion, profileName, c.instanceID, tagCounter, configName) err = db.Etcd.Put(cfgKey, strconv.Itoa(int(counter))) if err != nil { return pkgerrors.Wrap(err, "Counter DB Entry") @@ -344,15 +386,15 @@ func (c ConfigVersionStore) updateVersion(counter uint) error { } // Increment the version counter -func (c ConfigVersionStore) incrementVersion() (uint, error) { +func (c ConfigVersionStore) incrementVersion(configName string) (uint, error) { - counter, err := c.getCurrentVersion() + counter, err := c.getCurrentVersion(configName) if err != nil { return 0, pkgerrors.Wrap(err, "Get Next Counter Value") } //This is done while Profile lock is taken counter++ - err = c.updateVersion(counter) + err = c.updateVersion(configName, counter) if err != nil { return 0, pkgerrors.Wrap(err, "Store Next Counter Value") } @@ -361,15 +403,15 @@ func (c ConfigVersionStore) incrementVersion() (uint, error) { } // Decrement the version counter -func (c ConfigVersionStore) decrementVersion() error { +func (c ConfigVersionStore) decrementVersion(configName string) error { - counter, err := c.getCurrentVersion() + counter, err := c.getCurrentVersion(configName) if err != nil { return pkgerrors.Wrap(err, "Get Next Counter Value") } //This is done while Profile lock is taken counter-- - err = c.updateVersion(counter) + err = c.updateVersion(configName, counter) if err != nil { return pkgerrors.Wrap(err, "Store Next Counter Value") } @@ -377,45 +419,87 @@ func (c ConfigVersionStore) decrementVersion() error { return nil } +// Get tag version +func (c ConfigVersionStore) getTagVersion(configName, tagNameValue string) (string, error) { + rbName, rbVersion, profileName, _, err := resolveModelFromInstance(c.instanceID) + if err != nil { + return "", pkgerrors.Wrap(err, "Retrieving model info") + } + tagKey := constructKey(rbName, rbVersion, profileName, c.instanceID, tagName, configName, tagNameValue) + + value, err := db.Etcd.Get(tagKey) + if err != nil { + return "", pkgerrors.Wrap(err, "Config DB Entry Not found") + } + return string(value), nil +} + +// Tag current version +func (c ConfigVersionStore) tagCurrentVersion(configName, tagNameValue string) error { + currentVersion, err := c.getCurrentVersion(configName) + if err != nil { + return pkgerrors.Wrap(err, "Get Current Config Version ") + } + rbName, rbVersion, profileName, _, err := resolveModelFromInstance(c.instanceID) + if err != nil { + return pkgerrors.Wrap(err, "Retrieving model info") + } + tagKey := constructKey(rbName, rbVersion, profileName, c.instanceID, tagName, configName, tagNameValue) + + err = db.Etcd.Put(tagKey, strconv.Itoa(int(currentVersion))) + if err != nil { + return pkgerrors.Wrap(err, "TagIt store DB") + } + return nil +} + // Apply Config -func applyConfig(instanceID string, p Config, pChannel chan configResourceList, action string) error { +func applyConfig(instanceID string, p Config, pChannel chan configResourceList, action string, resources []helm.KubernetesResource) ([]helm.KubernetesResource, error) { rbName, rbVersion, profileName, releaseName, err := resolveModelFromInstance(instanceID) if err != nil { - return pkgerrors.Wrap(err, "Retrieving model info") + return []helm.KubernetesResource{}, pkgerrors.Wrap(err, "Retrieving model info") } // Get Template and Resolve the template with values crl, err := resolve(rbName, rbVersion, profileName, p, releaseName) if err != nil { - return pkgerrors.Wrap(err, "Resolve Config") + return []helm.KubernetesResource{}, pkgerrors.Wrap(err, "Resolve Config") } + var updatedResources (chan []helm.KubernetesResource) = make(chan []helm.KubernetesResource) crl.action = action + crl.resources = resources + crl.updatedResources = updatedResources // Send the configResourceList to the channel. Using select for non-blocking channel + log.Printf("Before Sent to goroutine %v", crl.profile) select { case pChannel <- crl: log.Printf("Message Sent to goroutine %v", crl.profile) default: } - return nil + var resultResources []helm.KubernetesResource = <-updatedResources + return resultResources, nil } // Per Profile Go routine to apply the configuration to Cloud Region func scheduleResources(c chan configResourceList) { // Keep thread running + log.Printf("[scheduleResources]: START thread") for { data := <-c //TODO: ADD Check to see if Application running ic := NewInstanceClient() resp, err := ic.Find(data.profile.RBName, data.profile.RBVersion, data.profile.ProfileName, nil) - if err != nil || len(resp) == 0 { + if (err != nil || len(resp) == 0) && data.action != "STOP" { log.Println("Error finding a running instance. Retrying later...") - time.Sleep(time.Second * 10) + data.updatedResources <- []helm.KubernetesResource{} continue } + breakThread := false switch { case data.action == "POST": log.Printf("[scheduleResources]: POST %v %v", data.profile, data.resourceTemplates) + var resources []helm.KubernetesResource for _, inst := range resp { k8sClient := KubernetesClient{} err = k8sClient.Init(inst.Request.CloudRegion, inst.ID) @@ -425,11 +509,11 @@ func scheduleResources(c chan configResourceList) { continue } //assuming - the resource is not exist already - data.createdResources, err = k8sClient.createResources(data.resourceTemplates, inst.Namespace) + resources, err = k8sClient.createResources(data.resourceTemplates, inst.Namespace) errCreate := err if err != nil { // assuming - the err represent the resource is already exist, so going for update - data.createdResources, err = k8sClient.updateResources(data.resourceTemplates, inst.Namespace) + resources, err = k8sClient.updateResources(data.resourceTemplates, inst.Namespace) if err != nil { log.Printf("Error Creating resources: %s", errCreate.Error()) log.Printf("Error Updating resources: %s", err.Error()) @@ -437,12 +521,28 @@ func scheduleResources(c chan configResourceList) { } } } - //TODO: Needs to add code to call Kubectl create + data.updatedResources <- resources case data.action == "PUT": log.Printf("[scheduleResources]: PUT %v %v", data.profile, data.resourceTemplates) - //TODO: Needs to add code to call Kubectl apply + var resources []helm.KubernetesResource + for _, inst := range resp { + k8sClient := KubernetesClient{} + err = k8sClient.Init(inst.Request.CloudRegion, inst.ID) + if err != nil { + log.Printf("Getting CloudRegion Information: %s", err.Error()) + //Move onto the next cloud region + continue + } + + resources, err = k8sClient.updateResources(data.resourceTemplates, inst.Namespace) + if err != nil { + log.Printf("Error Updating resources: %s", err.Error()) + continue + } + } + data.updatedResources <- resources case data.action == "DELETE": - log.Printf("[scheduleResources]: DELETE %v %v", data.profile, data.resourceTemplates) + log.Printf("[scheduleResources]: DELETE %v %v", data.profile, data.resources) for _, inst := range resp { k8sClient := KubernetesClient{} err = k8sClient.Init(inst.Request.CloudRegion, inst.ID) @@ -451,14 +551,22 @@ func scheduleResources(c chan configResourceList) { //Move onto the next cloud region continue } - err = k8sClient.deleteResources(data.createdResources, inst.Namespace) + err = k8sClient.deleteResources(helm.GetReverseK8sResources(data.resources), inst.Namespace) if err != nil { log.Printf("Error Deleting resources: %s", err.Error()) continue } } + data.updatedResources <- []helm.KubernetesResource{} + + case data.action == "STOP": + breakThread = true + } + if breakThread { + break } } + log.Printf("[scheduleResources]: STOP thread") } //Resolve returns the path where the helm chart merged with @@ -523,14 +631,18 @@ var resolve = func(rbName, rbVersion, profileName string, p Config, releaseName finalReleaseName) chartPath := filepath.Join(chartBasePath, t.ChartName) - resTemplates, _, err = helmClient.GenerateKubernetesArtifacts(chartPath, + resTemplates, crdList, _, err := helmClient.GenerateKubernetesArtifacts(chartPath, []string{outputfile.Name()}, nil) if err != nil { return configResourceList{}, pkgerrors.Wrap(err, "Generate final k8s yaml") } + for _, tmp := range resTemplates { + crdList = append(crdList, tmp) + } + crl := configResourceList{ - resourceTemplates: resTemplates, + resourceTemplates: crdList, profile: profile, } @@ -549,6 +661,25 @@ func getProfileData(key string) (*sync.Mutex, chan configResourceList) { if !ok { profileData.resourceChannel[key] = make(chan configResourceList) go scheduleResources(profileData.resourceChannel[key]) + time.Sleep(time.Second * 5) } return profileData.profileLockMap[key], profileData.resourceChannel[key] } + +func removeProfileData(key string) { + profileData.Lock() + defer profileData.Unlock() + _, ok := profileData.profileLockMap[key] + if ok { + delete(profileData.profileLockMap, key) + } + _, ok = profileData.resourceChannel[key] + if ok { + log.Printf("Stop config thread for %s", key) + crl := configResourceList{ + action: "STOP", + } + profileData.resourceChannel[key] <- crl + delete(profileData.resourceChannel, key) + } +} diff --git a/src/k8splugin/internal/app/config_test.go b/src/k8splugin/internal/app/config_test.go index 9ee96881..0cc3c3ce 100644 --- a/src/k8splugin/internal/app/config_test.go +++ b/src/k8splugin/internal/app/config_test.go @@ -293,7 +293,7 @@ func TestRollbackConfig(t *testing.T) { } } testCase.rollbackConfig.AnyOf.ConfigVersion = "2" - err = impl.Rollback(testCase.instanceID, testCase.rollbackConfig) + err = impl.Rollback(testCase.instanceID, testCase.inp.ConfigName, testCase.rollbackConfig) if err != nil { if testCase.expectedError == "" { t.Fatalf("Create returned an unexpected error %s", err) @@ -319,3 +319,6 @@ func TestRollbackConfig(t *testing.T) { }) } } + +func main() { +} diff --git a/src/k8splugin/internal/app/instance.go b/src/k8splugin/internal/app/instance.go index ad36aaa5..e50a59e5 100644 --- a/src/k8splugin/internal/app/instance.go +++ b/src/k8splugin/internal/app/instance.go @@ -30,11 +30,11 @@ import ( appsv1 "k8s.io/api/apps/v1" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" - apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/cli-runtime/pkg/resource" + "github.com/onap/multicloud-k8s/src/k8splugin/internal/config" "github.com/onap/multicloud-k8s/src/k8splugin/internal/db" "github.com/onap/multicloud-k8s/src/k8splugin/internal/helm" "github.com/onap/multicloud-k8s/src/k8splugin/internal/namegenerator" @@ -225,7 +225,7 @@ func (v *InstanceClient) Create(i InstanceRequest) (InstanceResponse, error) { } //Execute the kubernetes create command - sortedTemplates, hookList, releaseName, err := rb.NewProfileClient().Resolve(i.RBName, i.RBVersion, i.ProfileName, overrideValues, i.ReleaseName) + sortedTemplates, crdList, hookList, releaseName, err := rb.NewProfileClient().Resolve(i.RBName, i.RBVersion, i.ProfileName, overrideValues, i.ReleaseName) if err != nil { return InstanceResponse{}, pkgerrors.Wrap(err, "Error resolving helm charts") } @@ -245,6 +245,12 @@ func (v *InstanceClient) Create(i InstanceRequest) (InstanceResponse, error) { log.Printf(" Kind: %s", t.GVK.Kind) } + log.Printf("Crd rss info") + for _, t := range crdList { + log.Printf(" Path: %s", t.FilePath) + log.Printf(" Kind: %s", t.GVK.Kind) + } + log.Printf("Hook info") for _, h := range hookList { log.Printf(" Name: %s", h.Hook.Name) @@ -280,6 +286,15 @@ func (v *InstanceClient) Create(i InstanceRequest) (InstanceResponse, error) { return InstanceResponse{}, pkgerrors.Wrap(err, "Creating Namespace") } + if len(crdList) > 0 { + log.Printf("Pre-Installing CRDs") + _, err = k8sClient.createResources(crdList, profile.Namespace) + + if err != nil { + return InstanceResponse{}, pkgerrors.Wrap(err, "Pre-Installing CRDs") + } + } + hookClient := NewHookClient(profile.Namespace, id, v.storeName, v.tagInst) if len(hookClient.getHookByEvent(hookList, release.HookPreInstall)) != 0 { err = hookClient.ExecHook(k8sClient, hookList, release.HookPreInstall, preInstallTimeOut, 0, &dbData) @@ -308,7 +323,7 @@ func (v *InstanceClient) Create(i InstanceRequest) (InstanceResponse, error) { if err != nil { if len(createdResources) > 0 { log.Printf("[Instance] Reverting created resources on Error: %s", err.Error()) - k8sClient.deleteResources(createdResources, profile.Namespace) + k8sClient.deleteResources(helm.GetReverseK8sResources(createdResources), profile.Namespace) } log.Printf(" Instance: %s, Main rss are failed, skip post-install and remove instance in DB", id) //main rss creation failed -> remove instance in DB @@ -447,7 +462,15 @@ func (v *InstanceClient) Query(id, apiVersion, kind, name, labels string) (Insta return InstanceStatus{}, pkgerrors.Wrap(err, "Unmarshaling Instance Value") } - resources, err := queryClient.Query(resResp.Namespace, resResp.Request.CloudRegion, apiVersion, kind, name, labels, id) + if labels == "" || strings.Contains(strings.ToLower(labels), config.GetConfiguration().KubernetesLabelName) == false { + labelValue := config.GetConfiguration().KubernetesLabelName + "=" + id + if labels != "" { + labels = labels + "," + } + labels = labels + labelValue + } + + resources, err := queryClient.Query(resResp.Namespace, resResp.Request.CloudRegion, apiVersion, kind, name, labels) if err != nil { return InstanceStatus{}, pkgerrors.Wrap(err, "Querying Resources") } @@ -488,6 +511,11 @@ func (v *InstanceClient) Status(id string) (InstanceStatus, error) { if err != nil { return InstanceStatus{}, pkgerrors.Wrap(err, "Getting CloudRegion Information") } + req := resResp.Request + profile, err := rb.NewProfileClient().Get(req.RBName, req.RBVersion, req.ProfileName) + if err != nil { + return InstanceStatus{}, pkgerrors.New("Unable to find Profile instance status") + } cumulatedErrorMsg := make([]string, 0) podsStatus, err := k8sClient.getPodsByLabel(resResp.Namespace) @@ -520,12 +548,36 @@ Main: } } } + generalStatus = append(generalStatus, podsStatus...) + + if profile.ExtraResourceTypes != nil && len(profile.ExtraResourceTypes) > 0 { + queryClient := NewQueryClient() + labelValue := config.GetConfiguration().KubernetesLabelName + "=" + id + for _, extraType := range profile.ExtraResourceTypes { + queryStatus, err := queryClient.Query(resResp.Namespace, resResp.Request.CloudRegion, extraType.GroupVersion().Identifier(), extraType.Kind, "", labelValue) + if err != nil { + return InstanceStatus{}, pkgerrors.Wrap(err, "Querying Resources") + } + for _, rs := range queryStatus.ResourcesStatus { + foundRes := false + for _, res := range generalStatus { + if res.GVK == rs.GVK && res.Name == rs.Name { + foundRes = true + break + } + } + if !foundRes { + generalStatus = append(generalStatus, rs) + } + } + } + } //We still need to iterate through rss list even the status is not DONE, to gather status of rss + pod for the response resp := InstanceStatus{ Request: resResp.Request, - ResourceCount: int32(len(generalStatus) + len(podsStatus)), + ResourceCount: int32(len(generalStatus)), Ready: isReady && resResp.Status == "DONE", - ResourcesStatus: append(generalStatus, podsStatus...), + ResourcesStatus: generalStatus, } if len(cumulatedErrorMsg) != 0 { @@ -561,8 +613,6 @@ func (v *InstanceClient) checkRssStatus(rss helm.KubernetesResource, k8sClient K parsedRes = new(corev1.Service) case "DaemonSet": parsedRes = new(appsv1.DaemonSet) - case "CustomResourceDefinition": - parsedRes = new(apiextv1.CustomResourceDefinition) case "StatefulSet": parsedRes = new(appsv1.StatefulSet) case "ReplicationController": @@ -709,7 +759,8 @@ func (v *InstanceClient) Delete(id string) error { return nil } else if inst.Status != "DONE" { //Recover is ongoing, do nothing here - return nil + //return nil + //TODO: implement recovery } k8sClient := KubernetesClient{} @@ -717,6 +768,7 @@ func (v *InstanceClient) Delete(id string) error { if err != nil { return pkgerrors.Wrap(err, "Getting CloudRegion Information") } + inst.Status = "PRE-DELETE" inst.HookProgress = "" err = db.DBconn.Update(v.storeName, key, v.tagInst, inst) @@ -743,7 +795,14 @@ func (v *InstanceClient) Delete(id string) error { if err != nil { log.Printf("Update Instance DB Entry for release %s has error.", inst.ReleaseName) } - err = k8sClient.deleteResources(inst.Resources, inst.Namespace) + + configClient := NewConfigClient() + err = configClient.Cleanup(id) + if err != nil { + return pkgerrors.Wrap(err, "Cleanup Config Resources") + } + + err = k8sClient.deleteResources(helm.GetReverseK8sResources(inst.Resources), inst.Namespace) if err != nil { return pkgerrors.Wrap(err, "Deleting Instance Resources") } @@ -782,7 +841,7 @@ func (v *InstanceClient) RecoverCreateOrDelete(id string) error { ID: id, } log.Printf(" Resolving template for release %s", instance.Request.ReleaseName) - _, hookList, _, err := rb.NewProfileClient().Resolve(instance.Request.RBName, instance.Request.RBVersion, instance.Request.ProfileName, overrideValues, instance.Request.ReleaseName) + _, _, hookList, _, err := rb.NewProfileClient().Resolve(instance.Request.RBName, instance.Request.RBVersion, instance.Request.ProfileName, overrideValues, instance.Request.ReleaseName) instance.Hooks = hookList err = db.DBconn.Update(v.storeName, key, v.tagInst, instance) if err != nil { @@ -851,7 +910,7 @@ func (v *InstanceClient) RecoverCreateOrDelete(id string) error { return } - err = k8sClient.deleteResources(instance.Resources, instance.Namespace) + err = k8sClient.deleteResources(helm.GetReverseK8sResources(instance.Resources), instance.Namespace) if err != nil { log.Printf(" Error running deleting instance resources, error: %s", err) return diff --git a/src/k8splugin/internal/app/query.go b/src/k8splugin/internal/app/query.go index cb645afd..251b14e6 100644 --- a/src/k8splugin/internal/app/query.go +++ b/src/k8splugin/internal/app/query.go @@ -33,7 +33,7 @@ type QueryStatus struct { // QueryManager is an interface exposes the instantiation functionality type QueryManager interface { - Query(namespace, cloudRegion, apiVersion, kind, name, labels, id string) (QueryStatus, error) + Query(namespace, cloudRegion, apiVersion, kind, name, labels string) (QueryStatus, error) } // QueryClient implements the InstanceManager interface @@ -53,12 +53,12 @@ func NewQueryClient() *QueryClient { } // Query returns state of instance's filtered resources -func (v *QueryClient) Query(namespace, cloudRegion, apiVersion, kind, name, labels, id string) (QueryStatus, error) { +func (v *QueryClient) Query(namespace, cloudRegion, apiVersion, kind, name, labels string) (QueryStatus, error) { //Read the status from the DD k8sClient := KubernetesClient{} - err := k8sClient.Init(cloudRegion, id) + err := k8sClient.Init(cloudRegion, "dummy") //we don't care about instance id in this request if err != nil { return QueryStatus{}, pkgerrors.Wrap(err, "Getting CloudRegion Information") } diff --git a/src/k8splugin/internal/db/etcd.go b/src/k8splugin/internal/db/etcd.go index a435b435..e455cc1a 100644 --- a/src/k8splugin/internal/db/etcd.go +++ b/src/k8splugin/internal/db/etcd.go @@ -39,6 +39,7 @@ type EtcdStore interface { GetAll(key string) ([][]byte, error) Put(key, value string) error Delete(key string) error + DeletePrefix(keyPrefix string) error } // EtcdClient for Etcd @@ -151,3 +152,16 @@ func (e EtcdClient) Delete(key string) error { } return nil } + +// Delete values by prefix from Etcd DB +func (e EtcdClient) DeletePrefix(keyPrefix string) error { + + if e.cli == nil { + return pkgerrors.Errorf("Etcd Client not initialized") + } + _, err := e.cli.Delete(context.Background(), keyPrefix, clientv3.WithPrefix()) + if err != nil { + return pkgerrors.Errorf("Delete prefix failed etcd entry:%s", err.Error()) + } + return nil +} diff --git a/src/k8splugin/internal/db/etcd_testing.go b/src/k8splugin/internal/db/etcd_testing.go index 9dfcad82..4b4dfe3e 100644 --- a/src/k8splugin/internal/db/etcd_testing.go +++ b/src/k8splugin/internal/db/etcd_testing.go @@ -55,3 +55,12 @@ func (c *MockEtcdClient) Delete(key string) error { delete(c.Items, key) return c.Err } + +func (c *MockEtcdClient) DeletePrefix(key string) error { + for kvKey := range c.Items { + if strings.HasPrefix(kvKey, key) { + delete(c.Items, key) + } + } + return c.Err +} diff --git a/src/k8splugin/internal/db/mongo.go b/src/k8splugin/internal/db/mongo.go index c15b108b..aa05820a 100644 --- a/src/k8splugin/internal/db/mongo.go +++ b/src/k8splugin/internal/db/mongo.go @@ -17,9 +17,10 @@ package db import ( - "golang.org/x/net/context" "log" + "golang.org/x/net/context" + "github.com/onap/multicloud-k8s/src/k8splugin/internal/config" pkgerrors "github.com/pkg/errors" @@ -374,7 +375,7 @@ func (m *MongoStore) ReadAll(coll, tag string) (map[string][]byte, error) { //Get objectID of tag document tid, ok := d.Lookup(tag).ObjectIDOK() if !ok { - log.Printf("Did not find tag: %s", tag) + //"Did not find tag: %s", tag) continue } diff --git a/src/k8splugin/internal/helm/helm.go b/src/k8splugin/internal/helm/helm.go index 849674a9..b27c8aee 100644 --- a/src/k8splugin/internal/helm/helm.go +++ b/src/k8splugin/internal/helm/helm.go @@ -19,13 +19,14 @@ package helm import ( "fmt" - "github.com/onap/multicloud-k8s/src/k8splugin/internal/utils" "io/ioutil" "os" "path/filepath" "regexp" "strings" + "github.com/onap/multicloud-k8s/src/k8splugin/internal/utils" + pkgerrors "github.com/pkg/errors" "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/chart/loader" @@ -47,7 +48,7 @@ type Template interface { GenerateKubernetesArtifacts( chartPath string, valueFiles []string, - values []string) ([]KubernetesResourceTemplate, []*Hook, error) + values []string) ([]KubernetesResourceTemplate, []KubernetesResourceTemplate, []*Hook, error) } // TemplateClient implements the Template interface @@ -90,10 +91,11 @@ func (h *TemplateClient) processValues(valueFiles []string, values []string) (ma // GenerateKubernetesArtifacts a mapping of type to fully evaluated helm template func (h *TemplateClient) GenerateKubernetesArtifacts(inputPath string, valueFiles []string, - values []string) ([]KubernetesResourceTemplate, []*Hook, error) { + values []string) ([]KubernetesResourceTemplate, []KubernetesResourceTemplate, []*Hook, error) { var outputDir, chartPath, namespace, releaseName string var retData []KubernetesResourceTemplate + var crdData []KubernetesResourceTemplate var hookList []*Hook releaseName = h.releaseName @@ -102,16 +104,16 @@ func (h *TemplateClient) GenerateKubernetesArtifacts(inputPath string, valueFile // verify chart path exists if _, err := os.Stat(inputPath); err == nil { if chartPath, err = filepath.Abs(inputPath); err != nil { - return retData, hookList, err + return retData, crdData, hookList, err } } else { - return retData, hookList, err + return retData, crdData, hookList, err } //Create a temp directory in the system temp folder outputDir, err := ioutil.TempDir("", "helm-tmpl-") if err != nil { - return retData, hookList, pkgerrors.Wrap(err, "Got error creating temp dir") + return retData, crdData, hookList, pkgerrors.Wrap(err, "Got error creating temp dir") } if namespace == "" { @@ -121,11 +123,11 @@ func (h *TemplateClient) GenerateKubernetesArtifacts(inputPath string, valueFile // get combined values and create config rawVals, err := h.processValues(valueFiles, values) if err != nil { - return retData, hookList, err + return retData, crdData, hookList, err } if msgs := validation.IsDNS1123Label(releaseName); releaseName != "" && len(msgs) > 0 { - return retData, hookList, fmt.Errorf("release name %s is not a valid DNS label: %s", releaseName, strings.Join(msgs, ";")) + return retData, crdData, hookList, fmt.Errorf("release name %s is not a valid DNS label: %s", releaseName, strings.Join(msgs, ";")) } // Initialize the install client @@ -133,27 +135,52 @@ func (h *TemplateClient) GenerateKubernetesArtifacts(inputPath string, valueFile client.DryRun = true client.ClientOnly = true client.ReleaseName = releaseName - client.IncludeCRDs = true + client.IncludeCRDs = false client.DisableHooks = true //to ensure no duplicates in case of defined pre/post install hooks // Check chart dependencies to make sure all are present in /charts chartRequested, err := loader.Load(chartPath) if err != nil { - return retData, hookList, err + return retData, crdData, hookList, err } if chartRequested.Metadata.Type != "" && chartRequested.Metadata.Type != "application" { - return retData, hookList, fmt.Errorf( + return retData, crdData, hookList, fmt.Errorf( "chart %q has an unsupported type and is not installable: %q", chartRequested.Metadata.Name, chartRequested.Metadata.Type, ) } + for _, crd := range chartRequested.CRDObjects() { + if strings.HasPrefix(crd.Name, "_") { + continue + } + filePath := filepath.Join(outputDir, crd.Name) + data := string(crd.File.Data) + // blank template after execution + if h.emptyRegex.MatchString(data) { + continue + } + utils.EnsureDirectory(filePath) + err = ioutil.WriteFile(filePath, []byte(crd.File.Data), 0600) + if err != nil { + return retData, crdData, hookList, err + } + gvk, err := getGroupVersionKind(data) + if err != nil { + return retData, crdData, hookList, err + } + kres := KubernetesResourceTemplate{ + GVK: gvk, + FilePath: filePath, + } + crdData = append(crdData, kres) + } client.Namespace = namespace release, err := client.Run(chartRequested, rawVals) if err != nil { - return retData, hookList, err + return retData, crdData, hookList, err } // SplitManifests returns integer-sortable so that manifests get output // in the same order as the input by `BySplitManifestsOrder`. @@ -161,7 +188,7 @@ func (h *TemplateClient) GenerateKubernetesArtifacts(inputPath string, valueFile // We won't get any meaningful hooks from here _, m, err := releaseutil.SortManifests(rmap, nil, releaseutil.InstallOrder) if err != nil { - return retData, hookList, err + return retData, crdData, hookList, err } for _, k := range m { data := k.Content @@ -180,11 +207,11 @@ func (h *TemplateClient) GenerateKubernetesArtifacts(inputPath string, valueFile utils.EnsureDirectory(mfilePath) err = ioutil.WriteFile(mfilePath, []byte(k.Content), 0600) if err != nil { - return retData, hookList, err + return retData, crdData, hookList, err } gvk, err := getGroupVersionKind(data) if err != nil { - return retData, hookList, err + return retData, crdData, hookList, err } kres := KubernetesResourceTemplate{ GVK: gvk, @@ -197,15 +224,15 @@ func (h *TemplateClient) GenerateKubernetesArtifacts(inputPath string, valueFile utils.EnsureDirectory(hFilePath) err = ioutil.WriteFile(hFilePath, []byte(h.Manifest), 0600) if err != nil { - return retData, hookList, err + return retData, crdData, hookList, err } gvk, err := getGroupVersionKind(h.Manifest) if err != nil { - return retData, hookList, err + return retData, crdData, hookList, err } hookList = append(hookList, &Hook{*h, KubernetesResourceTemplate{gvk, hFilePath}}) } - return retData, hookList, nil + return retData, crdData, hookList, nil } func getGroupVersionKind(data string) (schema.GroupVersionKind, error) { @@ -222,3 +249,13 @@ func getGroupVersionKind(data string) (schema.GroupVersionKind, error) { return *gvk, nil } + +//GetReverseK8sResources reverse list of resources for delete purpose +func GetReverseK8sResources(resources []KubernetesResource) []KubernetesResource { + reversed := []KubernetesResource{} + + for i := len(resources) - 1; i >= 0; i-- { + reversed = append(reversed, resources[i]) + } + return reversed +} diff --git a/src/k8splugin/internal/helm/helm_test.go b/src/k8splugin/internal/helm/helm_test.go index 29d446fa..951ff92b 100644 --- a/src/k8splugin/internal/helm/helm_test.go +++ b/src/k8splugin/internal/helm/helm_test.go @@ -20,11 +20,13 @@ package helm import ( "crypto/sha256" "fmt" - "gopkg.in/yaml.v2" "io/ioutil" "path/filepath" "strings" "testing" + + "gopkg.in/yaml.v2" + "k8s.io/apimachinery/pkg/runtime/schema" ) func TestProcessValues(t *testing.T) { @@ -202,7 +204,7 @@ func TestGenerateKubernetesArtifacts(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.label, func(t *testing.T) { tc := NewTemplateClient("1.12.3", "testnamespace", "testreleasename") - out, hooks, err := tc.GenerateKubernetesArtifacts(testCase.chartPath, testCase.valueFiles, + out, _, hooks, err := tc.GenerateKubernetesArtifacts(testCase.chartPath, testCase.valueFiles, testCase.values) if err != nil { if testCase.expectedError == "" { @@ -264,3 +266,45 @@ func TestGenerateKubernetesArtifacts(t *testing.T) { }) } } + +func TestReverseResources(t *testing.T) { + + t.Run("Successfully reverse resources", func(t *testing.T) { + data := []KubernetesResource{ + { + GVK: schema.GroupVersionKind{ + Group: "apps", + Version: "v1", + Kind: "Deployment"}, + Name: "deployment-1", + }, + { + GVK: schema.GroupVersionKind{ + Group: "apps", + Version: "v1", + Kind: "Deployment"}, + Name: "deployment-2", + }, + { + GVK: schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: "Service"}, + Name: "service-1", + }, + { + GVK: schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: "Service"}, + Name: "service-2", + }, + } + + reversed := GetReverseK8sResources(data) + + if reversed[0] != data[len(data)-1] { + t.Fatalf("Unexpected k8s resource at position 0 %s", reversed[0]) + } + }) +} diff --git a/src/k8splugin/internal/rb/config_template.go b/src/k8splugin/internal/rb/config_template.go index cf45a6f2..97fe0fb4 100644 --- a/src/k8splugin/internal/rb/config_template.go +++ b/src/k8splugin/internal/rb/config_template.go @@ -20,14 +20,16 @@ import ( "bytes" "encoding/json" "io/ioutil" - "github.com/onap/multicloud-k8s/src/k8splugin/internal/db" "os" "path/filepath" + "github.com/onap/multicloud-k8s/src/k8splugin/internal/db" + "encoding/base64" - pkgerrors "github.com/pkg/errors" "log" + + pkgerrors "github.com/pkg/errors" ) // ConfigTemplate contains the parameters needed for ConfigTemplates @@ -39,8 +41,9 @@ type ConfigTemplate struct { // ConfigTemplateManager is an interface exposes the resource bundle ConfigTemplate functionality type ConfigTemplateManager interface { - Create(rbName, rbVersion string, p ConfigTemplate) error + CreateOrUpdate(rbName, rbVersion string, p ConfigTemplate, update bool) error Get(rbName, rbVersion, templateName string) (ConfigTemplate, error) + List(rbName, rbVersion string) ([]ConfigTemplate, error) Delete(rbName, rbVersion, templateName string) error Upload(rbName, rbVersion, templateName string, inp []byte) error } @@ -76,13 +79,13 @@ type ConfigTemplateClient struct { func NewConfigTemplateClient() *ConfigTemplateClient { return &ConfigTemplateClient{ storeName: "rbdef", - tagMeta: "metadata", - tagContent: "content", + tagMeta: "confdefmetadata", + tagContent: "confdefcontent", } } -// Create an entry for the resource bundle ConfigTemplate in the database -func (v *ConfigTemplateClient) Create(rbName, rbVersion string, p ConfigTemplate) error { +// CreateOrUpdate an entry for the resource bundle ConfigTemplate in the database +func (v *ConfigTemplateClient) CreateOrUpdate(rbName, rbVersion string, p ConfigTemplate, update bool) error { log.Printf("[ConfigiTemplate]: create %s", rbName) // Name is required @@ -92,9 +95,12 @@ func (v *ConfigTemplateClient) Create(rbName, rbVersion string, p ConfigTemplate //Check if ConfigTemplate already exists _, err := v.Get(rbName, rbVersion, p.TemplateName) - if err == nil { + if err == nil && !update { return pkgerrors.New(" ConfigTemplate already exists for this Definition") } + if err != nil && update { + return pkgerrors.New(" ConfigTemplate does not exist for this Definition") + } //Check if provided resource bundle information is valid _, err = NewDefinitionClient().Get(rbName, rbVersion) @@ -108,9 +114,16 @@ func (v *ConfigTemplateClient) Create(rbName, rbVersion string, p ConfigTemplate TemplateName: p.TemplateName, } - err = db.DBconn.Create(v.storeName, key, v.tagMeta, p) - if err != nil { - return pkgerrors.Wrap(err, "Creating ConfigTemplate DB Entry") + if update { + err = db.DBconn.Update(v.storeName, key, v.tagMeta, p) + if err != nil { + return pkgerrors.Wrap(err, "Updating ConfigTemplate DB Entry") + } + } else { + err = db.DBconn.Create(v.storeName, key, v.tagMeta, p) + if err != nil { + return pkgerrors.Wrap(err, "Creating ConfigTemplate DB Entry") + } } return nil @@ -141,6 +154,44 @@ func (v *ConfigTemplateClient) Get(rbName, rbVersion, templateName string) (Conf return ConfigTemplate{}, pkgerrors.New("Error getting ConfigTemplate") } +// List returns the Resource Bundle ConfigTemplate for corresponding ID +func (v *ConfigTemplateClient) List(rbName, rbVersion string) ([]ConfigTemplate, error) { + + //Get all config templates + dbres, err := db.DBconn.ReadAll(v.storeName, v.tagMeta) + if err != nil || len(dbres) == 0 { + return []ConfigTemplate{}, pkgerrors.Wrap(err, "No Config Templates Found") + } + + var results []ConfigTemplate + for key, value := range dbres { + //value is a byte array + if value != nil { + tmp := ConfigTemplate{} + err = db.DBconn.Unmarshal(value, &tmp) + if err != nil { + log.Printf("[ConfigTemplate] Error: %s Unmarshaling value for: %s", err.Error(), key) + continue + } + keyTmp := ConfigTemplateKey{ + RBName: rbName, + RBVersion: rbVersion, + TemplateName: tmp.TemplateName, + } + _, err := db.DBconn.Read(v.storeName, keyTmp, v.tagMeta) + if err == nil && keyTmp.RBName == rbName && keyTmp.RBVersion == rbVersion { + results = append(results, tmp) + } + } + } + + if len(results) == 0 { + return results, pkgerrors.New("No Config Templates Found for Definition and Version") + } + + return results, nil +} + // Delete the Resource Bundle ConfigTemplate from database func (v *ConfigTemplateClient) Delete(rbName, rbVersion, templateName string) error { key := ConfigTemplateKey{ diff --git a/src/k8splugin/internal/rb/definition.go b/src/k8splugin/internal/rb/definition.go index 73ea44da..aa76afaa 100644 --- a/src/k8splugin/internal/rb/definition.go +++ b/src/k8splugin/internal/rb/definition.go @@ -61,6 +61,7 @@ func (dk DefinitionKey) String() string { // DefinitionManager is an interface exposes the resource bundle definition functionality type DefinitionManager interface { Create(def Definition) (Definition, error) + Update(def Definition) (Definition, error) List(name string) ([]Definition, error) Get(name string, version string) (Definition, error) Delete(name string, version string) error @@ -104,13 +105,13 @@ func (v *DefinitionClient) Create(def Definition) (Definition, error) { // Create a default profile automatically prc := NewProfileClient() - pr, err := prc.Create(Profile{ + pr, err := prc.CreateOrUpdate(Profile{ RBName: def.RBName, RBVersion: def.RBVersion, ProfileName: "default", Namespace: "default", ReleaseName: "default", - }) + }, false) if err != nil { logutils.Error("Create Default Profile", logutils.Fields{ @@ -139,6 +140,26 @@ func (v *DefinitionClient) Create(def Definition) (Definition, error) { return def, nil } +// Update an entry for the resource in the database` +func (v *DefinitionClient) Update(def Definition) (Definition, error) { + + //Construct composite key consisting of name and version + key := DefinitionKey{RBName: def.RBName, RBVersion: def.RBVersion} + + //Check if this definition already exists + _, err := v.Get(def.RBName, def.RBVersion) + if err != nil { + return Definition{}, pkgerrors.New("Definition does not exists") + } + + err = db.DBconn.Update(v.storeName, key, v.tagMeta, def) + if err != nil { + return Definition{}, pkgerrors.Wrap(err, "Updating DB Entry") + } + + return def, nil +} + // List all resource entry's versions in the database func (v *DefinitionClient) List(name string) ([]Definition, error) { res, err := db.DBconn.ReadAll(v.storeName, v.tagMeta) diff --git a/src/k8splugin/internal/rb/definition_test.go b/src/k8splugin/internal/rb/definition_test.go index 0140b459..42fb5374 100644 --- a/src/k8splugin/internal/rb/definition_test.go +++ b/src/k8splugin/internal/rb/definition_test.go @@ -18,12 +18,13 @@ package rb import ( "bytes" - "github.com/onap/multicloud-k8s/src/k8splugin/internal/db" "reflect" "sort" "strings" "testing" + "github.com/onap/multicloud-k8s/src/k8splugin/internal/db" + pkgerrors "github.com/pkg/errors" ) diff --git a/src/k8splugin/internal/rb/profile.go b/src/k8splugin/internal/rb/profile.go index 3db6c40f..77398580 100644 --- a/src/k8splugin/internal/rb/profile.go +++ b/src/k8splugin/internal/rb/profile.go @@ -26,6 +26,7 @@ import ( "github.com/onap/multicloud-k8s/src/k8splugin/internal/db" "github.com/onap/multicloud-k8s/src/k8splugin/internal/helm" + "k8s.io/apimachinery/pkg/runtime/schema" pkgerrors "github.com/pkg/errors" ) @@ -33,18 +34,19 @@ import ( // Profile contains the parameters needed for resource bundle (rb) profiles // It implements the interface for managing the profiles type Profile struct { - RBName string `json:"rb-name"` - RBVersion string `json:"rb-version"` - ProfileName string `json:"profile-name"` - ReleaseName string `json:"release-name"` - Namespace string `json:"namespace"` - KubernetesVersion string `json:"kubernetes-version"` - Labels map[string]string `json:"labels"` + RBName string `json:"rb-name"` + RBVersion string `json:"rb-version"` + ProfileName string `json:"profile-name"` + ReleaseName string `json:"release-name"` + Namespace string `json:"namespace"` + KubernetesVersion string `json:"kubernetes-version"` + Labels map[string]string `json:"labels"` + ExtraResourceTypes []schema.GroupVersionKind `json:"extra-resource-types"` } // ProfileManager is an interface exposes the resource bundle profile functionality type ProfileManager interface { - Create(def Profile) (Profile, error) + CreateOrUpdate(def Profile, update bool) (Profile, error) Get(rbName, rbVersion, prName string) (Profile, error) List(rbName, rbVersion string) ([]Profile, error) Delete(rbName, rbVersion, prName string) error @@ -87,8 +89,8 @@ func NewProfileClient() *ProfileClient { } } -// Create an entry for the resource bundle profile in the database -func (v *ProfileClient) Create(p Profile) (Profile, error) { +// CreateOrUpdate an entry for the resource bundle profile in the database +func (v *ProfileClient) CreateOrUpdate(p Profile, update bool) (Profile, error) { // Name is required if p.ProfileName == "" { @@ -97,10 +99,12 @@ func (v *ProfileClient) Create(p Profile) (Profile, error) { //Check if profile already exists _, err := v.Get(p.RBName, p.RBVersion, p.ProfileName) - if err == nil { + if err == nil && !update { return Profile{}, pkgerrors.New("Profile already exists for this Definition") } - + if err != nil && update { + return Profile{}, pkgerrors.New("Profile does not exists for this Definition") + } //Check if provided resource bundle information is valid _, err = NewDefinitionClient().Get(p.RBName, p.RBVersion) if err != nil { @@ -118,9 +122,16 @@ func (v *ProfileClient) Create(p Profile) (Profile, error) { ProfileName: p.ProfileName, } - err = db.DBconn.Create(v.storeName, key, v.tagMeta, p) - if err != nil { - return Profile{}, pkgerrors.Wrap(err, "Creating Profile DB Entry") + if update { + err = db.DBconn.Update(v.storeName, key, v.tagMeta, p) + if err != nil { + return Profile{}, pkgerrors.Wrap(err, "Updating Profile DB Entry") + } + } else { + err = db.DBconn.Create(v.storeName, key, v.tagMeta, p) + if err != nil { + return Profile{}, pkgerrors.Wrap(err, "Creating Profile DB Entry") + } } return p, nil @@ -271,9 +282,10 @@ func (v *ProfileClient) Download(rbName, rbVersion, prName string) ([]byte, erro //Resolve returns the path where the helm chart merged with //configuration overrides resides and final ReleaseName picked for instantiation func (v *ProfileClient) Resolve(rbName string, rbVersion string, - profileName string, values []string, overrideReleaseName string) ([]helm.KubernetesResourceTemplate, []*helm.Hook, string, error) { + profileName string, values []string, overrideReleaseName string) ([]helm.KubernetesResourceTemplate, []helm.KubernetesResourceTemplate, []*helm.Hook, string, error) { var sortedTemplates []helm.KubernetesResourceTemplate + var crdList []helm.KubernetesResourceTemplate var hookList []*helm.Hook var finalReleaseName string @@ -281,40 +293,40 @@ func (v *ProfileClient) Resolve(rbName string, rbVersion string, //If everything seems okay, then download the definition prData, err := v.Download(rbName, rbVersion, profileName) if err != nil { - return sortedTemplates, hookList, finalReleaseName, pkgerrors.Wrap(err, "Downloading Profile") + return sortedTemplates, crdList, hookList, finalReleaseName, pkgerrors.Wrap(err, "Downloading Profile") } prPath, err := ExtractTarBall(bytes.NewBuffer(prData)) if err != nil { - return sortedTemplates, hookList, finalReleaseName, pkgerrors.Wrap(err, "Extracting Profile Content") + return sortedTemplates, crdList, hookList, finalReleaseName, pkgerrors.Wrap(err, "Extracting Profile Content") } prYamlClient, err := ProcessProfileYaml(prPath, v.manifestName) if err != nil { - return sortedTemplates, hookList, finalReleaseName, pkgerrors.Wrap(err, "Processing Profile Manifest") + return sortedTemplates, crdList, hookList, finalReleaseName, pkgerrors.Wrap(err, "Processing Profile Manifest") } definitionClient := NewDefinitionClient() definition, err := definitionClient.Get(rbName, rbVersion) if err != nil { - return sortedTemplates, hookList, finalReleaseName, pkgerrors.Wrap(err, "Getting Definition Metadata") + return sortedTemplates, crdList, hookList, finalReleaseName, pkgerrors.Wrap(err, "Getting Definition Metadata") } defData, err := definitionClient.Download(rbName, rbVersion) if err != nil { - return sortedTemplates, hookList, finalReleaseName, pkgerrors.Wrap(err, "Downloading Definition") + return sortedTemplates, crdList, hookList, finalReleaseName, pkgerrors.Wrap(err, "Downloading Definition") } chartBasePath, err := ExtractTarBall(bytes.NewBuffer(defData)) if err != nil { - return sortedTemplates, hookList, finalReleaseName, pkgerrors.Wrap(err, "Extracting Definition Charts") + return sortedTemplates, crdList, hookList, finalReleaseName, pkgerrors.Wrap(err, "Extracting Definition Charts") } //Get the definition ID and download its contents profile, err := v.Get(rbName, rbVersion, profileName) if err != nil { - return sortedTemplates, hookList, finalReleaseName, pkgerrors.Wrap(err, "Getting Profile") + return sortedTemplates, crdList, hookList, finalReleaseName, pkgerrors.Wrap(err, "Getting Profile") } //Copy the profile configresources to the chart locations @@ -324,7 +336,7 @@ func (v *ProfileClient) Resolve(rbName string, rbVersion string, // chartpath: chart/config/resources/config.yaml err = prYamlClient.CopyConfigurationOverrides(chartBasePath) if err != nil { - return sortedTemplates, hookList, finalReleaseName, pkgerrors.Wrap(err, "Copying configresources to chart") + return sortedTemplates, crdList, hookList, finalReleaseName, pkgerrors.Wrap(err, "Copying configresources to chart") } if overrideReleaseName == "" { @@ -338,14 +350,14 @@ func (v *ProfileClient) Resolve(rbName string, rbVersion string, finalReleaseName) chartPath := filepath.Join(chartBasePath, definition.ChartName) - sortedTemplates, hookList, err = helmClient.GenerateKubernetesArtifacts(chartPath, + sortedTemplates, crdList, hookList, err = helmClient.GenerateKubernetesArtifacts(chartPath, []string{prYamlClient.GetValues()}, values) if err != nil { - return sortedTemplates, hookList, finalReleaseName, pkgerrors.Wrap(err, "Generate final k8s yaml") + return sortedTemplates, crdList, hookList, finalReleaseName, pkgerrors.Wrap(err, "Generate final k8s yaml") } - return sortedTemplates, hookList, finalReleaseName, nil + return sortedTemplates, crdList, hookList, finalReleaseName, nil } // Returns an empty profile with the following contents diff --git a/src/k8splugin/internal/rb/profile_test.go b/src/k8splugin/internal/rb/profile_test.go index 3c40c2c9..e52897ce 100644 --- a/src/k8splugin/internal/rb/profile_test.go +++ b/src/k8splugin/internal/rb/profile_test.go @@ -105,7 +105,7 @@ func TestCreateProfile(t *testing.T) { t.Run(testCase.label, func(t *testing.T) { db.DBconn = testCase.mockdb impl := NewProfileClient() - got, err := impl.Create(testCase.inp) + got, err := impl.CreateOrUpdate(testCase.inp, false) if err != nil { if testCase.expectedError == "" { t.Fatalf("Create returned an unexpected error %s", err) @@ -773,7 +773,7 @@ func TestResolveProfile(t *testing.T) { t.Run(testCase.label, func(t *testing.T) { db.DBconn = testCase.mockdb impl := NewProfileClient() - data, _, releaseName, err := impl.Resolve(testCase.rbname, + data, _, _, releaseName, err := impl.Resolve(testCase.rbname, testCase.rbversion, testCase.prname, []string{}, testCase.releaseName) defer cleanup(data) if err != nil { diff --git a/src/k8splugin/plugins/generic/plugin.go b/src/k8splugin/plugins/generic/plugin.go index f71c436c..a210f6d6 100644 --- a/src/k8splugin/plugins/generic/plugin.go +++ b/src/k8splugin/plugins/generic/plugin.go @@ -22,6 +22,7 @@ import ( appsv1 "k8s.io/api/apps/v1" "k8s.io/client-go/kubernetes" + //appsv1beta1 "k8s.io/api/apps/v1beta1" //appsv1beta2 "k8s.io/api/apps/v1beta2" batchv1 "k8s.io/api/batch/v1" @@ -30,6 +31,7 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" pkgerrors "github.com/pkg/errors" + "github.com/prometheus/common/log" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -304,7 +306,18 @@ func (g genericPlugin) Create(yamlFilePath string, namespace string, client plug if err != nil { return "", pkgerrors.Wrap(err, "Mapping kind to resource error") } - + if gvk.Kind == "CustomResourceDefinition" { + //according the helm spec, CRD is created only once, and we raise only warn if we try to do it once more + resource := helm.KubernetesResource{} + resource.GVK = gvk + resource.Name = unstruct.GetName() + name, err := g.Get(resource, namespace, client) + if err == nil && name == resource.Name { + //CRD update is not supported according to Helm spec + log.Warn(fmt.Sprintf("CRD %s create will be skipped. It already exists", name)) + return name, nil + } + } //Add the tracking label to all resources created here labels := unstruct.GetLabels() //Check if labels exist for this object @@ -340,58 +353,70 @@ func (g genericPlugin) Create(yamlFilePath string, namespace string, client plug // Update deployment object in a specific Kubernetes cluster func (g genericPlugin) Update(yamlFilePath string, namespace string, client plugin.KubernetesConnector) (string, error) { - if namespace == "" { - namespace = "default" - } - - //Decode the yaml file to create a runtime.Object - unstruct := &unstructured.Unstructured{} - //Ignore the returned obj as we expect the data in unstruct - _, err := utils.DecodeYAML(yamlFilePath, unstruct) - if err != nil { - return "", pkgerrors.Wrap(err, "Decode deployment object error") - } - - dynClient := client.GetDynamicClient() - mapper := client.GetMapper() - - gvk := unstruct.GroupVersionKind() - mapping, err := mapper.RESTMapping(schema.GroupKind{Group: gvk.Group, Kind: gvk.Kind}, gvk.Version) - if err != nil { - return "", pkgerrors.Wrap(err, "Mapping kind to resource error") - } - - //Add the tracking label to all resources created here - labels := unstruct.GetLabels() - //Check if labels exist for this object - if labels == nil { - labels = map[string]string{} - } - labels[config.GetConfiguration().KubernetesLabelName] = client.GetInstanceID() - unstruct.SetLabels(labels) - - // This checks if the resource we are creating has a podSpec in it - // Eg: Deployment, StatefulSet, Job etc.. - // If a PodSpec is found, the label will be added to it too. - plugin.TagPodsIfPresent(unstruct, client.GetInstanceID()) - - gvr := mapping.Resource - var updatedObj *unstructured.Unstructured - - switch mapping.Scope.Name() { - case meta.RESTScopeNameNamespace: - updatedObj, err = dynClient.Resource(gvr).Namespace(namespace).Update(context.TODO(), unstruct, metav1.UpdateOptions{}) - case meta.RESTScopeNameRoot: - updatedObj, err = dynClient.Resource(gvr).Update(context.TODO(), unstruct, metav1.UpdateOptions{}) - default: - return "", pkgerrors.New("Got an unknown RESTSCopeName for mapping: " + gvk.String()) - } - - if err != nil { - return "", pkgerrors.Wrap(err, "Update object error") - } - - return updatedObj.GetName(), nil + if namespace == "" { + namespace = "default" + } + + //Decode the yaml file to create a runtime.Object + unstruct := &unstructured.Unstructured{} + //Ignore the returned obj as we expect the data in unstruct + _, err := utils.DecodeYAML(yamlFilePath, unstruct) + if err != nil { + return "", pkgerrors.Wrap(err, "Decode deployment object error") + } + + dynClient := client.GetDynamicClient() + mapper := client.GetMapper() + + gvk := unstruct.GroupVersionKind() + mapping, err := mapper.RESTMapping(schema.GroupKind{Group: gvk.Group, Kind: gvk.Kind}, gvk.Version) + if err != nil { + return "", pkgerrors.Wrap(err, "Mapping kind to resource error") + } + + if gvk.Kind == "CustomResourceDefinition" { + resource := helm.KubernetesResource{} + resource.GVK = gvk + resource.Name = unstruct.GetName() + name, err := g.Get(resource, namespace, client) + if err == nil && name == resource.Name { + //CRD update is not supported according to Helm spec + log.Warn(fmt.Sprintf("CRD %s update will be skipped", name)) + return name, nil + } + } + + //Add the tracking label to all resources created here + labels := unstruct.GetLabels() + //Check if labels exist for this object + if labels == nil { + labels = map[string]string{} + } + labels[config.GetConfiguration().KubernetesLabelName] = client.GetInstanceID() + unstruct.SetLabels(labels) + + // This checks if the resource we are creating has a podSpec in it + // Eg: Deployment, StatefulSet, Job etc.. + // If a PodSpec is found, the label will be added to it too. + plugin.TagPodsIfPresent(unstruct, client.GetInstanceID()) + + gvr := mapping.Resource + var updatedObj *unstructured.Unstructured + + switch mapping.Scope.Name() { + case meta.RESTScopeNameNamespace: + updatedObj, err = dynClient.Resource(gvr).Namespace(namespace).Update(context.TODO(), unstruct, metav1.UpdateOptions{}) + case meta.RESTScopeNameRoot: + updatedObj, err = dynClient.Resource(gvr).Update(context.TODO(), unstruct, metav1.UpdateOptions{}) + default: + return "", pkgerrors.New("Got an unknown RESTSCopeName for mapping: " + gvk.String()) + } + + if err != nil { + return "", pkgerrors.Wrap(err, "Update object error") + } + + return updatedObj.GetName(), nil } // Get an existing resource hosted in a specific Kubernetes cluster @@ -425,7 +450,7 @@ func (g genericPlugin) Get(resource helm.KubernetesResource, } if err != nil { - return "", pkgerrors.Wrap(err, "Delete object error") + return "", pkgerrors.Wrap(err, "Get object error") } return unstruct.GetName(), nil @@ -462,6 +487,11 @@ func (g genericPlugin) Delete(resource helm.KubernetesResource, namespace string opts := metav1.DeleteOptions{ PropagationPolicy: &deletePolicy, } + if resource.GVK.Kind == "CustomResourceDefinition" { + //CRD deletion is not supported according to Helm spec + log.Warn(fmt.Sprintf("CRD %s deletion will be skipped", resource.Name)) + return nil + } switch mapping.Scope.Name() { case meta.RESTScopeNameNamespace: diff --git a/src/k8splugin/plugins/namespace/plugin.go b/src/k8splugin/plugins/namespace/plugin.go index 8732442e..6c6d1f6c 100644 --- a/src/k8splugin/plugins/namespace/plugin.go +++ b/src/k8splugin/plugins/namespace/plugin.go @@ -21,10 +21,10 @@ import ( pkgerrors "github.com/pkg/errors" coreV1 "k8s.io/api/core/v1" - metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/api/meta" + metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" @@ -60,7 +60,12 @@ func (p namespacePlugin) Create(yamlFilePath string, namespace string, client pl Name: namespace, }, } - _, err := client.GetStandardClient().CoreV1().Namespaces().Create(context.TODO(), namespaceObj, metaV1.CreateOptions{}) + existingNs, err := client.GetStandardClient().CoreV1().Namespaces().Get(context.TODO(), namespace, metaV1.GetOptions{}) + if err == nil && len(existingNs.ManagedFields) > 0 && existingNs.ManagedFields[0].Manager == "k8plugin" { + log.Printf("Namespace (%s) already ensured by plugin. Skip", namespace) + return namespace, nil + } + _, err = client.GetStandardClient().CoreV1().Namespaces().Create(context.TODO(), namespaceObj, metaV1.CreateOptions{}) if err != nil { return "", pkgerrors.Wrap(err, "Create Namespace error") } @@ -128,5 +133,5 @@ func (p namespacePlugin) List(gvk schema.GroupVersionKind, namespace string, cli func (p namespacePlugin) Update(yamlFilePath string, namespace string, client plugin.KubernetesConnector) (string, error) { - return "", nil + return namespace, nil } diff --git a/src/k8splugin/plugins/service/plugin.go b/src/k8splugin/plugins/service/plugin.go index aa5c685c..52dd4591 100644 --- a/src/k8splugin/plugins/service/plugin.go +++ b/src/k8splugin/plugins/service/plugin.go @@ -21,10 +21,10 @@ import ( pkgerrors "github.com/pkg/errors" coreV1 "k8s.io/api/core/v1" - metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/api/meta" + metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" @@ -156,8 +156,43 @@ func (p servicePlugin) Get(resource helm.KubernetesResource, namespace string, c return service.Name, nil } +// Update a service object in a specific Kubernetes cluster func (p servicePlugin) Update(yamlFilePath string, namespace string, client plugin.KubernetesConnector) (string, error) { + if namespace == "" { + namespace = "default" + } - return "", nil + obj, err := utils.DecodeYAML(yamlFilePath, nil) + if err != nil { + return "", pkgerrors.Wrap(err, "Decode service object error") + } + service, ok := obj.(*coreV1.Service) + if !ok { + return "", pkgerrors.New("Decoded object contains another resource different than Service") + } + service.Namespace = namespace + + existingService, err := client.GetStandardClient().CoreV1().Services(namespace).Get(context.TODO(), service.Name, metaV1.GetOptions{}) + if err == nil { + service.ResourceVersion = existingService.ResourceVersion + service.Spec.ClusterIP = existingService.Spec.ClusterIP + } else { + return p.Create(yamlFilePath, namespace, client) + } + labels := service.GetLabels() + //Check if labels exist for this object + if labels == nil { + labels = map[string]string{} + } + labels[config.GetConfiguration().KubernetesLabelName] = client.GetInstanceID() + service.SetLabels(labels) + + _, err = client.GetStandardClient().CoreV1().Services(namespace).Update(context.TODO(), service, metaV1.UpdateOptions{}) + + if err != nil { + return "", pkgerrors.Wrap(err, "Update object error") + } + + return service.Name, nil } @@ -34,5 +34,8 @@ commands = bash -c "find {toxinidir} -not -path {toxinidir}/.tox/\* \ -name \*.rst -type f -print0 | xargs -0 rstcheck --report warning" [testenv:docs] -deps = sphinx +deps = + -r{toxinidir}/docs/requirements-docs.txt + -chttps://git.onap.org/doc/plain/etc/upper-constraints.os.txt + -chttps://git.onap.org/doc/plain/etc/upper-constraints.onap.txt commands = sphinx-build -W -b html docs docs/build/html |