Add apply API for network intents 33/104433/12
authorEric Multanen <eric.w.multanen@intel.com>
Wed, 15 Apr 2020 00:54:45 +0000 (17:54 -0700)
committerEric Multanen <eric.w.multanen@intel.com>
Thu, 16 Apr 2020 23:58:05 +0000 (16:58 -0700)
Support POST API to 'apply' and 'terminate' network and
providernetwork intents for a given cluster.

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

src/ncm/api/api.go
src/ncm/api/clusterhandler.go
src/ncm/api/clusterhandler_test.go
src/ncm/go.mod
src/ncm/pkg/module/cluster.go
src/ncm/pkg/module/module_definitions.go
src/ncm/pkg/module/network.go
src/ncm/pkg/module/providernet.go

index fcef7b4..7892113 100644 (file)
@@ -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")
index 8c50f72..78453aa 100644 (file)
@@ -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)
index a26c41b..b32df52 100644 (file)
@@ -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
index 32ff481..ddf9667 100644 (file)
@@ -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
index 4ca4e7c..2397a09 100644 (file)
 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
index 729a9db..36c865a 100644 (file)
@@ -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
index 2d4121e..cfb414c 100644 (file)
@@ -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)
index 5e2c034..0435f2b 100644 (file)
@@ -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"`