diff options
Diffstat (limited to 'src/ncm')
-rw-r--r-- | src/ncm/api/api.go | 3 | ||||
-rw-r--r-- | src/ncm/api/clusterhandler.go | 30 | ||||
-rw-r--r-- | src/ncm/api/clusterhandler_test.go | 18 | ||||
-rw-r--r-- | src/ncm/api/netcontrolintenthandler.go | 32 | ||||
-rw-r--r-- | src/ncm/go.mod | 15 | ||||
-rw-r--r-- | src/ncm/pkg/module/cluster.go | 223 | ||||
-rw-r--r-- | src/ncm/pkg/module/module_definitions.go | 30 | ||||
-rw-r--r-- | src/ncm/pkg/module/netcontrolintent.go | 133 | ||||
-rw-r--r-- | src/ncm/pkg/module/network.go | 14 | ||||
-rw-r--r-- | src/ncm/pkg/module/providernet.go | 18 | ||||
-rw-r--r-- | src/ncm/pkg/module/resources.go | 273 |
11 files changed, 763 insertions, 26 deletions
diff --git a/src/ncm/api/api.go b/src/ncm/api/api.go index fcef7b43..2b105716 100644 --- a/src/ncm/api/api.go +++ b/src/ncm/api/api.go @@ -98,6 +98,8 @@ func NewRouter(testClient interface{}) *mux.Router { router.HandleFunc("/cluster-providers/{provider-name}/clusters", clusterHandler.getClusterHandler).Queries("label", "{label}") router.HandleFunc("/cluster-providers/{provider-name}/clusters/{name}", clusterHandler.getClusterHandler).Methods("GET") router.HandleFunc("/cluster-providers/{provider-name}/clusters/{name}", clusterHandler.deleteClusterHandler).Methods("DELETE") + router.HandleFunc("/cluster-providers/{provider-name}/clusters/{name}/apply", clusterHandler.applyClusterHandler).Methods("POST") + router.HandleFunc("/cluster-providers/{provider-name}/clusters/{name}/terminate", clusterHandler.terminateClusterHandler).Methods("POST") router.HandleFunc("/cluster-providers/{provider-name}/clusters/{cluster-name}/labels", clusterHandler.createClusterLabelHandler).Methods("POST") router.HandleFunc("/cluster-providers/{provider-name}/clusters/{cluster-name}/labels", clusterHandler.getClusterLabelHandler).Methods("GET") router.HandleFunc("/cluster-providers/{provider-name}/clusters/{cluster-name}/labels/{label}", clusterHandler.getClusterLabelHandler).Methods("GET") @@ -133,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), diff --git a/src/ncm/api/clusterhandler.go b/src/ncm/api/clusterhandler.go index 8c50f720..78453aa8 100644 --- a/src/ncm/api/clusterhandler.go +++ b/src/ncm/api/clusterhandler.go @@ -324,6 +324,36 @@ func (h clusterHandler) deleteClusterHandler(w http.ResponseWriter, r *http.Requ w.WriteHeader(http.StatusNoContent) } +// apply network intents associated with the cluster +func (h clusterHandler) applyClusterHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + provider := vars["provider-name"] + name := vars["name"] + + err := h.client.ApplyNetworkIntents(provider, name) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusNoContent) +} + +// terminate network intents associated with the cluster +func (h clusterHandler) terminateClusterHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + provider := vars["provider-name"] + name := vars["name"] + + err := h.client.TerminateNetworkIntents(provider, name) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusNoContent) +} + // Create handles creation of the ClusterLabel entry in the database func (h clusterHandler) createClusterLabelHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) diff --git a/src/ncm/api/clusterhandler_test.go b/src/ncm/api/clusterhandler_test.go index a26c41bd..b32df527 100644 --- a/src/ncm/api/clusterhandler_test.go +++ b/src/ncm/api/clusterhandler_test.go @@ -28,6 +28,7 @@ import ( "testing" moduleLib "github.com/onap/multicloud-k8s/src/ncm/pkg/module" + "github.com/onap/multicloud-k8s/src/orchestrator/pkg/appcontext" pkgerrors "github.com/pkg/errors" ) @@ -41,6 +42,7 @@ type mockClusterManager struct { ClusterProviderItems []moduleLib.ClusterProvider ClusterItems []moduleLib.Cluster ClusterContentItems []moduleLib.ClusterContent + ClusterContextItems []appcontext.AppContext ClusterLabelItems []moduleLib.ClusterLabel ClusterKvPairsItems []moduleLib.ClusterKvPairs ClusterList []string @@ -99,6 +101,14 @@ func (m *mockClusterManager) GetClusterContent(provider, name string) (moduleLib return m.ClusterContentItems[0], nil } +func (m *mockClusterManager) GetClusterContext(provider, name string) (appcontext.AppContext, error) { + if m.Err != nil { + return appcontext.AppContext{}, m.Err + } + + return m.ClusterContextItems[0], nil +} + func (m *mockClusterManager) GetClusters(provider string) ([]moduleLib.Cluster, error) { if m.Err != nil { return []moduleLib.Cluster{}, m.Err @@ -119,6 +129,14 @@ func (m *mockClusterManager) DeleteCluster(provider, name string) error { return m.Err } +func (m *mockClusterManager) ApplyNetworkIntents(provider, name string) error { + return m.Err +} + +func (m *mockClusterManager) TerminateNetworkIntents(provider, name string) error { + return m.Err +} + func (m *mockClusterManager) CreateClusterLabel(provider, cluster string, inp moduleLib.ClusterLabel) (moduleLib.ClusterLabel, error) { if m.Err != nil { return moduleLib.ClusterLabel{}, m.Err diff --git a/src/ncm/api/netcontrolintenthandler.go b/src/ncm/api/netcontrolintenthandler.go index c59a389b..48ef1de2 100644 --- a/src/ncm/api/netcontrolintenthandler.go +++ b/src/ncm/api/netcontrolintenthandler.go @@ -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) +} diff --git a/src/ncm/go.mod b/src/ncm/go.mod index 32ff481a..8b45298f 100644 --- a/src/ncm/go.mod +++ b/src/ncm/go.mod @@ -1,21 +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 ( diff --git a/src/ncm/pkg/module/cluster.go b/src/ncm/pkg/module/cluster.go index 4ca4e7c8..2397a091 100644 --- a/src/ncm/pkg/module/cluster.go +++ b/src/ncm/pkg/module/cluster.go @@ -17,7 +17,10 @@ package module import ( + "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" + "gopkg.in/yaml.v2" pkgerrors "github.com/pkg/errors" ) @@ -79,6 +82,10 @@ type ClusterKvPairsKey struct { ClusterKvPairsName string `json:"kvname"` } +const SEPARATOR = "+" +const CONTEXT_CLUSTER_APP = "network-intents" +const CONTEXT_CLUSTER_RESOURCE = "network-intents" + // ClusterManager is an interface exposes the Cluster functionality type ClusterManager interface { CreateClusterProvider(pr ClusterProvider) (ClusterProvider, error) @@ -88,9 +95,12 @@ type ClusterManager interface { CreateCluster(provider string, pr Cluster, qr ClusterContent) (Cluster, error) GetCluster(provider, name string) (Cluster, error) GetClusterContent(provider, name string) (ClusterContent, error) + GetClusterContext(provider, name string) (appcontext.AppContext, error) GetClusters(provider string) ([]Cluster, error) GetClustersWithLabel(provider, label string) ([]string, error) DeleteCluster(provider, name string) error + ApplyNetworkIntents(provider, name string) error + TerminateNetworkIntents(provider, name string) error CreateClusterLabel(provider, cluster string, pr ClusterLabel) (ClusterLabel, error) GetClusterLabel(provider, cluster, label string) (ClusterLabel, error) GetClusterLabels(provider, cluster string) ([]ClusterLabel, error) @@ -115,6 +125,7 @@ func NewClusterClient() *ClusterClient { storeName: "cluster", tagMeta: "clustermetadata", tagContent: "clustercontent", + tagContext: "clustercontext", }, } } @@ -294,6 +305,33 @@ func (v *ClusterClient) GetClusterContent(provider, name string) (ClusterContent return ClusterContent{}, pkgerrors.New("Error getting Cluster Content") } +// GetClusterContext returns the AppContext for corresponding provider and name +func (v *ClusterClient) GetClusterContext(provider, name string) (appcontext.AppContext, error) { + //Construct key and tag to select the entry + key := ClusterKey{ + ClusterProviderName: provider, + ClusterName: name, + } + + value, err := db.DBconn.Find(v.db.storeName, key, v.db.tagContext) + if err != nil { + return appcontext.AppContext{}, pkgerrors.Wrap(err, "Get Cluster Context") + } + + //value is a byte array + if value != nil { + ctxVal := string(value[0]) + var cc appcontext.AppContext + _, err = cc.LoadAppContext(ctxVal) + if err != nil { + return appcontext.AppContext{}, pkgerrors.Wrap(err, "Reinitializing Cluster AppContext") + } + return cc, nil + } + + return appcontext.AppContext{}, pkgerrors.New("Error getting Cluster AppContext") +} + // GetClusters returns all the Clusters for corresponding provider func (v *ClusterClient) GetClusters(provider string) ([]Cluster, error) { //Construct key and tag to select the entry @@ -351,8 +389,12 @@ func (v *ClusterClient) DeleteCluster(provider, name string) error { ClusterProviderName: provider, ClusterName: name, } + _, err := v.GetClusterContext(provider, name) + if err == nil { + return pkgerrors.Errorf("Cannot delete cluster until context is deleted: %v, %v", provider, name) + } - err := db.DBconn.Remove(v.db.storeName, key) + err = db.DBconn.Remove(v.db.storeName, key) if err != nil { return pkgerrors.Wrap(err, "Delete Cluster Entry;") } @@ -360,6 +402,185 @@ func (v *ClusterClient) DeleteCluster(provider, name string) error { return nil } +// Apply Network Intents associated with a cluster +func (v *ClusterClient) ApplyNetworkIntents(provider, name string) error { + + _, err := v.GetClusterContext(provider, name) + if err == nil { + return pkgerrors.Errorf("Cluster network intents have already been applied: %v, %v", provider, name) + } + + type resource struct { + name string + value string + } + + var resources []resource + + // Find all Network Intents for this cluster + networkIntents, err := NewNetworkClient().GetNetworks(provider, name) + if err != nil { + return pkgerrors.Wrap(err, "Error finding Network Intents") + } + for _, intent := range networkIntents { + var crNetwork = CrNetwork{ + ApiVersion: NETWORK_APIVERSION, + Kind: NETWORK_KIND, + } + crNetwork.Network = intent + // Produce the yaml CR document for each intent + y, err := yaml.Marshal(&crNetwork) + if err != nil { + log.Info("Error marshalling network intent to yaml", log.Fields{ + "error": err, + "intent": intent, + }) + continue + } + resources = append(resources, resource{ + name: intent.Metadata.Name + SEPARATOR + NETWORK_KIND, + value: string(y), + }) + } + + // Find all Provider Network Intents for this cluster + providerNetworkIntents, err := NewProviderNetClient().GetProviderNets(provider, name) + if err != nil { + return pkgerrors.Wrap(err, "Error finding Provider Network Intents") + } + for _, intent := range providerNetworkIntents { + var crProviderNet = CrProviderNet{ + ApiVersion: PROVIDER_NETWORK_APIVERSION, + Kind: PROVIDER_NETWORK_KIND, + } + crProviderNet.ProviderNet = intent + // Produce the yaml CR document for each intent + y, err := yaml.Marshal(&crProviderNet) + if err != nil { + log.Info("Error marshalling provider network intent to yaml", log.Fields{ + "error": err, + "intent": intent, + }) + continue + } + resources = append(resources, resource{ + name: intent.Metadata.Name + SEPARATOR + PROVIDER_NETWORK_KIND, + value: string(y), + }) + } + + if len(resources) == 0 { + return nil + } + + // Make an app context for the network intent resources + context := appcontext.AppContext{} + ctxVal, err := context.InitAppContext() + if err != nil { + return pkgerrors.Wrap(err, "Error creating AppContext") + } + handle, err := context.CreateCompositeApp() + if err != nil { + return pkgerrors.Wrap(err, "Error creating AppContext CompositeApp") + } + + // Add an app (fixed value) to the app context + apphandle, err := context.AddApp(handle, CONTEXT_CLUSTER_APP) + if err != nil { + cleanuperr := context.DeleteCompositeApp() + if cleanuperr != nil { + log.Warn("Error cleaning AppContext CompositeApp create failure", log.Fields{ + "cluster-provider": provider, + "cluster": name, + }) + } + return pkgerrors.Wrap(err, "Error adding App to AppContext") + } + + // Add a cluster to the app + clusterhandle, err := context.AddCluster(apphandle, provider+SEPARATOR+name) + if err != nil { + cleanuperr := context.DeleteCompositeApp() + if cleanuperr != nil { + log.Warn("Error cleaning AppContext after add cluster failure", log.Fields{ + "cluster-provider": provider, + "cluster": name, + }) + } + return pkgerrors.Wrap(err, "Error adding Cluster to AppContext") + } + + // add the resources to the app context + for _, resource := range resources { + _, err = context.AddResource(clusterhandle, resource.name, resource.value) + if err != nil { + cleanuperr := context.DeleteCompositeApp() + if cleanuperr != nil { + log.Warn("Error cleaning AppContext after add resource failure", log.Fields{ + "cluster-provider": provider, + "cluster": name, + "resource": resource.name, + }) + } + return pkgerrors.Wrap(err, "Error adding Resource to AppContext") + } + } + + // save the context in the cluster db record + key := ClusterKey{ + ClusterProviderName: provider, + ClusterName: name, + } + err = db.DBconn.Insert(v.db.storeName, key, nil, v.db.tagContext, ctxVal) + if err != nil { + cleanuperr := context.DeleteCompositeApp() + if cleanuperr != nil { + log.Warn("Error cleaning AppContext after DB insert failure", log.Fields{ + "cluster-provider": provider, + "cluster": name, + }) + } + return pkgerrors.Wrap(err, "Error adding AppContext to DB") + } + + // TODO: call resource synchronizer to instantiate the CRs in the cluster + + return nil +} + +// Terminate Network Intents associated with a cluster +func (v *ClusterClient) TerminateNetworkIntents(provider, name string) error { + context, err := v.GetClusterContext(provider, name) + if err != nil { + return pkgerrors.Wrapf(err, "Error finding AppContext for cluster: %v, %v", provider, name) + } + + // TODO: call resource synchronizer to terminate the CRs in the cluster + + // remove the app context + cleanuperr := context.DeleteCompositeApp() + if cleanuperr != nil { + log.Warn("Error deleted AppContext", log.Fields{ + "cluster-provider": provider, + "cluster": name, + }) + } + + // remove the app context field from the cluster db record + key := ClusterKey{ + ClusterProviderName: provider, + ClusterName: name, + } + err = db.DBconn.RemoveTag(v.db.storeName, key, v.db.tagContext) + if err != nil { + log.Warn("Error removing AppContext from Cluster document", log.Fields{ + "cluster-provider": provider, + "cluster": name, + }) + } + return nil +} + // CreateClusterLabel - create a new Cluster Label mongo document for a cluster-provider/cluster func (v *ClusterClient) CreateClusterLabel(provider string, cluster string, p ClusterLabel) (ClusterLabel, error) { //Construct key and tag to select the entry diff --git a/src/ncm/pkg/module/module_definitions.go b/src/ncm/pkg/module/module_definitions.go index 729a9dbd..36c865a5 100644 --- a/src/ncm/pkg/module/module_definitions.go +++ b/src/ncm/pkg/module/module_definitions.go @@ -31,28 +31,32 @@ const CNI_TYPE_OVN4NFV string = "ovn4nfv" var CNI_TYPES = [...]string{CNI_TYPE_OVN4NFV} +const YAML_START = "---\n" +const YAML_END = "...\n" + // It implements the interface for managing the ClusterProviders const MAX_DESCRIPTION_LEN int = 1024 const MAX_USERDATA_LEN int = 4096 type Metadata struct { - Name string `json:"name"` - Description string `json:"description"` - UserData1 string `json:"userData1"` - UserData2 string `json:"userData2"` + Name string `json:"name" yaml:"name"` + Description string `json:"description" yaml:"-"` + UserData1 string `json:"userData1" yaml:"-"` + UserData2 string `json:"userData2" yaml:"-"` } type ClientDbInfo struct { storeName string // name of the mongodb collection to use for client documents tagMeta string // attribute key name for the json data of a client document tagContent string // attribute key name for the file data of a client document + tagContext string // attribute key name for context object in App Context } type Ipv4Subnet struct { - Subnet string `json:"subnet"` // CIDR notation, e.g. 172.16.33.0/24 - Name string `json:"name"` - Gateway string `json:"gateway"` // IPv4 addre, e.g. 172.16.33.1/24 - Exclude string `json:"excludeIps"` // space separated list of single IPs or ranges e.g. "172.16.33.2 172.16.33.5..172.16.33.10" + Subnet string `json:"subnet" yaml:"subnet"` // CIDR notation, e.g. 172.16.33.0/24 + Name string `json:"name" yaml:"name"` + Gateway string `json:"gateway" yaml:"gateway"` // IPv4 addre, e.g. 172.16.33.1/24 + Exclude string `json:"excludeIps" yaml:"excludeIps"` // space separated list of single IPs or ranges e.g. "172.16.33.2 172.16.33.5..172.16.33.10" } const VLAN_NODE_ANY = "any" @@ -61,11 +65,11 @@ const VLAN_NODE_SPECIFIC = "specific" var VLAN_NODE_SELECTORS = [...]string{VLAN_NODE_ANY, VLAN_NODE_SPECIFIC} type Vlan struct { - VlanId int `json:"vlanID"` - ProviderInterfaceName string `json:"providerInterfaceName"` - LogicalInterfaceName string `json:"logicalInterfaceName"` - VlanNodeSelector string `json:"vlanNodeSelector"` - NodeLabelList []string `json:"nodeLabelList"` + VlanId int `json:"vlanID" yaml:"vlanId"` + ProviderInterfaceName string `json:"providerInterfaceName" yaml:"providerInterfaceName"` + LogicalInterfaceName string `json:"logicalInterfaceName" yaml:"logicalInterfaceName"` + VlanNodeSelector string `json:"vlanNodeSelector" yaml:"vlanNodeSelector"` + NodeLabelList []string `json:"nodeLabelList" yaml:"nodeLabelList"` } // Check for valid format Metadata diff --git a/src/ncm/pkg/module/netcontrolintent.go b/src/ncm/pkg/module/netcontrolintent.go index 5ef9dffe..c005a935 100644 --- a/src/ncm/pkg/module/netcontrolintent.go +++ b/src/ncm/pkg/module/netcontrolintent.go @@ -17,8 +17,17 @@ 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/network.go b/src/ncm/pkg/module/network.go index 2d4121e9..cfb414c5 100644 --- a/src/ncm/pkg/module/network.go +++ b/src/ncm/pkg/module/network.go @@ -24,8 +24,8 @@ import ( // Network contains the parameters needed for dynamic networks type Network struct { - Metadata Metadata `json:"metadata"` - Spec NetworkSpec `json:"spec"` + Metadata Metadata `json:"metadata" yaml:"metadata"` + Spec NetworkSpec `json:"spec" yaml:"spec"` } type NetworkSpec struct { @@ -40,6 +40,16 @@ type NetworkKey struct { NetworkName string `json:"network"` } +// structure for the Network Custom Resource +type CrNetwork struct { + ApiVersion string `yaml:"apiVersion"` + Kind string `yaml:"kind"` + Network Network +} + +const NETWORK_APIVERSION = "k8s.plugin.opnfv.org/v1alpha1" +const NETWORK_KIND = "Network" + // Manager is an interface exposing the Network functionality type NetworkManager interface { CreateNetwork(pr Network, clusterProvider, cluster string, exists bool) (Network, error) diff --git a/src/ncm/pkg/module/providernet.go b/src/ncm/pkg/module/providernet.go index 5e2c0343..0435f2ba 100644 --- a/src/ncm/pkg/module/providernet.go +++ b/src/ncm/pkg/module/providernet.go @@ -29,12 +29,22 @@ type ProviderNet struct { } type ProviderNetSpec struct { - CniType string `json:"cniType"` - Ipv4Subnets []Ipv4Subnet `json:"ipv4Subnets"` - ProviderNetType string `json:"providerNetType"` - Vlan Vlan `json:"vlan"` + CniType string `json:"cniType" yaml:"cniType"` + Ipv4Subnets []Ipv4Subnet `json:"ipv4Subnets" yaml:"ipv4Subnets"` + ProviderNetType string `json:"providerNetType" yaml:"providerNetType"` + Vlan Vlan `json:"vlan" yaml:"vlan"` } +// structure for the Network Custom Resource +type CrProviderNet struct { + ApiVersion string `yaml:"apiVersion"` + Kind string `yaml:"kind"` + ProviderNet ProviderNet +} + +const PROVIDER_NETWORK_APIVERSION = "k8s.plugin.opnfv.org/v1alpha1" +const PROVIDER_NETWORK_KIND = "ProviderNetwork" + // ProviderNetKey is the key structure that is used in the database type ProviderNetKey struct { ClusterProviderName string `json:"provider"` diff --git a/src/ncm/pkg/module/resources.go b/src/ncm/pkg/module/resources.go new file mode 100644 index 00000000..24c9833e --- /dev/null +++ b/src/ncm/pkg/module/resources.go @@ -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, + }) + } +} |