diff options
98 files changed, 7017 insertions, 198 deletions
diff --git a/deployments/helm/servicemesh/istio-operator/Chart.yaml b/deployments/helm/servicemesh/istio-operator/Chart.yaml index 1da83af4..1ef9650f 100644 --- a/deployments/helm/servicemesh/istio-operator/Chart.yaml +++ b/deployments/helm/servicemesh/istio-operator/Chart.yaml @@ -1,5 +1,3 @@ - - #/*Copyright 2019 Intel Corporation, Inc # * # * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,7 +12,8 @@ # * See the License for the specific language governing permissions and # * limitations under the License. # */ +apiVersion: v1 name: istio-operator -version: 0.0.15 +version: 0.0.22 description: istio-operator manages Istio deployments on Kubernetes -appVersion: 0.2.1 +appVersion: 0.3.3 diff --git a/deployments/helm/servicemesh/istio-operator/README.md b/deployments/helm/servicemesh/istio-operator/README.md index 4611a81e..c32e016d 100644 --- a/deployments/helm/servicemesh/istio-operator/README.md +++ b/deployments/helm/servicemesh/istio-operator/README.md @@ -14,42 +14,43 @@ * limitations under the License. */ +# Istio-operator chart + ## Prerequisites - Kubernetes 1.10.0+ ## Installing the chart -To install the chart from local directory: +To install the chart: -``` -helm install --name=istio-operator --namespace=istio-system istio-operator +```bash +❯ helm install --name=istio-operator --namespace=istio-system istio-operator ``` ## Uninstalling the Chart To uninstall/delete the `istio-operator` release: -``` -$ helm del --purge istio-operator +```bash +❯ helm del --purge istio-operator ``` The command removes all the Kubernetes components associated with the chart and deletes the release. ## Configuration -The following table lists the configurable parameters of the Banzaicloud Istio Operator chart and their default values. - Parameter | Description | Default --------- | ----------- | ------- `operator.image.repository` | Operator container image repository | `banzaicloud/istio-operator` -`operator.image.tag` | Operator container image tag | `0.2.1` +`operator.image.tag` | Operator container image tag | `0.3.3` `operator.image.pullPolicy` | Operator container image pull policy | `IfNotPresent` `operator.resources` | CPU/Memory resource requests/limits (YAML) | Memory: `128Mi/256Mi`, CPU: `100m/200m` -`istioVersion` | Supported Istio version | `1.2` +`istioVersion` | Supported Istio version | `1.3` `prometheusMetrics.enabled` | If true, use direct access for Prometheus metrics | `false` `prometheusMetrics.authProxy.enabled` | If true, use auth proxy for Prometheus metrics | `true` `prometheusMetrics.authProxy.image.repository` | Auth proxy container image repository | `gcr.io/kubebuilder/kube-rbac-proxy` `prometheusMetrics.authProxy.image.tag` | Auth proxy container image tag | `v0.4.0` `prometheusMetrics.authProxy.image.pullPolicy` | Auth proxy container image pull policy | `IfNotPresent` `rbac.enabled` | Create rbac service account and roles | `true` +`rbac.psp.enabled` | Create pod security policy and binding | `false` diff --git a/deployments/helm/servicemesh/istio-operator/templates/authproxy-rbac.yaml b/deployments/helm/servicemesh/istio-operator/templates/authproxy-rbac.yaml index 8a047e03..aee3aeef 100644 --- a/deployments/helm/servicemesh/istio-operator/templates/authproxy-rbac.yaml +++ b/deployments/helm/servicemesh/istio-operator/templates/authproxy-rbac.yaml @@ -3,6 +3,7 @@ apiVersion: v1 kind: ServiceAccount metadata: name: {{ include "istio-operator.fullname" . }}-authproxy + namespace: {{ .Release.Namespace }} labels: app.kubernetes.io/name: {{ include "istio-operator.name" . }} helm.sh/chart: {{ include "istio-operator.chart" . }} diff --git a/deployments/helm/servicemesh/istio-operator/templates/authproxy-service.yaml b/deployments/helm/servicemesh/istio-operator/templates/authproxy-service.yaml index aad8a2be..6f7e6f9a 100644 --- a/deployments/helm/servicemesh/istio-operator/templates/authproxy-service.yaml +++ b/deployments/helm/servicemesh/istio-operator/templates/authproxy-service.yaml @@ -3,6 +3,7 @@ apiVersion: v1 kind: Service metadata: name: {{ include "istio-operator.fullname" . }}-authproxy + namespace: {{ .Release.Namespace }} annotations: prometheus.io/port: "8443" prometheus.io/scheme: https @@ -20,6 +21,7 @@ spec: ports: - name: https port: 8443 + protocol: TCP targetPort: https selector: control-plane: controller-manager diff --git a/deployments/helm/servicemesh/istio-operator/templates/namespace.yaml b/deployments/helm/servicemesh/istio-operator/templates/namespace.yaml new file mode 100644 index 00000000..daaf21d3 --- /dev/null +++ b/deployments/helm/servicemesh/istio-operator/templates/namespace.yaml @@ -0,0 +1,14 @@ +{{- if .Values.useNamespaceResource }} +apiVersion: v1 +kind: Namespace +metadata: + labels: + app: {{ include "istio-operator.name" . }} + app.kubernetes.io/name: {{ include "istio-operator.name" . }} + helm.sh/chart: {{ include "istio-operator.chart" . }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/version: "{{ .Chart.AppVersion | replace "+" "_" }}" + app.kubernetes.io/part-of: {{ include "istio-operator.name" . }} + name: {{ .Release.Namespace }} +{{- end }} diff --git a/deployments/helm/servicemesh/istio-operator/templates/operator-istio-1.1-crd.yaml b/deployments/helm/servicemesh/istio-operator/templates/operator-istio-1.1-crd.yaml new file mode 100644 index 00000000..287b9879 --- /dev/null +++ b/deployments/helm/servicemesh/istio-operator/templates/operator-istio-1.1-crd.yaml @@ -0,0 +1,565 @@ +{{ if eq .Values.istioVersion "1.1" }} +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: istios.istio.banzaicloud.io + labels: + controller-tools.k8s.io: "1.0" + app.kubernetes.io/name: {{ include "istio-operator.name" . }} + helm.sh/chart: {{ include "istio-operator.chart" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/version: {{ .Chart.AppVersion }} + app.kubernetes.io/component: operator +spec: + additionalPrinterColumns: + - JSONPath: .status.Status + description: Status of the resource + name: Status + type: string + - JSONPath: .status.ErrorMessage + description: Error message + name: Error + type: string + - JSONPath: .status.GatewayAddress + description: Ingress gateways of the resource + name: Gateways + type: string + - JSONPath: .metadata.creationTimestamp + name: Age + type: date + group: istio.banzaicloud.io + names: + kind: Istio + plural: istios + scope: Namespaced + subresources: + status: {} + validation: + openAPIV3Schema: + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + autoInjectionNamespaces: + description: List of namespaces to label with sidecar auto injection + enabled + items: + type: string + type: array + citadel: + description: Citadel configuration options + properties: + affinity: + type: object + caSecretName: + type: string + enabled: + type: boolean + image: + type: string + nodeSelector: + type: object + resources: + type: object + tolerations: + items: + type: object + type: array + type: object + controlPlaneSecurityEnabled: + description: ControlPlaneSecurityEnabled control plane services are + communicating through mTLS + type: boolean + defaultConfigVisibility: + description: Set the default set of namespaces to which services, service + entries, virtual services, destination rules should be exported to + type: string + defaultPodDisruptionBudget: + description: Enable pod disruption budget for the control plane, which + is used to ensure Istio control plane components are gradually upgraded + or recovered + properties: + enabled: + type: boolean + type: object + defaultResources: + description: DefaultResources are applied for all Istio components by + default, can be overridden for each component + type: object + excludeIPRanges: + description: ExcludeIPRanges the range where not to capture egress traffic + type: string + galley: + description: Galley configuration options + properties: + affinity: + type: object + enabled: + type: boolean + image: + type: string + nodeSelector: + type: object + replicaCount: + format: int32 + type: integer + resources: + type: object + tolerations: + items: + type: object + type: array + type: object + gateways: + description: Gateways configuration options + properties: + egress: + properties: + affinity: + type: object + applicationPorts: + type: string + enabled: + type: boolean + loadBalancerIP: + type: string + maxReplicas: + format: int32 + type: integer + minReplicas: + format: int32 + type: integer + nodeSelector: + type: object + ports: + items: + type: object + type: array + replicaCount: + format: int32 + type: integer + requestedNetworkView: + type: string + resources: + type: object + sds: + properties: + enabled: + type: boolean + image: + type: string + resources: + type: object + type: object + serviceAnnotations: + type: object + serviceLabels: + type: object + serviceType: + enum: + - ClusterIP + - NodePort + - LoadBalancer + type: string + tolerations: + items: + type: object + type: array + type: object + enabled: + type: boolean + ingress: + properties: + affinity: + type: object + applicationPorts: + type: string + enabled: + type: boolean + loadBalancerIP: + type: string + maxReplicas: + format: int32 + type: integer + minReplicas: + format: int32 + type: integer + nodeSelector: + type: object + ports: + items: + type: object + type: array + replicaCount: + format: int32 + type: integer + requestedNetworkView: + type: string + resources: + type: object + sds: + properties: + enabled: + type: boolean + image: + type: string + resources: + type: object + type: object + serviceAnnotations: + type: object + serviceLabels: + type: object + serviceType: + enum: + - ClusterIP + - NodePort + - LoadBalancer + type: string + tolerations: + items: + type: object + type: array + type: object + type: object + imagePullPolicy: + description: ImagePullPolicy describes a policy for if/when to pull + a container image + enum: + - Always + - Never + - IfNotPresent + type: string + includeIPRanges: + description: IncludeIPRanges the range where to capture egress traffic + type: string + istioCoreDNS: + description: Istio CoreDNS provides DNS resolution for services in multi + mesh setups + properties: + affinity: + type: object + enabled: + type: boolean + image: + type: string + nodeSelector: + type: object + pluginImage: + type: string + replicaCount: + format: int32 + type: integer + resources: + type: object + tolerations: + items: + type: object + type: array + type: object + meshExpansion: + description: If set to true, the pilot and citadel mtls will be exposed + on the ingress gateway also the remote istios will be connected through + gateways + type: boolean + mixer: + description: Mixer configuration options + properties: + affinity: + type: object + enabled: + type: boolean + image: + type: string + maxReplicas: + format: int32 + type: integer + minReplicas: + format: int32 + type: integer + nodeSelector: + type: object + replicaCount: + format: int32 + type: integer + resources: + type: object + tolerations: + items: + type: object + type: array + type: object + mtls: + description: MTLS enables or disables global mTLS + type: boolean + multiMesh: + description: Set to true to connect two or more meshes via their respective + ingressgateway services when workloads in each cluster cannot directly + talk to one another. All meshes should be using Istio mTLS and must + have a shared root CA for this model to work. + type: boolean + nodeAgent: + description: NodeAgent configuration options + properties: + affinity: + type: object + enabled: + type: boolean + image: + type: string + nodeSelector: + type: object + resources: + type: object + tolerations: + items: + type: object + type: array + type: object + outboundTrafficPolicy: + description: Set the default behavior of the sidecar for handling outbound + traffic from the application (ALLOW_ANY or REGISTRY_ONLY) + properties: + mode: + enum: + - ALLOW_ANY + - REGISTRY_ONLY + type: string + type: object + pilot: + description: Pilot configuration options + properties: + affinity: + type: object + enabled: + type: boolean + image: + type: string + maxReplicas: + format: int32 + type: integer + minReplicas: + format: int32 + type: integer + nodeSelector: + type: object + replicaCount: + format: int32 + type: integer + resources: + type: object + sidecar: + type: boolean + tolerations: + items: + type: object + type: array + traceSampling: + format: float + type: number + type: object + proxy: + description: Proxy configuration options + properties: + enableCoreDump: + description: If set, newly injected sidecars will have core dumps + enabled. + type: boolean + image: + type: string + privileged: + description: If set to true, istio-proxy container will have privileged + securityContext + type: boolean + resources: + type: object + type: object + proxyInit: + description: Proxy Init configuration options + properties: + image: + type: string + type: object + sds: + description: If SDS is configured, mTLS certificates for the sidecars + will be distributed through the SecretDiscoveryService instead of + using K8S secrets to mount the certificates + properties: + enabled: + description: If set to true, mTLS certificates for the sidecars + will be distributed through the SecretDiscoveryService instead + of using K8S secrets to mount the certificates. + type: boolean + udsPath: + description: Unix Domain Socket through which envoy communicates + with NodeAgent SDS to get key/cert for mTLS. Use secret-mount + files instead of SDS if set to empty. + type: string + useNormalJwt: + description: If set to true, envoy will fetch normal k8s service + account JWT from '/var/run/secrets/kubernetes.io/serviceaccount/token' + (https://kubernetes.io/docs/tasks/access-application-cluster/access-cluster/#accessing-the-api-from-a-pod) + and pass to sds server, which will be used to request key/cert + eventually this flag is ignored if UseTrustworthyJwt is set + type: boolean + useTrustworthyJwt: + description: 'If set to true, Istio will inject volumes mount for + k8s service account JWT, so that K8s API server mounts k8s service + account JWT to envoy container, which will be used to generate + key/cert eventually. (prerequisite: https://kubernetes.io/docs/concepts/storage/volumes/#projected)' + type: boolean + type: object + sidecarInjector: + description: SidecarInjector configuration options + properties: + affinity: + type: object + autoInjectionPolicyEnabled: + description: This controls the 'policy' in the sidecar injector + type: boolean + enabled: + type: boolean + image: + type: string + init: + properties: + resources: + type: object + type: object + initCNIConfiguration: + properties: + affinity: + type: object + binDir: + description: Must be the same as the environment’s --cni-bin-dir + setting (kubelet parameter) + type: string + confDir: + description: Must be the same as the environment’s --cni-conf-dir + setting (kubelet parameter) + type: string + enabled: + description: If true, the privileged initContainer istio-init + is not needed to perform the traffic redirect settings for + the istio-proxy + type: boolean + excludeNamespaces: + description: List of namespaces to exclude from Istio pod check + items: + type: string + type: array + image: + type: string + logLevel: + description: Logging level for CNI binary + type: string + type: object + nodeSelector: + type: object + replicaCount: + format: int32 + type: integer + resources: + type: object + rewriteAppHTTPProbe: + description: If true, sidecar injector will rewrite PodSpec for + liveness health check to redirect request to sidecar. This makes + liveness check work even when mTLS is enabled. + type: boolean + tolerations: + items: + type: object + type: array + type: object + tracing: + description: Configuration for each of the supported tracers + properties: + datadog: + properties: + address: + description: Host:Port for submitting traces to the Datadog + agent. + pattern: ^[^\:]+:[0-9]{1,5}$ + type: string + type: object + enabled: + type: boolean + lightstep: + properties: + accessToken: + description: required for sending data to the pool + type: string + address: + description: the <host>:<port> of the satellite pool + pattern: ^[^\:]+:[0-9]{1,5}$ + type: string + cacertPath: + description: the path to the file containing the cacert to use + when verifying TLS. If secure is true, this is required. If + a value is specified then a secret called "lightstep.cacert" + must be created in the destination namespace with the key + matching the base of the provided cacertPath and the value + being the cacert itself. + type: string + secure: + description: specifies whether data should be sent with TLS + type: boolean + type: object + tracer: + enum: + - zipkin + - lightstep + - datadog + type: string + zipkin: + properties: + address: + description: Host:Port for reporting trace data in zipkin format. + If not specified, will default to zipkin service (port 9411) + in the same namespace as the other istio components. + pattern: ^[^\:]+:[0-9]{1,5}$ + type: string + type: object + type: object + useMCP: + description: Use the Mesh Control Protocol (MCP) for configuring Mixer + and Pilot. Requires galley. + type: boolean + version: + description: Contains the intended Istio version + pattern: ^1.1 + type: string + watchAdapterCRDs: + description: Whether or not to establish watches for adapter-specific + CRDs + type: boolean + watchOneNamespace: + description: Whether to restrict the applications namespace the controller + manages + type: boolean + required: + - version + - mtls + type: object + status: + type: object + version: v1beta1 +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +{{- end }} diff --git a/deployments/helm/servicemesh/istio-operator/templates/operator-istio-1.2-crd.yaml b/deployments/helm/servicemesh/istio-operator/templates/operator-istio-1.2-crd.yaml index b52ffc39..31aae495 100644 --- a/deployments/helm/servicemesh/istio-operator/templates/operator-istio-1.2-crd.yaml +++ b/deployments/helm/servicemesh/istio-operator/templates/operator-istio-1.2-crd.yaml @@ -1,4 +1,4 @@ -{{ if eq .Values.istioVersion 1.2 }} +{{ if eq .Values.istioVersion "1.2" }} apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: diff --git a/deployments/helm/servicemesh/istio-operator/templates/operator-istio-1.3-crd.yaml b/deployments/helm/servicemesh/istio-operator/templates/operator-istio-1.3-crd.yaml new file mode 100644 index 00000000..540d43bd --- /dev/null +++ b/deployments/helm/servicemesh/istio-operator/templates/operator-istio-1.3-crd.yaml @@ -0,0 +1,889 @@ +{{ if eq .Values.istioVersion "1.3" }} +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: istios.istio.banzaicloud.io + labels: + controller-tools.k8s.io: "1.0" + app.kubernetes.io/name: {{ include "istio-operator.name" . }} + helm.sh/chart: {{ include "istio-operator.chart" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/version: {{ .Chart.AppVersion }} + app.kubernetes.io/component: operator +spec: + additionalPrinterColumns: + - JSONPath: .status.Status + description: Status of the resource + name: Status + type: string + - JSONPath: .status.ErrorMessage + description: Error message + name: Error + type: string + - JSONPath: .status.GatewayAddress + description: Ingress gateways of the resource + name: Gateways + type: string + - JSONPath: .metadata.creationTimestamp + name: Age + type: date + group: istio.banzaicloud.io + names: + kind: Istio + plural: istios + scope: Namespaced + subresources: + status: {} + validation: + openAPIV3Schema: + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + autoInjectionNamespaces: + description: List of namespaces to label with sidecar auto injection + enabled + items: + type: string + type: array + citadel: + description: Citadel configuration options + properties: + affinity: + type: object + caSecretName: + type: string + enableNamespacesByDefault: + description: 'Determines Citadel default behavior if the ca.istio.io/env + or ca.istio.io/override labels are not found on a given namespace. For + example: consider a namespace called "target", which has neither + the "ca.istio.io/env" nor the "ca.istio.io/override" namespace + labels. To decide whether or not to generate secrets for service + accounts created in this "target" namespace, Citadel will defer + to this option. If the value of this option is "true" in this + case, secrets will be generated for the "target" namespace. If + the value of this option is "false" Citadel will not generate + secrets upon service account creation.' + type: boolean + enabled: + type: boolean + healthCheck: + description: Enable health checking on the Citadel CSR signing API. + https://istio.io/docs/tasks/security/health-check/ + type: boolean + image: + type: string + maxWorkloadCertTTL: + description: Citadel uses a flag max-workload-cert-ttl to control + the maximum lifetime for Istio certificates issued to workloads. + The default value is 90 days. If workload-cert-ttl on Citadel + or node agent is greater than max-workload-cert-ttl, Citadel will + fail issuing the certificate. + type: string + nodeSelector: + type: object + resources: + type: object + tolerations: + items: + type: object + type: array + workloadCertTTL: + description: For the workloads running in Kubernetes, the lifetime + of their Istio certificates is controlled by the workload-cert-ttl + flag on Citadel. The default value is 90 days. This value should + be no greater than max-workload-cert-ttl of Citadel. + type: string + type: object + clusterName: + description: Should be set to the name of the cluster this installation + will run in. This is required for sidecar injection to properly label + proxies + type: string + controlPlaneSecurityEnabled: + description: ControlPlaneSecurityEnabled control plane services are + communicating through mTLS + type: boolean + defaultConfigVisibility: + description: Set the default set of namespaces to which services, service + entries, virtual services, destination rules should be exported to + type: string + defaultPodDisruptionBudget: + description: Enable pod disruption budget for the control plane, which + is used to ensure Istio control plane components are gradually upgraded + or recovered + properties: + enabled: + type: boolean + type: object + defaultResources: + description: DefaultResources are applied for all Istio components by + default, can be overridden for each component + type: object + excludeIPRanges: + description: ExcludeIPRanges the range where not to capture egress traffic + type: string + galley: + description: Galley configuration options + properties: + affinity: + type: object + configValidation: + type: boolean + enabled: + type: boolean + image: + type: string + nodeSelector: + type: object + replicaCount: + format: int32 + type: integer + resources: + type: object + tolerations: + items: + type: object + type: array + type: object + gateways: + description: Gateways configuration options + properties: + egress: + properties: + affinity: + type: object + applicationPorts: + type: string + enabled: + type: boolean + loadBalancerIP: + type: string + maxReplicas: + format: int32 + type: integer + minReplicas: + format: int32 + type: integer + nodeSelector: + type: object + ports: + items: + type: object + type: array + replicaCount: + format: int32 + type: integer + requestedNetworkView: + type: string + resources: + type: object + sds: + properties: + enabled: + type: boolean + image: + type: string + resources: + type: object + type: object + serviceAnnotations: + type: object + serviceLabels: + type: object + serviceType: + enum: + - ClusterIP + - NodePort + - LoadBalancer + type: string + tolerations: + items: + type: object + type: array + type: object + enabled: + type: boolean + ingress: + properties: + affinity: + type: object + applicationPorts: + type: string + enabled: + type: boolean + loadBalancerIP: + type: string + maxReplicas: + format: int32 + type: integer + minReplicas: + format: int32 + type: integer + nodeSelector: + type: object + ports: + items: + type: object + type: array + replicaCount: + format: int32 + type: integer + requestedNetworkView: + type: string + resources: + type: object + sds: + properties: + enabled: + type: boolean + image: + type: string + resources: + type: object + type: object + serviceAnnotations: + type: object + serviceLabels: + type: object + serviceType: + enum: + - ClusterIP + - NodePort + - LoadBalancer + type: string + tolerations: + items: + type: object + type: array + type: object + type: object + imagePullPolicy: + description: ImagePullPolicy describes a policy for if/when to pull + a container image + enum: + - Always + - Never + - IfNotPresent + type: string + includeIPRanges: + description: IncludeIPRanges the range where to capture egress traffic + type: string + istioCoreDNS: + description: Istio CoreDNS provides DNS resolution for services in multi + mesh setups + properties: + affinity: + type: object + enabled: + type: boolean + image: + type: string + nodeSelector: + type: object + pluginImage: + type: string + replicaCount: + format: int32 + type: integer + resources: + type: object + tolerations: + items: + type: object + type: array + type: object + localityLB: + description: Locality based load balancing distribution or failover + settings. + properties: + distribute: + description: 'Optional: only one of distribute or failover can be + set. Explicitly specify loadbalancing weight across different + zones and geographical locations. Refer to [Locality weighted + load balancing](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/load_balancing/locality_weight) + If empty, the locality weight is set according to the endpoints + number within it.' + items: + properties: + from: + description: Originating locality, '/' separated, e.g. 'region/zone'. + type: string + to: + description: Map of upstream localities to traffic distribution + weights. The sum of all weights should be == 100. Any locality + not assigned a weight will receive no traffic. + type: object + type: object + type: array + enabled: + description: If set to true, locality based load balancing will + be enabled + type: boolean + failover: + description: 'Optional: only failover or distribute can be set. + Explicitly specify the region traffic will land on when endpoints + in local region becomes unhealthy. Should be used together with + OutlierDetection to detect unhealthy endpoints. Note: if no OutlierDetection + specified, this will not take effect.' + items: + properties: + from: + description: Originating region. + type: string + to: + description: Destination region the traffic will fail over + to when endpoints in the 'from' region becomes unhealthy. + type: string + type: object + type: array + type: object + meshExpansion: + description: If set to true, the pilot and citadel mtls will be exposed + on the ingress gateway also the remote istios will be connected through + gateways + type: boolean + meshID: + description: Mesh ID means Mesh Identifier. It should be unique within + the scope where meshes will interact with each other, but it is not + required to be globally/universally unique. + type: string + trustDomain: + description: The domain serves to identify the system with SPIFFE. (default "cluster.local") + type: string + mixer: + description: Mixer configuration options + properties: + affinity: + type: object + checksEnabled: + type: boolean + enabled: + type: boolean + image: + type: string + maxReplicas: + format: int32 + type: integer + minReplicas: + format: int32 + type: integer + multiClusterSupport: + description: Turn it on if you use mixer that supports multi cluster + telemetry + type: boolean + nodeSelector: + type: object + replicaCount: + format: int32 + type: integer + reportBatchMaxEntries: + description: Set reportBatchMaxEntries to 0 to use the default batching + behavior (i.e., every 100 requests). A positive value indicates + the number of requests that are batched before telemetry data + is sent to the mixer server + format: int32 + type: integer + reportBatchMaxTime: + description: Set reportBatchMaxTime to 0 to use the default batching + behavior (i.e., every 1 second). A positive time value indicates + the maximum wait time since the last request will telemetry data + be batched before being sent to the mixer server + type: string + resources: + type: object + sessionAffinityEnabled: + description: Set whether to create a STRICT_DNS type cluster for + istio-telemetry. + type: boolean + stdioAdapterEnabled: + description: stdio is a debug adapter in Istio telemetry, it is + not recommended for production use + type: boolean + tolerations: + items: + type: object + type: array + type: object + mixerlessTelemetry: + description: Mixerless telemetry configuration + properties: + enabled: + description: If set to true, experimental Mixerless http telemetry + will be enabled + type: boolean + type: object + mtls: + description: MTLS enables or disables global mTLS + type: boolean + multiMesh: + description: Set to true to connect two or more meshes via their respective + ingressgateway services when workloads in each cluster cannot directly + talk to one another. All meshes should be using Istio mTLS and must + have a shared root CA for this model to work. + type: boolean + nodeAgent: + description: NodeAgent configuration options + properties: + affinity: + type: object + enabled: + type: boolean + image: + type: string + nodeSelector: + type: object + resources: + type: object + tolerations: + items: + type: object + type: array + type: object + outboundTrafficPolicy: + description: Set the default behavior of the sidecar for handling outbound + traffic from the application (ALLOW_ANY or REGISTRY_ONLY) + properties: + mode: + enum: + - ALLOW_ANY + - REGISTRY_ONLY + type: string + type: object + pilot: + description: Pilot configuration options + properties: + affinity: + type: object + enableProtocolSniffing: + type: boolean + enabled: + type: boolean + image: + type: string + maxReplicas: + format: int32 + type: integer + minReplicas: + format: int32 + type: integer + nodeSelector: + type: object + replicaCount: + format: int32 + type: integer + resources: + type: object + sidecar: + type: boolean + tolerations: + items: + type: object + type: array + traceSampling: + format: float + type: number + type: object + policy: + description: Policy configuration options + properties: + affinity: + type: object + checksEnabled: + type: boolean + enabled: + type: boolean + image: + type: string + maxReplicas: + format: int32 + type: integer + minReplicas: + format: int32 + type: integer + nodeSelector: + type: object + replicaCount: + format: int32 + type: integer + resources: + type: object + sessionAffinityEnabled: + description: Set whether to create a STRICT_DNS type cluster for + istio-telemetry. + type: boolean + tolerations: + items: + type: object + type: array + type: object + proxy: + description: Proxy configuration options + properties: + accessLogEncoding: + description: Configure the access log for sidecar to JSON or TEXT. + enum: + - JSON + - TEXT + type: string + accessLogFile: + description: 'Configures the access log for each sidecar. Options: "" + - disables access log "/dev/stdout" - enables access log' + enum: + - "" + - /dev/stdout + type: string + accessLogFormat: + description: 'Configure how and what fields are displayed in sidecar + access log. Setting to empty string will result in default log + format. If accessLogEncoding is TEXT, value will be used directly + as the log format example: "[%START_TIME%] %REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% + %PROTOCOL%\n" If AccessLogEncoding is JSON, value will be parsed + as map[string]string example: ''{"start_time": "%START_TIME%", + "req_method": "%REQ(:METHOD)%"}''' + type: string + componentLogLevel: + description: Per Component log level for proxy, applies to gateways + and sidecars. If a component level is not set, then the "LogLevel" + will be used. If left empty, "misc:error" is used. + type: string + coreDumpImage: + description: Image used to enable core dumps. This is only used, + when "EnableCoreDump" is set to true. + type: string + dnsRefreshRate: + description: Configure the DNS refresh rate for Envoy cluster of + type STRICT_DNS This must be given it terms of seconds. For example, + 300s is valid but 5m is invalid. + pattern: ^[0-9]{1,5}s$ + type: string + enableCoreDump: + description: If set, newly injected sidecars will have core dumps + enabled. + type: boolean + envoyAccessLogService: + properties: + enabled: + type: boolean + host: + type: string + port: + format: int32 + type: integer + tcpKeepalive: + properties: + interval: + type: string + probes: + format: int32 + type: integer + time: + type: string + type: object + tlsSettings: + properties: + caCertificates: + type: string + clientCertificate: + type: string + mode: + type: string + privateKey: + type: string + sni: + type: string + subjectAltNames: + items: + type: string + type: array + type: object + type: object + envoyMetricsService: + properties: + enabled: + type: boolean + host: + type: string + port: + format: int32 + type: integer + type: object + envoyStatsD: + properties: + enabled: + type: boolean + host: + type: string + port: + format: int32 + type: integer + type: object + image: + type: string + logLevel: + description: 'Log level for proxy, applies to gateways and sidecars. + If left empty, "warning" is used. Expected values are: trace|debug|info|warning|error|critical|off' + enum: + - trace + - debug + - info + - warning + - error + - critical + - "off" + type: string + privileged: + description: If set to true, istio-proxy container will have privileged + securityContext + type: boolean + protocolDetectionTimeout: + type: string + resources: + type: object + type: object + proxyInit: + description: Proxy Init configuration options + properties: + image: + type: string + type: object + sds: + description: If SDS is configured, mTLS certificates for the sidecars + will be distributed through the SecretDiscoveryService instead of + using K8S secrets to mount the certificates + properties: + customTokenDirectory: + type: string + enabled: + description: If set to true, mTLS certificates for the sidecars + will be distributed through the SecretDiscoveryService instead + of using K8S secrets to mount the certificates. + type: boolean + tokenAudience: + description: "The JWT token for SDS and the aud field of such JWT. + See RFC 7519, section 4.1.3. When a CSR is sent from Citadel Agent + to the CA (e.g. Citadel), this aud is to make sure the \tJWT is + intended for the CA." + type: string + udsPath: + description: Unix Domain Socket through which envoy communicates + with NodeAgent SDS to get key/cert for mTLS. Use secret-mount + files instead of SDS if set to empty. + type: string + type: object + sidecarInjector: + description: SidecarInjector configuration options + properties: + affinity: + type: object + alwaysInjectSelector: + description: 'AlwaysInjectSelector: Forces the injection on pods + whose labels match this selector. It''s an array of label selectors, + that will be OR''ed, meaning we will iterate over it and stop + at the first match' + items: + type: object + type: array + autoInjectionPolicyEnabled: + description: This controls the 'policy' in the sidecar injector + type: boolean + enableNamespacesByDefault: + description: This controls whether the webhook looks for namespaces + for injection enabled or disabled + type: boolean + enabled: + type: boolean + image: + type: string + init: + properties: + resources: + type: object + type: object + initCNIConfiguration: + properties: + affinity: + type: object + binDir: + description: Must be the same as the environment’s --cni-bin-dir + setting (kubelet parameter) + type: string + confDir: + description: Must be the same as the environment’s --cni-conf-dir + setting (kubelet parameter) + type: string + enabled: + description: If true, the privileged initContainer istio-init + is not needed to perform the traffic redirect settings for + the istio-proxy + type: boolean + excludeNamespaces: + description: List of namespaces to exclude from Istio pod check + items: + type: string + type: array + image: + type: string + logLevel: + description: Logging level for CNI binary + type: string + type: object + neverInjectSelector: + description: 'NeverInjectSelector: Refuses the injection on pods + whose labels match this selector. It''s an array of label selectors, + that will be OR''ed, meaning we will iterate over it and stop + at the first match Takes precedence over AlwaysInjectSelector.' + items: + type: object + type: array + nodeSelector: + type: object + replicaCount: + format: int32 + type: integer + resources: + type: object + rewriteAppHTTPProbe: + description: If true, sidecar injector will rewrite PodSpec for + liveness health check to redirect request to sidecar. This makes + liveness check work even when mTLS is enabled. + type: boolean + tolerations: + items: + type: object + type: array + type: object + telemetry: + description: Telemetry configuration options + properties: + affinity: + type: object + enabled: + type: boolean + image: + type: string + maxReplicas: + format: int32 + type: integer + minReplicas: + format: int32 + type: integer + nodeSelector: + type: object + replicaCount: + format: int32 + type: integer + reportBatchMaxEntries: + description: Set reportBatchMaxEntries to 0 to use the default batching + behavior (i.e., every 100 requests). A positive value indicates + the number of requests that are batched before telemetry data + is sent to the mixer server + format: int32 + type: integer + reportBatchMaxTime: + description: Set reportBatchMaxTime to 0 to use the default batching + behavior (i.e., every 1 second). A positive time value indicates + the maximum wait time since the last request will telemetry data + be batched before being sent to the mixer server + type: string + resources: + type: object + tolerations: + items: + type: object + type: array + type: object + tracing: + description: Configuration for each of the supported tracers + properties: + datadog: + properties: + address: + description: Host:Port for submitting traces to the Datadog + agent. + pattern: ^[^\:]+:[0-9]{1,5}$ + type: string + type: object + enabled: + type: boolean + lightstep: + properties: + accessToken: + description: required for sending data to the pool + type: string + address: + description: the <host>:<port> of the satellite pool + pattern: ^[^\:]+:[0-9]{1,5}$ + type: string + cacertPath: + description: the path to the file containing the cacert to use + when verifying TLS. If secure is true, this is required. If + a value is specified then a secret called "lightstep.cacert" + must be created in the destination namespace with the key + matching the base of the provided cacertPath and the value + being the cacert itself. + type: string + secure: + description: specifies whether data should be sent with TLS + type: boolean + type: object + stackdriver: + type: object + tracer: + enum: + - zipkin + - lightstep + - datadog + type: string + zipkin: + properties: + address: + description: Host:Port for reporting trace data in zipkin format. + If not specified, will default to zipkin service (port 9411) + in the same namespace as the other istio components. + pattern: ^[^\:]+:[0-9]{1,5}$ + type: string + type: object + type: object + useMCP: + description: Use the Mesh Control Protocol (MCP) for configuring Mixer + and Pilot. Requires galley. + type: boolean + version: + description: Contains the intended Istio version + pattern: ^1.3 + type: string + watchAdapterCRDs: + description: Whether or not to establish watches for adapter-specific + CRDs + type: boolean + watchOneNamespace: + description: Whether to restrict the applications namespace the controller + manages + type: boolean + required: + - version + - mtls + type: object + status: + type: object + version: v1beta1 +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +{{- end }} diff --git a/deployments/helm/servicemesh/istio-operator/templates/operator-psp-basic.yaml b/deployments/helm/servicemesh/istio-operator/templates/operator-psp-basic.yaml new file mode 100644 index 00000000..b6e5eac6 --- /dev/null +++ b/deployments/helm/servicemesh/istio-operator/templates/operator-psp-basic.yaml @@ -0,0 +1,97 @@ +{{- if and .Values.rbac.enabled .Values.rbac.psp.enabled }} +apiVersion: policy/v1beta1 +kind: PodSecurityPolicy +metadata: + name: {{ include "istio-operator.fullname" . }}-basic + labels: + app.kubernetes.io/name: {{ include "istio-operator.name" . }} + helm.sh/chart: {{ include "istio-operator.chart" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/version: {{ .Chart.AppVersion }} + app.kubernetes.io/component: operator +spec: + fsGroup: + rule: RunAsAny + runAsUser: + rule: RunAsAny + seLinux: + rule: RunAsAny + supplementalGroups: + rule: RunAsAny + volumes: + - secret + - configMap + - emptyDir +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: psp:{{ include "istio-operator.fullname" . }}-basic + labels: + app.kubernetes.io/name: {{ include "istio-operator.name" . }} + helm.sh/chart: {{ include "istio-operator.chart" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/version: {{ .Chart.AppVersion }} + app.kubernetes.io/component: operator +rules: +- apiGroups: + - policy + resourceNames: + - {{ include "istio-operator.fullname" . }}-basic + resources: + - podsecuritypolicies + verbs: + - use +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: psp:{{ include "istio-operator.fullname" . }}-basic + labels: + app.kubernetes.io/name: {{ include "istio-operator.name" . }} + helm.sh/chart: {{ include "istio-operator.chart" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/version: {{ .Chart.AppVersion }} + app.kubernetes.io/component: operator +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: psp:{{ include "istio-operator.fullname" . }}-basic +subjects: + - kind: ServiceAccount + name: istio-citadel-service-account + namespace: {{ .Release.Namespace }} + - kind: ServiceAccount + name: istio-galley-service-account + namespace: {{ .Release.Namespace }} + - kind: ServiceAccount + name: istio-egressgateway-service-account + namespace: {{ .Release.Namespace }} + - kind: ServiceAccount + name: istio-ingressgateway-service-account + namespace: {{ .Release.Namespace }} + - kind: ServiceAccount + name: istio-mixer-service-account + namespace: {{ .Release.Namespace }} + - kind: ServiceAccount + name: istio-operator-authproxy + namespace: {{ .Release.Namespace }} + - kind: ServiceAccount + name: istio-pilot-service-account + namespace: {{ .Release.Namespace }} + - kind: ServiceAccount + name: istio-sidecar-injector-service-account + namespace: {{ .Release.Namespace }} + - kind: ServiceAccount + name: istiocoredns-service-account + namespace: {{ .Release.Namespace }} + - kind: ServiceAccount + name: istio-nodeagent-service-account + namespace: {{ .Release.Namespace }} + - kind: ServiceAccount + name: istio-operator-operator + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/deployments/helm/servicemesh/istio-operator/templates/operator-rbac.yaml b/deployments/helm/servicemesh/istio-operator/templates/operator-rbac.yaml index d506ee41..e25462af 100644 --- a/deployments/helm/servicemesh/istio-operator/templates/operator-rbac.yaml +++ b/deployments/helm/servicemesh/istio-operator/templates/operator-rbac.yaml @@ -3,6 +3,7 @@ apiVersion: v1 kind: ServiceAccount metadata: name: {{ include "istio-operator.fullname" . }}-operator + namespace: {{ .Release.Namespace }} labels: app.kubernetes.io/name: {{ include "istio-operator.name" . }} helm.sh/chart: {{ include "istio-operator.chart" . }} diff --git a/deployments/helm/servicemesh/istio-operator/templates/operator-remoteistio-1.1-crd.yaml b/deployments/helm/servicemesh/istio-operator/templates/operator-remoteistio-1.1-crd.yaml new file mode 100644 index 00000000..f298ccde --- /dev/null +++ b/deployments/helm/servicemesh/istio-operator/templates/operator-remoteistio-1.1-crd.yaml @@ -0,0 +1,208 @@ +{{ if eq .Values.istioVersion "1.1" }} +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: remoteistios.istio.banzaicloud.io + labels: + controller-tools.k8s.io: "1.0" + app.kubernetes.io/name: {{ include "istio-operator.name" . }} + helm.sh/chart: {{ include "istio-operator.chart" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/version: {{ .Chart.AppVersion }} + app.kubernetes.io/component: operator +spec: + additionalPrinterColumns: + - JSONPath: .status.Status + description: Status of the resource + name: Status + type: string + - JSONPath: .status.ErrorMessage + description: Error message + name: Error + type: string + - JSONPath: .status.GatewayAddress + description: Ingress gateways of the resource + name: Gateways + type: string + - JSONPath: .metadata.creationTimestamp + name: Age + type: date + group: istio.banzaicloud.io + names: + kind: RemoteIstio + plural: remoteistios + scope: Namespaced + subresources: + status: {} + validation: + openAPIV3Schema: + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + autoInjectionNamespaces: + description: List of namespaces to label with sidecar auto injection + enabled + items: + type: string + type: array + citadel: + description: Citadel configuration options + properties: + affinity: + type: object + caSecretName: + type: string + enabled: + type: boolean + image: + type: string + nodeSelector: + type: object + resources: + type: object + tolerations: + items: + type: object + type: array + type: object + defaultResources: + description: DefaultResources are applied for all Istio components by + default, can be overridden for each component + type: object + enabledServices: + description: EnabledServices the Istio component services replicated + to remote side + items: + properties: + labelSelector: + type: string + name: + type: string + podIPs: + items: + type: string + type: array + ports: + items: + type: object + type: array + required: + - name + type: object + type: array + excludeIPRanges: + description: ExcludeIPRanges the range where not to capture egress traffic + type: string + includeIPRanges: + description: IncludeIPRanges the range where to capture egress traffic + type: string + proxy: + description: Proxy configuration options + properties: + enableCoreDump: + description: If set, newly injected sidecars will have core dumps + enabled. + type: boolean + image: + type: string + privileged: + description: If set to true, istio-proxy container will have privileged + securityContext + type: boolean + resources: + type: object + type: object + proxyInit: + description: Proxy Init configuration options + properties: + image: + type: string + type: object + sidecarInjector: + description: SidecarInjector configuration options + properties: + affinity: + type: object + autoInjectionPolicyEnabled: + description: This controls the 'policy' in the sidecar injector + type: boolean + enabled: + type: boolean + image: + type: string + init: + properties: + resources: + type: object + type: object + initCNIConfiguration: + properties: + affinity: + type: object + binDir: + description: Must be the same as the environment’s --cni-bin-dir + setting (kubelet parameter) + type: string + confDir: + description: Must be the same as the environment’s --cni-conf-dir + setting (kubelet parameter) + type: string + enabled: + description: If true, the privileged initContainer istio-init + is not needed to perform the traffic redirect settings for + the istio-proxy + type: boolean + excludeNamespaces: + description: List of namespaces to exclude from Istio pod check + items: + type: string + type: array + image: + type: string + logLevel: + description: Logging level for CNI binary + type: string + type: object + nodeSelector: + type: object + replicaCount: + format: int32 + type: integer + resources: + type: object + rewriteAppHTTPProbe: + description: If true, sidecar injector will rewrite PodSpec for + liveness health check to redirect request to sidecar. This makes + liveness check work even when mTLS is enabled. + type: boolean + tolerations: + items: + type: object + type: array + type: object + required: + - enabledServices + type: object + status: + type: object + version: v1beta1 +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +{{- end }} diff --git a/deployments/helm/servicemesh/istio-operator/templates/operator-remoteistio-1.2-crd.yaml b/deployments/helm/servicemesh/istio-operator/templates/operator-remoteistio-1.2-crd.yaml index 37741898..6df6ba72 100644 --- a/deployments/helm/servicemesh/istio-operator/templates/operator-remoteistio-1.2-crd.yaml +++ b/deployments/helm/servicemesh/istio-operator/templates/operator-remoteistio-1.2-crd.yaml @@ -1,4 +1,4 @@ -{{ if eq .Values.istioVersion 1.2 }} +{{ if eq .Values.istioVersion "1.2" }} apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: diff --git a/deployments/helm/servicemesh/istio-operator/templates/operator-remoteistio-1.3-crd.yaml b/deployments/helm/servicemesh/istio-operator/templates/operator-remoteistio-1.3-crd.yaml new file mode 100644 index 00000000..bb411904 --- /dev/null +++ b/deployments/helm/servicemesh/istio-operator/templates/operator-remoteistio-1.3-crd.yaml @@ -0,0 +1,369 @@ +{{ if eq .Values.istioVersion "1.3" }} +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: remoteistios.istio.banzaicloud.io + labels: + controller-tools.k8s.io: "1.0" + app.kubernetes.io/name: {{ include "istio-operator.name" . }} + helm.sh/chart: {{ include "istio-operator.chart" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/version: {{ .Chart.AppVersion }} + app.kubernetes.io/component: operator +spec: + additionalPrinterColumns: + - JSONPath: .status.Status + description: Status of the resource + name: Status + type: string + - JSONPath: .status.ErrorMessage + description: Error message + name: Error + type: string + - JSONPath: .status.GatewayAddress + description: Ingress gateways of the resource + name: Gateways + type: string + - JSONPath: .metadata.creationTimestamp + name: Age + type: date + group: istio.banzaicloud.io + names: + kind: RemoteIstio + plural: remoteistios + scope: Namespaced + subresources: + status: {} + validation: + openAPIV3Schema: + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + autoInjectionNamespaces: + description: List of namespaces to label with sidecar auto injection + enabled + items: + type: string + type: array + citadel: + description: Citadel configuration options + properties: + affinity: + type: object + caSecretName: + type: string + enableNamespacesByDefault: + description: 'Determines Citadel default behavior if the ca.istio.io/env + or ca.istio.io/override labels are not found on a given namespace. For + example: consider a namespace called "target", which has neither + the "ca.istio.io/env" nor the "ca.istio.io/override" namespace + labels. To decide whether or not to generate secrets for service + accounts created in this "target" namespace, Citadel will defer + to this option. If the value of this option is "true" in this + case, secrets will be generated for the "target" namespace. If + the value of this option is "false" Citadel will not generate + secrets upon service account creation.' + type: boolean + enabled: + type: boolean + healthCheck: + description: Enable health checking on the Citadel CSR signing API. + https://istio.io/docs/tasks/security/health-check/ + type: boolean + image: + type: string + maxWorkloadCertTTL: + description: Citadel uses a flag max-workload-cert-ttl to control + the maximum lifetime for Istio certificates issued to workloads. + The default value is 90 days. If workload-cert-ttl on Citadel + or node agent is greater than max-workload-cert-ttl, Citadel will + fail issuing the certificate. + type: string + nodeSelector: + type: object + resources: + type: object + tolerations: + items: + type: object + type: array + workloadCertTTL: + description: For the workloads running in Kubernetes, the lifetime + of their Istio certificates is controlled by the workload-cert-ttl + flag on Citadel. The default value is 90 days. This value should + be no greater than max-workload-cert-ttl of Citadel. + type: string + type: object + clusterName: + description: Should be set to the name of the cluster, this is required + for sidecar injection to properly label proxies + type: string + defaultResources: + description: DefaultResources are applied for all Istio components by + default, can be overridden for each component + type: object + enabledServices: + description: EnabledServices the Istio component services replicated + to remote side + items: + properties: + labelSelector: + type: string + name: + type: string + podIPs: + items: + type: string + type: array + ports: + items: + type: object + type: array + required: + - name + type: object + type: array + excludeIPRanges: + description: ExcludeIPRanges the range where not to capture egress traffic + type: string + includeIPRanges: + description: IncludeIPRanges the range where to capture egress traffic + type: string + proxy: + description: Proxy configuration options + properties: + accessLogEncoding: + description: Configure the access log for sidecar to JSON or TEXT. + enum: + - JSON + - TEXT + type: string + accessLogFile: + description: 'Configures the access log for each sidecar. Options: "" + - disables access log "/dev/stdout" - enables access log' + enum: + - "" + - /dev/stdout + type: string + accessLogFormat: + description: 'Configure how and what fields are displayed in sidecar + access log. Setting to empty string will result in default log + format. If accessLogEncoding is TEXT, value will be used directly + as the log format example: "[%START_TIME%] %REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% + %PROTOCOL%\n" If AccessLogEncoding is JSON, value will be parsed + as map[string]string example: ''{"start_time": "%START_TIME%", + "req_method": "%REQ(:METHOD)%"}''' + type: string + componentLogLevel: + description: Per Component log level for proxy, applies to gateways + and sidecars. If a component level is not set, then the "LogLevel" + will be used. If left empty, "misc:error" is used. + type: string + coreDumpImage: + description: Image used to enable core dumps. This is only used, + when "EnableCoreDump" is set to true. + type: string + dnsRefreshRate: + description: Configure the DNS refresh rate for Envoy cluster of + type STRICT_DNS This must be given it terms of seconds. For example, + 300s is valid but 5m is invalid. + pattern: ^[0-9]{1,5}s$ + type: string + enableCoreDump: + description: If set, newly injected sidecars will have core dumps + enabled. + type: boolean + envoyAccessLogService: + properties: + enabled: + type: boolean + host: + type: string + port: + format: int32 + type: integer + tcpKeepalive: + properties: + interval: + type: string + probes: + format: int32 + type: integer + time: + type: string + type: object + tlsSettings: + properties: + caCertificates: + type: string + clientCertificate: + type: string + mode: + type: string + privateKey: + type: string + sni: + type: string + subjectAltNames: + items: + type: string + type: array + type: object + type: object + envoyMetricsService: + properties: + enabled: + type: boolean + host: + type: string + port: + format: int32 + type: integer + type: object + envoyStatsD: + properties: + enabled: + type: boolean + host: + type: string + port: + format: int32 + type: integer + type: object + image: + type: string + logLevel: + description: 'Log level for proxy, applies to gateways and sidecars. + If left empty, "warning" is used. Expected values are: trace|debug|info|warning|error|critical|off' + enum: + - trace + - debug + - info + - warning + - error + - critical + - "off" + type: string + privileged: + description: If set to true, istio-proxy container will have privileged + securityContext + type: boolean + protocolDetectionTimeout: + type: string + resources: + type: object + type: object + proxyInit: + description: Proxy Init configuration options + properties: + image: + type: string + type: object + sidecarInjector: + description: SidecarInjector configuration options + properties: + affinity: + type: object + alwaysInjectSelector: + description: 'AlwaysInjectSelector: Forces the injection on pods + whose labels match this selector. It''s an array of label selectors, + that will be OR''ed, meaning we will iterate over it and stop + at the first match' + items: + type: object + type: array + autoInjectionPolicyEnabled: + description: This controls the 'policy' in the sidecar injector + type: boolean + enableNamespacesByDefault: + description: This controls whether the webhook looks for namespaces + for injection enabled or disabled + type: boolean + enabled: + type: boolean + image: + type: string + init: + properties: + resources: + type: object + type: object + initCNIConfiguration: + properties: + affinity: + type: object + binDir: + description: Must be the same as the environment’s --cni-bin-dir + setting (kubelet parameter) + type: string + confDir: + description: Must be the same as the environment’s --cni-conf-dir + setting (kubelet parameter) + type: string + enabled: + description: If true, the privileged initContainer istio-init + is not needed to perform the traffic redirect settings for + the istio-proxy + type: boolean + excludeNamespaces: + description: List of namespaces to exclude from Istio pod check + items: + type: string + type: array + image: + type: string + logLevel: + description: Logging level for CNI binary + type: string + type: object + neverInjectSelector: + description: 'NeverInjectSelector: Refuses the injection on pods + whose labels match this selector. It''s an array of label selectors, + that will be OR''ed, meaning we will iterate over it and stop + at the first match Takes precedence over AlwaysInjectSelector.' + items: + type: object + type: array + nodeSelector: + type: object + replicaCount: + format: int32 + type: integer + resources: + type: object + rewriteAppHTTPProbe: + description: If true, sidecar injector will rewrite PodSpec for + liveness health check to redirect request to sidecar. This makes + liveness check work even when mTLS is enabled. + type: boolean + tolerations: + items: + type: object + type: array + type: object + required: + - enabledServices + type: object + status: + type: object + version: v1beta1 +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +{{- end }} diff --git a/deployments/helm/servicemesh/istio-operator/templates/operator-service.yaml b/deployments/helm/servicemesh/istio-operator/templates/operator-service.yaml index 04ffc835..bc49dcfd 100644 --- a/deployments/helm/servicemesh/istio-operator/templates/operator-service.yaml +++ b/deployments/helm/servicemesh/istio-operator/templates/operator-service.yaml @@ -2,6 +2,7 @@ apiVersion: v1 kind: Service metadata: name: "{{ include "istio-operator.fullname" . }}-operator" + namespace: {{ .Release.Namespace }} {{- if and .Values.prometheusMetrics.enabled (not .Values.prometheusMetrics.authProxy.enabled) }} annotations: prometheus.io/scrape: "true" @@ -26,8 +27,12 @@ spec: app.kubernetes.io/component: operator ports: - name: https + protocol: TCP port: 443 + targetPort: 443 {{- if and .Values.prometheusMetrics.enabled (not .Values.prometheusMetrics.authProxy.enabled) }} - name: metrics + protocol: TCP port: 8080 + targetPort: 8080 {{- end }} diff --git a/deployments/helm/servicemesh/istio-operator/templates/operator-statefulset.yaml b/deployments/helm/servicemesh/istio-operator/templates/operator-statefulset.yaml index 9e90ee80..0b59d23f 100644 --- a/deployments/helm/servicemesh/istio-operator/templates/operator-statefulset.yaml +++ b/deployments/helm/servicemesh/istio-operator/templates/operator-statefulset.yaml @@ -2,6 +2,7 @@ apiVersion: apps/v1 kind: StatefulSet metadata: name: "{{ include "istio-operator.fullname" . }}-operator" + namespace: {{ .Release.Namespace }} labels: control-plane: controller-manager controller-tools.k8s.io: "1.0" diff --git a/deployments/helm/servicemesh/istio-operator/values.yaml b/deployments/helm/servicemesh/istio-operator/values.yaml index cb937c11..f1ff6575 100644 --- a/deployments/helm/servicemesh/istio-operator/values.yaml +++ b/deployments/helm/servicemesh/istio-operator/values.yaml @@ -1,12 +1,26 @@ - - +#/*Copyright 2019 Intel Corporation, Inc +# * +# * Licensed under the Apache License, Version 2.0 (the "License"); +# * you may not use this file except in compliance with the License. +# * You may obtain a copy of the License at +# * +# * http://www.apache.org/licenses/LICENSE-2.0 +# * +# * Unless required by applicable law or agreed to in writing, software +# * distributed under the License is distributed on an "AS IS" BASIS, +# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# * See the License for the specific language governing permissions and +# * limitations under the License. +# */ + +# Default values for istio-operator. # This is a YAML-formatted file. # Declare variables to be passed into your templates. operator: image: repository: banzaicloud/istio-operator - tag: 0.2.1 + tag: 0.3.3 pullPolicy: IfNotPresent resources: limits: @@ -16,21 +30,30 @@ operator: cpu: 100m memory: 128Mi -istioVersion: 1.2 +istioVersion: "1.3" -## Prometheus Metrics +# If you want the operator to expose the /metrics prometheusMetrics: enabled: false -# Enable or disable the auth proxy (https://github.com/brancz/kube-rbac-proxy) -# which protects your /metrics endpoint. + # Enable or disable the auth proxy (https://github.com/brancz/kube-rbac-proxy) + # which protects your /metrics endpoint. authProxy: - enabled: false + enabled: true + image: + repository: gcr.io/kubebuilder/kube-rbac-proxy + tag: v0.4.0 + pullPolicy: IfNotPresent ## Role Based Access ## Ref: https://kubernetes.io/docs/admin/authorization/rbac/ ## rbac: enabled: true + ## Pod Security Policies + ## Ref: https://kubernetes.io/docs/concepts/policy/pod-security-policy/ + ## + psp: + enabled: true nameOverride: "" fullnameOverride: "" diff --git a/deployments/helm/servicemesh/istio/README.md b/deployments/helm/servicemesh/istio/README.md index 8fcba4f8..868c116a 100644 --- a/deployments/helm/servicemesh/istio/README.md +++ b/deployments/helm/servicemesh/istio/README.md @@ -17,4 +17,6 @@ # Steps for Instaling Istio with Istio- Operator # Step 1 - Add the helm chart to install Istio in sds configuration +# NOTE - Edit the namespaces in istio/istio-instance/values.yaml +# to enable istio-injection helm install istio-instance --name istio --namespace istio-system diff --git a/deployments/helm/servicemesh/istio/istio-instance/templates/istio-sds.yaml b/deployments/helm/servicemesh/istio/istio-instance/templates/istio-sds.yaml index 8c440a4e..4a8b11b2 100644 --- a/deployments/helm/servicemesh/istio/istio-instance/templates/istio-sds.yaml +++ b/deployments/helm/servicemesh/istio/istio-instance/templates/istio-sds.yaml @@ -29,8 +29,7 @@ spec: sds: enabled: {{ .Values.spec.sds.enabled }} udsPath: {{ .Values.spec.sds.udsPath | quote }} - useTrustworthyJwt: {{ .Values.spec.sds.useTrustworthyJwt }} - useNormalJwt: {{ .Values.spec.sds.useNormalJwt }} + tokenAudience: {{ .Values.spec.sds.tokenAudience | quote}} gateways: enabled: {{ .Values.spec.gateways.enabled }} ingress: diff --git a/deployments/helm/servicemesh/istio/istio-instance/values.yaml b/deployments/helm/servicemesh/istio/istio-instance/values.yaml index 091999ac..dd77ef1b 100644 --- a/deployments/helm/servicemesh/istio/istio-instance/values.yaml +++ b/deployments/helm/servicemesh/istio/istio-instance/values.yaml @@ -15,25 +15,25 @@ # * limitations under the License. # */ #Declare variables to be passed into Istio SDS template file. +#NOTE : EDit the namespace for which you need Istio injection metadata: name: "istio-sample" spec: - version: "1.2.2" + version: "1.3.3" mtls: true autoInjectionNamespaces: - - + - "default" sds: enabled: true udsPath: "unix:/var/run/sds/uds_path" - useTrustworthyJwt: false - useNormalJwt: true + tokenAudience: "istio-ca" gateways: enabled: true ingress: enabled: true sds: enabled: true - image: "docker.io/istio/node-agent-k8s:1.2.2" + image: "docker.io/istio/node-agent-k8s:1.3.3" nodeAgent: enabled: true - image : "docker.io/istio/node-agent-k8s:1.2.2" + image : "docker.io/istio/node-agent-k8s:1.3.3" diff --git a/deployments/helm/servicemesh/rbac/.helmignore b/deployments/helm/servicemesh/rbac/.helmignore new file mode 100644 index 00000000..50af0317 --- /dev/null +++ b/deployments/helm/servicemesh/rbac/.helmignore @@ -0,0 +1,22 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/deployments/helm/servicemesh/rbac/Chart.yaml b/deployments/helm/servicemesh/rbac/Chart.yaml new file mode 100644 index 00000000..8b3bfdc1 --- /dev/null +++ b/deployments/helm/servicemesh/rbac/Chart.yaml @@ -0,0 +1,18 @@ +# Copyright @ 2019 Intel Corporation +# # +# # Licensed under the Apache License, Version 2.0 (the "License"); +# # you may not use this file except in compliance with the License. +# # You may obtain a copy of the License at +# # +# # http://www.apache.org/licenses/LICENSE-2.0 +# # +# # Unless required by applicable law or agreed to in writing, software +# # distributed under the License is distributed on an "AS IS" BASIS, +# # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# # See the License for the specific language governing permissions and +# # limitations under the License. +apiVersion: v1 +appVersion: "1.0" +description: A Helm chart for Istio Rbac Rules +name: rbac +version: 0.1.0 diff --git a/deployments/helm/servicemesh/rbac/templates/_helpers.tpl b/deployments/helm/servicemesh/rbac/templates/_helpers.tpl new file mode 100644 index 00000000..866dd71e --- /dev/null +++ b/deployments/helm/servicemesh/rbac/templates/_helpers.tpl @@ -0,0 +1,69 @@ +# Copyright @ 2019 Intel Corporation +# # +# # Licensed under the Apache License, Version 2.0 (the "License"); +# # you may not use this file except in compliance with the License. +# # You may obtain a copy of the License at +# # +# # http://www.apache.org/licenses/LICENSE-2.0 +# # +# # Unless required by applicable law or agreed to in writing, software +# # distributed under the License is distributed on an "AS IS" BASIS, +# # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# # See the License for the specific language governing permissions and +# # limitations under the License. +{{/* +Expand the name of the chart. +*/}} +{{- define "name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{- define "rbacname" -}} + {{ default "default" .Values.rbacName }} +{{- end -}} + +{{- define "servicerolename" -}} + {{ default "default" .Values.serviceRoleRule.name }} +{{- end -}} + +{{- define "servicerolebindingname" -}} + {{ default "default" .Values.serviceRoleBinding.name }} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Common labels +*/}} +{{- define "labels" -}} +app.kubernetes.io/name: {{ include "name" . }} +helm.sh/chart: {{ include "chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} diff --git a/deployments/helm/servicemesh/rbac/templates/rbacenablement.yaml b/deployments/helm/servicemesh/rbac/templates/rbacenablement.yaml new file mode 100644 index 00000000..486993a3 --- /dev/null +++ b/deployments/helm/servicemesh/rbac/templates/rbacenablement.yaml @@ -0,0 +1,23 @@ +#{{/* +# Copyright @ 2019 Intel Corporation +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# imitations under the License. +#*/}} +apiVersion: "rbac.istio.io/v1alpha1" +kind: ClusterRbacConfig +metadata: + name: {{ template "rbacname" . }} +spec: + mode: 'ON_WITH_INCLUSION' + inclusion: + namespaces: [{{ .Values.namespace | quote }}] + enforcement_mode: {{ .Values.policyEnforcementMode }} diff --git a/deployments/helm/servicemesh/rbac/templates/servicerole.yaml b/deployments/helm/servicemesh/rbac/templates/servicerole.yaml new file mode 100644 index 00000000..d2791379 --- /dev/null +++ b/deployments/helm/servicemesh/rbac/templates/servicerole.yaml @@ -0,0 +1,24 @@ +#{{/* +# Copyright @ 2019 Intel Corporation +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# imitations under the License. +#*/}} +apiVersion: "rbac.istio.io/v1alpha1" +kind: ServiceRole +metadata: + name: {{ template "servicerolename" . }} + namespace: {{ .Values.namespace }} +spec: + rules: + - services: [{{ .Values.serviceRoleRule.services | quote }}] + paths: [{{ .Values.serviceRoleRule.paths | quote }}] + methods: {{ .Values.serviceRoleRule.methods| toJson }} diff --git a/deployments/helm/servicemesh/rbac/templates/servicerolebinding.yaml b/deployments/helm/servicemesh/rbac/templates/servicerolebinding.yaml new file mode 100644 index 00000000..c17adf7e --- /dev/null +++ b/deployments/helm/servicemesh/rbac/templates/servicerolebinding.yaml @@ -0,0 +1,26 @@ +#{{/* +# Copyright @ 2019 Intel Corporation +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# imitations under the License. +#*/}} +apiVersion: "rbac.istio.io/v1alpha1" +kind: ServiceRoleBinding +metadata: + name: {{ template "servicerolebindingname" . }} + namespace: {{ .Values.namespace }} +spec: + subjects: + - user: {{ .Values.serviceRoleBinding.users | quote }} + roleRef: + kind: ServiceRole + name: {{ .Values.serviceRoleBinding.serviceRoleName | quote }} + mode: {{ .Values.policyEnforcementMode }} diff --git a/deployments/helm/servicemesh/rbac/values.yaml b/deployments/helm/servicemesh/rbac/values.yaml new file mode 100644 index 00000000..45208ffa --- /dev/null +++ b/deployments/helm/servicemesh/rbac/values.yaml @@ -0,0 +1,26 @@ +# Copyright @ 2019 Intel Corporation +# # +# # Licensed under the Apache License, Version 2.0 (the "License"); +# # you may not use this file except in compliance with the License. +# # You may obtain a copy of the License at +# # +# # http://www.apache.org/licenses/LICENSE-2.0 +# # +# # Unless required by applicable law or agreed to in writing, software +# # distributed under the License is distributed on an "AS IS" BASIS, +# # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# # See the License for the specific language governing permissions and +# # limitations under the License. + +namespace: multicloud +policyEnforcementMode: PERMISSIVE +rbacName: "" +serviceRoleRule: + name: "" + service: multicloud-k8s.multicloud.svc.cluster.local + paths: "*" + methods: [ "GET","HEAD"] +serviceRoleBinding: + name: "" + users: "*" + serviceRoleName: "" diff --git a/kud/build/Dockerfile b/kud/build/Dockerfile index da100bb7..38c63295 100644 --- a/kud/build/Dockerfile +++ b/kud/build/Dockerfile @@ -1,4 +1,8 @@ FROM ubuntu:18.04 as base +ARG KUD_ENABLE_TESTS=false +ARG KUD_PLUGIN_ENABLED=false +ENV KUD_ENABLE_TESTS=$KUD_ENABLE_TESTS +ENV KUD_PLUGIN_ENABLED=$KUD_PLUGIN_ENABLED ADD . /usr/src/multicloud-k8s USER root SHELL ["/bin/bash", "-c"] diff --git a/kud/deployment_infra/images/sriov-daemonset.yml b/kud/deployment_infra/images/sriov-daemonset.yml index 1edbc6c3..72f33869 100644 --- a/kud/deployment_infra/images/sriov-daemonset.yml +++ b/kud/deployment_infra/images/sriov-daemonset.yml @@ -13,8 +13,8 @@ data: "resourceList": [{ "resourceName": "intel_sriov_700", "selectors": { - "vendors": ["8086"] - "devices": ["37cd"] + "vendors": ["8086"], + "drivers": ["i40evf", "iavf"] } }] } diff --git a/kud/deployment_infra/playbooks/configure-onap4k8s.yml b/kud/deployment_infra/playbooks/configure-onap4k8s.yml index cacb41c9..11729171 100644 --- a/kud/deployment_infra/playbooks/configure-onap4k8s.yml +++ b/kud/deployment_infra/playbooks/configure-onap4k8s.yml @@ -19,6 +19,14 @@ repo: 'https://github.com/onap/multicloud-k8s.git' dest: /opt/multicloud + - name: install make package for ubuntu systems + apt: name=make state=present update_cache=yes + when: ansible_distribution == "Ubuntu" + + - name: install make package for centos systems + yum: name=make state=present update_cache=yes + when: ansible_distribution == "CentOS" + - name: Change the onap4k8s directory and run the command make repo command: /usr/bin/make repo register: make_repo diff --git a/kud/deployment_infra/playbooks/configure-ovn.yml b/kud/deployment_infra/playbooks/configure-ovn.yml index 3fd2c765..28de6e94 100644 --- a/kud/deployment_infra/playbooks/configure-ovn.yml +++ b/kud/deployment_infra/playbooks/configure-ovn.yml @@ -15,14 +15,6 @@ file: "{{ item }}" with_items: - "{{ ansible_os_family }}.yml" - - name: get Wand GPI files - get_url: - url: https://packages.wand.net.nz/keyring.gpg - dest: /etc/apt/trusted.gpg.d/wand.gpg - - name: add WAND Debian Repo - apt_repository: - repo: "deb https://packages.wand.net.nz {{ ansible_lsb.codename }} main" - state: present - name: install OpenVSwitch packages package: name: "{{ item }}" diff --git a/kud/deployment_infra/playbooks/configure-sriov.yml b/kud/deployment_infra/playbooks/configure-sriov.yml index 8ba6cf48..45f276c6 100644 --- a/kud/deployment_infra/playbooks/configure-sriov.yml +++ b/kud/deployment_infra/playbooks/configure-sriov.yml @@ -12,25 +12,18 @@ - hosts: localhost become: yes - pre_tasks: - - block: - - name: "End play if SRIOV is False" - debug: - msg: "SRIOV option not available, ending play" - - meta: end_play - when: SRIOV_NODE == "False" tasks: - debug: var: SRIOV_NODE - name: Apply Multus shell: "/usr/local/bin/kubectl apply -f {{ playbook_dir }}/../images/multus-daemonset.yml" - when: SRIOV_NODE==True + when: SRIOV_NODE - name: Apply SRIOV CNI - shell: "/usr/local/bin/kubectl apply -f {{ playbook_dir }}/../images/sriov-cni.yaml" - when: SRIOV_NODE==True + shell: "/usr/local/bin/kubectl apply -f {{ playbook_dir }}/../images/sriov-cni.yml" + when: SRIOV_NODE - name: Apply SRIOV DaemonSet - shell: "/usr/local/bin/kubectl apply -f {{ playbook_dir }}/../images/sriov-daemonset.yaml" - when: SRIOV_NODE==True + shell: "/usr/local/bin/kubectl apply -f {{ playbook_dir }}/../images/sriov-daemonset.yml" + when: SRIOV_NODE - name: Apply SRIOV Network Attachment definition shell: "/usr/local/bin/kubectl apply -f {{ playbook_dir }}/sriov-nad.yml" - when: SRIOV_NODE==True + when: SRIOV_NODE diff --git a/kud/deployment_infra/playbooks/install_iavf_drivers.sh b/kud/deployment_infra/playbooks/install_iavf_drivers.sh index d44483de..7a54e9f2 100755 --- a/kud/deployment_infra/playbooks/install_iavf_drivers.sh +++ b/kud/deployment_infra/playbooks/install_iavf_drivers.sh @@ -3,6 +3,10 @@ # Based on: # https://gerrit.akraino.org/r/#/c/icn/+/1359/1/deploy/kud-plugin-addons/device-plugins/sriov/driver/install_iavf_drivers.sh +nic_models=(XL710 X722) +nic_drivers=(i40e) +device_checkers=(is_not_used is_driver_match is_model_match) + function install_iavf_driver { local ifname=$1 @@ -27,22 +31,55 @@ function install_iavf_driver { echo '8' > /sys/class/net/$ifname/device/sriov_numvfs } -function is_used { +function is_not_used { local ifname=$1 route_info=`ip route show | grep $ifname` if [ -z "$route_info" ]; then - return 0 - else return 1 + else + return 0 + fi +} + +function is_driver_match { + local ifname=$1 + driver=`cat /sys/class/net/$ifname/device/uevent | grep DRIVER | cut -f2 -d "="` + if [ ! -z "$driver" ]; then + for nic_driver in ${nic_drivers[@]}; do + if [ "$driver" = "$nic_driver" ]; then + return 1 + fi + done + fi + return 0 +} + +function is_model_match { + local ifname=$1 + pci_addr=`cat /sys/class/net/$ifname/device/uevent | grep PCI_SLOT_NAME | cut -f2 -d "=" | cut -f2,3 -d ":"` + if [ ! -z "$pci_addr" ]; then + for nic_model in ${nic_models[@]}; do + model_match=$(lspci | grep $pci_addr | grep $nic_model) + if [ ! -z "$model_match" ]; then + return 1 + fi + done fi + return 0 } function get_sriov_ifname { for net_device in /sys/class/net/*/ ; do if [ -e $net_device/device/sriov_numvfs ] ; then ifname=$(basename $net_device) - is_used $ifname - if [ "$?" = "0" ]; then + for device_checker in ${device_checkers[@]}; do + eval $device_checker $ifname + if [ "$?" = "0" ]; then + ifname="" + break + fi + done + if [ ! -z "$ifname" ]; then echo $ifname return fi diff --git a/kud/deployment_infra/playbooks/preconfigure-sriov.yml b/kud/deployment_infra/playbooks/preconfigure-sriov.yml index c4276e1b..fd16d935 100644 --- a/kud/deployment_infra/playbooks/preconfigure-sriov.yml +++ b/kud/deployment_infra/playbooks/preconfigure-sriov.yml @@ -31,7 +31,7 @@ command: sriov/sriov_hardware_check.sh register: output - set_fact: - SRIOV: "{{ output.stdout }}" + _SRIOV: "{{ output.stdout }}" - name: Recreate the conf file for every host file: path: /tmp/sriov.conf @@ -40,7 +40,7 @@ - lineinfile : > dest=/tmp/sriov.conf create=yes - line='{{SRIOV}}' + line='{{_SRIOV}}' delegate_to: localhost - name: Clean the script and folder. file: @@ -58,32 +58,30 @@ become: yes - set_fact: SRIOV_NODE: "{{ installer_output.stdout }}" - - meta: end_play - when: SRIOV_NODE == "False" - name: Load kud variables include_vars: file: kud-vars.yml - when: SRIOV_NODE == "True" + when: SRIOV_NODE tasks: - name: Create sriov folder file: state: directory path: "{{ sriov_dest }}" - when: SRIOV_NODE == "True" ignore_errors: yes + when: SRIOV_NODE - name: Get SRIOV compatible driver get_url: "url={{ driver_url }} dest=/tmp/{{ package }}.tar.gz" - when: SRIOV_NODE == "True" + when: SRIOV_NODE - name: Extract sriov source code unarchive: src: "/tmp/{{ package }}.tar.gz" dest: "{{ sriov_dest }}" - when: SRIOV_NODE == "True" + when: SRIOV_NODE - name: Build the default target make: chdir: "/tmp/sriov/{{ package }}/src" become: yes - when: SRIOV_NODE == "True" + when: SRIOV_NODE # Copy all the driver and install script into target node - hosts: kube-node become: yes @@ -91,7 +89,7 @@ - name: Load kud variables include_vars: file: kud-vars.yml - when: SRIOV == "True" + when: _SRIOV tasks: - name: create SRIOV driver folder in the target destination file: @@ -99,18 +97,22 @@ path: "{{ item }}" with_items: - sriov_driver - when: SRIOV == "True" - - name: Copy SRIOV driver to target destination - command: "cp {{ sriov_dest }}/{{ package }}/src/iavf.ko /root/sriov_driver/" - when: SRIOV == "True" - - name: Copy SRIOV driver install script to target folder - command: "cp {{ playbook_dir }}/install_iavf_drivers.sh /root/sriov_driver/install.sh" - when: SRIOV == "True" + when: _SRIOV + - copy: + src: "{{ sriov_dest }}/{{ package }}/src/iavf.ko" + dest: sriov_driver + remote_src: no + when: _SRIOV + - copy: + src: "{{ playbook_dir }}/install_iavf_drivers.sh" + dest: sriov_driver/install.sh + remote_src: no + when: _SRIOV - name: Changing perm of "install.sh", adding "+x" - file: dest=/root/sriov_driver/install.sh mode=a+x - when: SRIOV == "True" + file: dest=sriov_driver/install.sh mode=a+x + when: _SRIOV - name: Run a script with arguments shell: ./install.sh args: - chdir: "/root/sriov_driver" - when: SRIOV == "True" + chdir: "sriov_driver" + when: _SRIOV diff --git a/kud/deployment_infra/playbooks/sriov_hardware_check.sh b/kud/deployment_infra/playbooks/sriov_hardware_check.sh index ea1b7b0c..662c28c8 100644 --- a/kud/deployment_infra/playbooks/sriov_hardware_check.sh +++ b/kud/deployment_infra/playbooks/sriov_hardware_check.sh @@ -12,14 +12,14 @@ set -o pipefail source /etc/environment -ethernet_adpator_version=$( lspci | grep "Ethernet Controller X710" | head -n 1 | cut -d " " -f 8 ) +ethernet_adpator_version=$( lspci | grep "Ethernet Controller XL710" | head -n 1 | cut -d " " -f 8 ) if [ -z "$ethernet_adpator_version" ]; then echo "False" exit 0 fi SRIOV_ENABLED=${ethernet_adpator_version:-"false"} #checking for the right hardware version of NIC on the machine -if [ "$ethernet_adpator_version" == "X710" ]; then +if [ "$ethernet_adpator_version" == "XL710" ]; then echo "True" else echo "False" diff --git a/kud/hosting_providers/containerized/README.md b/kud/hosting_providers/containerized/README.md index 4119ca78..12ce1a19 100644 --- a/kud/hosting_providers/containerized/README.md +++ b/kud/hosting_providers/containerized/README.md @@ -27,7 +27,7 @@ Kubernetes jobs(a cluster per job) are used to install multiple clusters and log ## Quickstart Installation Guide -Build the kud docker images as follows: +Build the kud docker images as follows, add KUD_ENABLE_TESTS & KUD_PLUGIN_ENABLED for the testing only: ``` $ git clone https://github.com/onap/multicloud-k8s.git && cd multicloud-k8s @@ -38,6 +38,8 @@ $ docker build --rm \ --build-arg HTTPS_PROXY=${HTTPS_PROXY} \ --build-arg no_proxy=${no_proxy} \ --build-arg NO_PROXY=${NO_PROXY} \ + --build-arg KUD_ENABLE_TESTS=true \ + --build-arg KUD_PLUGIN_ENABLED=true \ -t github.com/onap/multicloud-k8s:latest . -f build/Dockerfile ``` Let's create a cluster-101 and cluster-102 hosts.ini as follows @@ -100,7 +102,7 @@ spec: - name: secret-volume mountPath: "/.ssh" command: ["/bin/sh","-c"] - args: ["cp -r /.ssh /root/; chmod -R 600 /root/.ssh; ./installer --cluster $CLUSTER_NAME"] + args: ["cp -r /.ssh /root/; chmod -R 600 /root/.ssh; ./installer --cluster $CLUSTER_NAME --plugins onap4k8s"] securityContext: privileged: true volumes: diff --git a/kud/hosting_providers/containerized/installer.sh b/kud/hosting_providers/containerized/installer.sh index 52fe6279..8739ca23 100755 --- a/kud/hosting_providers/containerized/installer.sh +++ b/kud/hosting_providers/containerized/installer.sh @@ -17,10 +17,13 @@ INSTALLER_DIR="$(readlink -f "$(dirname "${BASH_SOURCE[0]}")")" function install_prerequisites { #install package for docker images + echo "Removing ppa for jonathonf/python-3.6" + ls /etc/apt/sources.list.d/ || true + find /etc/apt/sources.list.d -maxdepth 1 -name '*jonathonf*' -delete || true apt-get update apt-get install -y curl vim wget git \ - software-properties-common python-pip - add-apt-repository ppa:longsleep/golang-backports + software-properties-common python-pip sudo + add-apt-repository -y ppa:longsleep/golang-backports apt-get update apt-get install -y golang-go rsync } @@ -100,7 +103,14 @@ function install_k8s { # install_addons() - Install Kubenertes AddOns function install_addons { - local plugins_name=$1 + if [ ${1:+1} ]; then + local plugins_name="$1" + echo "additional addons plugins $1" + else + local plugins_name="" + echo "no additional addons pluigns" + fi + source /etc/environment echo "Installing Kubernetes AddOns" ansible-galaxy install $verbose -r \ @@ -109,36 +119,52 @@ function install_addons { ansible-playbook $verbose -i \ $kud_inventory $kud_playbooks/configure-kud.yml | \ tee $cluster_log/setup-kud.log - for addon in ${KUD_ADDONS:-virtlet ovn4nfv nfd $plugins_name}; do + for addon in ${KUD_ADDONS:-virtlet ovn4nfv nfd sriov $plugins_name}; do echo "Deploying $addon using configure-$addon.yml playbook.." ansible-playbook $verbose -i \ $kud_inventory $kud_playbooks/configure-${addon}.yml | \ tee $cluster_log/setup-${addon}.log - if [[ "${testing_enabled}" == "true" ]]; then + done + + echo "Run the test cases if testing_enabled is set to true." + if [[ "${testing_enabled}" == "true" ]]; then + for addon in ${KUD_ADDONS:-virtlet ovn4nfv nfd sriov $plugins_name}; do pushd $kud_tests bash ${addon}.sh popd - fi - done + done + fi + echo "Add-ons deployment complete..." } # install_plugin() - Install ONAP Multicloud Kubernetes plugin function install_plugin { - echo "Installing multicloud/k8s plugin" - mkdir -p /opt/{kubeconfig,consul/config} - cp $HOME/.kube/config /opt/kubeconfig/kud - - pushd $kud_folder/../../../deployments - ./build.sh + echo "Installing multicloud/k8s onap4k8s plugin" if [[ "${testing_enabled}" == "true" ]]; then - ./start.sh pushd $kud_tests - for functional_test in plugin plugin_edgex plugin_fw; do - bash ${functional_test}.sh + echo "Test the onap4k8s installation" + bash onap4k8s.sh + echo "Test the onap4k8s plugin installation" + for functional_test in plugin_edgex plugin_fw; do + bash ${functional_test}.sh --external done popd fi - popd +} + +# install_controllers() - Install ONAP Multicloud Kubernetes controllers +function install_controllers { + echo "Installing multicloud/k8s onap4k8s controllers" + if [[ "${testing_enabled}" == "true" ]]; then + echo "Test controllers installation" + for controller_test in sdwan; do + pushd $kud_tests/$controller_test + ansible-playbook $verbose -i \ + $kud_inventory ${controller_test}.yml | \ + tee $cluster_log/test-${controller_test}.log + popd + done + fi } # _print_kubernetes_info() - Prints the login Kubernetes information @@ -179,6 +205,7 @@ k8s_info_file=$kud_folder/k8s_info.log testing_enabled=${KUD_ENABLE_TESTS:-false} mkdir -p /opt/csar +export CSAR_DIR=/opt/csar function install_pkg { # Install dependencies @@ -189,11 +216,19 @@ function install_pkg { function install_cluster { install_k8s $1 - install_addons $2 + if [ ${2:+1} ]; then + echo "install default addons and $2" + install_addons "$2" + else + install_addons + fi + echo "installed the addons" if ${KUD_PLUGIN_ENABLED:-false}; then install_plugin echo "installed the install_plugin" + install_controllers + echo "installed controllers" fi _print_kubernetes_info } @@ -254,7 +289,7 @@ if [ "$1" == "--cluster" ]; then cp $kud_multi_cluster_path/$cluster_name/hosts.ini $kud_inventory_folder/ cp -rf $kud_folder/inventory/group_vars $kud_inventory_folder/ - if [ -n "$3" ]; then + if [ ${3:+1} ]; then if [ "$3" == "--plugins" ]; then if [ -z "${4-}" ]; then echo "Error: plugins arguments is null; Refer the usage" @@ -262,7 +297,7 @@ if [ "$1" == "--cluster" ]; then exit 1 fi plugins_name=${@:4:$#} - install_cluster $cluster_name $plugins_name + install_cluster $cluster_name "$plugins_name" exit 0 else echo "Error: cluster argument should have plugins; \ diff --git a/kud/hosting_providers/vagrant/Vagrantfile b/kud/hosting_providers/vagrant/Vagrantfile index 58251fe9..2d1b5ab4 100644 --- a/kud/hosting_providers/vagrant/Vagrantfile +++ b/kud/hosting_providers/vagrant/Vagrantfile @@ -10,8 +10,8 @@ ############################################################################## box = { - :virtualbox => { :name => 'elastic/ubuntu-18.04-x86_64', :version => '20191013.0.0' }, - :libvirt => { :name => 'peru/ubuntu-18.04-server-amd64'} + :virtualbox => { :name => 'elastic/ubuntu-16.04-x86_64', :version => '20180708.0.0' }, + :libvirt => { :name => 'elastic/ubuntu-16.04-x86_64', :version=> '20180210.0.0'} } require 'yaml' diff --git a/kud/hosting_providers/vagrant/installer.sh b/kud/hosting_providers/vagrant/installer.sh index e5138c24..15974863 100755 --- a/kud/hosting_providers/vagrant/installer.sh +++ b/kud/hosting_providers/vagrant/installer.sh @@ -154,23 +154,19 @@ function install_addons { echo "Installing Kubernetes AddOns" _install_ansible sudo ansible-galaxy install $verbose -r $kud_infra_folder/galaxy-requirements.yml --ignore-errors - ansible-playbook $verbose -i $kud_inventory $kud_playbooks/configure-kud.yml | sudo tee $log_folder/setup-kud.log - for addon in ${KUD_ADDONS:-virtlet ovn4nfv nfd}; do + for addon in ${KUD_ADDONS:-virtlet ovn4nfv nfd sriov}; do echo "Deploying $addon using configure-$addon.yml playbook.." ansible-playbook $verbose -i $kud_inventory $kud_playbooks/configure-${addon}.yml | sudo tee $log_folder/setup-${addon}.log - if [[ "${testing_enabled}" == "true" ]]; then - pushd $kud_tests - bash ${addon}.sh - popd - fi done - ansible-playbook $verbose -i $kud_inventory $kud_playbooks/configure-sriov.yml | sudo tee $log_folder/setup-sriov.log - if [[ "${testing_enabled}" == "true" ]]; then + echo "Run the test cases if testing_enabled is set to true." + if [[ "${testing_enabled}" == "true" ]]; then + for addon in ${KUD_ADDONS:-virtlet ovn4nfv nfd sriov}; do pushd $kud_tests - bash sriov.sh + bash ${addon}.sh popd - fi + done + fi echo "Add-ons deployment complete..." } @@ -251,6 +247,9 @@ if [ -f $kud_folder/sources.list ]; then sudo mv /etc/apt/sources.list /etc/apt/sources.list.backup sudo cp $kud_folder/sources.list /etc/apt/sources.list fi +echo "Removing ppa for jonathonf/python-3.6" +sudo ls /etc/apt/sources.list.d/ || true +sudo find /etc/apt/sources.list.d -maxdepth 1 -name '*jonathonf*' -delete || true sudo apt-get update install_k8s _set_environment_file diff --git a/kud/tests/plugin_edgex.sh b/kud/tests/plugin_edgex.sh index 8eae5692..ae390add 100755 --- a/kud/tests/plugin_edgex.sh +++ b/kud/tests/plugin_edgex.sh @@ -17,7 +17,16 @@ source _common_test.sh source _functions.sh source _common.sh -base_url="http://localhost:9015/v1" +if [ ${1:+1} ]; then + if [ "$1" == "--external" ]; then + master_ip=$(kubectl cluster-info | grep "Kubernetes master" | \ + awk -F ":" '{print $2}' | awk -F "//" '{print $2}') + onap_svc_node_port=30498 + base_url="http://$master_ip:$onap_svc_node_port/v1" + fi +fi + +base_url=${base_url:-"http://localhost:9015/v1"} kubeconfig_path="$HOME/.kube/config" csar_id=cb009bfe-bbee-11e8-9766-525400435678 rb_name="edgex" @@ -91,6 +100,9 @@ response="$(call_api -d "${payload}" "${base_url}/instance")" echo "$response" vnf_id="$(jq -r '.id' <<< "${response}")" +print_msg "Waiting for EdgeX instances" +sleep 240 + print_msg "Validating Kubernetes" kubectl get --no-headers=true --namespace=${namespace} deployment edgex-core-command kubectl get --no-headers=true --namespace=${namespace} service edgex-core-command diff --git a/kud/tests/plugin_fw.sh b/kud/tests/plugin_fw.sh index d7bed4fd..eec467c3 100755 --- a/kud/tests/plugin_fw.sh +++ b/kud/tests/plugin_fw.sh @@ -17,7 +17,16 @@ source _common_test.sh source _functions.sh source _common.sh -base_url="http://localhost:9015/v1" +if [ ${1:+1} ]; then + if [ "$1" == "--external" ]; then + master_ip=$(kubectl cluster-info | grep "Kubernetes master" | \ + awk -F ":" '{print $2}' | awk -F "//" '{print $2}') + onap_svc_node_port=30498 + base_url="http://$master_ip:$onap_svc_node_port/v1" + fi +fi + +base_url=${base_url:-"http://localhost:9015/v1"} kubeconfig_path="$HOME/.kube/config" csar_id=cc009bfe-bbee-11e8-9766-525400435678 rb_name="vfw" @@ -98,6 +107,9 @@ wait_for_pod -n "${namespace}" -l app=firewall wait_for_pod -n "${namespace}" -l app=packetgen # TODO: Provide some health check to verify vFW work +print_msg "Waiting for VNF instances" +sleep 480 + print_msg "Retrieving VNF details" call_api "${base_url}/instance/${vnf_id}" diff --git a/kud/tests/sdwan.sh b/kud/tests/sdwan.sh new file mode 100755 index 00000000..64b10f22 --- /dev/null +++ b/kud/tests/sdwan.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# SPDX-license-identifier: Apache-2.0 +############################################################################## +# Copyright (c) 2018 +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +set -o errexit +set -o nounset +set -o pipefail + +echo "Create pods ..." +kubectl apply -f sdwan/ovn-pod.yml +kubectl apply -f sdwan/sdwan-openwrt-ovn.yml + +bash sdwan/test.sh + +echo "Clear pods ..." +kubectl delete -f sdwan/ovn-pod.yml +kubectl delete -f sdwan/sdwan-openwrt-ovn.yml + +echo "Test Completed!" diff --git a/kud/tests/sdwan/build/Dockerfile_1806_mwan3.tpl b/kud/tests/sdwan/build/Dockerfile_1806_mwan3.tpl new file mode 100644 index 00000000..85c7d358 --- /dev/null +++ b/kud/tests/sdwan/build/Dockerfile_1806_mwan3.tpl @@ -0,0 +1,26 @@ +FROM openwrt-1806-4-base + +#EXPOSE 80 +ENV http_proxy={docker_proxy} +ENV https_proxy={docker_proxy} +ENV no_proxy=localhost,120.0.0.1,192.168.* + +RUN mkdir /var/lock && \ + opkg update && \ + opkg install uhttpd-mod-lua && \ + uci set uhttpd.main.interpreter='.lua=/usr/bin/lua' && \ + uci commit uhttpd && \ + opkg install mwan3 && \ + opkg install luci-app-mwan3; exit 0 + +COPY system /etc/config/system +COPY commands.lua /usr/lib/lua/luci/controller/ + +ENV http_proxy= +ENV https_proxy= +ENV no_proxy= + +USER root + +# using exec format so that /sbin/init is proc 1 (see procd docs) +CMD ["/sbin/init"] diff --git a/kud/tests/sdwan/build/Dockerfile_1806_mwan3_noproxy.tpl b/kud/tests/sdwan/build/Dockerfile_1806_mwan3_noproxy.tpl new file mode 100644 index 00000000..8b5c57d2 --- /dev/null +++ b/kud/tests/sdwan/build/Dockerfile_1806_mwan3_noproxy.tpl @@ -0,0 +1,19 @@ +FROM openwrt-1806-4-base + +#EXPOSE 80 + +RUN mkdir /var/lock && \ + opkg update && \ + opkg install uhttpd-mod-lua && \ + uci set uhttpd.main.interpreter='.lua=/usr/bin/lua' && \ + uci commit uhttpd && \ + opkg install mwan3 && \ + opkg install luci-app-mwan3; exit 0 + +COPY system /etc/config/system +COPY commands.lua /usr/lib/lua/luci/controller/ + +USER root + +# using exec format so that /sbin/init is proc 1 (see procd docs) +CMD ["/sbin/init"] diff --git a/kud/tests/sdwan/build/README.md b/kud/tests/sdwan/build/README.md new file mode 100644 index 00000000..87e21956 --- /dev/null +++ b/kud/tests/sdwan/build/README.md @@ -0,0 +1,10 @@ +# Introduction: +Please refer ICN SDWAN Module Design for architecture introduction +link:https://wiki.akraino.org/display/AK/SDWAN+Module+Design + +# SDWAN Docker Image build instructions: +Use below steps to build openwrt docker image: openwrt-1806-mwan3 +(1) update set_proxy file with proxy used for docker build +(2) execute build_image.sh +cd build +sudo bash build_image.sh diff --git a/kud/tests/sdwan/build/build_image.sh b/kud/tests/sdwan/build/build_image.sh new file mode 100644 index 00000000..7ff6e20b --- /dev/null +++ b/kud/tests/sdwan/build/build_image.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +# usage: build_images.sh + +set -ex +base_image_tag=openwrt-1806-4-base +docker_file=Dockerfile_1806_mwan3 +image_tag=openwrt-1806-mwan3 +package=openwrt-18.06.4-x86-64-generic-rootfs + +# build openwrt base docker images +base_image=`docker images | grep $base_image_tag | awk '{print $1}'` +if [ -z "$base_image" ]; then + # download driver source package + if [ ! -e /tmp/$package.tar.gz ]; then + wget -P /tmp https://downloads.openwrt.org/releases/18.06.4/targets/x86/64/$package.tar.gz + fi + cp /tmp/$package.tar.gz . + + docker import $package.tar.gz $base_image_tag +fi + +# generate Dockerfile +test -f ./set_proxy && . set_proxy +docker_proxy=${docker_proxy-""} +if [ -z "$docker_proxy" ]; then + cp ${docker_file}_noproxy.tpl $docker_file +else + cp $docker_file.tpl $docker_file + sed -i "s,{docker_proxy},$docker_proxy,g" $docker_file +fi + +# build docker images for openwrt with wman3 +docker build --network=host -f $docker_file -t $image_tag . + +# clear +docker image rm $base_image_tag +rm -rf $docker_file +rm -rf $package.tar.gz diff --git a/kud/tests/sdwan/build/commands.lua b/kud/tests/sdwan/build/commands.lua new file mode 100644 index 00000000..d99f4579 --- /dev/null +++ b/kud/tests/sdwan/build/commands.lua @@ -0,0 +1,43 @@ +-- Licensed to the public under the GNU General Public License v2. + +module("luci.controller.commands", package.seeall) + +sys = require "luci.sys" +ut = require "luci.util" +io = require "io" + +ip = "ip -4 " + +function index() + entry({"admin", "config", "command"}, + call("execute")).dependent = false +end + +function trim(s) + return s:match("^%s*(.-)%s*$") +end + +function split_and_trim(str, sep) + local array = {} + local reg = string.format("([^%s]+)", sep) + for item in string.gmatch(str, reg) do + item_trimed = trim(item) + if string.len(item_trimed) > 0 then + table.insert(array, item_trimed) + end + end + return array +end + +function execute() + local commands = luci.http.formvalue("command") + io.stderr:write("Execute command: %s\n" % commands) + + local command_array = split_and_trim(commands, ";") + for index, command in ipairs(command_array) do + sys.exec(command) + end + + luci.http.prepare_content("application/json") + luci.http.write_json("{'status':'ok'}") +end diff --git a/kud/tests/sdwan/build/set_proxy b/kud/tests/sdwan/build/set_proxy new file mode 100644 index 00000000..7a195fe5 --- /dev/null +++ b/kud/tests/sdwan/build/set_proxy @@ -0,0 +1,2 @@ +# set docker proxy with below line, the build script will use this info +#docker_proxy= diff --git a/kud/tests/sdwan/build/system b/kud/tests/sdwan/build/system new file mode 100644 index 00000000..5165430f --- /dev/null +++ b/kud/tests/sdwan/build/system @@ -0,0 +1,7 @@ +config system + option log_file '/var/log/mylog' + option timezone 'UTC' + option ttylogin '0' + option log_size '64' + option urandom_seed '0' +EOF diff --git a/kud/tests/sdwan/ovn-pod.yml b/kud/tests/sdwan/ovn-pod.yml new file mode 100644 index 00000000..0715c030 --- /dev/null +++ b/kud/tests/sdwan/ovn-pod.yml @@ -0,0 +1,40 @@ +# Create 2 ovn4nfv network attachment definition +--- +apiVersion: k8s.plugin.opnfv.org/v1alpha1 +kind: Network +metadata: + name: ovn-port-net +spec: + cniType : ovn4nfv + ipv4Subnets: + - subnet: 172.16.33.0/24 + name: subnet1 + gateway: 172.16.33.1/24 + +--- +apiVersion: k8s.plugin.opnfv.org/v1alpha1 +kind: Network +metadata: + name: ovn-priv-net +spec: + cniType : ovn4nfv + ipv4Subnets: + - subnet: 172.16.44.0/24 + name: subnet1 + gateway: 172.16.44.1/24 + +--- +apiVersion: v1 +kind: Pod +metadata: + name: ovn-pod + annotations: + k8s.v1.cni.cncf.io/networks: '[{ "name": "ovn-networkobj"}]' + k8s.plugin.opnfv.org/nfn-network: '{ "type": "ovn4nfv", "interface": [{ "name": "ovn-port-net", "interface": "net0" , "defaultGateway": "false"}, + { "name": "ovn-priv-net", "interface": "net1" , "defaultGateway": "false"}]}' +spec: + containers: + - name: ovn-pod + image: docker.io/centos/tools:latest + command: + - /sbin/init diff --git a/kud/tests/sdwan/sdwan-openwrt-ovn.yml b/kud/tests/sdwan/sdwan-openwrt-ovn.yml new file mode 100644 index 00000000..2accdc6c --- /dev/null +++ b/kud/tests/sdwan/sdwan-openwrt-ovn.yml @@ -0,0 +1,82 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: sdwan-config-ovn +data: + entrypoint.sh: | + #!/bin/bash + # Always exit on errors. + set -e + + interface0=net0 + ipaddr0=`ifconfig $interface0 | awk '/inet/{print $2}' | cut -f2 -d ":" | awk 'NR==1 {print $1}'` + + interface1=net1 + ipaddr1=`ifconfig $interface1 | awk '/inet/{print $2}' | cut -f2 -d ":" | awk 'NR==1 {print $1}'` + + net_config=/etc/config/network + cat >> $net_config << EOF + config interface 'wan' + option ifname '$interface0' + option proto 'static' + option ipaddr '$ipaddr0' + option netmask '255.255.255.0' + + config interface 'wanb' + option ifname '$interface1' + option proto 'static' + option ipaddr '$ipaddr1' + option netmask '255.255.255.0' + EOF + + /sbin/procd & + /sbin/ubusd & + iptables -S + sleep 1 + /etc/init.d/rpcd start + /etc/init.d/dnsmasq start + /etc/init.d/network start + /etc/init.d/odhcpd start + /etc/init.d/uhttpd start + /etc/init.d/log start + /etc/init.d/dropbear start + /etc/init.d/mwan3 restart + + echo "Entering sleep... (success)" + + # Sleep forever. + while true; do sleep 100; done + +--- +apiVersion: v1 +kind: Pod +metadata: + name: sdwan-ovn-pod + annotations: + k8s.v1.cni.cncf.io/networks: '[{ "name": "ovn-networkobj"}]' + k8s.plugin.opnfv.org/nfn-network: '{ "type": "ovn4nfv", "interface": [{ "name": "ovn-port-net", "interface": "net0" , "defaultGateway": "false"}, + { "name": "ovn-priv-net", "interface": "net1" , "defaultGateway": "false"}]}' +spec: + containers: + - name: sdwan-ovn-pod + image: hle2/openwrt-1806-mwan3:v0.1.0 + ports: + - containerPort: 22 + - containerPort: 80 + command: + - /bin/sh + - /init/entrypoint.sh + imagePullPolicy: IfNotPresent + securityContext: + privileged: true + volumeMounts: + - name: entrypoint-sh + mountPath: /init + volumes: + - name: entrypoint-sh + configMap: + name: sdwan-config-ovn + items: + - key: entrypoint.sh + path: entrypoint.sh diff --git a/kud/tests/sdwan/sdwan.yml b/kud/tests/sdwan/sdwan.yml new file mode 100644 index 00000000..760d8599 --- /dev/null +++ b/kud/tests/sdwan/sdwan.yml @@ -0,0 +1,44 @@ +--- +# SPDX-license-identifier: Apache-2.0 +############################################################################## +# Copyright (c) 2018 +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## +- hosts: localhost + become: yes + tasks: + - name: create ovn network and client workload + command: "/usr/local/bin/kubectl apply -f {{ playbook_dir }}/ovn-pod.yml" + + - name: create sdwan controller + command: "/usr/local/bin/kubectl apply -f {{ playbook_dir }}/sdwan-openwrt-ovn.yml" + +- hosts: kube-master + become: yes + tasks: + - name: install wget package for ubuntu systems + apt: name=wget state=present update_cache=yes + when: ansible_distribution == "Ubuntu" + + - name: install wget package for centos systems + yum: name=wget state=present update_cache=yes + when: ansible_distribution == "CentOS" + + - name: Execute sdwan test script in cluster master + script: test.sh + register: sdwan + + - debug: + var: sdwan.stdout_lines + +- hosts: localhost + become: yes + tasks: + - name: delete ovn network and client workload + command: "/usr/local/bin/kubectl delete -f {{ playbook_dir }}/ovn-pod.yml" + + - name: delete sdwan controller + command: "/usr/local/bin/kubectl delete -f {{ playbook_dir }}/sdwan-openwrt-ovn.yml" diff --git a/kud/tests/sdwan/test.sh b/kud/tests/sdwan/test.sh new file mode 100755 index 00000000..ba4b4173 --- /dev/null +++ b/kud/tests/sdwan/test.sh @@ -0,0 +1,120 @@ +#!/bin/bash +# SPDX-license-identifier: Apache-2.0 +############################################################################## +# Copyright (c) 2018 +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +set -o errexit +set -o nounset +set -o pipefail + +sdwan_pod_name=sdwan-ovn-pod +ovn_pod_name=ovn-pod +wan_interface=net0 + +function login { + login_url=http://$1/cgi-bin/luci/ + echo $(wget -S --spider --post-data "luci_username=root&luci_password=" $login_url 2>&1 | grep sysauth= | sed -r 's/.*sysauth=([^;]+);.*/\1/') +} + +function disable_ping { + command_url=http://$2/cgi-bin/luci/admin/config/command + command="uci set firewall.@rule[1].target='REJECT';fw3 reload" + echo $(wget -S --spider --header="Cookie:sysauth=$1" --post-data "command=$command" $command_url 2>&1) +} + +function enable_ping { + command_url=http://$2/cgi-bin/luci/admin/config/command + command="uci set firewall.@rule[1].target='ACCEPT';fw3 reload" + echo $(wget -S --spider --header="Cookie:sysauth=$1" --post-data "command=$command" $command_url 2>&1) +} + +function wait_for_pod { + status_phase="" + while [[ "$status_phase" != "Running" ]]; do + new_phase="$(kubectl get pods -o wide | grep ^$1 | awk '{print $3}')" + if [[ "$new_phase" != "$status_phase" ]]; then + status_phase="$new_phase" + fi + if [[ "$new_phase" == "Err"* ]]; then + exit 1 + fi + sleep 2 + done +} + +function wait_for_pod_namespace { + status_phase="" + while [[ "$status_phase" != "Running" ]]; do + new_phase="$(kubectl get pods -o wide -n $2 | grep ^$1 | awk '{print $3}')" + if [[ "$new_phase" != "$status_phase" ]]; then + status_phase="$new_phase" + fi + if [[ "$new_phase" == "Err"* ]]; then + exit 1 + fi + sleep 2 + done +} + +echo "Waiting for pods to be ready ..." +wait_for_pod $ovn_pod_name +wait_for_pod $sdwan_pod_name +echo "* Create pods success" + +sdwan_pod_ip=$(kubectl get pods -o wide | grep ^$sdwan_pod_name | awk '{print $6}') +ovn_pod_ip=$(kubectl get pods -o wide | grep ^$ovn_pod_name | awk '{print $6}') +echo "SDWAN pod ip:"$sdwan_pod_ip +echo "OVN pod ip:"$ovn_pod_ip + +echo "Login to sdwan ..." +security_token="" +while [[ "$security_token" == "" ]]; do + echo "Get Security Token ..." + security_token=$(login $sdwan_pod_ip) + sleep 2 +done +echo "* Security Token: "$security_token + +kubectl exec $sdwan_pod_name ifconfig + +sdwan_pod_wan_ip=$(kubectl exec $sdwan_pod_name ifconfig $wan_interface | awk '/inet/{print $2}' | cut -f2 -d ":" | awk 'NR==1 {print $1}') +echo "Verify ping is work through wan interface between $sdwan_pod_name and $ovn_pod_name" +ping_result=$(kubectl exec $ovn_pod_name -- ping -c 3 $sdwan_pod_wan_ip) +if [[ $ping_result == *", 0% packet loss"* ]]; then + echo "* Ping is work through wan interface" +else + echo "* Test failed!" + exit 1 +fi + +echo "Disable ping rule of wan interface ..." +ret=$(disable_ping $security_token $sdwan_pod_ip) + +echo "Verify ping is not work through wan interface after ping rule disabled" +ping_result=$(kubectl exec $ovn_pod_name -- ping -c 3 $sdwan_pod_wan_ip 2>&1 || true) +if [[ $ping_result == *", 100% packet loss"* ]]; then + echo "* Ping is disabled" +else + echo "* Test failed!" + exit 1 +fi + +echo "Enable ping rule of wan interface ..." +ret=$(enable_ping $security_token $sdwan_pod_ip) + +echo "Verify ping is work through wan interface after ping rule enabled" +ping_result=$(kubectl exec $ovn_pod_name -- ping -c 3 $sdwan_pod_wan_ip) +if [[ $ping_result == *", 0% packet loss"* ]]; then + echo "* Ping is enabled" +else + echo "* Test failed!" + exit 1 +fi + + +echo "Test Completed!" diff --git a/kud/tests/sriov.sh b/kud/tests/sriov.sh index c66f5db8..a721b722 100755 --- a/kud/tests/sriov.sh +++ b/kud/tests/sriov.sh @@ -10,13 +10,13 @@ set -o pipefail -ethernet_adpator_version=$( lspci | grep "Ethernet Controller X710" | head -n 1 | cut -d " " -f 8 ) +ethernet_adpator_version=$( lspci | grep "Ethernet Controller XL710" | head -n 1 | cut -d " " -f 8 ) if [ -z "$ethernet_adpator_version" ]; then echo " Ethernet adapator version is not set. SRIOV test case cannot run on this machine" exit 0 fi #checking for the right hardware version of NIC on the machine -if [ $ethernet_adpator_version == "X710" ]; then +if [ $ethernet_adpator_version == "XL710" ]; then echo "NIC card specs match. SRIOV option avaiable for this version." else echo -e "Failed. The version supplied does not match.\nTest cannot be executed." diff --git a/kud/tests/vIPSec/README.md b/kud/tests/vIPSec/README.md new file mode 100644 index 00000000..3046db7a --- /dev/null +++ b/kud/tests/vIPSec/README.md @@ -0,0 +1,36 @@ +# vIPSec use case in ONAP +This use case is composed of four virtual functions (VFs) including two +IPSec gateways, a packet generator and a traffic sink, each running in +separate Ubuntu Virtual Machines: + + * [Packet generator][1]: Sends packets to the packet sink through the +tunnel constructed thru IPSec. This includes a script that installs the +packet generator based on packetgen[4]. + * [IPsec gateways][2]: Two IPSec gateways constructed the secure tunnel +for traffic transportation. This includes a script to install and configure +the IPSec gateways thru VPP. + * [Traffic sink][3]: Displays the traffic volume that lands at the sink +VM using the link http://192.168.80.250:667 through your browser +and enable automatic page refresh by clicking the "Off" button. You +can see the traffic volume in the charts. + +This set of scripts aims to construct the vIPSec use case in order to set +up a secure tunnel between peers and improve its performance along with +hardware acceleration technologies such as SRIOV and QAT. + +User can apply the helm chart named 'vipsec' inside the k8s/kud/demo folder +to set up the whole use case. A fully-functional Kubernetes cluster, Virtlet +as well as ovn4nfv-k8s[5] plugin need to be pre-installed for the usage. +*[Place needs improvements] After having the virtual machines ready, please +manually change the MAC address inside the ipsec.conf to enable the routing. +And also start up the packetgen to send packet with src and dst defined in +the templates/values.yaml inside the helm chart. Detail instructions will be +put inside the helm chart. + +If you'd like to test the performance with QAT/SRIOV involved, first get +these hardwares pre-configured. Then change the value of 'qat_enabled' and +'sriov_enabled' inside templates/values.yaml of the helm chart accordingly. +User could observe variance in throughput inside the traffic sink. + +[4] https://pktgen-dpdk.readthedocs.io/en/latest/ +[5] https://github.com/opnfv/ovn4nfv-k8s-plugin diff --git a/kud/tests/vIPSec/ipsec b/kud/tests/vIPSec/ipsec new file mode 100755 index 00000000..4b278574 --- /dev/null +++ b/kud/tests/vIPSec/ipsec @@ -0,0 +1,163 @@ +#!/bin/bash +# COPYRIGHT NOTICE STARTS HERE +# +# Copyright 2019 Intel Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# COPYRIGHT NOTICE ENDS HERE + +# This script prepares the runtime environment +# for running vIPSec shell scripts on Ubuntu 18.04 + +set -o nounset +set -o pipefail +set -o xtrace +set -o errexit + +function setup_dependencies { + apt-get update + apt-get install -y curl gnupg2 pciutils make gcc libnuma-dev python git linux-headers-`uname -r` module-init-tools libssl-dev + echo "deb [trusted=yes] https://packagecloud.io/fdio/release/ubuntu bionic main" >> /etc/apt/sources.list.d/99fd.io.list + curl -L https://packagecloud.io/fdio/master/gpgkey | apt-key add - +} + +function install_vpp { + apt-get update + apt-get install -y vpp vpp-plugin-core vpp-plugin-dpdk +} + +function install_dpdk { + cd /opt + git clone http://dpdk.org/git/dpdk + cd /opt/dpdk + export RTE_TARGET=x86_64-native-linux-gcc/ && export DESTDIR=/opt/dpdk && export RTE_SDK=/opt/dpdk && make install T=x86_64-native-linux-gcc + modprobe uio + insmod x86_64-native-linux-gcc/kmod/igb_uio.ko +} + +function ipsec_settings { +# Create vpp configuration file + cat > /opt/config/vpp.config << EOF + unix { + exec /opt/config/ipsec.conf + nodaemon + cli-listen /run/vpp/cli.sock + log /tmp/vpp.log + } + + cpu { + main-core 0 + corelist-workers 1 + } + + dpdk { + socket-mem 512 + log-level debug + no-tx-checksum-offload + dev default{ + num-tx-desc 512 + num-rx-desc 512 + } + dev interfaceABus + { + workers 0 + } + dev interfaceBBus + { + workers 0 + } + vdev crypto_aesni_mb0 + + no-multi-seg + + #enable_cryptodev + + } +EOF + +# Check if sriov and qat are enabled, bind the pci devices with igb_uio driver + if [ "$sriov_enabled" = true ]; then + export interfaceABus=$(lspci -D -nn | grep -m1 '8086:154c' | cut -d ' ' -f 1) + export interfaceBBus=$(lspci -D -nn | grep -m2 '8086:154c' | cut -d ' ' -f 1 | tail -n1) + else + export interfaceABus=$(ls -la /sys/class/net | grep 'eth1' | cut -d '/' -f 5) + export interfaceBBus=$(ls -la /sys/class/net | grep 'eth3' | cut -d '/' -f 5) + fi + sed -i -e "s/interfaceABus/${interfaceABus}/g" -e "s/interfaceBBus/${interfaceBBus}/g" /opt/config/vpp.config + python /opt/dpdk/usertools/dpdk-devbind.py -b igb_uio $interfaceABus $interfaceBBus + export interfaceA=$(vppctl sh int | awk '$2 == "1"' | cut -d ' ' -f 1) + export interfaceB=$(vppctl sh int | awk '$2 == "2"' | cut -d ' ' -f 1) + + if [ "$qat_enabled" = true ]; then + export qatABus=$(lspci -D -nn | grep -m1 '8086:37c9' | cut -d ' ' -f 1) + export qatBBus=$(lspci -D -nn | grep -m2 '8086:37c9' | cut -d ' ' -f 1 | tail -n1) + python /opt/dpdk/usertools/dpdk-devbind.py -b igb_uio $qatABus $qatBBus + sed -i "/#enable_cryptodev/a\n dev $qatABus\n dev $qatBBus\n" /opt/config/vpp.config + sed -i "/vdev crypto_aesni_mb0/d" /opt/config/vpp.config + fi + +# Create the sample ipsec configuration file + cat > /opt/config/ipsec.conf << EOF + set interface state VirtualFunctionEthernet0/5/0 up + set interface state VirtualFunctionEthernet0/6/0 up + + set interface ip address VirtualFunctionEthernet0/5/0 input_interface_ip/24 + set interface ip address VirtualFunctionEthernet0/6/0 output_interface_ip/24 + + set int promiscuous on VirtualFunctionEthernet0/5/0 + set int promiscuous on VirtualFunctionEthernet0/6/0 + + set ip arp VirtualFunctionEthernet0/6/0 remote_tunnel_ip fa:16:3e:a6:e4:c7 + set ip arp VirtualFunctionEthernet0/5/0 input_interface_ip fa:16:3e:f1:65:dc + + ip route add count 1 packet_dst/32 via route_interface VirtualFunctionEthernet0/6/0 + + ipsec spd add 1 + set interface ipsec spd VirtualFunctionEthernet0/6/0 1 + ipsec sa add 1 spi 1921681003 esp tunnel-src output_interface_ip tunnel-dst remote_tunnel_ip crypto-key 2b7e151628aed2a6abf7158809cf4f3d crypto-alg aes-cbc-128 integ-key 6867666568676665686766656867666568676669 integ-alg sha1-96 + ipsec policy add spd 1 traffic_direction priority 100 action protect sa 1 local-ip-range packet_src-packet_src remote-ip-range packet_dst-packet_dst + ipsec policy add spd 1 traffic_direction priority 90 protocol 50 action bypass local-ip-range packet_src-255.255.255.255 remote-ip-range remote_tunnel_ip-remote_tunnel_ip +EOF + +# Replace all ip and interfaces inside the ipsec configuration file + sed -i -e "s/input_interface_ip/${input_interface_ip}/g" -e "s/output_interface_ip/${output_interface_ip}/g" -e "s/remote_tunnel_ip/${remote_tunnel_ip}/g" -e "s/route_interface/${route_interface}/g" -e "s#VirtualFunctionEthernet0/5/0#${interfaceA}#g" -e "s#VirtualFunctionEthernet0/6/0#${interfaceB}/g" -e "s/packet_src/${packet_src}/g" -e "s/packet_dst/${packet_dst}/g" -e "s/traffic_direction/${traffic_direction}/g" /opt/config/ipsec.conf + vpp -c /opt/config/vpp.config +} + + +mkdir /opt/config +echo "$demo_artifacts_version" > /opt/config/demo_artifacts_version.txt +echo "$dcae_collector_ip" > /opt/config/dcae_collector_ip.txt +echo "$dcae_collector_port" > /opt/config/dcae_collector_port.txt +echo "$ipsec_private_net_gw" > /opt/config/ipsec_private_net_gw_ip.txt +echo "$ipsec_private_net_cidr" > /opt/config/ipsec_private_net_cidr.txt +echo "$ipsec_private_network_name" > /opt/config/ipsec_private_network_name.txt +echo "$packet_src" > /opt/config/packet_source_ip.txt +echo "$packet_dst" > /opt/config/packet_destination_ip.txt +echo "$remote_tunnel_ip" > /opt/config/remote_tunnel.txt +echo "$route_interface" > /opt/config/route_interface.txt +echo "$traffic_direction" > /opt/config/traffic_direction.txt +echo "$vipsecA_private_ip_0" > /opt/config/vipsecA_private_ip0.txt +echo "$vipsecA_private_ip_2" > /opt/config/vipsecA_private_ip2.txt +echo "$protected_clientA_network_name" > /opt/config/protected_clientA_network_name.txt +echo "$protected_clientA_net_gw" > /opt/config/protected_clientA_net_gw.txt +echo "$protected_clientA_net_cidr" > /opt/config/protected_clientA_net_cidr.txt + +echo 'vm.nr_hugepages = 1024' >> /etc/sysctl.conf +sysctl -p + +setup_dependencies +install_vpp +install_dpdk +ipsec_settings diff --git a/kud/tests/vIPSec/pktgen b/kud/tests/vIPSec/pktgen new file mode 100755 index 00000000..14d7e6ca --- /dev/null +++ b/kud/tests/vIPSec/pktgen @@ -0,0 +1,77 @@ +#!/bin/bash + +# COPYRIGHT NOTICE STARTS HERE +# +# Copyright 2019 Intel Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# COPYRIGHT NOTICE ENDS HERE + +# This script prepares the runtime environment +# for running vIPSec shell scripts on Ubuntu18.04 + +set -o nounset +set -o pipefail +set -o xtrace +set -o errexit + + +DPDK_DIR=$PWD/dpdk +Pktgen_Dir=$PWD/pktgen-dpdk + +function setup_dependencies { + sudo apt-get update + git clone http://dpdk.org/git/dpdk + git clone http://dpdk.org/git/apps/pktgen-dpdk + KERNEL_VERSION=$(uname -r) + echo $KERNEL_VERSION + sudo apt-get install -y linux-headers-$KERNEL_VERSION libpcap-dev gcc make libnuma-dev liblua5.3-dev python +} + +function build_dpdk { + export RTE_SDK=$DPDK_DIR + export RTE_TARGET=x86_64-native-linux-gcc + export DESTDIR=$DPDK_DIR + cd $RTE_SDK + make install T=x86_64-native-linux-gcc + echo "DPDK install finished" + modprobe uio + insmod x86_64-native-linux-gcc/kmod/igb_uio.ko + export interface=$(lspci -nn | grep -m1 'Ethernet controller' | cut -d ' ' -f 1) + python ./usertools/dpdk-devbind.py -b igb_uio $interface +} + +function build_pktgen { + cd $Pktgen_Dir + export RTE_SDK=$DPDK_DIR + export RTE_TARGET=x86_64-native-linux-gcc + make +} + +mkdir /opt/config +echo "$demo_artifacts_version" > /opt/config/demo_artifacts_version.txt +echo "$vpg_private_ip_0" > /opt/config/vpg_private_ip0.txt +echo "$ipsec_a_private_ip_0" > /opt/config/ipsec_a_private_ip0.txt +echo "$protected_clientA_network_name" > /opt/config/protected_clientA_network_name.txt +echo "$dcae_collector_ip" > /opt/config/dcae_collector_ip.txt +echo "$dcae_collector_port" > /opt/config/dcae_collector_port.txt +echo "$protected_clientA_net_gw" > /opt/config/protected_clientA_net_gw.txt +echo "$protected_clientA_net_cidr" > /opt/config/protected_clientA_net_cidr.txt + +echo 'vm.nr_hugepages = 1024' >> /etc/sysctl.conf +sysctl -p + +setup_dependencies +build_dpdk +build_pktgen diff --git a/kud/tests/vIPSec/remote_ipsec b/kud/tests/vIPSec/remote_ipsec new file mode 100755 index 00000000..6a676c96 --- /dev/null +++ b/kud/tests/vIPSec/remote_ipsec @@ -0,0 +1,164 @@ +#!/bin/bash + +# COPYRIGHT NOTICE STARTS HERE +# +# Copyright 2019 Intel Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# COPYRIGHT NOTICE ENDS HERE + +# This script prepares the runtime environment +# for running vIPSec shell scripts on Ubuntu18.04 + +set -o nounset +set -o pipefail +set -o xtrace +set -o errexit + +function setup_dependencies { + apt-get update + apt-get install -y curl gnupg2 pciutils make gcc libnuma-dev python git linux-headers-`uname -r` module-init-tools libssl-dev + echo "deb [trusted=yes] https://packagecloud.io/fdio/release/ubuntu bionic main" >> /etc/apt/sources.list.d/99fd.io.list + curl -L https://packagecloud.io/fdio/master/gpgkey | apt-key add - +} + +function install_vpp { + apt-get update + apt-get install -y vpp vpp-plugin-core vpp-plugin-dpdk +} + +function install_dpdk { + cd /opt + git clone http://dpdk.org/git/dpdk + cd /opt/dpdk + export RTE_TARGET=x86_64-native-linux-gcc/ && export DESTDIR=/opt/dpdk && export RTE_SDK=/opt/dpdk && make install T=x86_64-native-linux-gcc + modprobe uio + insmod x86_64-native-linux-gcc/kmod/igb_uio.ko +} + +function ipsec_settings { +# Create vpp configuration file + cat > /opt/config/vpp.config << EOF + unix { + exec /opt/config/ipsec.conf + nodaemon + cli-listen /run/vpp/cli.sock + log /tmp/vpp.log + } + + cpu { + main-core 0 + corelist-workers 1 + } + + dpdk { + socket-mem 512 + log-level debug + no-tx-checksum-offload + dev default{ + num-tx-desc 512 + num-rx-desc 512 + } + dev interfaceABus + { + workers 0 + } + dev interfaceBBus + { + workers 0 + } + vdev crypto_aesni_mb0 + + no-multi-seg + + #enable_cryptodev + + } +EOF + +# Check if sriov and qat are enabled, bind the pci devices with igb_uio driver + if [ "$sriov_enabled" = true ]; then + export interfaceABus=$(lspci -D -nn | grep -m1 '8086:154c' | cut -d ' ' -f 1) + export interfaceBBus=$(lspci -D -nn | grep -m2 '8086:154c' | cut -d ' ' -f 1 | tail -n1) + else + export interfaceABus=$(ls -la /sys/class/net | grep 'eth1' | cut -d '/' -f 5) + export interfaceBBus=$(ls -la /sys/class/net | grep 'eth3' | cut -d '/' -f 5) + fi + sed -i -e "s/interfaceABus/${interfaceABus}/g" -e "s/interfaceBBus/${interfaceBBus}/g" /opt/config/vpp.config + python /opt/dpdk/usertools/dpdk-devbind.py -b igb_uio $interfaceABus $interfaceBBus + export interfaceA=$(vppctl sh int | awk '$2 == "1"' | cut -d ' ' -f 1) + export interfaceB=$(vppctl sh int | awk '$2 == "2"' | cut -d ' ' -f 1) + + if [ "$qat_enabled" = true ]; then + export qatABus=$(lspci -D -nn | grep -m1 '8086:37c9' | cut -d ' ' -f 1) + export qatBBus=$(lspci -D -nn | grep -m2 '8086:37c9' | cut -d ' ' -f 1 | tail -n1) + python /opt/dpdk/usertools/dpdk-devbind.py -b igb_uio $qatABus $qatBBus + sed -i "/#enable_cryptodev/a\n dev $qatABus\n dev $qatBBus\n" /opt/config/vpp.config + sed -i "/vdev crypto_aesni_mb0/d" /opt/config/vpp.config + fi + +# Create ipsec configuration file + cat > /opt/config/ipsec.conf << EOF + set interface state VirtualFunctionEthernet0/5/0 up + set interface state VirtualFunctionEthernet0/6/0 up + + set interface ip address VirtualFunctionEthernet0/5/0 input_interface_ip/24 + set interface ip address VirtualFunctionEthernet0/6/0 output_interface_ip/24 + + set int promiscuous on VirtualFunctionEthernet0/5/0 + set int promiscuous on VirtualFunctionEthernet0/6/0 + + set ip arp VirtualFunctionEthernet0/6/0 remote_tunnel_ip fa:16:3e:a6:e4:c7 + set ip arp VirtualFunctionEthernet0/5/0 routing_ip fa:16:3e:f1:65:dc + + ip route add count 1 packet_dst/32 via route_interface VirtualFunctionEthernet0/6/0 + + ipsec spd add 1 + set interface ipsec spd VirtualFunctionEthernet0/6/0 1 + ipsec sa add 1 spi 1921681004 esp tunnel-src local_tunnel_ip tunnel-dst remote_tunnel_ip crypto-key 2b7e151628aed2a6abf7158809cf4f3d crypto-alg aes-cbc-128 integ-key 6867666568676665686766656867666568676669 integ-alg sha1-96 + ipsec policy add spd 1 traffic_direction priority 100 action protect sa 1 local-ip-range packet_src-packet_src remote-ip-range packet_dst-packet_dst + ipsec policy add spd 1 traffic_direction priority 90 protocol 50 action bypass local-ip-range packet_src-255.255.255.255 remote-ip-range remote_tunnel_ip-remote_tunnel_ip +EOF + +# Replace the actual ip and interfaces into the ipsec configuration + sed -i -e "s/input_interface_ip/${input_interface_ip}/g" -e "s/output_interface_ip/${output_interface_ip}/g" -e "s/routing_ip/${vsn_private_ip_0}/g" -e "s#VirtualFunctionEthernet0/5/0#${interfaceA}#g" -e "s#VirtualFunctionEthernet0/6/0#${interfaceB}#g" -e "s/local_tunnel_ip/${local_tunnel_ip}/g" -e "s/remote_tunnel_ip/${remote_tunnel_ip}/g" -e "s/route_interface/${route_interface}/g" -e "s/packet_src/${packet_src}/g" -e "s/packet_dst/${packet_dst}/g" -e "s/traffic_direction/${traffic_direction}/g" /opt/config/ipsec.conf + vpp -c /opt/config/vpp.config +} + + +mkdir /opt/config +echo "$demo_artifacts_version" > /opt/config/demo_artifacts_version.txt +echo "$dcae_collector_ip" > /opt/config/dcae_collector_ip.txt +echo "$dcae_collector_port" > /opt/config/dcae_collector_port.txt +echo "$ipsec_private_net_gw" > /opt/config/ipsec_private_net_gw_ip.txt +echo "$ipsec_private_net_cidr" > /opt/config/ipsec_private_net_cidr.txt +echo "$ipsec_private_network_name" > /opt/config/ipsec_private_network_name.txt +echo "$packet_src" > /opt/config/packet_source_ip.txt +echo "$packet_dst" > /opt/config/packet_destination_ip.txt +echo "$remote_tunnel_ip" > /opt/config/remote_tunnel.txt +echo "$route_interface" > /opt/config/route_interface.txt +echo "$traffic_direction" > /opt/config/traffic_direction.txt +echo "$vipsecB_private_ip_0" > /opt/config/vipsecB_private_ip0.txt +echo "$vipsecB_private_ip_2" > /opt/config/vipsecB_private_ip2.txt +echo "$protected_clientB_network_name" > /opt/config/protected_clientB_network_name.txt +echo "$protected_clientB_net_gw" > /opt/config/protected_clientB_net_gw.txt +echo "$protected_clientB_net_cidr" > /opt/config/protected_clientB_net_cidr.txt + +echo 'vm.nr_hugepages = 1024' >> /etc/sysctl.conf +sysctl -p + +setup_dependencies +install_vpp +install_dpdk +ipsec_settings diff --git a/kud/tests/vIPSec/sink b/kud/tests/vIPSec/sink new file mode 100755 index 00000000..c180d43c --- /dev/null +++ b/kud/tests/vIPSec/sink @@ -0,0 +1,48 @@ +#!/bin/bash + +# COPYRIGHT NOTICE STARTS HERE +# +# Copyright 2019 Intel Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# COPYRIGHT NOTICE ENDS HERE + +# This script prepares the runtime environment +# for running vIPSec shell scripts on Ubuntu 18.04 + +set -o nounset +set -o pipefail +set -o xtrace +set -o errexit + +function setup_dependencies { + apt-get update + apt install -y wget darkstat net-tools unzip + + # Configure and run Darkstat + sed -i "s/START_DARKSTAT=.*/START_DARKSTAT=yes/g;s/INTERFACE=.*/INTERFACE=\"-i eth1\"/g" /etc/darkstat/init.cfg + + systemctl restart darkstat +} + +mkdir -p /opt/config/ +echo "$protected_net_cidr" > /opt/config/protected_net_cidr.txt +echo "$vfw_private_ip_0" > /opt/config/fw_ipaddr.txt +echo "$vsn_private_ip_0" > /opt/config/sink_ipaddr.txt +echo "$demo_artifacts_version" > /opt/config/demo_artifacts_version.txt +echo "$protected_net_gw" > /opt/config/protected_net_gw.txt +echo "$protected_private_net_cidr" > /opt/config/unprotected_net.txt + +setup_dependencies + diff --git a/src/Makefile b/src/Makefile index 94359287..8d856563 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,15 +1,19 @@ build: $(MAKE) -C monitor build $(MAKE) -C k8splugin build + $(MAKE) -C orchestrator build deploy: $(MAKE) -C monitor deploy $(MAKE) -C k8splugin deploy + $(MAKE) -C orchestrator deploy all: $(MAKE) -C monitor all $(MAKE) -C k8splugin all + $(MAKE) -C orchestrator all clean: $(MAKE) -C monitor clean $(MAKE) -C k8splugin clean + $(MAKE) -C orchestrator clean diff --git a/src/k8splugin/Makefile b/src/k8splugin/Makefile index 7d41158c..77196afa 100644 --- a/src/k8splugin/Makefile +++ b/src/k8splugin/Makefile @@ -25,8 +25,8 @@ deploy: build .PHONY: test test: clean - @go build -buildmode=plugin -o ./mock_files/mock_plugins/mockplugin.so ./mock_files/mock_plugins/mockplugin.go - @go test -v ./... + @go build -race -buildmode=plugin -o ./mock_files/mock_plugins/mockplugin.so ./mock_files/mock_plugins/mockplugin.go + @go test -race ./... format: @go fmt ./... @@ -40,5 +40,5 @@ clean: .PHONY: cover cover: - @go test ./... -coverprofile=coverage.out + @go test -race ./... -coverprofile=coverage.out @go tool cover -html=coverage.out -o coverage.html diff --git a/src/k8splugin/api/brokerhandler.go b/src/k8splugin/api/brokerhandler.go index 669b539f..7671db44 100644 --- a/src/k8splugin/api/brokerhandler.go +++ b/src/k8splugin/api/brokerhandler.go @@ -134,21 +134,19 @@ func (b brokerInstanceHandler) createHandler(w http.ResponseWriter, r *http.Requ return } - rbName := req.getAttributeValue(req.UserDirectives, "definition-name") - if rbName == "" { - http.Error(w, "definition-name is missing from user-directives", http.StatusBadRequest) + if req.VFModuleModelInvariantID == "" { + http.Error(w, "vf-module-model-invariant-id is empty", http.StatusBadRequest) return } - rbVersion := req.getAttributeValue(req.UserDirectives, "definition-version") - if rbVersion == "" { - http.Error(w, "definition-version is missing from user-directives", http.StatusBadRequest) + if req.VFModuleModelVersionID == "" { + http.Error(w, "vf-module-model-version-id is empty", http.StatusBadRequest) return } - profileName := req.getAttributeValue(req.UserDirectives, "profile-name") + profileName := req.getAttributeValue(req.SDNCDirectives, "k8s-rb-profile-name") if profileName == "" { - http.Error(w, "profile-name is missing from user-directives", http.StatusBadRequest) + http.Error(w, "k8s-rb-profile-name is missing from sdnc-directives", http.StatusBadRequest) return } @@ -160,8 +158,8 @@ func (b brokerInstanceHandler) createHandler(w http.ResponseWriter, r *http.Requ // Setup the resource parameters for making the request var instReq app.InstanceRequest - instReq.RBName = rbName - instReq.RBVersion = rbVersion + instReq.RBName = req.VFModuleModelInvariantID + instReq.RBVersion = req.VFModuleModelVersionID instReq.ProfileName = profileName instReq.CloudRegion = cloudRegion instReq.Labels = map[string]string{ diff --git a/src/k8splugin/api/brokerhandler_test.go b/src/k8splugin/api/brokerhandler_test.go index 8ef5e184..83ff588b 100644 --- a/src/k8splugin/api/brokerhandler_test.go +++ b/src/k8splugin/api/brokerhandler_test.go @@ -48,18 +48,19 @@ func TestBrokerCreateHandler(t *testing.T) { expectedCode: http.StatusUnprocessableEntity, }, { - label: "Missing parameter failure", + label: "Missing vf-module-*-id parameter", input: bytes.NewBuffer([]byte(`{ "vf-module-model-customization-id": "84sdfkio938", - "user_directives": { + "vf-module-model-invariant-id": "123456qwerty", + "sdnc_directives": { "attributes": [ { - "attribute_name": "definition-name", - "attribute_value": "test-rbdef" + "attribute_name": "vf_module_name", + "attribute_value": "test-vf-module-name" }, { - "attribute_name": "definition-version", - "attribute_value": "v1" + "attribute_name": "k8s-rb-profile-name", + "attribute_value": "profile1" } ] } @@ -67,9 +68,11 @@ func TestBrokerCreateHandler(t *testing.T) { expectedCode: http.StatusBadRequest, }, { - label: "Succesfully create an Instance", + label: "Missing parameter from sdnc_directives", input: bytes.NewBuffer([]byte(`{ "vf-module-model-customization-id": "84sdfkio938", + "vf-module-model-invariant-id": "123456qwerty", + "vf-module-model-version-id": "123qweasdzxc", "sdnc_directives": { "attributes": [ { @@ -77,19 +80,24 @@ func TestBrokerCreateHandler(t *testing.T) { "attribute_value": "test-vf-module-name" } ] - }, - "user_directives": { + } + }`)), + expectedCode: http.StatusBadRequest, + }, + { + label: "Succesfully create an Instance", + input: bytes.NewBuffer([]byte(`{ + "vf-module-model-customization-id": "84sdfkio938", + "vf-module-model-invariant-id": "123456qwerty", + "vf-module-model-version-id": "123qweasdzxc", + "sdnc_directives": { "attributes": [ { - "attribute_name": "definition-name", - "attribute_value": "test-rbdef" - }, - { - "attribute_name": "definition-version", - "attribute_value": "v1" + "attribute_name": "vf_module_name", + "attribute_value": "test-vf-module-name" }, { - "attribute_name": "profile-name", + "attribute_name": "k8s-rb-profile-name", "attribute_value": "profile1" } ] @@ -122,8 +130,8 @@ func TestBrokerCreateHandler(t *testing.T) { { ID: "HaKpys8e", Request: app.InstanceRequest{ - RBName: "test-rbdef", - RBVersion: "v1", + RBName: "123456qwerty", + RBVersion: "123qweasdzxc", ProfileName: "profile1", CloudRegion: "region1", }, diff --git a/src/k8splugin/api/instancehandler.go b/src/k8splugin/api/instancehandler.go index 1dcbcda9..b0437426 100644 --- a/src/k8splugin/api/instancehandler.go +++ b/src/k8splugin/api/instancehandler.go @@ -20,10 +20,10 @@ import ( "net/http" "github.com/onap/multicloud-k8s/src/k8splugin/internal/app" + log "github.com/onap/multicloud-k8s/src/k8splugin/internal/logutils" "github.com/gorilla/mux" pkgerrors "github.com/pkg/errors" - log "github.com/onap/multicloud-k8s/src/k8splugin/internal/logutils" ) // Used to store the backend implementation objects @@ -37,18 +37,25 @@ func (i instanceHandler) validateBody(body interface{}) error { switch b := body.(type) { case app.InstanceRequest: if b.CloudRegion == "" { - log.WithFields("CreateVnfRequest bad request", "CloudRegion", "Invalid/Missing CloudRegion in POST request") + log.Error("CreateVnfRequest Bad Request", log.Fields{ + "cloudRegion": "Missing CloudRegion in POST request", + }) werr := pkgerrors.Wrap(errors.New("Invalid/Missing CloudRegion in POST request"), "CreateVnfRequest bad request") return werr } if b.RBName == "" || b.RBVersion == "" { - log.WithFields("CreateVnfRequest bad request", "RBName", "Invalid/Missing resource bundle parameters in POST request") - log.WithFields("CreateVnfRequest bad request", "RBVersion", "Invalid/Missing resource bundle parameters in POST request") + log.Error("CreateVnfRequest Bad Request", log.Fields{ + "message": "One of RBName, RBVersion is missing", + "RBName": b.RBName, + "RBVersion": b.RBVersion, + }) werr := pkgerrors.Wrap(errors.New("Invalid/Missing resource bundle parameters in POST request"), "CreateVnfRequest bad request") return werr } if b.ProfileName == "" { - log.WithFields("CreateVnfRequest bad request", "ProfileName", "Invalid/Missing profile name in POST request") + log.Error("CreateVnfRequest bad request", log.Fields{ + "ProfileName": "Missing profile name in POST request", + }) werr := pkgerrors.Wrap(errors.New("Invalid/Missing profile name in POST request"), "CreateVnfRequest bad request") return werr } @@ -62,11 +69,15 @@ func (i instanceHandler) createHandler(w http.ResponseWriter, r *http.Request) { err := json.NewDecoder(r.Body).Decode(&resource) switch { case err == io.EOF: - log.WithFields("http.StatusBadRequest", "Error", "Body empty") + log.Error("Body Empty", log.Fields{ + "error": io.EOF, + }) http.Error(w, "Body empty", http.StatusBadRequest) return case err != nil: - log.WithFields("http.StatusUnprocessableEntity", "Error", "http.StatusUnprocessableEntity") + log.Error("Error unmarshaling Body", log.Fields{ + "error": err, + }) http.Error(w, err.Error(), http.StatusUnprocessableEntity) return } @@ -74,14 +85,19 @@ func (i instanceHandler) createHandler(w http.ResponseWriter, r *http.Request) { // Check body for expected parameters err = i.validateBody(resource) if err != nil { - log.WithFields("StatusUnprocessableEntity", "Error", "http.StatusUnprocessableEntity") + log.Error("Invalid Parameters in Body", log.Fields{ + "error": err, + }) http.Error(w, err.Error(), http.StatusUnprocessableEntity) return } resp, err := i.client.Create(resource) if err != nil { - log.WithFields("StatusInternalServerError", "Error", "http.StatusInternalServerError") + log.Error("Error Creating Resource", log.Fields{ + "error": err, + "resource": resource, + }) http.Error(w, err.Error(), http.StatusInternalServerError) return } @@ -90,7 +106,10 @@ func (i instanceHandler) createHandler(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusCreated) err = json.NewEncoder(w).Encode(resp) if err != nil { - log.WithFields("StatusInternalServerError", "Error", "http.StatusInternalServerError") + log.Error("Error Marshaling Response", log.Fields{ + "error": err, + "response": resp, + }) http.Error(w, err.Error(), http.StatusInternalServerError) return } @@ -103,7 +122,10 @@ func (i instanceHandler) getHandler(w http.ResponseWriter, r *http.Request) { resp, err := i.client.Get(id) if err != nil { - log.WithFields("StatusInternalServerError", "Error", "http.StatusInternalServerError") + log.Error("Error getting Instance", log.Fields{ + "error": err, + "id": id, + }) http.Error(w, err.Error(), http.StatusInternalServerError) return } @@ -112,7 +134,10 @@ func (i instanceHandler) getHandler(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) err = json.NewEncoder(w).Encode(resp) if err != nil { - log.WithFields("StatusInternalServerError", "Error", "http.StatusInternalServerError") + log.Error("Error Marshaling Response", log.Fields{ + "error": err, + "response": resp, + }) http.Error(w, err.Error(), http.StatusInternalServerError) return } @@ -125,7 +150,10 @@ func (i instanceHandler) statusHandler(w http.ResponseWriter, r *http.Request) { resp, err := i.client.Status(id) if err != nil { - log.WithFields("StatusInternalServerError", "Error", "http.StatusInternalServerError") + log.Error("Error getting Status", log.Fields{ + "error": err, + "id": id, + }) http.Error(w, err.Error(), http.StatusInternalServerError) return } @@ -134,7 +162,10 @@ func (i instanceHandler) statusHandler(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) err = json.NewEncoder(w).Encode(resp) if err != nil { - log.WithFields("StatusInternalServerError", "Error", "http.StatusInternalServerError") + log.Error("Error Marshaling Response", log.Fields{ + "error": err, + "response": resp, + }) http.Error(w, err.Error(), http.StatusInternalServerError) return } @@ -147,11 +178,16 @@ func (i instanceHandler) listHandler(w http.ResponseWriter, r *http.Request) { //Which will list all instances rbName := r.FormValue("rb-name") rbVersion := r.FormValue("rb-version") - ProfileName := r.FormValue("profile-name") + profileName := r.FormValue("profile-name") - resp, err := i.client.List(rbName, rbVersion, ProfileName) + resp, err := i.client.List(rbName, rbVersion, profileName) if err != nil { - log.WithFields("StatusInternalServerError", "Error", "http.StatusInternalServerError") + log.Error("Error listing instances", log.Fields{ + "error": err, + "rb-name": rbName, + "rb-version": rbVersion, + "profile-name": profileName, + }) http.Error(w, err.Error(), http.StatusInternalServerError) return } @@ -160,7 +196,10 @@ func (i instanceHandler) listHandler(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) err = json.NewEncoder(w).Encode(resp) if err != nil { - log.WithFields("StatusInternalServerError", "Error", "http.StatusInternalServerError") + log.Error("Error Marshaling Response", log.Fields{ + "error": err, + "response": resp, + }) http.Error(w, err.Error(), http.StatusInternalServerError) return } @@ -173,7 +212,9 @@ func (i instanceHandler) deleteHandler(w http.ResponseWriter, r *http.Request) { err := i.client.Delete(id) if err != nil { - log.WithFields("StatusInternalServerError", "Error", "http.StatusInternalServerError") + log.Error("Error Deleting Instance", log.Fields{ + "error": err, + }) http.Error(w, err.Error(), http.StatusInternalServerError) return } @@ -181,4 +222,3 @@ func (i instanceHandler) deleteHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) } - diff --git a/src/k8splugin/api/profilehandler.go b/src/k8splugin/api/profilehandler.go index 9aed2990..acd23060 100644 --- a/src/k8splugin/api/profilehandler.go +++ b/src/k8splugin/api/profilehandler.go @@ -21,6 +21,7 @@ import ( "io" "io/ioutil" "net/http" + "strings" "github.com/onap/multicloud-k8s/src/k8splugin/internal/rb" @@ -107,8 +108,14 @@ func (h rbProfileHandler) getHandler(w http.ResponseWriter, r *http.Request) { ret, err := h.client.Get(rbName, rbVersion, prName) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return + // 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 + } } w.Header().Set("Content-Type", "application/json") diff --git a/src/k8splugin/api/profilehandler_test.go b/src/k8splugin/api/profilehandler_test.go index 4dae377c..9ec9c54c 100644 --- a/src/k8splugin/api/profilehandler_test.go +++ b/src/k8splugin/api/profilehandler_test.go @@ -184,10 +184,19 @@ func TestRBProfileGetHandler(t *testing.T) { }, }, { - label: "Get Non-Exiting Bundle Profile", - expectedCode: http.StatusInternalServerError, + label: "Get Non-Existing Profile", + expectedCode: http.StatusNotFound, prname: "non-existing-profile", rbProClient: &mockRBProfile{ + Items: nil, + Err: pkgerrors.New("Error finding master table"), + }, + }, + { + label: "Faulty DB response", + expectedCode: http.StatusInternalServerError, + prname: "profile", + rbProClient: &mockRBProfile{ // list of Profiles that will be returned by the mockclient Items: []rb.Profile{}, Err: pkgerrors.New("Internal Error"), diff --git a/src/k8splugin/internal/app/client.go b/src/k8splugin/internal/app/client.go index e52225d4..d3e5081a 100644 --- a/src/k8splugin/internal/app/client.go +++ b/src/k8splugin/internal/app/client.go @@ -14,12 +14,13 @@ limitations under the License. package app import ( - "log" "os" + "strings" "time" "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" pkgerrors "github.com/pkg/errors" @@ -116,11 +117,27 @@ func (k *KubernetesClient) ensureNamespace(namespace string) error { }, }, namespace, k) + // Check for errors getting the namespace while ignoring errors where the namespace does not exist + // Error message when namespace does not exist: "namespaces "namespace-name" not found" + if err != nil && strings.Contains(err.Error(), "not found") == false { + log.Error("Error checking for namespace", log.Fields{ + "error": err, + "namespace": namespace, + }) + return pkgerrors.Wrap(err, "Error checking for namespace: "+namespace) + } + if ns == "" { - log.Println("Creating " + namespace + " namespace") + log.Info("Creating Namespace", log.Fields{ + "namespace": namespace, + }) _, err = pluginImpl.Create("", namespace, k) if err != nil { + log.Error("Error Creating Namespace", log.Fields{ + "error": err, + "namespace": namespace, + }) return pkgerrors.Wrap(err, "Error creating "+namespace+" namespace") } } @@ -134,7 +151,9 @@ func (k *KubernetesClient) createKind(resTempl helm.KubernetesResourceTemplate, return helm.KubernetesResource{}, pkgerrors.New("File " + resTempl.FilePath + "does not exists") } - log.Println("Processing file: " + resTempl.FilePath) + log.Info("Processing Kubernetes Resource", log.Fields{ + "filepath": resTempl.FilePath, + }) pluginImpl, err := plugin.GetPluginByKind(resTempl.GVK.Kind) if err != nil { @@ -143,11 +162,19 @@ func (k *KubernetesClient) createKind(resTempl helm.KubernetesResourceTemplate, createdResourceName, err := pluginImpl.Create(resTempl.FilePath, namespace, k) if err != nil { - log.Printf("Error: %s while creating: %s", err.Error(), resTempl.GVK.Kind) + log.Error("Error Creating Resource", log.Fields{ + "error": err, + "gvk": resTempl.GVK, + "filepath": resTempl.FilePath, + }) return helm.KubernetesResource{}, pkgerrors.Wrap(err, "Error in plugin "+resTempl.GVK.Kind+" plugin") } - log.Print(createdResourceName + " created") + log.Info("Created Kubernetes Resource", log.Fields{ + "resource": createdResourceName, + "gvk": resTempl.GVK, + }) + return helm.KubernetesResource{ GVK: resTempl.GVK, Name: createdResourceName, @@ -175,14 +202,16 @@ func (k *KubernetesClient) createResources(sortedTemplates []helm.KubernetesReso } func (k *KubernetesClient) deleteKind(resource helm.KubernetesResource, namespace string) error { - log.Println("Deleting Kind: " + resource.GVK.Kind) + log.Warn("Deleting Resource", log.Fields{ + "gvk": resource.GVK, + "resource": resource.Name, + }) pluginImpl, err := plugin.GetPluginByKind(resource.GVK.Kind) if err != nil { return pkgerrors.Wrap(err, "Error loading plugin") } - log.Println("Deleting resource: " + resource.Name) err = pluginImpl.Delete(resource, namespace, k) if err != nil { return pkgerrors.Wrap(err, "Error deleting "+resource.Name) diff --git a/src/k8splugin/internal/app/instance.go b/src/k8splugin/internal/app/instance.go index fef9962f..5d8b2100 100644 --- a/src/k8splugin/internal/app/instance.go +++ b/src/k8splugin/internal/app/instance.go @@ -32,19 +32,21 @@ import ( // InstanceRequest contains the parameters needed for instantiation // of profiles type InstanceRequest struct { - RBName string `json:"rb-name"` - RBVersion string `json:"rb-version"` - ProfileName string `json:"profile-name"` - CloudRegion string `json:"cloud-region"` - Labels map[string]string `json:"labels"` + RBName string `json:"rb-name"` + RBVersion string `json:"rb-version"` + ProfileName string `json:"profile-name"` + CloudRegion string `json:"cloud-region"` + Labels map[string]string `json:"labels"` + OverrideValues map[string]string `json:"override-values"` } // InstanceResponse contains the response from instantiation type InstanceResponse struct { - ID string `json:"id"` - Request InstanceRequest `json:"request"` - Namespace string `json:"namespace"` - Resources []helm.KubernetesResource `json:"resources"` + ID string `json:"id"` + Request InstanceRequest `json:"request"` + Namespace string `json:"namespace"` + Resources []helm.KubernetesResource `json:"resources"` + OverrideValues map[string]string `json:"override-values"` } // InstanceMiniResponse contains the response from instantiation @@ -133,7 +135,14 @@ func (v *InstanceClient) Create(i InstanceRequest) (InstanceResponse, error) { return InstanceResponse{}, pkgerrors.New("Unable to find Profile to create instance") } + //Convert override values from map to array of strings of the following format + //foo=bar overrideValues := []string{} + if i.OverrideValues != nil { + for k, v := range i.OverrideValues { + overrideValues = append(overrideValues, k+"="+v) + } + } //Execute the kubernetes create command sortedTemplates, err := rb.NewProfileClient().Resolve(i.RBName, i.RBVersion, i.ProfileName, overrideValues) diff --git a/src/k8splugin/internal/config/config.go b/src/k8splugin/internal/config/config.go index 0e45308c..89f2553d 100644 --- a/src/k8splugin/internal/config/config.go +++ b/src/k8splugin/internal/config/config.go @@ -82,9 +82,9 @@ func defaultConfiguration() *Configuration { DatabaseType: "mongo", PluginDir: cwd, EtcdIP: "127.0.0.1", - EtcdCert: "etcd.cert", - EtcdKey: "etcd.key", - EtcdCAFile: "etcd-ca.cert", + EtcdCert: "", + EtcdKey: "", + EtcdCAFile: "", ServicePort: "9015", KubernetesLabelName: "k8splugin.io/rb-instance-id", } diff --git a/src/k8splugin/internal/db/README.md b/src/k8splugin/internal/db/README.md new file mode 100644 index 00000000..cba1b7ea --- /dev/null +++ b/src/k8splugin/internal/db/README.md @@ -0,0 +1,123 @@ +# Database Abstraction Layer + +This package contains implementations of the Database interface defined in `store.go` +Any database can be used as the backend as long as the following interface is implemented; + +```go +type Store interface { + // Returns nil if db health is good + HealthCheck() error + + // Unmarshal implements any unmarshaling needed for the database + Unmarshal(inp []byte, out interface{}) error + + // Creates a new master table with key and links data with tag and + // creates a pointer to the newly added data in the master table + Create(table string, key Key, tag string, data interface{}) error + + // Reads data for a particular key with specific tag. + Read(table string, key Key, tag string) ([]byte, error) + + // Update data for particular key with specific tag + Update(table string, key Key, tag string, data interface{}) error + + // Deletes a specific tag data for key. + // TODO: If tag is empty, it will delete all tags under key. + Delete(table string, key Key, tag string) error + + // Reads all master tables and data from the specified tag in table + ReadAll(table string, tag string) (map[string][]byte, error) +} +``` + +Therefore, `mongo.go`, `consul.go` implement the above interface and can be used as the backend as needed based on initial configuration. + +## Details on Mongo Implementation + +`mongo.go` implements the above interface using the `go.mongodb.org/mongo-driver` package. +The code converts incoming binary data and creates a new document in the database. + +### Create + +Arguments: +```go +collection string +key interface +tag string +data []byte +``` + +Create inserts the provided `data` into the `collection` which returns an auto-generated (by `mongodb`) ID which we then associate with the `key` that is provided as one of the arguments. + +We use the `FindOneAndUpdate` mongo API to achieve this with the `upsert` option set to `true`. +We create the following documents in mongodb for each new definition added to the database: + +There is a Master Key document that contains references to other documents which are related to this `key`. + +#### Master Key Entry +```json +{ + "_id" : ObjectId("5e0a8554b78a15f71d2dce7e"), + "key" : { "rbname" : "edgex", "rbversion" : "v1"}, + "defmetadata" : ObjectId("5e0a8554be261ecb57f067eb"), + "defcontent" : ObjectId("5e0a8377bcfcdd0f01dc7b0d") +} +``` +#### Metadata Key Entry +```json +{ + "_id" : ObjectId("5e0a8554be261ecb57f067eb"), + "defmetadata" : { "rbname" : "edgex", "rbversion" : "v1", "chartname" : "", "description" : "", "labels" : null } +} +``` +#### Definition Content +```json +{ + "_id" : ObjectId("5e0a8377bcfcdd0f01dc7b0d"), + "defcontent" : "H4sICCVd3FwAA3Byb2ZpbGUxLnRhcgDt1NEKgjAUxvFd7ylG98aWOsGXiYELxLRwJvj2rbyoIPDGiuD/uzmwM9iB7Vvruvrgw7CdXHsUn6Ejm2W3aopcP9eZLYRJM1voPN+ZndAm16kVSn9onheXMLheKeGqfdM0rq07/3bfUv9PJUkiR9+H+tSVajRymM6+lEqN7njxoVSbU+z2deX388r9nWzkr8fGSt5d79pnLOZfm0f+dRrzb7P4DZD/LyDJAAAAAAAAAAAAAAAA/+0Ksq1N5QAoAAA=" +} +``` + +### Unmarshal + +Data in mongo is stored as `bson` which is a compressed form of `json`. We need mongo to convert the stored `bson` data to regular `json` +that we can use in our code when returned. + +We just use the `bson.Unmarshal` API to achieve this. + +### Read + +Arguments: +```go +collection string +key interface +tag string +``` + +Read is straight forward and it uses the `FindOne` API to find our Mongo document based on the provided `key` and then gets the corresponding data for the given `tag`. It will return []byte which can then be passed to the `Unmarshal` function to get the desired GO object. + +### Delete + +Delete is similar to Read and deletes all the objectIDs being stored for a given `key` in the collection. + +## Testing Interfaces + +The following interface exists to allow for the development of unit tests which don't require mongo to be running. +It is mentioned so in the code as well. + +```go +// MongoCollection defines the a subset of MongoDB operations +// Note: This interface is defined mainly for mock testing +type MongoCollection interface { + InsertOne(ctx context.Context, document interface{}, + opts ...*options.InsertOneOptions) (*mongo.InsertOneResult, error) + FindOne(ctx context.Context, filter interface{}, + opts ...*options.FindOneOptions) *mongo.SingleResult + FindOneAndUpdate(ctx context.Context, filter interface{}, + update interface{}, opts ...*options.FindOneAndUpdateOptions) *mongo.SingleResult + DeleteOne(ctx context.Context, filter interface{}, + opts ...*options.DeleteOptions) (*mongo.DeleteResult, error) + Find(ctx context.Context, filter interface{}, + opts ...*options.FindOptions) (*mongo.Cursor, error) +} +```
\ No newline at end of file diff --git a/src/k8splugin/internal/db/etcd.go b/src/k8splugin/internal/db/etcd.go index fda44b2f..97771a07 100644 --- a/src/k8splugin/internal/db/etcd.go +++ b/src/k8splugin/internal/db/etcd.go @@ -71,7 +71,12 @@ func newClient(store *clientv3.Client, c EtcdConfig) (EtcdClient, error) { if len(c.CertFile) == 0 && len(c.KeyFile) == 0 && len(c.CAFile) == 0 { tlsConfig = nil } - endpoint := "https://" + c.Endpoint + ":2379" + endpoint := "" + if tlsConfig == nil { + endpoint = "http://" + c.Endpoint + ":2379" + } else { + endpoint = "https://" + c.Endpoint + ":2379" + } store, err = clientv3.New(clientv3.Config{ Endpoints: []string{endpoint}, diff --git a/src/k8splugin/internal/db/testing.go b/src/k8splugin/internal/db/testing.go index 5f69dcb4..9a427e03 100644 --- a/src/k8splugin/internal/db/testing.go +++ b/src/k8splugin/internal/db/testing.go @@ -15,6 +15,7 @@ package db import ( "encoding/json" + pkgerrors "github.com/pkg/errors" ) @@ -40,6 +41,19 @@ func (m *MockDB) HealthCheck() error { } func (m *MockDB) Create(table string, key Key, tag string, data interface{}) error { + djs, err := json.Marshal(data) + if err != nil { + return err + } + + d := make(map[string][]byte) + d[tag] = djs + + if m.Items == nil { + m.Items = make(map[string]map[string][]byte) + } + m.Items[key.String()] = d + return m.Err } diff --git a/src/k8splugin/internal/logutils/logger.go b/src/k8splugin/internal/logutils/logger.go index 7df23474..2e8f9969 100644 --- a/src/k8splugin/internal/logutils/logger.go +++ b/src/k8splugin/internal/logutils/logger.go @@ -4,12 +4,25 @@ import ( log "github.com/sirupsen/logrus" ) +//Fields is type that will be used by the calling function +type Fields map[string]interface{} + func init() { // Log as JSON instead of the default ASCII formatter. log.SetFormatter(&log.JSONFormatter{}) } -func WithFields(msg string, fkey string, fvalue string) { - log.WithFields(log.Fields{fkey: fvalue}).Error(msg) +// Error uses the fields provided and logs +func Error(msg string, fields Fields) { + log.WithFields(log.Fields(fields)).Error(msg) +} + +// Warn uses the fields provided and logs +func Warn(msg string, fields Fields) { + log.WithFields(log.Fields(fields)).Warn(msg) } +// Info uses the fields provided and logs +func Info(msg string, fields Fields) { + log.WithFields(log.Fields(fields)).Info(msg) +} diff --git a/src/k8splugin/internal/rb/definition.go b/src/k8splugin/internal/rb/definition.go index 65ae8e00..73ea44da 100644 --- a/src/k8splugin/internal/rb/definition.go +++ b/src/k8splugin/internal/rb/definition.go @@ -26,6 +26,7 @@ import ( "path/filepath" "github.com/onap/multicloud-k8s/src/k8splugin/internal/db" + "github.com/onap/multicloud-k8s/src/k8splugin/internal/logutils" pkgerrors "github.com/pkg/errors" ) @@ -101,6 +102,40 @@ func (v *DefinitionClient) Create(def Definition) (Definition, error) { return Definition{}, pkgerrors.Wrap(err, "Creating DB Entry") } + // Create a default profile automatically + prc := NewProfileClient() + pr, err := prc.Create(Profile{ + RBName: def.RBName, + RBVersion: def.RBVersion, + ProfileName: "default", + Namespace: "default", + ReleaseName: "default", + }) + + if err != nil { + logutils.Error("Create Default Profile", logutils.Fields{ + "error": err, + "rb-name": def.RBName, + "rb-version": def.RBVersion, + "profile-name": "default", + "namespace": "default", + "release-name": "default", + }) + return Definition{}, pkgerrors.Wrap(err, "Creating Default Profile") + } + + err = prc.Upload(pr.RBName, pr.RBVersion, pr.ProfileName, prc.getEmptyProfile()) + if err != nil { + logutils.Error("Upload Empty Profile", logutils.Fields{ + "error": err, + "rb-name": pr.RBName, + "rb-version": pr.RBVersion, + "profile-name": pr.ProfileName, + "profile-content": prc.getEmptyProfile(), + }) + return Definition{}, pkgerrors.Wrap(err, "Upload Empty Profile") + } + return def, nil } @@ -173,6 +208,19 @@ func (v *DefinitionClient) Delete(name string, version string) error { return pkgerrors.Wrap(err, "Delete Resource Bundle Definition Content") } + //Delete the default profile as well + prc := NewProfileClient() + err = prc.Delete(name, version, "default") + if err != nil { + logutils.Error("Delete Default Profile", logutils.Fields{ + "error": err, + "rb-name": name, + "rb-version": version, + "profile-name": "default", + }) + return pkgerrors.Wrap(err, "Deleting default profile") + } + return nil } diff --git a/src/k8splugin/internal/rb/profile.go b/src/k8splugin/internal/rb/profile.go index 49768d4b..6efa23b8 100644 --- a/src/k8splugin/internal/rb/profile.go +++ b/src/k8splugin/internal/rb/profile.go @@ -338,3 +338,31 @@ func (v *ProfileClient) Resolve(rbName string, rbVersion string, return sortedTemplates, nil } + +// Returns an empty profile with the following contents +// Contains a manifest.yaml pointing to an override_values.yaml +// The override_values.yaml file is empty. +func (v *ProfileClient) getEmptyProfile() []byte { + return []byte{ + 0x1F, 0x8B, 0x08, 0x08, 0x25, 0x5D, 0xDC, 0x5C, 0x00, 0x03, 0x70, + 0x72, 0x6F, 0x66, 0x69, 0x6C, 0x65, 0x31, 0x2E, 0x74, 0x61, 0x72, + 0x00, 0xED, 0xD4, 0xD1, 0x0A, 0x82, 0x30, 0x14, 0xC6, 0xF1, 0x5D, + 0xEF, 0x29, 0x46, 0xF7, 0xC6, 0x96, 0x3A, 0xC1, 0x97, 0x89, 0x81, + 0x0B, 0xC4, 0xB4, 0x70, 0x26, 0xF8, 0xF6, 0xAD, 0xBC, 0xA8, 0x20, + 0xF0, 0xC6, 0x8A, 0xE0, 0xFF, 0xBB, 0x39, 0xB0, 0x33, 0xD8, 0x81, + 0xED, 0x5B, 0xEB, 0xBA, 0xFA, 0xE0, 0xC3, 0xB0, 0x9D, 0x5C, 0x7B, + 0x14, 0x9F, 0xA1, 0x23, 0x9B, 0x65, 0xB7, 0x6A, 0x8A, 0x5C, 0x3F, + 0xD7, 0x99, 0x2D, 0x84, 0x49, 0x33, 0x5B, 0xE8, 0x3C, 0xDF, 0x99, + 0x9D, 0xD0, 0x26, 0xD7, 0xA9, 0x15, 0x4A, 0x7F, 0x68, 0x9E, 0x17, + 0x97, 0x30, 0xB8, 0x5E, 0x29, 0xE1, 0xAA, 0x7D, 0xD3, 0x34, 0xAE, + 0xAD, 0x3B, 0xFF, 0x76, 0xDF, 0x52, 0xFF, 0x4F, 0x25, 0x49, 0x22, + 0x47, 0xDF, 0x87, 0xFA, 0xD4, 0x95, 0x6A, 0x34, 0x72, 0x98, 0xCE, + 0xBE, 0x94, 0x4A, 0x8D, 0xEE, 0x78, 0xF1, 0xA1, 0x54, 0x9B, 0x53, + 0xEC, 0xF6, 0x75, 0xE5, 0xF7, 0xF3, 0xCA, 0xFD, 0x9D, 0x6C, 0xE4, + 0xAF, 0xC7, 0xC6, 0x4A, 0xDE, 0x5D, 0xEF, 0xDA, 0x67, 0x2C, 0xE6, + 0x5F, 0x9B, 0x47, 0xFE, 0x75, 0x1A, 0xF3, 0x6F, 0xB3, 0xF8, 0x0D, + 0x90, 0xFF, 0x2F, 0x20, 0xC9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xED, 0x0A, 0xB2, 0xAD, + 0x4D, 0xE5, 0x00, 0x28, 0x00, 0x00, + } +} diff --git a/src/orchestrator/Makefile b/src/orchestrator/Makefile new file mode 100644 index 00000000..b17485ca --- /dev/null +++ b/src/orchestrator/Makefile @@ -0,0 +1,37 @@ +# SPDX-license-identifier: Apache-2.0 +############################################################################## +# Copyright (c) 2019 Intel Corporation +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +export GO111MODULE=on + +all: clean + CGO_ENABLED=1 GOOS=linux GOARCH=amd64 + @go build -tags netgo -o ./orchestrator ./cmd/main.go + +# The following is done this way as each patch on CI runs build and each merge runs deploy. So for build we don't need to build binary and hence +# no need to create a static binary with additional flags. However, for generating binary, additional build flags are necessary. This if used with +# mock plugin errors out for unit tests. So the seperation avoids the error. + +build: clean test cover +deploy: build + +.PHONY: test +test: clean + @go test -race ./... + +format: + @go fmt ./... + +clean: + @find . -name "*so" -delete + @rm -f orchestrator coverage.html coverage.out + +.PHONY: cover +cover: + @go test -race ./... -coverprofile=coverage.out + @go tool cover -html=coverage.out -o coverage.html diff --git a/src/orchestrator/api/api.go b/src/orchestrator/api/api.go new file mode 100644 index 00000000..83f17bbe --- /dev/null +++ b/src/orchestrator/api/api.go @@ -0,0 +1,38 @@ +/* +Copyright 2018 Intel Corporation. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package api + +import ( + "github.com/onap/multicloud-k8s/src/orchestrator/internal/project" + + "github.com/gorilla/mux" +) + +// NewRouter creates a router that registers the various urls that are supported +func NewRouter(projectClient project.ProjectManager) *mux.Router { + + router := mux.NewRouter().PathPrefix("/v2").Subrouter() + + if projectClient == nil { + projectClient = project.NewProjectClient() + } + projHandler := projectHandler{ + client: projectClient, + } + router.HandleFunc("/project", projHandler.createHandler).Methods("POST") + router.HandleFunc("/project/{project-name}", projHandler.getHandler).Methods("GET") + router.HandleFunc("/project/{project-name}", projHandler.deleteHandler).Methods("DELETE") + + return router +} diff --git a/src/orchestrator/api/projecthandler.go b/src/orchestrator/api/projecthandler.go new file mode 100644 index 00000000..30f21de3 --- /dev/null +++ b/src/orchestrator/api/projecthandler.go @@ -0,0 +1,105 @@ +/* + * Copyright 2019 Intel Corporation, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package api + +import ( + "encoding/json" + "io" + "net/http" + + "github.com/onap/multicloud-k8s/src/orchestrator/internal/project" + + "github.com/gorilla/mux" +) + +// Used to store backend implementations objects +// Also simplifies mocking for unit testing purposes +type projectHandler struct { + // Interface that implements Project operations + // We will set this variable with a mock interface for testing + client project.ProjectManager +} + +// Create handles creation of the Project entry in the database +func (h projectHandler) createHandler(w http.ResponseWriter, r *http.Request) { + var p project.Project + + 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.ProjectName == "" { + http.Error(w, "Missing name in POST request", http.StatusBadRequest) + return + } + + ret, err := h.client.Create(p) + 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(ret) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } +} + +// Get handles GET operations on a particular Project Name +// Returns a rb.Project +func (h projectHandler) getHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + name := vars["project-name"] + + ret, err := h.client.Get(name) + 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 + } +} + +// Delete handles DELETE operations on a particular Project Name +func (h projectHandler) deleteHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + name := vars["project-name"] + + err := h.client.Delete(name) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusNoContent) +} diff --git a/src/orchestrator/api/projecthandler_test.go b/src/orchestrator/api/projecthandler_test.go new file mode 100644 index 00000000..2699f2e3 --- /dev/null +++ b/src/orchestrator/api/projecthandler_test.go @@ -0,0 +1,228 @@ +/* + * Copyright 2018 Intel Corporation, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package api + +import ( + "bytes" + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "reflect" + "testing" + + "github.com/onap/multicloud-k8s/src/orchestrator/internal/project" + + pkgerrors "github.com/pkg/errors" +) + +//Creating an embedded interface via anonymous variable +//This allows us to make mockDB satisfy the DatabaseConnection +//interface even if we are not implementing all the methods in it +type mockProjectManager struct { + // Items and err will be used to customize each test + // via a localized instantiation of mockProjectManager + Items []project.Project + Err error +} + +func (m *mockProjectManager) Create(inp project.Project) (project.Project, error) { + if m.Err != nil { + return project.Project{}, m.Err + } + + return m.Items[0], nil +} + +func (m *mockProjectManager) Get(name string) (project.Project, error) { + if m.Err != nil { + return project.Project{}, m.Err + } + + return m.Items[0], nil +} + +func (m *mockProjectManager) Delete(name string) error { + return m.Err +} + +func TestProjectCreateHandler(t *testing.T) { + testCases := []struct { + label string + reader io.Reader + expected project.Project + expectedCode int + projectClient *mockProjectManager + }{ + { + label: "Missing Body Failure", + expectedCode: http.StatusBadRequest, + projectClient: &mockProjectManager{}, + }, + { + label: "Create Project", + expectedCode: http.StatusCreated, + reader: bytes.NewBuffer([]byte(`{ + "project-name":"testProject", + "description":"Test Project used for unit testing" + }`)), + expected: project.Project{ + ProjectName: "testProject", + Description: "Test Project used for unit testing", + }, + projectClient: &mockProjectManager{ + //Items that will be returned by the mocked Client + Items: []project.Project{ + { + ProjectName: "testProject", + Description: "Test Project used for unit testing", + }, + }, + }, + }, + { + label: "Missing Project Name in Request Body", + reader: bytes.NewBuffer([]byte(`{ + "description":"test description" + }`)), + expectedCode: http.StatusBadRequest, + projectClient: &mockProjectManager{}, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + request := httptest.NewRequest("POST", "/v2/project", testCase.reader) + resp := executeRequest(request, NewRouter(testCase.projectClient)) + + //Check returned code + if resp.StatusCode != testCase.expectedCode { + t.Fatalf("Expected %d; Got: %d", testCase.expectedCode, resp.StatusCode) + } + + //Check returned body only if statusCreated + if resp.StatusCode == http.StatusCreated { + got := project.Project{} + json.NewDecoder(resp.Body).Decode(&got) + + if reflect.DeepEqual(testCase.expected, got) == false { + t.Errorf("createHandler returned unexpected body: got %v;"+ + " expected %v", got, testCase.expected) + } + } + }) + } +} + +func TestProjectGetHandler(t *testing.T) { + + testCases := []struct { + label string + expected project.Project + name, version string + expectedCode int + projectClient *mockProjectManager + }{ + { + label: "Get Project", + expectedCode: http.StatusOK, + expected: project.Project{ + ProjectName: "testProject", + Description: "A Test project for unit testing", + }, + name: "testProject", + projectClient: &mockProjectManager{ + Items: []project.Project{ + { + ProjectName: "testProject", + Description: "A Test project for unit testing", + }, + }, + }, + }, + { + label: "Get Non-Exiting Project", + expectedCode: http.StatusInternalServerError, + name: "nonexistingproject", + projectClient: &mockProjectManager{ + Items: []project.Project{}, + Err: pkgerrors.New("Internal Error"), + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + request := httptest.NewRequest("GET", "/v2/project/"+testCase.name, nil) + resp := executeRequest(request, NewRouter(testCase.projectClient)) + + //Check returned code + if resp.StatusCode != testCase.expectedCode { + t.Fatalf("Expected %d; Got: %d", testCase.expectedCode, resp.StatusCode) + } + + //Check returned body only if statusOK + if resp.StatusCode == http.StatusOK { + got := project.Project{} + json.NewDecoder(resp.Body).Decode(&got) + + if reflect.DeepEqual(testCase.expected, got) == false { + t.Errorf("listHandler returned unexpected body: got %v;"+ + " expected %v", got, testCase.expected) + } + } + }) + } +} + +func TestProjectDeleteHandler(t *testing.T) { + + testCases := []struct { + label string + name string + version string + expectedCode int + projectClient *mockProjectManager + }{ + { + label: "Delete Project", + expectedCode: http.StatusNoContent, + name: "testProject", + projectClient: &mockProjectManager{}, + }, + { + label: "Delete Non-Exiting Project", + expectedCode: http.StatusInternalServerError, + name: "testProject", + projectClient: &mockProjectManager{ + Err: pkgerrors.New("Internal Error"), + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + request := httptest.NewRequest("DELETE", "/v2/project/"+testCase.name, nil) + resp := executeRequest(request, NewRouter(testCase.projectClient)) + + //Check returned code + if resp.StatusCode != testCase.expectedCode { + t.Fatalf("Expected %d; Got: %d", testCase.expectedCode, resp.StatusCode) + } + }) + } +} diff --git a/src/orchestrator/api/testing.go b/src/orchestrator/api/testing.go new file mode 100644 index 00000000..e99ec75b --- /dev/null +++ b/src/orchestrator/api/testing.go @@ -0,0 +1,31 @@ +/* + * Copyright 2018 Intel Corporation, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package api + +import ( + "net/http" + "net/http/httptest" + + "github.com/gorilla/mux" +) + +func executeRequest(request *http.Request, router *mux.Router) *http.Response { + recorder := httptest.NewRecorder() + router.ServeHTTP(recorder, request) + resp := recorder.Result() + return resp +} diff --git a/src/orchestrator/cmd/main.go b/src/orchestrator/cmd/main.go new file mode 100644 index 00000000..657d5bf5 --- /dev/null +++ b/src/orchestrator/cmd/main.go @@ -0,0 +1,71 @@ +/* +Copyright 2018 Intel Corporation. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "context" + "log" + "math/rand" + "net/http" + "os" + "os/signal" + "time" + + "github.com/onap/multicloud-k8s/src/orchestrator/api" + "github.com/onap/multicloud-k8s/src/orchestrator/internal/auth" + "github.com/onap/multicloud-k8s/src/orchestrator/internal/config" + "github.com/onap/multicloud-k8s/src/orchestrator/internal/db" + + "github.com/gorilla/handlers" +) + +func main() { + + rand.Seed(time.Now().UnixNano()) + + err := db.InitializeDatabaseConnection() + if err != nil { + log.Println("Unable to initialize database connection...") + log.Println(err) + log.Fatalln("Exiting...") + } + + httpRouter := api.NewRouter(nil) + loggedRouter := handlers.LoggingHandler(os.Stdout, httpRouter) + log.Println("Starting Kubernetes Multicloud API") + + httpServer := &http.Server{ + Handler: loggedRouter, + Addr: ":" + config.GetConfiguration().ServicePort, + } + + connectionsClose := make(chan struct{}) + go func() { + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + <-c + httpServer.Shutdown(context.Background()) + close(connectionsClose) + }() + + tlsConfig, err := auth.GetTLSConfig("ca.cert", "server.cert", "server.key") + if err != nil { + log.Println("Error Getting TLS Configuration. Starting without TLS...") + log.Fatal(httpServer.ListenAndServe()) + } else { + httpServer.TLSConfig = tlsConfig + // empty strings because tlsconfig already has this information + err = httpServer.ListenAndServeTLS("", "") + } +} diff --git a/src/orchestrator/go.mod b/src/orchestrator/go.mod new file mode 100644 index 00000000..d6fada43 --- /dev/null +++ b/src/orchestrator/go.mod @@ -0,0 +1,34 @@ +module github.com/onap/multicloud-k8s/src/orchestrator + +require ( + github.com/docker/engine v0.0.0-20190620014054-c513a4c6c298 + github.com/ghodss/yaml v1.0.0 + github.com/gogo/protobuf v1.3.1 // indirect + github.com/golang/snappy v0.0.1 // indirect + github.com/gorilla/handlers v1.3.0 + github.com/gorilla/mux v1.6.2 + github.com/hashicorp/consul v1.4.0 + github.com/json-iterator/go v1.1.8 // indirect + github.com/onap/multicloud-k8s/src/k8splugin v0.0.0-20191115005109-f168ebb73d8d // indirect + github.com/pkg/errors v0.8.1 + github.com/sirupsen/logrus v1.4.2 + go.etcd.io/etcd v3.3.12+incompatible + go.mongodb.org/mongo-driver v1.0.0 + golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 + golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect + k8s.io/api v0.0.0-20190831074750-7364b6bdad65 + k8s.io/apimachinery v0.0.0-20190831074630-461753078381 + k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible + k8s.io/helm v2.14.3+incompatible + k8s.io/klog v1.0.0 // indirect +) + +replace ( + k8s.io/api => k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b + k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.0.0-20190409022649-727a075fdec8 + k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d + k8s.io/apiserver => k8s.io/apiserver v0.0.0-20190409021813-1ec86e4da56c + k8s.io/cli-runtime => k8s.io/cli-runtime v0.0.0-20190409023024-d644b00f3b79 + k8s.io/client-go => k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible + k8s.io/cloud-provider => k8s.io/cloud-provider v0.0.0-20190409023720-1bc0c81fa51d +) diff --git a/src/orchestrator/go.sum b/src/orchestrator/go.sum new file mode 100644 index 00000000..732bc280 --- /dev/null +++ b/src/orchestrator/go.sum @@ -0,0 +1,362 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/MakeNowJust/heredoc v0.0.0-20171113091838-e9091a26100e/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= +github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/sprig v2.17.1+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/aokoli/goutils v1.1.0/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/chai2010/gettext-go v0.0.0-20170215093142-bf70f2a70fb1/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.12+incompatible h1:pAWNwdf7QiT1zfaWyqCtNZQWCLByQyA3JrSQyuYAqnQ= +github.com/coreos/etcd v3.3.12+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= +github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/docker/distribution v2.7.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v0.7.3-0.20190912223608-ad718029b705/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/engine v0.0.0-20190620014054-c513a4c6c298/go.mod h1:3CPr2caMgTHxxIAZgEMd3uLYPDlRvPqCpyeRf6ncPcY= +github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/elazarl/goproxy v0.0.0-20190911111923-ecfe977594f1/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= +github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= +github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q= +github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk= +github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20181024230925-c65c006176ff/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= +github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/handlers v1.3.0 h1:tsg9qP3mjt1h4Roxp+M1paRjrVBfPSOpBuVclh6YluI= +github.com/gorilla/handlers v1.3.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= +github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v1.1.0/go.mod h1:f5nM7jw/oeRSadq3xCzHAvxcr8HZnzsqU6ILg/0NiiE= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.11.1/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul v1.4.0 h1:PQTW4xCuAExEiSbhrsFsikzbW5gVBoi74BjUvYFyKHw= +github.com/hashicorp/consul v1.4.0/go.mod h1:mFrjN1mfidgJfYP1xrJCF+AfRhr6Eaqhb2+sfyn/OOI= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6KdvN3Gig= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-rootcerts v0.0.0-20160503143440-6bb64b370b90 h1:VBj0QYQ0u2MCJzBfeYXGexnAl17GsH1yidnoxCqqD9E= +github.com/hashicorp/go-rootcerts v0.0.0-20160503143440-6bb64b370b90/go.mod h1:o4zcYY1e0GEZI6eSEr+43QDYmuGglw1qSO6qdHUHCgg= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/memberlist v0.1.5/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.1 h1:mYs6SMzu72+90OcPa5wr3nfznA4Dw9UyR791ZFNOIf4= +github.com/hashicorp/serf v0.8.1/go.mod h1:h/Ru6tmZazX7WO/GDmwdpS975F019L4t5ng5IgwbNrE= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/onap/multicloud-k8s v0.0.0-20191115005109-f168ebb73d8d h1:3uFucXVv6gqa3H1u85CjoLOvGraREfD8/NL7m/9W9tc= +github.com/onap/multicloud-k8s/src/k8splugin v0.0.0-20191115005109-f168ebb73d8d h1:ucIEjqzNVeFPnQofeuBfUqro0OnilX//fajEFxuLsgA= +github.com/onap/multicloud-k8s/src/k8splugin v0.0.0-20191115005109-f168ebb73d8d/go.mod h1:EnQd/vQGZR1/55IihaHxiux4ZUig/zfXZux7bfmU0S8= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= +github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rubenv/sql-migrate v0.0.0-20190902133344-8926f37f0bc1/go.mod h1:WS0rl9eEliYI8DPnr3TOwz4439pay+qNgzJoVya/DmY= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/technosophos/moniker v0.0.0-20180509230615-a5dbd03a2245/go.mod h1:O1c8HleITsZqzNZDjSNzirUGsMT0oGu9LhHKoJrqO+A= +github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk= +github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= +github.com/xdg/stringprep v1.0.0 h1:d9X0esnoa3dFsV0FG35rAT0RIhYFlPq7MiP+DW89La0= +github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/etcd v3.3.12+incompatible h1:V6PRYRGpU4k5EajJaaj/GL3hqIdzyPnBU8aPUp+35yw= +go.etcd.io/etcd v3.3.12+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI= +go.mongodb.org/mongo-driver v1.0.0 h1:KxPRDyfB2xXnDE2My8acoOWBQkfv3tz0SaWTRZjJR0c= +go.mongodb.org/mongo-driver v1.0.0/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4 h1:ydJNl0ENAG67pFbB+9tfhiL2pYqLhfoaZFw/cjLhY4A= +golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f h1:25KHgbfyiSm6vwQLbM3zZIe1v9p/3ea4Rz+nnM5K/i4= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.19.0 h1:cfg4PD8YEdSFnm7qLV4++93WcmhH2nIUhMjhdCvl3j8= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b h1:aBGgKJUM9Hk/3AE8WaZIApnTxG35kbuQba2w+SXqezo= +k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= +k8s.io/apiextensions-apiserver v0.0.0-20190409022649-727a075fdec8/go.mod h1:IxkesAMoaCRoLrPJdZNZUQp9NfZnzqaVzLhb2VEQzXE= +k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d h1:Jmdtdt1ZnoGfWWIIik61Z7nKYgO3J+swQJtPYsP9wHA= +k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= +k8s.io/apiserver v0.0.0-20190409021813-1ec86e4da56c/go.mod h1:6bqaTSOSJavUIXUtfaR9Os9JtTCm8ZqH2SUl2S60C4w= +k8s.io/cli-runtime v0.0.0-20190409023024-d644b00f3b79/go.mod h1:qWnH3/b8sp/l7EvlDh7ulDU3UWA4P4N1NFbEEP791tM= +k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible h1:U5Bt+dab9K8qaUmXINrkXO135kA11/i5Kg1RUydgaMQ= +k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= +k8s.io/cloud-provider v0.0.0-20190409023720-1bc0c81fa51d/go.mod h1:LlIffnLBu+GG7d4ppPzC8UnA1Ex8S+ntmSRVsnr7Xy4= +k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/helm v2.14.3+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI= +k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= +k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= +k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= +k8s.io/kubernetes v1.14.1/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= +k8s.io/utils v0.0.0-20190907131718-3d4f5b7dea0b/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU= +sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= +sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI= diff --git a/src/orchestrator/internal/auth/auth.go b/src/orchestrator/internal/auth/auth.go new file mode 100644 index 00000000..3da8f2af --- /dev/null +++ b/src/orchestrator/internal/auth/auth.go @@ -0,0 +1,107 @@ +/* + * Copyright 2018 Intel Corporation, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package auth + +import ( + "crypto/tls" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "io/ioutil" + "log" + + pkgerrors "github.com/pkg/errors" +) + +// GetTLSConfig initializes a tlsConfig using the CA's certificate +// This config is then used to enable the server for mutual TLS +func GetTLSConfig(caCertFile string, certFile string, keyFile string) (*tls.Config, error) { + + // Initialize tlsConfig once + caCert, err := ioutil.ReadFile(caCertFile) + + if err != nil { + return nil, pkgerrors.Wrap(err, "Read CA Cert file") + } + + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM(caCert) + + tlsConfig := &tls.Config{ + // Change to RequireAndVerify once we have mandatory certs + ClientAuth: tls.VerifyClientCertIfGiven, + ClientCAs: caCertPool, + MinVersion: tls.VersionTLS12, + } + + certPEMBlk, err := readPEMBlock(certFile) + if err != nil { + return nil, pkgerrors.Wrap(err, "Read Cert File") + } + + keyPEMBlk, err := readPEMBlock(keyFile) + if err != nil { + return nil, pkgerrors.Wrap(err, "Read Key File") + } + + tlsConfig.Certificates = make([]tls.Certificate, 1) + tlsConfig.Certificates[0], err = tls.X509KeyPair(certPEMBlk, keyPEMBlk) + if err != nil { + return nil, pkgerrors.Wrap(err, "Load x509 cert and key") + } + + tlsConfig.BuildNameToCertificate() + return tlsConfig, nil +} + +func readPEMBlock(filename string) ([]byte, error) { + + pemData, err := ioutil.ReadFile(filename) + if err != nil { + return nil, pkgerrors.Wrap(err, "Read PEM File") + } + + pemBlock, rest := pem.Decode(pemData) + if len(rest) > 0 { + log.Println("Pemfile has extra data") + } + + if x509.IsEncryptedPEMBlock(pemBlock) { + password, err := ioutil.ReadFile(filename + ".pass") + if err != nil { + return nil, pkgerrors.Wrap(err, "Read Password File") + } + + pByte, err := base64.StdEncoding.DecodeString(string(password)) + if err != nil { + return nil, pkgerrors.Wrap(err, "Decode PEM Password") + } + + pemData, err = x509.DecryptPEMBlock(pemBlock, pByte) + if err != nil { + return nil, pkgerrors.Wrap(err, "Decrypt PEM Data") + } + var newPEMBlock pem.Block + newPEMBlock.Type = pemBlock.Type + newPEMBlock.Bytes = pemData + // Converting back to PEM from DER data you get from + // DecryptPEMBlock + pemData = pem.EncodeToMemory(&newPEMBlock) + } + + return pemData, nil +} diff --git a/src/orchestrator/internal/auth/auth_test.go b/src/orchestrator/internal/auth/auth_test.go new file mode 100644 index 00000000..e41cb1ac --- /dev/null +++ b/src/orchestrator/internal/auth/auth_test.go @@ -0,0 +1,47 @@ +/* +* Copyright 2018 TechMahindra +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. + */ + +package auth + +import ( + "crypto/tls" + "testing" +) + +//Unit test to varify GetTLSconfig func and varify the tls config min version to be 771 +//Assuming cert file name as auth_test.cert +func TestGetTLSConfig(t *testing.T) { + _, err := GetTLSConfig("filedoesnotexist.cert", "filedoesnotexist.cert", "filedoesnotexist.cert") + if err == nil { + t.Errorf("Test failed, expected error but got none") + } + tlsConfig, err := GetTLSConfig("../../tests/certs/auth_test_certificate.pem", + "../../tests/certs/auth_test_certificate.pem", + "../../tests/certs/auth_test_key.pem") + if err != nil { + t.Fatal("Test Failed as GetTLSConfig returned error: " + err.Error()) + } + expected := tls.VersionTLS12 + actual := tlsConfig.MinVersion + if tlsConfig != nil { + if int(actual) != expected { + t.Errorf("Test Failed due to version mismatch") + } + if tlsConfig == nil { + t.Errorf("Test Failed due to GetTLSConfig returned nil") + } + } +} diff --git a/src/orchestrator/internal/config/config.go b/src/orchestrator/internal/config/config.go new file mode 100644 index 00000000..cb4656f0 --- /dev/null +++ b/src/orchestrator/internal/config/config.go @@ -0,0 +1,130 @@ +/* + * Copyright 2019 Intel Corporation, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package config + +import ( + "encoding/json" + "log" + "os" + "reflect" +) + +// Configuration loads up all the values that are used to configure +// backend implementations +type Configuration struct { + CAFile string `json:"ca-file"` + ServerCert string `json:"server-cert"` + ServerKey string `json:"server-key"` + Password string `json:"password"` + DatabaseIP string `json:"database-ip"` + DatabaseType string `json:"database-type"` + PluginDir string `json:"plugin-dir"` + EtcdIP string `json:"etcd-ip"` + EtcdCert string `json:"etcd-cert"` + EtcdKey string `json:"etcd-key"` + EtcdCAFile string `json:"etcd-ca-file"` + ServicePort string `json:"service-port"` + KubernetesLabelName string `json:"kubernetes-label-name"` +} + +// Config is the structure that stores the configuration +var gConfig *Configuration + +// readConfigFile reads the specified smsConfig file to setup some env variables +func readConfigFile(file string) (*Configuration, error) { + f, err := os.Open(file) + if err != nil { + return defaultConfiguration(), err + } + defer f.Close() + + // Setup some defaults here + // If the json file has values in it, the defaults will be overwritten + conf := defaultConfiguration() + + // Read the configuration from json file + decoder := json.NewDecoder(f) + decoder.DisallowUnknownFields() + err = decoder.Decode(conf) + if err != nil { + return conf, err + } + + return conf, nil +} + +func defaultConfiguration() *Configuration { + cwd, err := os.Getwd() + if err != nil { + log.Println("Error getting cwd. Using .") + cwd = "." + } + + return &Configuration{ + CAFile: "ca.cert", + ServerCert: "server.cert", + ServerKey: "server.key", + Password: "", + DatabaseIP: "127.0.0.1", + DatabaseType: "mongo", + PluginDir: cwd, + EtcdIP: "127.0.0.1", + EtcdCert: "etcd.cert", + EtcdKey: "etcd.key", + EtcdCAFile: "etcd-ca.cert", + ServicePort: "9015", + KubernetesLabelName: "orchestrator.io/rb-instance-id", + } +} + +// GetConfiguration returns the configuration for the app. +// It will try to load it if it is not already loaded. +func GetConfiguration() *Configuration { + if gConfig == nil { + conf, err := readConfigFile("config.json") + if err != nil { + log.Println("Error loading config file: ", err) + log.Println("Using defaults...") + } + gConfig = conf + } + + return gConfig +} + +// SetConfigValue sets a value in the configuration +// This is mostly used to customize the application and +// should be used carefully. +func SetConfigValue(key string, value string) *Configuration { + c := GetConfiguration() + if value == "" || key == "" { + return c + } + + v := reflect.ValueOf(c).Elem() + if v.Kind() == reflect.Struct { + f := v.FieldByName(key) + if f.IsValid() { + if f.CanSet() { + if f.Kind() == reflect.String { + f.SetString(value) + } + } + } + } + return c +} diff --git a/src/orchestrator/internal/config/config_test.go b/src/orchestrator/internal/config/config_test.go new file mode 100644 index 00000000..ce7641ae --- /dev/null +++ b/src/orchestrator/internal/config/config_test.go @@ -0,0 +1,40 @@ +/* + * Copyright 2019 Intel Corporation, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package config + +import ( + "testing" +) + +func TestReadConfigurationFile(t *testing.T) { + t.Run("Non Existent Configuration File", func(t *testing.T) { + _, err := readConfigFile("filedoesnotexist.json") + if err == nil { + t.Fatal("ReadConfiguationFile: Expected Error, got nil") + } + }) + + t.Run("Read Configuration File", func(t *testing.T) { + conf, err := readConfigFile("../../tests/configs/mock_config.json") + if err != nil { + t.Fatal("ReadConfigurationFile: Error reading file: ", err) + } + if conf.DatabaseType != "mock_db_test" { + t.Fatal("ReadConfigurationFile: Incorrect entry read from file") + } + }) +} diff --git a/src/orchestrator/internal/db/README.md b/src/orchestrator/internal/db/README.md new file mode 100644 index 00000000..cba1b7ea --- /dev/null +++ b/src/orchestrator/internal/db/README.md @@ -0,0 +1,123 @@ +# Database Abstraction Layer + +This package contains implementations of the Database interface defined in `store.go` +Any database can be used as the backend as long as the following interface is implemented; + +```go +type Store interface { + // Returns nil if db health is good + HealthCheck() error + + // Unmarshal implements any unmarshaling needed for the database + Unmarshal(inp []byte, out interface{}) error + + // Creates a new master table with key and links data with tag and + // creates a pointer to the newly added data in the master table + Create(table string, key Key, tag string, data interface{}) error + + // Reads data for a particular key with specific tag. + Read(table string, key Key, tag string) ([]byte, error) + + // Update data for particular key with specific tag + Update(table string, key Key, tag string, data interface{}) error + + // Deletes a specific tag data for key. + // TODO: If tag is empty, it will delete all tags under key. + Delete(table string, key Key, tag string) error + + // Reads all master tables and data from the specified tag in table + ReadAll(table string, tag string) (map[string][]byte, error) +} +``` + +Therefore, `mongo.go`, `consul.go` implement the above interface and can be used as the backend as needed based on initial configuration. + +## Details on Mongo Implementation + +`mongo.go` implements the above interface using the `go.mongodb.org/mongo-driver` package. +The code converts incoming binary data and creates a new document in the database. + +### Create + +Arguments: +```go +collection string +key interface +tag string +data []byte +``` + +Create inserts the provided `data` into the `collection` which returns an auto-generated (by `mongodb`) ID which we then associate with the `key` that is provided as one of the arguments. + +We use the `FindOneAndUpdate` mongo API to achieve this with the `upsert` option set to `true`. +We create the following documents in mongodb for each new definition added to the database: + +There is a Master Key document that contains references to other documents which are related to this `key`. + +#### Master Key Entry +```json +{ + "_id" : ObjectId("5e0a8554b78a15f71d2dce7e"), + "key" : { "rbname" : "edgex", "rbversion" : "v1"}, + "defmetadata" : ObjectId("5e0a8554be261ecb57f067eb"), + "defcontent" : ObjectId("5e0a8377bcfcdd0f01dc7b0d") +} +``` +#### Metadata Key Entry +```json +{ + "_id" : ObjectId("5e0a8554be261ecb57f067eb"), + "defmetadata" : { "rbname" : "edgex", "rbversion" : "v1", "chartname" : "", "description" : "", "labels" : null } +} +``` +#### Definition Content +```json +{ + "_id" : ObjectId("5e0a8377bcfcdd0f01dc7b0d"), + "defcontent" : "H4sICCVd3FwAA3Byb2ZpbGUxLnRhcgDt1NEKgjAUxvFd7ylG98aWOsGXiYELxLRwJvj2rbyoIPDGiuD/uzmwM9iB7Vvruvrgw7CdXHsUn6Ejm2W3aopcP9eZLYRJM1voPN+ZndAm16kVSn9onheXMLheKeGqfdM0rq07/3bfUv9PJUkiR9+H+tSVajRymM6+lEqN7njxoVSbU+z2deX388r9nWzkr8fGSt5d79pnLOZfm0f+dRrzb7P4DZD/LyDJAAAAAAAAAAAAAAAA/+0Ksq1N5QAoAAA=" +} +``` + +### Unmarshal + +Data in mongo is stored as `bson` which is a compressed form of `json`. We need mongo to convert the stored `bson` data to regular `json` +that we can use in our code when returned. + +We just use the `bson.Unmarshal` API to achieve this. + +### Read + +Arguments: +```go +collection string +key interface +tag string +``` + +Read is straight forward and it uses the `FindOne` API to find our Mongo document based on the provided `key` and then gets the corresponding data for the given `tag`. It will return []byte which can then be passed to the `Unmarshal` function to get the desired GO object. + +### Delete + +Delete is similar to Read and deletes all the objectIDs being stored for a given `key` in the collection. + +## Testing Interfaces + +The following interface exists to allow for the development of unit tests which don't require mongo to be running. +It is mentioned so in the code as well. + +```go +// MongoCollection defines the a subset of MongoDB operations +// Note: This interface is defined mainly for mock testing +type MongoCollection interface { + InsertOne(ctx context.Context, document interface{}, + opts ...*options.InsertOneOptions) (*mongo.InsertOneResult, error) + FindOne(ctx context.Context, filter interface{}, + opts ...*options.FindOneOptions) *mongo.SingleResult + FindOneAndUpdate(ctx context.Context, filter interface{}, + update interface{}, opts ...*options.FindOneAndUpdateOptions) *mongo.SingleResult + DeleteOne(ctx context.Context, filter interface{}, + opts ...*options.DeleteOptions) (*mongo.DeleteResult, error) + Find(ctx context.Context, filter interface{}, + opts ...*options.FindOptions) (*mongo.Cursor, error) +} +```
\ No newline at end of file diff --git a/src/orchestrator/internal/db/mock.go b/src/orchestrator/internal/db/mock.go new file mode 100644 index 00000000..1dbca4b4 --- /dev/null +++ b/src/orchestrator/internal/db/mock.go @@ -0,0 +1,94 @@ +/* +Copyright 2018 Intel Corporation. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package db + +import ( + "encoding/json" + + pkgerrors "github.com/pkg/errors" +) + +type MockKey struct { + Key string +} + +func (m MockKey) String() string { + return m.Key +} + +//Creating an embedded interface via anonymous variable +//This allows us to make mockDB satisfy the DatabaseConnection +//interface even if we are not implementing all the methods in it +type MockDB struct { + Store + Items map[string]map[string][]byte + Err error +} + +func (m *MockDB) HealthCheck() error { + return m.Err +} + +func (m *MockDB) Create(table string, key Key, tag string, data interface{}) error { + return m.Err +} + +func (m *MockDB) Update(table string, key Key, tag string, data interface{}) error { + return m.Err +} + +// MockDB uses simple JSON and not BSON +func (m *MockDB) Unmarshal(inp []byte, out interface{}) error { + err := json.Unmarshal(inp, out) + if err != nil { + return pkgerrors.Wrap(err, "Unmarshaling json") + } + return nil +} + +func (m *MockDB) Read(table string, key Key, tag string) ([]byte, error) { + if m.Err != nil { + return nil, m.Err + } + + for k, v := range m.Items { + if k == key.String() { + return v[tag], nil + } + } + + return nil, m.Err +} + +func (m *MockDB) Delete(table string, key Key, tag string) error { + return m.Err +} + +func (m *MockDB) ReadAll(table string, tag string) (map[string][]byte, error) { + if m.Err != nil { + return nil, m.Err + } + + ret := make(map[string][]byte) + + for k, v := range m.Items { + for k1, v1 := range v { + if k1 == tag { + ret[k] = v1 + } + } + } + + return ret, nil +} diff --git a/src/orchestrator/internal/db/mongo.go b/src/orchestrator/internal/db/mongo.go new file mode 100644 index 00000000..3720a4f2 --- /dev/null +++ b/src/orchestrator/internal/db/mongo.go @@ -0,0 +1,396 @@ +/* + * Copyright 2018 Intel Corporation, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package db + +import ( + "log" + + "golang.org/x/net/context" + + "github.com/onap/multicloud-k8s/src/orchestrator/internal/config" + + pkgerrors "github.com/pkg/errors" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +// MongoCollection defines the a subset of MongoDB operations +// Note: This interface is defined mainly for mock testing +type MongoCollection interface { + InsertOne(ctx context.Context, document interface{}, + opts ...*options.InsertOneOptions) (*mongo.InsertOneResult, error) + FindOne(ctx context.Context, filter interface{}, + opts ...*options.FindOneOptions) *mongo.SingleResult + FindOneAndUpdate(ctx context.Context, filter interface{}, + update interface{}, opts ...*options.FindOneAndUpdateOptions) *mongo.SingleResult + DeleteOne(ctx context.Context, filter interface{}, + opts ...*options.DeleteOptions) (*mongo.DeleteResult, error) + Find(ctx context.Context, filter interface{}, + opts ...*options.FindOptions) (*mongo.Cursor, error) +} + +// MongoStore is an implementation of the db.Store interface +type MongoStore struct { + db *mongo.Database +} + +// This exists only for allowing us to mock the collection object +// for testing purposes +var getCollection = func(coll string, m *MongoStore) MongoCollection { + return m.db.Collection(coll) +} + +// This exists only for allowing us to mock the DecodeBytes function +// Mainly because we cannot construct a SingleResult struct from our +// tests. All fields in that struct are private. +var decodeBytes = func(sr *mongo.SingleResult) (bson.Raw, error) { + return sr.DecodeBytes() +} + +// These exists only for allowing us to mock the cursor.Next function +// Mainly because we cannot construct a mongo.Cursor struct from our +// tests. All fields in that struct are private and there is no public +// constructor method. +var cursorNext = func(ctx context.Context, cursor *mongo.Cursor) bool { + return cursor.Next(ctx) +} +var cursorClose = func(ctx context.Context, cursor *mongo.Cursor) error { + return cursor.Close(ctx) +} + +// NewMongoStore initializes a Mongo Database with the name provided +// If a database with that name exists, it will be returned +func NewMongoStore(name string, store *mongo.Database) (Store, error) { + if store == nil { + ip := "mongodb://" + config.GetConfiguration().DatabaseIP + ":27017" + clientOptions := options.Client() + clientOptions.ApplyURI(ip) + mongoClient, err := mongo.NewClient(clientOptions) + if err != nil { + return nil, err + } + + err = mongoClient.Connect(context.Background()) + if err != nil { + return nil, err + } + store = mongoClient.Database(name) + } + + return &MongoStore{ + db: store, + }, nil +} + +// HealthCheck verifies if the database is up and running +func (m *MongoStore) HealthCheck() error { + + _, err := decodeBytes(m.db.RunCommand(context.Background(), bson.D{{"serverStatus", 1}})) + if err != nil { + return pkgerrors.Wrap(err, "Error getting server status") + } + + return nil +} + +// validateParams checks to see if any parameters are empty +func (m *MongoStore) validateParams(args ...interface{}) bool { + for _, v := range args { + val, ok := v.(string) + if ok { + if val == "" { + return false + } + } else { + if v == nil { + return false + } + } + } + + return true +} + +// Create is used to create a DB entry +func (m *MongoStore) Create(coll string, key Key, tag string, data interface{}) error { + if data == nil || !m.validateParams(coll, key, tag) { + return pkgerrors.New("No Data to store") + } + + c := getCollection(coll, m) + ctx := context.Background() + + //Insert the data and then add the objectID to the masterTable + res, err := c.InsertOne(ctx, bson.D{ + {tag, data}, + }) + if err != nil { + return pkgerrors.Errorf("Error inserting into database: %s", err.Error()) + } + + //Add objectID of created data to masterKey document + //Create masterkey document if it does not exist + filter := bson.D{{"key", key}} + + _, err = decodeBytes( + c.FindOneAndUpdate( + ctx, + filter, + bson.D{ + {"$set", bson.D{ + {tag, res.InsertedID}, + }}, + }, + options.FindOneAndUpdate().SetUpsert(true).SetReturnDocument(options.After))) + + if err != nil { + return pkgerrors.Errorf("Error updating master table: %s", err.Error()) + } + + return nil +} + +// Update is used to update a DB entry +func (m *MongoStore) Update(coll string, key Key, tag string, data interface{}) error { + if data == nil || !m.validateParams(coll, key, tag) { + return pkgerrors.New("No Data to update") + } + + c := getCollection(coll, m) + ctx := context.Background() + + //Get the masterkey document based on given key + filter := bson.D{{"key", key}} + keydata, err := decodeBytes(c.FindOne(context.Background(), filter)) + if err != nil { + return pkgerrors.Errorf("Error finding master table: %s", err.Error()) + } + + //Read the tag objectID from document + tagoid, ok := keydata.Lookup(tag).ObjectIDOK() + if !ok { + return pkgerrors.Errorf("Error finding objectID for tag %s", tag) + } + + //Update the document with new data + filter = bson.D{{"_id", tagoid}} + + _, err = decodeBytes( + c.FindOneAndUpdate( + ctx, + filter, + bson.D{ + {"$set", bson.D{ + {tag, data}, + }}, + }, + options.FindOneAndUpdate().SetReturnDocument(options.After))) + + if err != nil { + return pkgerrors.Errorf("Error updating record: %s", err.Error()) + } + + return nil +} + +// Unmarshal implements an unmarshaler for bson data that +// is produced from the mongo database +func (m *MongoStore) Unmarshal(inp []byte, out interface{}) error { + err := bson.Unmarshal(inp, out) + if err != nil { + return pkgerrors.Wrap(err, "Unmarshaling bson") + } + return nil +} + +// Read method returns the data stored for this key and for this particular tag +func (m *MongoStore) Read(coll string, key Key, tag string) ([]byte, error) { + if !m.validateParams(coll, key, tag) { + return nil, pkgerrors.New("Mandatory fields are missing") + } + + c := getCollection(coll, m) + ctx := context.Background() + + //Get the masterkey document based on given key + filter := bson.D{{"key", key}} + keydata, err := decodeBytes(c.FindOne(context.Background(), filter)) + if err != nil { + return nil, pkgerrors.Errorf("Error finding master table: %s", err.Error()) + } + + //Read the tag objectID from document + tagoid, ok := keydata.Lookup(tag).ObjectIDOK() + if !ok { + return nil, pkgerrors.Errorf("Error finding objectID for tag %s", tag) + } + + //Use tag objectID to read the data from store + filter = bson.D{{"_id", tagoid}} + tagdata, err := decodeBytes(c.FindOne(ctx, filter)) + if err != nil { + return nil, pkgerrors.Errorf("Error reading found object: %s", err.Error()) + } + + //Return the data as a byte array + //Convert string data to byte array using the built-in functions + switch tagdata.Lookup(tag).Type { + case bson.TypeString: + return []byte(tagdata.Lookup(tag).StringValue()), nil + default: + return tagdata.Lookup(tag).Value, nil + } +} + +// Helper function that deletes an object by its ID +func (m *MongoStore) deleteObjectByID(coll string, objID primitive.ObjectID) error { + + c := getCollection(coll, m) + ctx := context.Background() + + _, err := c.DeleteOne(ctx, bson.D{{"_id", objID}}) + if err != nil { + return pkgerrors.Errorf("Error Deleting from database: %s", err.Error()) + } + + log.Printf("Deleted Obj with ID %s", objID.String()) + return nil +} + +// Delete method removes a document from the Database that matches key +// TODO: delete all referenced docs if tag is empty string +func (m *MongoStore) Delete(coll string, key Key, tag string) error { + if !m.validateParams(coll, key, tag) { + return pkgerrors.New("Mandatory fields are missing") + } + + c := getCollection(coll, m) + ctx := context.Background() + + //Get the masterkey document based on given key + filter := bson.D{{"key", key}} + //Remove the tag ID entry from masterkey table + update := bson.D{ + { + "$unset", bson.D{ + {tag, ""}, + }, + }, + } + keydata, err := decodeBytes(c.FindOneAndUpdate(ctx, filter, update, + options.FindOneAndUpdate().SetReturnDocument(options.Before))) + if err != nil { + //No document was found. Return nil. + if err == mongo.ErrNoDocuments { + return nil + } + //Return any other error that was found. + return pkgerrors.Errorf("Error decoding master table after update: %s", + err.Error()) + } + + //Read the tag objectID from document + elems, err := keydata.Elements() + if err != nil { + return pkgerrors.Errorf("Error reading elements from database: %s", err.Error()) + } + + tagoid, ok := keydata.Lookup(tag).ObjectIDOK() + if !ok { + return pkgerrors.Errorf("Error finding objectID for tag %s", tag) + } + + //Use tag objectID to read the data from store + err = m.deleteObjectByID(coll, tagoid) + if err != nil { + return pkgerrors.Errorf("Error deleting from database: %s", err.Error()) + } + + //Delete master table if no more tags left + //_id, key and tag should be elements in before doc + //if master table needs to be removed too + if len(elems) == 3 { + keyid, ok := keydata.Lookup("_id").ObjectIDOK() + if !ok { + return pkgerrors.Errorf("Error finding objectID for key %s", key) + } + err = m.deleteObjectByID(coll, keyid) + if err != nil { + return pkgerrors.Errorf("Error deleting master table from database: %s", err.Error()) + } + } + + return nil +} + +// ReadAll is used to get all documents in db of a particular tag +func (m *MongoStore) ReadAll(coll, tag string) (map[string][]byte, error) { + if !m.validateParams(coll, tag) { + return nil, pkgerrors.New("Missing collection or tag name") + } + + c := getCollection(coll, m) + ctx := context.Background() + + //Get all master tables in this collection + filter := bson.D{ + {"key", bson.D{ + {"$exists", true}, + }}, + } + cursor, err := c.Find(ctx, filter) + if err != nil { + return nil, pkgerrors.Errorf("Error reading from database: %s", err.Error()) + } + defer cursorClose(ctx, cursor) + + //Iterate over all the master tables + result := make(map[string][]byte) + for cursorNext(ctx, cursor) { + d := cursor.Current + + //Read key of each master table + key, ok := d.Lookup("key").DocumentOK() + if !ok { + //Throw error if key is not found + pkgerrors.New("Unable to read key from mastertable") + } + + //Get objectID of tag document + tid, ok := d.Lookup(tag).ObjectIDOK() + if !ok { + log.Printf("Did not find tag: %s", tag) + continue + } + + //Find tag document and unmarshal it into []byte + tagData, err := decodeBytes(c.FindOne(ctx, bson.D{{"_id", tid}})) + if err != nil { + log.Printf("Unable to decode tag data %s", err.Error()) + continue + } + result[key.String()] = tagData.Lookup(tag).Value + } + + if len(result) == 0 { + return result, pkgerrors.Errorf("Did not find any objects with tag: %s", tag) + } + + return result, nil +} diff --git a/src/orchestrator/internal/db/mongo_test.go b/src/orchestrator/internal/db/mongo_test.go new file mode 100644 index 00000000..171c908f --- /dev/null +++ b/src/orchestrator/internal/db/mongo_test.go @@ -0,0 +1,597 @@ +/* + * Copyright 2018 Intel Corporation, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package db + +import ( + "bytes" + "context" + "reflect" + "strings" + "testing" + + pkgerrors "github.com/pkg/errors" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +//Implements the functions used currently in mongo.go +type mockCollection struct { + Err error + mCursor *mongo.Cursor + mCursorCount int +} + +func (c *mockCollection) InsertOne(ctx context.Context, document interface{}, + opts ...*options.InsertOneOptions) (*mongo.InsertOneResult, error) { + + if c.Err != nil { + return nil, c.Err + } + + return &mongo.InsertOneResult{InsertedID: "_id1234"}, nil +} + +func (c *mockCollection) FindOne(ctx context.Context, filter interface{}, + opts ...*options.FindOneOptions) *mongo.SingleResult { + + return &mongo.SingleResult{} +} + +func (c *mockCollection) FindOneAndUpdate(ctx context.Context, filter interface{}, + update interface{}, opts ...*options.FindOneAndUpdateOptions) *mongo.SingleResult { + + return &mongo.SingleResult{} +} + +func (c *mockCollection) DeleteOne(ctx context.Context, filter interface{}, + opts ...*options.DeleteOptions) (*mongo.DeleteResult, error) { + + return nil, c.Err +} + +func (c *mockCollection) Find(ctx context.Context, filter interface{}, + opts ...*options.FindOptions) (*mongo.Cursor, error) { + + return c.mCursor, c.Err +} + +func TestCreate(t *testing.T) { + testCases := []struct { + label string + input map[string]interface{} + mockColl *mockCollection + bson bson.Raw + expectedError string + }{ + { + label: "Successfull creation of entry", + input: map[string]interface{}{ + "coll": "collname", + "key": MockKey{Key: "keyvalue"}, + "tag": "tagName", + "data": "Data In String Format", + }, + bson: bson.Raw{'\x08', '\x00', '\x00', '\x00', '\x0A', 'x', '\x00', '\x00'}, + mockColl: &mockCollection{}, + }, + { + label: "UnSuccessfull creation of entry", + input: map[string]interface{}{ + "coll": "collname", + "key": MockKey{Key: "keyvalue"}, + "tag": "tagName", + "data": "Data In String Format", + }, + mockColl: &mockCollection{ + Err: pkgerrors.New("DB Error"), + }, + expectedError: "DB Error", + }, + { + label: "Missing input fields", + input: map[string]interface{}{ + "coll": "", + "key": MockKey{Key: ""}, + "tag": "", + "data": "", + }, + expectedError: "No Data to store", + mockColl: &mockCollection{}, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + m, _ := NewMongoStore("name", &mongo.Database{}) + // Override the getCollection function with our mocked version + getCollection = func(coll string, m *MongoStore) MongoCollection { + return testCase.mockColl + } + + decodeBytes = func(sr *mongo.SingleResult) (bson.Raw, error) { + return testCase.bson, testCase.mockColl.Err + } + + err := m.Create(testCase.input["coll"].(string), testCase.input["key"].(Key), + testCase.input["tag"].(string), testCase.input["data"]) + if err != nil { + if testCase.expectedError == "" { + t.Fatalf("Create method returned an un-expected (%s)", err) + } + if !strings.Contains(string(err.Error()), testCase.expectedError) { + t.Fatalf("Create method returned an error (%s)", err) + } + } + }) + } +} + +func TestUpdate(t *testing.T) { + testCases := []struct { + label string + input map[string]interface{} + mockColl *mockCollection + bson bson.Raw + expectedError string + }{ + { + label: "Successfull update of entry", + input: map[string]interface{}{ + "coll": "collname", + "key": MockKey{Key: "keyvalue"}, + "tag": "metadata", + "data": "Data In String Format", + }, + // Binary form of + // { + // "_id" : ObjectId("5c115156777ff85654248ae1"), + // "key" : bson.D{{"name","testdef"},{"version","v1"}}, + // "metadata" : ObjectId("5c115156c9755047e318bbfd") + // } + bson: bson.Raw{ + '\x58', '\x00', '\x00', '\x00', '\x03', '\x6b', '\x65', '\x79', + '\x00', '\x27', '\x00', '\x00', '\x00', '\x02', '\x6e', '\x61', + '\x6d', '\x65', '\x00', '\x08', '\x00', '\x00', '\x00', '\x74', + '\x65', '\x73', '\x74', '\x64', '\x65', '\x66', '\x00', '\x02', + '\x76', '\x65', '\x72', '\x73', '\x69', '\x6f', '\x6e', '\x00', + '\x03', '\x00', '\x00', '\x00', '\x76', '\x31', '\x00', '\x00', + '\x07', '\x6d', '\x65', '\x74', '\x61', '\x64', '\x61', '\x74', + '\x61', '\x00', '\x5c', '\x11', '\x51', '\x56', '\x77', '\x7f', + '\xf8', '\x56', '\x54', '\x24', '\x8a', '\xe1', '\x07', '\x5f', + '\x69', '\x64', '\x00', '\x5c', '\x11', '\x51', '\x56', '\x77', + '\x7f', '\xf8', '\x56', '\x54', '\x24', '\x8a', '\xe1', '\x00', + }, + mockColl: &mockCollection{}, + }, + { + label: "Entry does not exist", + input: map[string]interface{}{ + "coll": "collname", + "key": MockKey{Key: "keyvalue"}, + "tag": "tagName", + "data": "Data In String Format", + }, + mockColl: &mockCollection{ + Err: pkgerrors.New("DB Error"), + }, + expectedError: "DB Error", + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + m, _ := NewMongoStore("name", &mongo.Database{}) + // Override the getCollection function with our mocked version + getCollection = func(coll string, m *MongoStore) MongoCollection { + return testCase.mockColl + } + + decodeBytes = func(sr *mongo.SingleResult) (bson.Raw, error) { + return testCase.bson, testCase.mockColl.Err + } + + err := m.Update(testCase.input["coll"].(string), testCase.input["key"].(Key), + testCase.input["tag"].(string), testCase.input["data"]) + if err != nil { + if testCase.expectedError == "" { + t.Fatalf("Create method returned an un-expected (%s)", err) + } + if !strings.Contains(string(err.Error()), testCase.expectedError) { + t.Fatalf("Create method returned an error (%s)", err) + } + } + }) + } +} + +func TestRead(t *testing.T) { + testCases := []struct { + label string + input map[string]interface{} + mockColl *mockCollection + bson bson.Raw + expectedError string + expected []byte + }{ + { + label: "Successfull Read of entry", + input: map[string]interface{}{ + "coll": "collname", + "key": MockKey{Key: "keyvalue"}, + "tag": "metadata", + }, + // Binary form of + // { + // "_id" : ObjectId("5c115156777ff85654248ae1"), + // "key" : bson.D{{"name","testdef"},{"version","v1"}}, + // "metadata" : ObjectId("5c115156c9755047e318bbfd") + // } + bson: bson.Raw{ + '\x58', '\x00', '\x00', '\x00', '\x03', '\x6b', '\x65', '\x79', + '\x00', '\x27', '\x00', '\x00', '\x00', '\x02', '\x6e', '\x61', + '\x6d', '\x65', '\x00', '\x08', '\x00', '\x00', '\x00', '\x74', + '\x65', '\x73', '\x74', '\x64', '\x65', '\x66', '\x00', '\x02', + '\x76', '\x65', '\x72', '\x73', '\x69', '\x6f', '\x6e', '\x00', + '\x03', '\x00', '\x00', '\x00', '\x76', '\x31', '\x00', '\x00', + '\x07', '\x6d', '\x65', '\x74', '\x61', '\x64', '\x61', '\x74', + '\x61', '\x00', '\x5c', '\x11', '\x51', '\x56', '\x77', '\x7f', + '\xf8', '\x56', '\x54', '\x24', '\x8a', '\xe1', '\x07', '\x5f', + '\x69', '\x64', '\x00', '\x5c', '\x11', '\x51', '\x56', '\x77', + '\x7f', '\xf8', '\x56', '\x54', '\x24', '\x8a', '\xe1', '\x00', + }, + mockColl: &mockCollection{}, + // This is not the document because we are mocking decodeBytes + expected: []byte{92, 17, 81, 86, 119, 127, 248, 86, 84, 36, 138, 225}, + }, + { + label: "UnSuccessfull Read of entry: object not found", + input: map[string]interface{}{ + "coll": "collname", + "key": MockKey{Key: "keyvalue"}, + "tag": "badtag", + }, + // Binary form of + // { + // "_id" : ObjectId("5c115156777ff85654248ae1"), + // "key" : bson.D{{"name","testdef"},{"version","v1"}}, + // "metadata" : ObjectId("5c115156c9755047e318bbfd") + // } + bson: bson.Raw{ + '\x58', '\x00', '\x00', '\x00', '\x03', '\x6b', '\x65', '\x79', + '\x00', '\x27', '\x00', '\x00', '\x00', '\x02', '\x6e', '\x61', + '\x6d', '\x65', '\x00', '\x08', '\x00', '\x00', '\x00', '\x74', + '\x65', '\x73', '\x74', '\x64', '\x65', '\x66', '\x00', '\x02', + '\x76', '\x65', '\x72', '\x73', '\x69', '\x6f', '\x6e', '\x00', + '\x03', '\x00', '\x00', '\x00', '\x76', '\x31', '\x00', '\x00', + '\x07', '\x6d', '\x65', '\x74', '\x61', '\x64', '\x61', '\x74', + '\x61', '\x00', '\x5c', '\x11', '\x51', '\x56', '\x77', '\x7f', + '\xf8', '\x56', '\x54', '\x24', '\x8a', '\xe1', '\x07', '\x5f', + '\x69', '\x64', '\x00', '\x5c', '\x11', '\x51', '\x56', '\x77', + '\x7f', '\xf8', '\x56', '\x54', '\x24', '\x8a', '\xe1', '\x00', + }, + mockColl: &mockCollection{}, + expectedError: "Error finding objectID", + }, + { + label: "UnSuccessfull Read of entry", + input: map[string]interface{}{ + "coll": "collname", + "key": MockKey{Key: "keyvalue"}, + "tag": "tagName", + }, + mockColl: &mockCollection{ + Err: pkgerrors.New("DB Error"), + }, + expectedError: "DB Error", + }, + { + label: "Missing input fields", + input: map[string]interface{}{ + "coll": "", + "key": MockKey{Key: ""}, + "tag": "", + }, + expectedError: "Mandatory fields are missing", + mockColl: &mockCollection{}, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + m, _ := NewMongoStore("name", &mongo.Database{}) + // Override the getCollection function with our mocked version + getCollection = func(coll string, m *MongoStore) MongoCollection { + return testCase.mockColl + } + + decodeBytes = func(sr *mongo.SingleResult) (bson.Raw, error) { + return testCase.bson, testCase.mockColl.Err + } + got, err := m.Read(testCase.input["coll"].(string), testCase.input["key"].(Key), + testCase.input["tag"].(string)) + if err != nil { + if testCase.expectedError == "" { + t.Fatalf("Read method returned an un-expected (%s)", err) + } + if !strings.Contains(string(err.Error()), testCase.expectedError) { + t.Fatalf("Read method returned an error (%s)", err) + } + } else { + if bytes.Compare(got, testCase.expected) != 0 { + t.Fatalf("Read returned unexpected data: %v, expected: %v", + string(got), testCase.expected) + } + } + }) + } +} + +func TestDelete(t *testing.T) { + testCases := []struct { + label string + input map[string]interface{} + mockColl *mockCollection + bson bson.Raw + expectedError string + }{ + { + label: "Successfull Delete of entry", + input: map[string]interface{}{ + "coll": "collname", + "key": MockKey{Key: "keyvalue"}, + "tag": "metadata", + }, + // Binary form of + // { + // "_id" : ObjectId("5c115156777ff85654248ae1"), + // "key" : bson.D{{"name","testdef"},{"version","v1"}}, + // "metadata" : ObjectId("5c115156c9755047e318bbfd") + // } + bson: bson.Raw{ + '\x58', '\x00', '\x00', '\x00', '\x03', '\x6b', '\x65', '\x79', + '\x00', '\x27', '\x00', '\x00', '\x00', '\x02', '\x6e', '\x61', + '\x6d', '\x65', '\x00', '\x08', '\x00', '\x00', '\x00', '\x74', + '\x65', '\x73', '\x74', '\x64', '\x65', '\x66', '\x00', '\x02', + '\x76', '\x65', '\x72', '\x73', '\x69', '\x6f', '\x6e', '\x00', + '\x03', '\x00', '\x00', '\x00', '\x76', '\x31', '\x00', '\x00', + '\x07', '\x6d', '\x65', '\x74', '\x61', '\x64', '\x61', '\x74', + '\x61', '\x00', '\x5c', '\x11', '\x51', '\x56', '\x77', '\x7f', + '\xf8', '\x56', '\x54', '\x24', '\x8a', '\xe1', '\x07', '\x5f', + '\x69', '\x64', '\x00', '\x5c', '\x11', '\x51', '\x56', '\x77', + '\x7f', '\xf8', '\x56', '\x54', '\x24', '\x8a', '\xe1', '\x00', + }, + mockColl: &mockCollection{}, + }, + { + label: "UnSuccessfull Delete of entry", + input: map[string]interface{}{ + "coll": "collname", + "key": MockKey{Key: "keyvalue"}, + "tag": "tagName", + }, + mockColl: &mockCollection{ + Err: pkgerrors.New("DB Error"), + }, + expectedError: "DB Error", + }, + { + label: "UnSuccessfull Delete, key not found", + input: map[string]interface{}{ + "coll": "collname", + "key": MockKey{Key: "keyvalue"}, + "tag": "tagName", + }, + // Binary form of + // { + // "_id" : ObjectId("5c115156777ff85654248ae1"), + // "key" : bson.D{{"name","testdef"},{"version","v1"}}, + // "metadata" : ObjectId("5c115156c9755047e318bbfd") + // } + bson: bson.Raw{ + '\x58', '\x00', '\x00', '\x00', '\x03', '\x6b', '\x65', '\x79', + '\x00', '\x27', '\x00', '\x00', '\x00', '\x02', '\x6e', '\x61', + '\x6d', '\x65', '\x00', '\x08', '\x00', '\x00', '\x00', '\x74', + '\x65', '\x73', '\x74', '\x64', '\x65', '\x66', '\x00', '\x02', + '\x76', '\x65', '\x72', '\x73', '\x69', '\x6f', '\x6e', '\x00', + '\x03', '\x00', '\x00', '\x00', '\x76', '\x31', '\x00', '\x00', + '\x07', '\x6d', '\x65', '\x74', '\x61', '\x64', '\x61', '\x74', + '\x61', '\x00', '\x5c', '\x11', '\x51', '\x56', '\x77', '\x7f', + '\xf8', '\x56', '\x54', '\x24', '\x8a', '\xe1', '\x07', '\x5f', + '\x69', '\x64', '\x00', '\x5c', '\x11', '\x51', '\x56', '\x77', + '\x7f', '\xf8', '\x56', '\x54', '\x24', '\x8a', '\xe1', '\x00', + }, + mockColl: &mockCollection{}, + expectedError: "Error finding objectID", + }, + { + label: "Missing input fields", + input: map[string]interface{}{ + "coll": "", + "key": MockKey{Key: ""}, + "tag": "", + }, + expectedError: "Mandatory fields are missing", + mockColl: &mockCollection{}, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + m, _ := NewMongoStore("name", &mongo.Database{}) + // Override the getCollection function with our mocked version + getCollection = func(coll string, m *MongoStore) MongoCollection { + return testCase.mockColl + } + + decodeBytes = func(sr *mongo.SingleResult) (bson.Raw, error) { + return testCase.bson, testCase.mockColl.Err + } + err := m.Delete(testCase.input["coll"].(string), testCase.input["key"].(Key), + testCase.input["tag"].(string)) + if err != nil { + if testCase.expectedError == "" { + t.Fatalf("Delete method returned an un-expected (%s)", err) + } + if !strings.Contains(string(err.Error()), testCase.expectedError) { + t.Fatalf("Delete method returned an error (%s)", err) + } + } + }) + } +} + +func TestReadAll(t *testing.T) { + testCases := []struct { + label string + input map[string]interface{} + mockColl *mockCollection + bson bson.Raw + expectedError string + expected map[string][]byte + }{ + { + label: "Successfully Read all entries", + input: map[string]interface{}{ + "coll": "collname", + "tag": "metadata", + }, + mockColl: &mockCollection{ + mCursor: &mongo.Cursor{ + // Binary form of + // { + // "_id" : ObjectId("5c115156777ff85654248ae1"), + // "key" : bson.D{{"name","testdef"},{"version","v1"}}, + // "metadata" : ObjectId("5c115156c9755047e318bbfd") + // } + + Current: bson.Raw{ + '\x58', '\x00', '\x00', '\x00', '\x03', '\x6b', '\x65', '\x79', + '\x00', '\x27', '\x00', '\x00', '\x00', '\x02', '\x6e', '\x61', + '\x6d', '\x65', '\x00', '\x08', '\x00', '\x00', '\x00', '\x74', + '\x65', '\x73', '\x74', '\x64', '\x65', '\x66', '\x00', '\x02', + '\x76', '\x65', '\x72', '\x73', '\x69', '\x6f', '\x6e', '\x00', + '\x03', '\x00', '\x00', '\x00', '\x76', '\x31', '\x00', '\x00', + '\x07', '\x6d', '\x65', '\x74', '\x61', '\x64', '\x61', '\x74', + '\x61', '\x00', '\x5c', '\x11', '\x51', '\x56', '\x77', '\x7f', + '\xf8', '\x56', '\x54', '\x24', '\x8a', '\xe1', '\x07', '\x5f', + '\x69', '\x64', '\x00', '\x5c', '\x11', '\x51', '\x56', '\x77', + '\x7f', '\xf8', '\x56', '\x54', '\x24', '\x8a', '\xe1', '\x00', + }, + }, + mCursorCount: 1, + }, + expected: map[string][]byte{ + `{"name": "testdef","version": "v1"}`: []byte{ + 92, 17, 81, 86, 119, 127, 248, 86, 84, 36, 138, 225}, + }, + }, + { + label: "UnSuccessfully Read of all entries", + input: map[string]interface{}{ + "coll": "collname", + "tag": "tagName", + }, + mockColl: &mockCollection{ + Err: pkgerrors.New("DB Error"), + }, + expectedError: "DB Error", + }, + { + label: "UnSuccessfull Readall, tag not found", + input: map[string]interface{}{ + "coll": "collname", + "tag": "tagName", + }, + mockColl: &mockCollection{ + mCursor: &mongo.Cursor{ + // Binary form of + // { + // "_id" : ObjectId("5c115156777ff85654248ae1"), + // "key" : bson.D{{"name","testdef"},{"version","v1"}}, + // "metadata" : ObjectId("5c115156c9755047e318bbfd") + // } + Current: bson.Raw{ + '\x58', '\x00', '\x00', '\x00', '\x03', '\x6b', '\x65', '\x79', + '\x00', '\x27', '\x00', '\x00', '\x00', '\x02', '\x6e', '\x61', + '\x6d', '\x65', '\x00', '\x08', '\x00', '\x00', '\x00', '\x74', + '\x65', '\x73', '\x74', '\x64', '\x65', '\x66', '\x00', '\x02', + '\x76', '\x65', '\x72', '\x73', '\x69', '\x6f', '\x6e', '\x00', + '\x03', '\x00', '\x00', '\x00', '\x76', '\x31', '\x00', '\x00', + '\x07', '\x6d', '\x65', '\x74', '\x61', '\x64', '\x61', '\x74', + '\x61', '\x00', '\x5c', '\x11', '\x51', '\x56', '\x77', '\x7f', + '\xf8', '\x56', '\x54', '\x24', '\x8a', '\xe1', '\x07', '\x5f', + '\x69', '\x64', '\x00', '\x5c', '\x11', '\x51', '\x56', '\x77', + '\x7f', '\xf8', '\x56', '\x54', '\x24', '\x8a', '\xe1', '\x00', + }, + }, + mCursorCount: 1, + }, + expectedError: "Did not find any objects with tag", + }, + { + label: "Missing input fields", + input: map[string]interface{}{ + "coll": "", + "tag": "", + }, + expectedError: "Missing collection or tag name", + mockColl: &mockCollection{}, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + m, _ := NewMongoStore("name", &mongo.Database{}) + // Override the getCollection function with our mocked version + getCollection = func(coll string, m *MongoStore) MongoCollection { + return testCase.mockColl + } + + decodeBytes = func(sr *mongo.SingleResult) (bson.Raw, error) { + return testCase.mockColl.mCursor.Current, testCase.mockColl.Err + } + + cursorNext = func(ctx context.Context, cursor *mongo.Cursor) bool { + if testCase.mockColl.mCursorCount > 0 { + testCase.mockColl.mCursorCount -= 1 + return true + } + return false + } + + cursorClose = func(ctx context.Context, cursor *mongo.Cursor) error { + return nil + } + + got, err := m.ReadAll(testCase.input["coll"].(string), testCase.input["tag"].(string)) + if err != nil { + if testCase.expectedError == "" { + t.Fatalf("Readall method returned an un-expected (%s)", err) + } + if !strings.Contains(string(err.Error()), testCase.expectedError) { + t.Fatalf("Readall method returned an error (%s)", err) + } + } else { + if reflect.DeepEqual(got, testCase.expected) == false { + t.Fatalf("Readall returned unexpected data: %v, expected: %v", + got, testCase.expected) + } + } + }) + } +} diff --git a/src/orchestrator/internal/db/store.go b/src/orchestrator/internal/db/store.go new file mode 100644 index 00000000..ed394205 --- /dev/null +++ b/src/orchestrator/internal/db/store.go @@ -0,0 +1,106 @@ +/* +Copyright 2018 Intel Corporation. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package db + +import ( + "encoding/json" + "reflect" + + "github.com/onap/multicloud-k8s/src/orchestrator/internal/config" + + pkgerrors "github.com/pkg/errors" +) + +// DBconn interface used to talk a concrete Database connection +var DBconn Store + +// Key is an interface that will be implemented by anypackage +// that wants to use the Store interface. This allows various +// db backends and key types. +type Key interface { + String() string +} + +// Store is an interface for accessing the database +type Store interface { + // Returns nil if db health is good + HealthCheck() error + + // Unmarshal implements any unmarshaling needed for the database + Unmarshal(inp []byte, out interface{}) error + + // Creates a new master document with key and links data with tag and + // creates a pointer(row) to the newly added data in the master table + Create(table string, key Key, tag string, data interface{}) error + + // Reads data for a particular key with specific tag. + Read(table string, key Key, tag string) ([]byte, error) + + // Update data for particular key with specific tag + Update(table string, key Key, tag string, data interface{}) error + + // Deletes a specific tag data for key. + // TODO: If tag is empty, it will delete all tags under key. + Delete(table string, key Key, tag string) error + + // Reads all master tables and data from the specified tag in table + ReadAll(table string, tag string) (map[string][]byte, error) +} + +// CreateDBClient creates the DB client +func createDBClient(dbType string) error { + var err error + switch dbType { + case "mongo": + // create a mongodb database with orchestrator as the name + DBconn, err = NewMongoStore("orchestrator", nil) + default: + return pkgerrors.New(dbType + "DB not supported") + } + return err +} + +// Serialize converts given data into a JSON string +func Serialize(v interface{}) (string, error) { + out, err := json.Marshal(v) + if err != nil { + return "", pkgerrors.Wrap(err, "Error serializing "+reflect.TypeOf(v).String()) + } + return string(out), nil +} + +// DeSerialize converts string to a json object specified by type +func DeSerialize(str string, v interface{}) error { + err := json.Unmarshal([]byte(str), &v) + if err != nil { + return pkgerrors.Wrap(err, "Error deSerializing "+str) + } + return nil +} + +// InitializeDatabaseConnection sets up the connection to the +// configured database to allow the application to talk to it. +func InitializeDatabaseConnection() error { + err := createDBClient(config.GetConfiguration().DatabaseType) + if err != nil { + return pkgerrors.Cause(err) + } + + err = DBconn.HealthCheck() + if err != nil { + return pkgerrors.Cause(err) + } + + return nil +} diff --git a/src/orchestrator/internal/db/store_test.go b/src/orchestrator/internal/db/store_test.go new file mode 100644 index 00000000..42a41787 --- /dev/null +++ b/src/orchestrator/internal/db/store_test.go @@ -0,0 +1,121 @@ +/* +Copyright 2018 Intel Corporation. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package db + +import ( + "reflect" + "strings" + "testing" +) + +func TestCreateDBClient(t *testing.T) { + t.Run("Successfully create DB client", func(t *testing.T) { + expected := &MongoStore{} + + err := createDBClient("mongo") + if err != nil { + t.Fatalf("CreateDBClient returned an error (%s)", err) + } + if reflect.TypeOf(DBconn) != reflect.TypeOf(expected) { + t.Fatalf("CreateDBClient set DBconn as:\n result=%T\n expected=%T", DBconn, expected) + } + }) + t.Run("Fail to create client for unsupported DB", func(t *testing.T) { + err := createDBClient("fakeDB") + if err == nil { + t.Fatal("CreateDBClient didn't return an error") + } + if !strings.Contains(string(err.Error()), "DB not supported") { + t.Fatalf("CreateDBClient method returned an error (%s)", err) + } + }) +} + +func TestSerialize(t *testing.T) { + + inp := map[string]interface{}{ + "UUID": "123e4567-e89b-12d3-a456-426655440000", + "Data": "sdaijsdiodalkfjsdlagf", + "Number": 23, + "Float": 34.4, + "Map": map[string]interface{}{ + "m1": "m1", + "m2": 2, + "m3": 3.0, + }, + } + + got, err := Serialize(inp) + if err != nil { + t.Fatal(err) + } + + expected := "{\"Data\":\"sdaijsdiodalkfjsdlagf\"," + + "\"Float\":34.4,\"Map\":{\"m1\":\"m1\",\"m2\":2,\"m3\":3}," + + "\"Number\":23,\"UUID\":\"123e4567-e89b-12d3-a456-426655440000\"}" + + if expected != got { + t.Errorf("Serialize returned unexpected string: %s;"+ + " expected %sv", got, expected) + } +} + +func TestDeSerialize(t *testing.T) { + testCases := []struct { + label string + input string + expected map[string]interface{} + errMsg string + }{ + { + label: "Sucessful deserialize entry", + input: "{\"Data\":\"sdaijsdiodalkfjsdlagf\"," + + "\"Float\":34.4,\"Map\":{\"m1\":\"m1\",\"m3\":3}," + + "\"UUID\":\"123e4567-e89b-12d3-a456-426655440000\"}", + expected: map[string]interface{}{ + "UUID": "123e4567-e89b-12d3-a456-426655440000", + "Data": "sdaijsdiodalkfjsdlagf", + "Float": 34.4, + "Map": map[string]interface{}{ + "m1": "m1", + "m3": 3.0, + }, + }, + }, + { + label: "Fail to deserialize invalid entry", + input: "{invalid}", + errMsg: "Error deSerializing {invalid}: invalid character 'i' looking for beginning of object key string", + }, + } + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + got := make(map[string]interface{}) + err := DeSerialize(testCase.input, &got) + if err != nil { + if testCase.errMsg == "" { + t.Fatalf("DeSerialize method return an un-expected (%s)", err) + } + if !strings.Contains(string(err.Error()), testCase.errMsg) { + t.Fatalf("DeSerialize method returned an error (%s)", err) + } + } else { + if !reflect.DeepEqual(testCase.expected, got) { + t.Errorf("Serialize returned unexpected : %v;"+ + " expected %v", got, testCase.expected) + } + } + }) + } +} diff --git a/src/orchestrator/internal/logutils/logger.go b/src/orchestrator/internal/logutils/logger.go new file mode 100644 index 00000000..2e8f9969 --- /dev/null +++ b/src/orchestrator/internal/logutils/logger.go @@ -0,0 +1,28 @@ +package logutils + +import ( + log "github.com/sirupsen/logrus" +) + +//Fields is type that will be used by the calling function +type Fields map[string]interface{} + +func init() { + // Log as JSON instead of the default ASCII formatter. + log.SetFormatter(&log.JSONFormatter{}) +} + +// Error uses the fields provided and logs +func Error(msg string, fields Fields) { + log.WithFields(log.Fields(fields)).Error(msg) +} + +// Warn uses the fields provided and logs +func Warn(msg string, fields Fields) { + log.WithFields(log.Fields(fields)).Warn(msg) +} + +// Info uses the fields provided and logs +func Info(msg string, fields Fields) { + log.WithFields(log.Fields(fields)).Info(msg) +} diff --git a/src/orchestrator/internal/project/project.go b/src/orchestrator/internal/project/project.go new file mode 100644 index 00000000..f0c50065 --- /dev/null +++ b/src/orchestrator/internal/project/project.go @@ -0,0 +1,133 @@ +/* + * Copyright 2019 Intel Corporation, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package project + +import ( + "encoding/json" + + "github.com/onap/multicloud-k8s/src/orchestrator/internal/db" + + pkgerrors "github.com/pkg/errors" +) + +// Project contains the parameters needed for Projects +// It implements the interface for managing the Projects +type Project struct { + ProjectName string `json:"project-name"` + Description string `json:"description"` +} + +// ProjectKey is the key structure that is used in the database +type ProjectKey struct { + ProjectName string `json:"rb-name"` +} + +// We will use json marshalling to convert to string to +// preserve the underlying structure. +func (pk ProjectKey) String() string { + out, err := json.Marshal(pk) + if err != nil { + return "" + } + + return string(out) +} + +// ProjectManager is an interface exposes the Project functionality +type ProjectManager interface { + Create(pr Project) (Project, error) + Get(name string) (Project, error) + Delete(name string) error +} + +// ProjectClient implements the ProjectManager +// It will also be used to maintain some localized state +type ProjectClient struct { + storeName string + tagMeta, tagContent string +} + +// NewProjectClient returns an instance of the ProjectClient +// which implements the ProjectManager +func NewProjectClient() *ProjectClient { + return &ProjectClient{ + tagMeta: "projectmetadata", + } +} + +// Create a new collection based on the project +func (v *ProjectClient) Create(p Project) (Project, error) { + + //Construct the composite key to select the entry + key := ProjectKey{ + ProjectName: p.ProjectName, + } + + //Check if this Project already exists + _, err := v.Get(p.ProjectName) + if err == nil { + return Project{}, pkgerrors.New("Project already exists") + } + + err = db.DBconn.Create(p.ProjectName, key, v.tagMeta, p) + if err != nil { + return Project{}, pkgerrors.Wrap(err, "Creating DB Entry") + } + + return p, nil +} + +// Get returns the Project for corresponding name +func (v *ProjectClient) Get(name string) (Project, error) { + + //Construct the composite key to select the entry + key := ProjectKey{ + ProjectName: name, + } + value, err := db.DBconn.Read(name, key, v.tagMeta) + if err != nil { + return Project{}, pkgerrors.Wrap(err, "Get Project") + } + + //value is a byte array + if value != nil { + proj := Project{} + err = db.DBconn.Unmarshal(value, &proj) + if err != nil { + return Project{}, pkgerrors.Wrap(err, "Unmarshaling Value") + } + return proj, nil + } + + return Project{}, pkgerrors.New("Error getting Project") +} + +// Delete the Project from database +func (v *ProjectClient) Delete(name string) error { + + //Construct the composite key to select the entry + key := ProjectKey{ + ProjectName: name, + } + err := db.DBconn.Delete(name, key, v.tagMeta) + if err != nil { + return pkgerrors.Wrap(err, "Delete Project Entry;") + } + + //TODO: Delete the collection when the project is deleted + return nil +} diff --git a/src/orchestrator/internal/project/project_test.go b/src/orchestrator/internal/project/project_test.go new file mode 100644 index 00000000..cc691e33 --- /dev/null +++ b/src/orchestrator/internal/project/project_test.go @@ -0,0 +1,177 @@ +/* + * Copyright 2018 Intel Corporation, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package project + +import ( + "reflect" + "strings" + "testing" + + "github.com/onap/multicloud-k8s/src/orchestrator/internal/db" + + pkgerrors "github.com/pkg/errors" +) + +func TestCreateProject(t *testing.T) { + testCases := []struct { + label string + inp Project + expectedError string + mockdb *db.MockDB + expected Project + }{ + { + label: "Create Project", + inp: Project{ + ProjectName: "testProject", + Description: "A sample Project used for unit testing", + }, + expected: Project{ + ProjectName: "testProject", + Description: "A sample Project used for unit testing", + }, + expectedError: "", + mockdb: &db.MockDB{}, + }, + { + label: "Failed Create Project", + expectedError: "Error Creating Project", + mockdb: &db.MockDB{ + Err: pkgerrors.New("Error Creating Project"), + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + db.DBconn = testCase.mockdb + impl := NewProjectClient() + got, err := impl.Create(testCase.inp) + if err != nil { + if testCase.expectedError == "" { + t.Fatalf("Create returned an unexpected error %s", err) + } + if strings.Contains(err.Error(), testCase.expectedError) == false { + t.Fatalf("Create returned an unexpected error %s", err) + } + } else { + if reflect.DeepEqual(testCase.expected, got) == false { + t.Errorf("Create returned unexpected body: got %v;"+ + " expected %v", got, testCase.expected) + } + } + }) + } +} + +func TestGetProject(t *testing.T) { + + testCases := []struct { + label string + name string + expectedError string + mockdb *db.MockDB + inp string + expected Project + }{ + { + label: "Get Project", + name: "testProject", + expected: Project{ + ProjectName: "testProject", + Description: "Test project for unit testing", + }, + expectedError: "", + mockdb: &db.MockDB{ + Items: map[string]map[string][]byte{ + ProjectKey{ProjectName: "testProject"}.String(): { + "projectmetadata": []byte( + "{\"project-name\":\"testProject\"," + + "\"description\":\"Test project for unit testing\"}"), + }, + }, + }, + }, + { + label: "Get Error", + expectedError: "DB Error", + mockdb: &db.MockDB{ + Err: pkgerrors.New("DB Error"), + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + db.DBconn = testCase.mockdb + impl := NewProjectClient() + got, err := impl.Get(testCase.name) + if err != nil { + if testCase.expectedError == "" { + t.Fatalf("Get returned an unexpected error: %s", err) + } + if strings.Contains(err.Error(), testCase.expectedError) == false { + t.Fatalf("Get returned an unexpected error: %s", err) + } + } else { + if reflect.DeepEqual(testCase.expected, got) == false { + t.Errorf("Get returned unexpected body: got %v;"+ + " expected %v", got, testCase.expected) + } + } + }) + } +} + +func TestDeleteProject(t *testing.T) { + + testCases := []struct { + label string + name string + expectedError string + mockdb *db.MockDB + }{ + { + label: "Delete Project", + name: "testProject", + mockdb: &db.MockDB{}, + }, + { + label: "Delete Error", + expectedError: "DB Error", + mockdb: &db.MockDB{ + Err: pkgerrors.New("DB Error"), + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + db.DBconn = testCase.mockdb + impl := NewProjectClient() + err := impl.Delete(testCase.name) + if err != nil { + if testCase.expectedError == "" { + t.Fatalf("Delete returned an unexpected error %s", err) + } + if strings.Contains(err.Error(), testCase.expectedError) == false { + t.Fatalf("Delete returned an unexpected error %s", err) + } + } + }) + } +} diff --git a/src/orchestrator/tests/certs/auth_test_certificate.pem b/src/orchestrator/tests/certs/auth_test_certificate.pem new file mode 100644 index 00000000..42e77491 --- /dev/null +++ b/src/orchestrator/tests/certs/auth_test_certificate.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDXTCCAkWgAwIBAgIJAKAHJi8eUs73MA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQwHhcNMTgwNTE1MDQ0MDQwWhcNMTkwNTE1MDQ0MDQwWjBF +MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 +ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEA5PHDk+RRFh5o3Xe2nZuLn0Vo+5BjnHp/ul2NNYSG00Slc8F86gW4xcNA +wm6xC8tYCSangV2lFG3E8H2L7SCEVaM5VDV2GCOpOoMihc+2Qenk/YbHwuYenwjo +OgTK4aCItqjcAJ2DB1KC7AxARxHBDu9Kif+M/pc49so+G9ObQuS8k2vmTTaRYkMK +ZvbTJcWsc0vbNnPhxcG5PVj4unlaREM+yQDm18/glUkkBNnYKMHABRvPnBrDktTT +BQWsqkbQTw7ZuLOrl9rCzVTstZX9wEXarrstIdQJj3KZjbFOp2opND8bjNIjcdVt +iRFnP1nHQYr7EgRqcx/YMJZ+gmCy3wIDAQABo1AwTjAdBgNVHQ4EFgQU9qPNwwIu +kZh41sJqFtnMC2blSYMwHwYDVR0jBBgwFoAU9qPNwwIukZh41sJqFtnMC2blSYMw +DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEA4+daLY1wE10IMPaOKurG +QBvfYeO/rgNXGeg0TisTIKAfx/We9Hmwo/37Bd2Nk5gxfy/DIJ4lMbrzXjgWITlm +XOrS5QfluwvaEcREtHFtPFa3NZqn2VzKNDFTR+rJj7I5o600NKdcPrGeQ1i/vny2 +K0g68ogw2jfufcuePvZTYGst8RclomPr7ZXxI24kIjcE1MbiViy68sQueTXBEr5s +Th6RsvPfVnLxjR/m/V6VJl31nn4T6hbmKzXCHo/X7aC3I8Isui4bQGKgfAxyBkhE +0T7tP+GgymiEKQ6qJ/1c4HFFSuFRUQjLnK7uJu9jM/HMKoLCPayx6birHZRIMF94 +pg== +-----END CERTIFICATE----- diff --git a/src/orchestrator/tests/certs/auth_test_key.pem b/src/orchestrator/tests/certs/auth_test_key.pem new file mode 100644 index 00000000..5f01f572 --- /dev/null +++ b/src/orchestrator/tests/certs/auth_test_key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDk8cOT5FEWHmjd +d7adm4ufRWj7kGOcen+6XY01hIbTRKVzwXzqBbjFw0DCbrELy1gJJqeBXaUUbcTw +fYvtIIRVozlUNXYYI6k6gyKFz7ZB6eT9hsfC5h6fCOg6BMrhoIi2qNwAnYMHUoLs +DEBHEcEO70qJ/4z+lzj2yj4b05tC5LyTa+ZNNpFiQwpm9tMlxaxzS9s2c+HFwbk9 +WPi6eVpEQz7JAObXz+CVSSQE2dgowcAFG8+cGsOS1NMFBayqRtBPDtm4s6uX2sLN +VOy1lf3ARdquuy0h1AmPcpmNsU6naik0PxuM0iNx1W2JEWc/WcdBivsSBGpzH9gw +ln6CYLLfAgMBAAECggEAYB3H2D0QddLKf8AUoNJ+qZ1AV+zkhPtAyIMiF4fN+sBl +HdXrlWxViGFSvM4v8h2qlhzuUfd4qLz042ox5pmyNSnTlbDkJXpDP9dyFO+BOubx +Ribhksd9r5LTvBfq/RKikt0NkAyQx/AyGtuB2NRxUs3PY2QwU2o1dhauQIx0MH5/ +6D8PgQf6+5njKQaKa4e8Kp4kB+KjnALvt6JgYuNJUHWap+nnDbuuVy5dl1bKkAZ+ +qa7CITKWO4kE2EqaCb2asFc2w3538+w72UJZtwQCmOaxtKpRSl9fQXu54N8jIGoZ +1FvEj5x3X6QkglE+iVJYaX3RmiJ3uzZ2LICDr89vEQKBgQD7fquIw4p1idSxz3Cm +5o3Y5kD0CKm61ZaRJWKd+tNlSsxubmV9HROYW6vj2xEPSDvyp1na00pDXxMJQLLc +O5Awd1SaU+d45jnoi70fsEY8X0WH1rDTYfnU+zQBmpbGqX5qTIfpy4yoADiUD1CQ +EBdaSBWiKPx2jFSct58TwDP9YwKBgQDpC64TScZYz7uQq4gAbDso/7TjNwgt/Bw8 +JgLSdx1UdUclh81smTujsouyCFwJSvRjKik8e/Qt0f5patukFbFRINxUGUDhOKbA +7zqeNQbeYaP7Rvw+3z01CU2BTBmB/EWa2xWDam8B9xQvjiHSOrubqkt3sIQJb045 +hzuigdV7VQKBgQD7Gnd0nyCwyMSIIMGuswYv6X4y6i9lr3qdQ4GakOTe/vbsz+cf +K5f0CJuwbnszEgFg/zzVIx/D8rqUA3hSMlp+ObdMO7gi22Q4TsWvTRZjkxBeV7rH +48xJneNIMqyWgIcK5YzSn3y6BTZ4hm3+2UInz09iUJ/6UZTtwNzhIIgIVwKBgQCT +LxRHDE4gIzrT+PHRSonmr/DfnA8nc9WlS2B26lH02IkRs/5Su0iGb6p4y3zNRbCp +vKQElki2c60ZiSqlLCosEfP1jWmDlRMEQVMlPlpTMxmtBr0jPDzc9T4lDhoCFYEk +d3/T2vG3LQRrsHm92+hHPTuioTIS/2BJRxar4RIibQKBgQC8zoayoQ7zfEYxy3Ax +OSao8g85hj0EAJk/VKQP2POgz6KoPay3JE9D7P7OvkebTyv/pijAuTPby4XipCNI +K0JbFC2Kn7RW/ZV23UdnoO9crh2omOh+/52prStWXKoc+/pJe70Af+4rU7FUiI7F +y1mIE9krIoVis6iYsyFEmkP7iw== +-----END PRIVATE KEY----- diff --git a/src/orchestrator/tests/configs/mock_config.json b/src/orchestrator/tests/configs/mock_config.json new file mode 100644 index 00000000..47a6b627 --- /dev/null +++ b/src/orchestrator/tests/configs/mock_config.json @@ -0,0 +1,5 @@ +{ + "database-type": "mock_db_test", + "database-ip": "127.0.0.1", + "plugin-dir": "." +}
\ No newline at end of file |