From b7a4e00a6cd9d4ce903448ea958d85f17c8d68ab Mon Sep 17 00:00:00 2001 From: Eric Multanen Date: Tue, 14 Apr 2020 17:54:45 -0700 Subject: Add apply API for network intents Support POST API to 'apply' and 'terminate' network and providernetwork intents for a given cluster. Issue-ID: MULTICLOUD-1029 Signed-off-by: Eric Multanen Change-Id: I8c9bae9e93aeeb68654eaab1f15de9883c22215c --- src/ncm/api/api.go | 2 + src/ncm/api/clusterhandler.go | 30 +++++ src/ncm/api/clusterhandler_test.go | 18 +++ src/ncm/go.mod | 1 + src/ncm/pkg/module/cluster.go | 223 ++++++++++++++++++++++++++++++- src/ncm/pkg/module/module_definitions.go | 30 +++-- src/ncm/pkg/module/network.go | 14 +- src/ncm/pkg/module/providernet.go | 18 ++- 8 files changed, 316 insertions(+), 20 deletions(-) diff --git a/src/ncm/api/api.go b/src/ncm/api/api.go index fcef7b43..7892113a 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") 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/go.mod b/src/ncm/go.mod index 32ff481a..ddf96677 100644 --- a/src/ncm/go.mod +++ b/src/ncm/go.mod @@ -12,6 +12,7 @@ require ( 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 + 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 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/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"` -- cgit 1.2.3-korg