Merge "Allow KuD installer to specify Kubespray 2.14.1 or 2.16.0"
authorRitu Sood <ritu.sood@intel.com>
Fri, 22 Oct 2021 17:08:02 +0000 (17:08 +0000)
committerGerrit Code Review <gerrit@onap.org>
Fri, 22 Oct 2021 17:08:02 +0000 (17:08 +0000)
45 files changed:
deployments/build.sh
docs/conf.py
docs/requirements-docs.txt
docs/tox.ini
kud/deployment_infra/emco/examples/01-cluster.yaml
kud/deployment_infra/emco/examples/02-project.yaml
kud/deployment_infra/emco/examples/03-addons-app.yaml
kud/deployment_infra/emco/examples/04-addon-resources-app.yaml [deleted file]
kud/deployment_infra/emco/examples/README.md
kud/deployment_infra/emco/examples/values.yaml.example
kud/deployment_infra/installers/Dockerfile.iavf-driver-installer
kud/deployment_infra/installers/entrypoint-iavf-driver-installer.sh
kud/deployment_infra/installers/skb-frag-off.patch [new file with mode: 0644]
kud/hosting_providers/containerized/addons/README.md.tmpl
kud/hosting_providers/containerized/addons/values.yaml.tmpl
kud/tests/plugin_fw_v2.sh
releases/0.9.1-container.yaml [new file with mode: 0644]
src/k8splugin/api/api.go
src/k8splugin/api/confighandler.go
src/k8splugin/api/configtemplatehandler.go
src/k8splugin/api/defhandler.go
src/k8splugin/api/profilehandler.go
src/k8splugin/api/profilehandler_test.go
src/k8splugin/api/queryhandler.go
src/k8splugin/internal/app/client.go
src/k8splugin/internal/app/client_test.go
src/k8splugin/internal/app/config.go
src/k8splugin/internal/app/config_backend.go
src/k8splugin/internal/app/config_test.go
src/k8splugin/internal/app/instance.go
src/k8splugin/internal/app/query.go
src/k8splugin/internal/db/etcd.go
src/k8splugin/internal/db/etcd_testing.go
src/k8splugin/internal/db/mongo.go
src/k8splugin/internal/helm/helm.go
src/k8splugin/internal/helm/helm_test.go
src/k8splugin/internal/rb/config_template.go
src/k8splugin/internal/rb/definition.go
src/k8splugin/internal/rb/definition_test.go
src/k8splugin/internal/rb/profile.go
src/k8splugin/internal/rb/profile_test.go
src/k8splugin/plugins/generic/plugin.go
src/k8splugin/plugins/namespace/plugin.go
src/k8splugin/plugins/service/plugin.go
tox.ini

index 05a076e..c905365 100755 (executable)
@@ -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 {
index 8f40e8b..3b28eb7 100644 (file)
@@ -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")
index b3188dd..74a3b7a 100644 (file)
@@ -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
index edac8c3..42ffa68 100644 (file)
@@ -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"
index 6f7ce4b..18d05f7 100644 (file)
@@ -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 }}
index d62a4f6..224126c 100644 (file)
@@ -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 }}
 
index 0fd15e0..c5fb7ea 100644 (file)
@@ -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 (file)
index 92fd953..0000000
+++ /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
index 203b83f..dcf9edd 100644 (file)
@@ -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`
index 67944eb..41e3cc8 100644 (file)
@@ -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
index 9882c2f..8d4593a 100644 (file)
@@ -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
index 6cfc373..d47e3b9 100755 (executable)
@@ -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 (file)
index 0000000..935828e
--- /dev/null
@@ -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;
index 4ed4610..eed30b4 100644 (file)
@@ -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\`
index b3e5845..25e4cc4 100644 (file)
@@ -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
index 86ef97f..6c8d842 100755 (executable)
@@ -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 (file)
index 0000000..dd0b031
--- /dev/null
@@ -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'
index 94fb9b3..a3e53dc 100644 (file)
@@ -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 {
index c223637..a4f0813 100644 (file)
@@ -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
index bd7c2db..e8750fd 100644 (file)
@@ -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)
index 480d4be..3dea8ad 100644 (file)
@@ -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
index acd2306..1babc4a 100644 (file)
@@ -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) {
index 32d0061..181b775 100644 (file)
@@ -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
        }
index 9c11954..f5950cd 100644 (file)
@@ -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,
index 9813333..a2868cd 100644 (file)
@@ -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
index 0ba244d..f51c15f 100644 (file)
@@ -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"
index 94acadc..8952c16 100644 (file)
 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
 }
index 30a480d..1f22922 100644 (file)
@@ -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)
+       }
+}
index 9ee9688..0cc3c3c 100644 (file)
@@ -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() {
+}
index ad36aaa..e50a59e 100644 (file)
@@ -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
index cb645af..251b14e 100644 (file)
@@ -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")
        }
index a435b43..e455cc1 100644 (file)
@@ -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
+}
index 9dfcad8..4b4dfe3 100644 (file)
@@ -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
+}
index c15b108..aa05820 100644 (file)
 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
                }
 
index 849674a..b27c8ae 100644 (file)
@@ -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
+}
index 29d446f..951ff92 100644 (file)
@@ -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])
+               }
+       })
+}
index cf45a6f..97fe0fb 100644 (file)
@@ -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{
index 73ea44d..aa76afa 100644 (file)
@@ -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)
index 0140b45..42fb537 100644 (file)
@@ -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"
 )
 
index 3db6c40..7739858 100644 (file)
@@ -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
index 3c40c2c..e52897c 100644 (file)
@@ -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 {
index f71c436..a210f6d 100644 (file)
@@ -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:
index 8732442..6c6d1f6 100644 (file)
@@ -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
 }
index aa5c685..52dd459 100644 (file)
@@ -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
 }
diff --git a/tox.ini b/tox.ini
index 7c66248..dff3a8b 100644 (file)
--- a/tox.ini
+++ b/tox.ini
@@ -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