Add code to apply workload network annotations 48/105448/9
authorEric Multanen <eric.w.multanen@intel.com>
Wed, 15 Apr 2020 03:51:50 +0000 (20:51 -0700)
committerEric Multanen <eric.w.multanen@intel.com>
Thu, 23 Apr 2020 05:47:52 +0000 (22:47 -0700)
Apply workload network intents to indicated
resources in the AppContext.
This will add/update network annotations for
pods or resources that have pod templates.

Issue-ID: MULTICLOUD-1029
Signed-off-by: Eric Multanen <eric.w.multanen@intel.com>
Change-Id: I9ae4387a8c28a95510406da361cfef3f8257bc80

src/ncm/api/api.go
src/ncm/api/netcontrolintenthandler.go
src/ncm/go.mod
src/ncm/pkg/module/netcontrolintent.go
src/ncm/pkg/module/resources.go [new file with mode: 0644]

index 7892113..2b10571 100644 (file)
@@ -135,6 +135,7 @@ func NewRouter(testClient interface{}) *mux.Router {
        router.HandleFunc("/projects/{project}/composite-apps/{composite-app-name}/{version}/network-controller-intent/{name}", netcontrolintentHandler.putHandler).Methods("PUT")
        router.HandleFunc("/projects/{project}/composite-apps/{composite-app-name}/{version}/network-controller-intent/{name}", netcontrolintentHandler.getHandler).Methods("GET")
        router.HandleFunc("/projects/{project}/composite-apps/{composite-app-name}/{version}/network-controller-intent/{name}", netcontrolintentHandler.deleteHandler).Methods("DELETE")
+       router.HandleFunc("/projects/{project}/composite-apps/{composite-app-name}/{version}/network-controller-intent/{name}/apply", netcontrolintentHandler.applyHandler).Methods("POST")
 
        workloadintentHandler := workloadintentHandler{
                client: setClient(moduleClient.WorkloadIntent, testClient).(moduleLib.WorkloadIntentManager),
index c59a389..48ef1de 100644 (file)
@@ -196,3 +196,35 @@ func (h netcontrolintentHandler) deleteHandler(w http.ResponseWriter, r *http.Re
 
        w.WriteHeader(http.StatusNoContent)
 }
+
+// Apply handles POST operations to Apply a particular NetControlIntent to the App Context
+func (h netcontrolintentHandler) applyHandler(w http.ResponseWriter, r *http.Request) {
+       vars := mux.Vars(r)
+       name := vars["name"]
+       project := vars["project"]
+       compositeApp := vars["composite-app-name"]
+       compositeAppVersion := vars["version"]
+
+       var aci struct {
+               AppContextId string `json:"appContextId"`
+       }
+
+       err := json.NewDecoder(r.Body).Decode(&aci)
+
+       switch {
+       case err == io.EOF:
+               http.Error(w, "Empty body", http.StatusBadRequest)
+               return
+       case err != nil:
+               http.Error(w, err.Error(), http.StatusUnprocessableEntity)
+               return
+       }
+
+       err = h.client.ApplyNetControlIntent(name, project, compositeApp, compositeAppVersion, aci.AppContextId)
+       if err != nil {
+               http.Error(w, err.Error(), http.StatusInternalServerError)
+               return
+       }
+
+       w.WriteHeader(http.StatusNoContent)
+}
index ddf9667..8b45298 100644 (file)
@@ -1,22 +1,26 @@
 module github.com/onap/multicloud-k8s/src/ncm
 
 require (
-       github.com/coreos/etcd v3.3.12+incompatible
-       github.com/docker/engine v0.0.0-20190620014054-c513a4c6c298
+       github.com/docker/distribution v2.7.1+incompatible // indirect
        github.com/ghodss/yaml v1.0.0
+       github.com/google/gofuzz v1.1.0 // 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.9 // indirect
+       github.com/k8snetworkplumbingwg/network-attachment-definition-client v0.0.0-20200127152046-0ee521d56061
        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/net v0.0.0-20190930134127-c5a3c61f89f3
        gopkg.in/yaml.v2 v2.2.8
        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/kube-openapi v0.0.0-20191107075043-30be4d16710a // indirect
+       k8s.io/kubernetes v1.14.1
+       k8s.io/utils v0.0.0-20200414100711-2df71ebbae66 // indirect
+       sigs.k8s.io/yaml v1.2.0 // indirect
 )
 
 replace (
index 5ef9dff..c005a93 100644 (file)
 package module
 
 import (
-       "github.com/onap/multicloud-k8s/src/orchestrator/pkg/infra/db"
+       "encoding/json"
+       "strings"
+
+       jyaml "github.com/ghodss/yaml"
 
+       nettypes "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1"
+       "github.com/onap/multicloud-k8s/src/orchestrator/pkg/appcontext"
+       "github.com/onap/multicloud-k8s/src/orchestrator/pkg/infra/db"
+       log "github.com/onap/multicloud-k8s/src/orchestrator/pkg/infra/logutils"
+       "k8s.io/apimachinery/pkg/runtime"
+       "k8s.io/client-go/kubernetes/scheme"
 
        pkgerrors "github.com/pkg/errors"
 )
@@ -42,6 +51,7 @@ type NetControlIntentManager interface {
        GetNetControlIntent(name, project, compositeapp, compositeappversion string) (NetControlIntent, error)
        GetNetControlIntents(project, compositeapp, compositeappversion string) ([]NetControlIntent, error)
        DeleteNetControlIntent(name, project, compositeapp, compositeappversion string) error
+       ApplyNetControlIntent(name, project, compositeapp, compositeappversion, appContextId string) error
 }
 
 // NetControlIntentClient implements the Manager
@@ -162,3 +172,124 @@ func (v *NetControlIntentClient) DeleteNetControlIntent(name, project, composite
 
        return nil
 }
+
+// (Test Routine) - Apply network-control-intent
+func (v *NetControlIntentClient) ApplyNetControlIntent(name, project, compositeapp, compositeappversion, appContextId string) error {
+       // TODO: Handle all Network Chain Intents for the Network Control Intent
+
+       // Handle all Workload Intents for the Network Control Intent
+       wis, err := NewWorkloadIntentClient().GetWorkloadIntents(project, compositeapp, compositeappversion, name)
+       if err != nil {
+               return pkgerrors.Wrapf(err, "Error getting Workload Intents for Network Control Intent %v for %v/%v%v not found", name, project, compositeapp, compositeappversion)
+       }
+
+       // Setup the AppContext
+       var context appcontext.AppContext
+       _, err = context.LoadAppContext(appContextId)
+       if err != nil {
+               return pkgerrors.Wrapf(err, "Error getting AppContext with Id: %v for %v/%v%v",
+                       appContextId, project, compositeapp, compositeappversion)
+       }
+
+       // Handle all intents (currently just Interface intents) for each Workload Intent
+       for _, wi := range wis {
+               // The app/resource identified in the workload intent needs to be updated with two annotations.
+               // 1 - The "k8s.v1.cni.cncf.io/networks" annotation will have {"name": "ovn-networkobj", "namespace": "default"} added
+               //     to it (preserving any existing values for this annotation.
+               // 2 - The "k8s.plugin.opnfv.org/nfn-network" annotation will add any network interfaces that are provided by the
+               //     workload/interfaces intents.
+
+               // Prepare the list of interfaces from the workload intent
+               wifs, err := NewWorkloadIfIntentClient().GetWorkloadIfIntents(project,
+                       compositeapp,
+                       compositeappversion,
+                       name,
+                       wi.Metadata.Name)
+               if err != nil {
+                       return pkgerrors.Wrapf(err,
+                               "Error getting Workload Interface Intents for Workload Intent %v under Network Control Intent %v for %v/%v%v not found",
+                               wi.Metadata.Name, name, project, compositeapp, compositeappversion)
+               }
+               if len(wifs) == 0 {
+                       log.Warn("No interface intents provided for workload intent", log.Fields{
+                               "project":                project,
+                               "composite app":          compositeapp,
+                               "composite app version":  compositeappversion,
+                               "network control intent": name,
+                               "workload intent":        wi.Metadata.Name,
+                       })
+                       continue
+               }
+
+               // Get all clusters for the current App from the AppContext
+               clusters, err := context.GetClusterNames(wi.Spec.AppName)
+               for _, c := range clusters {
+                       rh, err := context.GetResourceHandle(wi.Spec.AppName, c,
+                               strings.Join([]string{wi.Spec.WorkloadResource, wi.Spec.Type}, "+"))
+                       if err != nil {
+                               log.Warn("App Context resource handle not found", log.Fields{
+                                       "project":                project,
+                                       "composite app":          compositeapp,
+                                       "composite app version":  compositeappversion,
+                                       "network control intent": name,
+                                       "workload name":          wi.Metadata.Name,
+                                       "app":                    wi.Spec.AppName,
+                                       "resource":               wi.Spec.WorkloadResource,
+                                       "resource type":          wi.Spec.Type,
+                               })
+                               continue
+                       }
+                       r, err := context.GetValue(rh)
+                       if err != nil {
+                               log.Error("Error retrieving resource from App Context", log.Fields{
+                                       "error":           err,
+                                       "resource handle": rh,
+                               })
+                       }
+
+                       // Unmarshal resource to K8S object
+                       robj, err := runtime.Decode(scheme.Codecs.UniversalDeserializer(), []byte(r.(string)))
+
+                       // Add network annotation to object
+                       netAnnot := nettypes.NetworkSelectionElement{
+                               Name:      "ovn-networkobj",
+                               Namespace: "default",
+                       }
+                       AddNetworkAnnotation(robj, netAnnot)
+
+                       // Add nfn interface annotations to object
+                       var newNfnIfs []WorkloadIfIntentSpec
+                       for _, i := range wifs {
+                               newNfnIfs = append(newNfnIfs, i.Spec)
+                       }
+                       AddNfnAnnotation(robj, newNfnIfs)
+
+                       // Marshal object back to yaml format (via json - seems to eliminate most clutter)
+                       j, err := json.Marshal(robj)
+                       if err != nil {
+                               log.Error("Error marshalling resource to JSON", log.Fields{
+                                       "error": err,
+                               })
+                               continue
+                       }
+                       y, err := jyaml.JSONToYAML(j)
+                       if err != nil {
+                               log.Error("Error marshalling resource to YAML", log.Fields{
+                                       "error": err,
+                               })
+                               continue
+                       }
+
+                       // Update resource in AppContext
+                       err = context.UpdateResourceValue(rh, string(y))
+                       if err != nil {
+                               log.Error("Network updating app context resource handle", log.Fields{
+                                       "error":           err,
+                                       "resource handle": rh,
+                               })
+                       }
+               }
+       }
+
+       return nil
+}
diff --git a/src/ncm/pkg/module/resources.go b/src/ncm/pkg/module/resources.go
new file mode 100644 (file)
index 0000000..24c9833
--- /dev/null
@@ -0,0 +1,273 @@
+/*
+ * Copyright 2020 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 module
+
+import (
+       "encoding/json"
+       "fmt"
+
+       nettypes "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1"
+       netutils "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/utils"
+       log "github.com/onap/multicloud-k8s/src/orchestrator/pkg/infra/logutils"
+       v1 "k8s.io/api/apps/v1"
+       batch "k8s.io/api/batch/v1"
+       batchv1beta1 "k8s.io/api/batch/v1beta1"
+       v1core "k8s.io/api/core/v1"
+       _ "k8s.io/kubernetes/pkg/apis/apps/install"
+       _ "k8s.io/kubernetes/pkg/apis/batch/install"
+       _ "k8s.io/kubernetes/pkg/apis/core/install"
+       _ "k8s.io/kubernetes/pkg/apis/extensions/install"
+
+       pkgerrors "github.com/pkg/errors"
+)
+
+type NfnAnnotation struct {
+       CniType   string
+       Interface []WorkloadIfIntentSpec
+}
+
+const NfnAnnotationKey = "k8s.plugin.opnfv.org/nfn-network"
+
+// ParsePodTemplateNetworkAnnotation parses Pod annotation in PodTemplate
+func ParsePodTemplateNetworkAnnotation(pt *v1core.PodTemplateSpec) ([]*nettypes.NetworkSelectionElement, error) {
+       netAnnot := pt.Annotations[nettypes.NetworkAttachmentAnnot]
+       defaultNamespace := pt.Namespace
+
+       if len(netAnnot) == 0 {
+               return nil, pkgerrors.Errorf("No kubernetes network annotation found")
+       }
+
+       networks, err := netutils.ParseNetworkAnnotation(netAnnot, defaultNamespace)
+       if err != nil {
+               return nil, err
+       }
+       return networks, nil
+}
+
+// GetPodTemplateNetworkAnnotation gets Pod Nfn annotation in PodTemplate
+func GetPodTemplateNfnAnnotation(pt *v1core.PodTemplateSpec) NfnAnnotation {
+       var nfn NfnAnnotation
+
+       nfnAnnot := pt.Annotations[NfnAnnotationKey]
+       if len(nfnAnnot) == 0 {
+               return nfn
+       }
+
+       err := json.Unmarshal([]byte(nfnAnnot), &nfn)
+       if err != nil {
+               log.Warn("Error unmarshalling nfn annotation", log.Fields{
+                       "annotation": nfnAnnot,
+               })
+       }
+       return nfn
+}
+
+// GetPodNetworkAnnotation gets Pod Nfn annotation in PodTemplate
+func GetPodNfnAnnotation(p *v1core.Pod) NfnAnnotation {
+       var nfn NfnAnnotation
+
+       nfnAnnot := p.Annotations[NfnAnnotationKey]
+       if len(nfnAnnot) == 0 {
+               return nfn
+       }
+
+       err := json.Unmarshal([]byte(nfnAnnot), &nfn)
+       if err != nil {
+               log.Warn("Error unmarshalling nfn annotation", log.Fields{
+                       "annotation": nfnAnnot,
+               })
+       }
+       return nfn
+}
+
+func addNetworkAnnotation(a nettypes.NetworkSelectionElement, as []*nettypes.NetworkSelectionElement) []*nettypes.NetworkSelectionElement {
+       var netElements []*nettypes.NetworkSelectionElement
+
+       found := false
+       for _, e := range as {
+               if e.Name == a.Name {
+                       found = true
+               }
+               netElements = append(netElements, e)
+       }
+       if !found {
+               netElements = append(netElements, &a)
+       }
+
+       return netElements
+}
+
+// Add the interfaces in the 'new' parameter to the nfn annotation
+func newNfnIfs(nfn NfnAnnotation, new []WorkloadIfIntentSpec) NfnAnnotation {
+       // Prepare a new interface list - combining the original plus new ones
+       var newNfn NfnAnnotation
+
+       if nfn.CniType != CNI_TYPE_OVN4NFV {
+               if len(nfn.CniType) > 0 {
+                       log.Warn("Error existing nfn cnitype is invalid", log.Fields{
+                               "existing cnitype": nfn.CniType,
+                               "using cnitype":    CNI_TYPE_OVN4NFV,
+                       })
+               }
+       }
+       newNfn.CniType = CNI_TYPE_OVN4NFV
+
+       // update any interfaces already in the list with the updated interface
+       for _, i := range nfn.Interface {
+               for _, j := range new {
+                       if i.NetworkName == j.NetworkName && i.IfName == j.IfName {
+                               i.DefaultGateway = j.DefaultGateway
+                               i.IpAddr = j.IpAddr
+                               i.MacAddr = j.MacAddr
+                               break
+                       }
+               }
+               newNfn.Interface = append(newNfn.Interface, i)
+       }
+
+       // add new interfaces not present in original list
+       for _, j := range new {
+               found := false
+               for _, i := range nfn.Interface {
+                       if i.NetworkName == j.NetworkName && i.IfName == j.IfName {
+                               found = true
+                               break
+                       }
+               }
+               if !found {
+                       newNfn.Interface = append(newNfn.Interface, j)
+               }
+       }
+       return newNfn
+}
+
+func updatePodTemplateNetworkAnnotation(pt *v1core.PodTemplateSpec, a nettypes.NetworkSelectionElement) {
+       netAnnotation, _ := ParsePodTemplateNetworkAnnotation(pt)
+       elements := addNetworkAnnotation(a, netAnnotation)
+       j, err := json.Marshal(elements)
+       if err != nil {
+               log.Error("Existing network annotation has invalid format", log.Fields{
+                       "error": err,
+               })
+               return
+       }
+       if pt.Annotations == nil {
+               pt.Annotations = make(map[string]string)
+       }
+       pt.Annotations[nettypes.NetworkAttachmentAnnot] = string(j)
+}
+
+// Add a network annotation to the resource
+func AddNetworkAnnotation(r interface{}, a nettypes.NetworkSelectionElement) {
+
+       switch o := r.(type) {
+       case *batch.Job:
+               updatePodTemplateNetworkAnnotation(&o.Spec.Template, a)
+       case *batchv1beta1.CronJob:
+               updatePodTemplateNetworkAnnotation(&o.Spec.JobTemplate.Spec.Template, a)
+       case *v1.DaemonSet:
+               updatePodTemplateNetworkAnnotation(&o.Spec.Template, a)
+       case *v1.Deployment:
+               updatePodTemplateNetworkAnnotation(&o.Spec.Template, a)
+       case *v1.ReplicaSet:
+               updatePodTemplateNetworkAnnotation(&o.Spec.Template, a)
+       case *v1.StatefulSet:
+               updatePodTemplateNetworkAnnotation(&o.Spec.Template, a)
+       case *v1core.Pod:
+               netAnnotation, _ := netutils.ParsePodNetworkAnnotation(o)
+               elements := addNetworkAnnotation(a, netAnnotation)
+               j, err := json.Marshal(elements)
+               if err != nil {
+                       log.Error("Existing network annotation has invalid format", log.Fields{
+                               "error": err,
+                       })
+                       break
+               }
+               if o.Annotations == nil {
+                       o.Annotations = make(map[string]string)
+               }
+               o.Annotations[nettypes.NetworkAttachmentAnnot] = string(j)
+               return
+       case *v1core.ReplicationController:
+               updatePodTemplateNetworkAnnotation(o.Spec.Template, a)
+       default:
+               typeStr := fmt.Sprintf("%T", o)
+               log.Warn("Network annotations not supported for resource type", log.Fields{
+                       "resource type": typeStr,
+               })
+       }
+}
+
+func updatePodTemplateNfnAnnotation(pt *v1core.PodTemplateSpec, new []WorkloadIfIntentSpec) {
+       nfnAnnotation := GetPodTemplateNfnAnnotation(pt)
+       newNfnAnnotation := newNfnIfs(nfnAnnotation, new)
+       j, err := json.Marshal(newNfnAnnotation)
+       if err != nil {
+               log.Error("Network nfn annotation has invalid format", log.Fields{
+                       "error": err,
+               })
+               return
+       }
+       if pt.Annotations == nil {
+               pt.Annotations = make(map[string]string)
+       }
+       pt.Annotations[NfnAnnotationKey] = string(j)
+}
+
+// Add an nfn annotation to the resource
+func AddNfnAnnotation(r interface{}, new []WorkloadIfIntentSpec) {
+
+       switch o := r.(type) {
+       case *batch.Job:
+               updatePodTemplateNfnAnnotation(&o.Spec.Template, new)
+       case *batchv1beta1.CronJob:
+               updatePodTemplateNfnAnnotation(&o.Spec.JobTemplate.Spec.Template, new)
+       case *v1.DaemonSet:
+               updatePodTemplateNfnAnnotation(&o.Spec.Template, new)
+               return
+       case *v1.Deployment:
+               updatePodTemplateNfnAnnotation(&o.Spec.Template, new)
+               return
+       case *v1.ReplicaSet:
+               updatePodTemplateNfnAnnotation(&o.Spec.Template, new)
+       case *v1.StatefulSet:
+               updatePodTemplateNfnAnnotation(&o.Spec.Template, new)
+       case *v1core.Pod:
+               nfnAnnotation := GetPodNfnAnnotation(o)
+               newNfnAnnotation := newNfnIfs(nfnAnnotation, new)
+               j, err := json.Marshal(newNfnAnnotation)
+               if err != nil {
+                       log.Error("Network nfn annotation has invalid format", log.Fields{
+                               "error": err,
+                       })
+                       break
+               }
+               if o.Annotations == nil {
+                       o.Annotations = make(map[string]string)
+               }
+               o.Annotations[NfnAnnotationKey] = string(j)
+               return
+       case *v1core.ReplicationController:
+               updatePodTemplateNfnAnnotation(o.Spec.Template, new)
+               return
+       default:
+               typeStr := fmt.Sprintf("%T", o)
+               log.Warn("Network nfn annotations not supported for resource type", log.Fields{
+                       "resource type": typeStr,
+               })
+       }
+}