diff options
author | Eric Multanen <eric.w.multanen@intel.com> | 2020-05-28 17:07:20 -0700 |
---|---|---|
committer | Eric Multanen <eric.w.multanen@intel.com> | 2020-06-02 14:00:07 -0700 |
commit | ad7782cbf83c11f152a6457f3808a4da99a1ae56 (patch) | |
tree | e88276d8f0d55bd58a903d1c31ab4e43e4011193 /src/ovnaction/pkg/module | |
parent | c257a136355a794f5bf778f670c041e8958c3608 (diff) |
Create OVN network action controller from ncm
Split out part of ncm microservice to act as the
Onv4k8s network action controller for the orchestrator.
No code changes really - just moving around to fit the
architectural plan.
Issue-ID: MULTICLOUD-1029
Signed-off-by: Eric Multanen <eric.w.multanen@intel.com>
Change-Id: I17292ac72d041050269f05fc4a0c2a6ca741aeb5
Diffstat (limited to 'src/ovnaction/pkg/module')
-rw-r--r-- | src/ovnaction/pkg/module/chaining.go | 209 | ||||
-rw-r--r-- | src/ovnaction/pkg/module/module.go | 37 | ||||
-rw-r--r-- | src/ovnaction/pkg/module/module_definitions.go | 68 | ||||
-rw-r--r-- | src/ovnaction/pkg/module/netcontrolintent.go | 295 | ||||
-rw-r--r-- | src/ovnaction/pkg/module/resources.go | 273 | ||||
-rw-r--r-- | src/ovnaction/pkg/module/workloadifintent.go | 188 | ||||
-rw-r--r-- | src/ovnaction/pkg/module/workloadintent.go | 181 |
7 files changed, 1251 insertions, 0 deletions
diff --git a/src/ovnaction/pkg/module/chaining.go b/src/ovnaction/pkg/module/chaining.go new file mode 100644 index 00000000..45f061fa --- /dev/null +++ b/src/ovnaction/pkg/module/chaining.go @@ -0,0 +1,209 @@ +/* + * 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 ( + "github.com/onap/multicloud-k8s/src/orchestrator/pkg/infra/db" + + pkgerrors "github.com/pkg/errors" +) + +// Chain defines the high level structure of a network chain document +type Chain struct { + Metadata Metadata `json:"metadata" yaml:"metadata"` + Spec NetworkChainingSpec `json:"spec" yaml:"spec"` +} + +// NetworkChainingSpec contains the specification of a network chain +type NetworkChainingSpec struct { + ChainType string `json:"type"` + RoutingSpec RouteSpec `json:"routingSpec"` +} + +// RouteSpec contains the routing specificaiton of a network chain +type RouteSpec struct { + LeftNetwork []RoutingNetwork `json:"leftNetwork"` + RightNetwork []RoutingNetwork `json:"rightNetwork"` + NetworkChain string `json:"networkChain"` + Namespace string `json:"namespace"` +} + +// RoutingNetwork contains the route networkroute network details for en element of a network chain +type RoutingNetwork struct { + NetworkName string `json:"networkName"` + GatewayIP string `json:"gatewayIp"` + Subnet string `json:"subnet"` +} + +// ChainKey is the key structure that is used in the database +type ChainKey struct { + Project string `json:"project"` + CompositeApp string `json:"compositeapp"` + CompositeAppVersion string `json:"compositeappversion"` + NetControlIntent string `json:"netcontrolintent"` + NetworkChain string `json:"networkchain"` +} + +// CrChain is the structure for the Network Chain Custom Resource +type CrChain struct { + APIVersion string `yaml:"apiVersion"` + Kind string `yaml:"kind"` + Chain Chain +} + +// RoutingChainType is currently only defined chaining type +const RoutingChainType = "routing" + +// ChainingAPIVersion is the kubernetes version of a network chain custom resource +const ChainingAPIVersion = "k8s.plugin.opnfv.org/v1" + +// ChainingKind is the Kind string for a network chain +const ChainingKind = "NetworkChaining" + +// ChainManager is an interface exposing the Chain functionality +type ChainManager interface { + CreateChain(ch Chain, pr, ca, caver, netctrlint string, exists bool) (Chain, error) + GetChain(name, pr, ca, caver, netctrlint string) (Chain, error) + GetChains(pr, ca, caver, netctrlint string) ([]Chain, error) + DeleteChain(name, pr, ca, caver, netctrlint string) error +} + +// ChainClient implements the Manager +// It will also be used to maintain some localized state +type ChainClient struct { + db ClientDbInfo +} + +// NewChainClient returns an instance of the ChainClient +// which implements the Manager +func NewChainClient() *ChainClient { + return &ChainClient{ + db: ClientDbInfo{ + storeName: "orchestrator", + tagMeta: "chainmetadata", + }, + } +} + +// CreateChain - create a new Chain +func (v *ChainClient) CreateChain(ch Chain, pr, ca, caver, netctrlint string, exists bool) (Chain, error) { + //Construct key and tag to select the entry + key := ChainKey{ + Project: pr, + CompositeApp: ca, + CompositeAppVersion: caver, + NetControlIntent: netctrlint, + NetworkChain: ch.Metadata.Name, + } + + //Check if the Network Control Intent exists + _, err := NewNetControlIntentClient().GetNetControlIntent(netctrlint, pr, ca, caver) + if err != nil { + return Chain{}, pkgerrors.Errorf("Network Control Intent %v does not exist", netctrlint) + } + + //Check if this Chain already exists + _, err = v.GetChain(ch.Metadata.Name, pr, ca, caver, netctrlint) + if err == nil && !exists { + return Chain{}, pkgerrors.New("Chain already exists") + } + + err = db.DBconn.Insert(v.db.storeName, key, nil, v.db.tagMeta, ch) + if err != nil { + return Chain{}, pkgerrors.Wrap(err, "Creating DB Entry") + } + + return ch, nil +} + +// GetChain returns the Chain for corresponding name +func (v *ChainClient) GetChain(name, pr, ca, caver, netctrlint string) (Chain, error) { + //Construct key and tag to select the entry + key := ChainKey{ + Project: pr, + CompositeApp: ca, + CompositeAppVersion: caver, + NetControlIntent: netctrlint, + NetworkChain: name, + } + + value, err := db.DBconn.Find(v.db.storeName, key, v.db.tagMeta) + if err != nil { + return Chain{}, pkgerrors.Wrap(err, "Get Chain") + } + + //value is a byte array + if value != nil { + ch := Chain{} + err = db.DBconn.Unmarshal(value[0], &ch) + if err != nil { + return Chain{}, pkgerrors.Wrap(err, "Unmarshalling Value") + } + return ch, nil + } + + return Chain{}, pkgerrors.New("Error getting Chain") +} + +// GetChains returns all of the Chains for for the given network control intent +func (v *ChainClient) GetChains(pr, ca, caver, netctrlint string) ([]Chain, error) { + //Construct key and tag to select the entry + key := ChainKey{ + Project: pr, + CompositeApp: ca, + CompositeAppVersion: caver, + NetControlIntent: netctrlint, + NetworkChain: "", + } + + var resp []Chain + values, err := db.DBconn.Find(v.db.storeName, key, v.db.tagMeta) + if err != nil { + return []Chain{}, pkgerrors.Wrap(err, "Get Chains") + } + + for _, value := range values { + cp := Chain{} + err = db.DBconn.Unmarshal(value, &cp) + if err != nil { + return []Chain{}, pkgerrors.Wrap(err, "Unmarshalling Value") + } + resp = append(resp, cp) + } + + return resp, nil +} + +// DeleteChain deletes the Chain from the database +func (v *ChainClient) DeleteChain(name, pr, ca, caver, netctrlint string) error { + + //Construct key and tag to select the entry + key := ChainKey{ + Project: pr, + CompositeApp: ca, + CompositeAppVersion: caver, + NetControlIntent: netctrlint, + NetworkChain: name, + } + + err := db.DBconn.Remove(v.db.storeName, key) + if err != nil { + return pkgerrors.Wrap(err, "Delete Chain Entry;") + } + + return nil +} diff --git a/src/ovnaction/pkg/module/module.go b/src/ovnaction/pkg/module/module.go new file mode 100644 index 00000000..2b4b9358 --- /dev/null +++ b/src/ovnaction/pkg/module/module.go @@ -0,0 +1,37 @@ +/* + * 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 + +// Client for using the services in the ncm +type Client struct { + NetControlIntent *NetControlIntentClient + WorkloadIntent *WorkloadIntentClient + WorkloadIfIntent *WorkloadIfIntentClient + Chain *ChainClient + // Add Clients for API's here +} + +// NewClient creates a new client for using the services +func NewClient() *Client { + c := &Client{} + c.NetControlIntent = NewNetControlIntentClient() + c.WorkloadIntent = NewWorkloadIntentClient() + c.WorkloadIfIntent = NewWorkloadIfIntentClient() + c.Chain = NewChainClient() + // Add Client API handlers here + return c +} diff --git a/src/ovnaction/pkg/module/module_definitions.go b/src/ovnaction/pkg/module/module_definitions.go new file mode 100644 index 00000000..a868fdaf --- /dev/null +++ b/src/ovnaction/pkg/module/module_definitions.go @@ -0,0 +1,68 @@ +/* + * 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 ( + "github.com/onap/multicloud-k8s/src/orchestrator/pkg/infra/validation" + pkgerrors "github.com/pkg/errors" +) + +const CNI_TYPE_OVN4NFV string = "ovn4nfv" + +var CNI_TYPES = [...]string{CNI_TYPE_OVN4NFV} + +// 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" 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 +} + +// Check for valid format Metadata +func IsValidMetadata(metadata Metadata) error { + errs := validation.IsValidName(metadata.Name) + if len(errs) > 0 { + return pkgerrors.Errorf("Invalid Metadata name=[%v], errors: %v", metadata.Name, errs) + } + + errs = validation.IsValidString(metadata.Description, 0, MAX_DESCRIPTION_LEN, validation.VALID_ANY_STR) + if len(errs) > 0 { + return pkgerrors.Errorf("Invalid Metadata description=[%v], errors: %v", metadata.Description, errs) + } + + errs = validation.IsValidString(metadata.UserData1, 0, MAX_DESCRIPTION_LEN, validation.VALID_ANY_STR) + if len(errs) > 0 { + return pkgerrors.Errorf("Invalid Metadata description=[%v], errors: %v", metadata.UserData1, errs) + } + + errs = validation.IsValidString(metadata.UserData2, 0, MAX_DESCRIPTION_LEN, validation.VALID_ANY_STR) + if len(errs) > 0 { + return pkgerrors.Errorf("Invalid Metadata description=[%v], errors: %v", metadata.UserData2, errs) + } + + return nil +} diff --git a/src/ovnaction/pkg/module/netcontrolintent.go b/src/ovnaction/pkg/module/netcontrolintent.go new file mode 100644 index 00000000..c005a935 --- /dev/null +++ b/src/ovnaction/pkg/module/netcontrolintent.go @@ -0,0 +1,295 @@ +/* + * 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" + "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" +) + +// NetControlIntent contains the parameters needed for dynamic networks +type NetControlIntent struct { + Metadata Metadata `json:"metadata"` +} + +// NetControlIntentKey is the key structure that is used in the database +type NetControlIntentKey struct { + NetControlIntent string `json:"netcontrolintent"` + Project string `json:"project"` + CompositeApp string `json:"compositeapp"` + CompositeAppVersion string `json:"compositeappversion"` +} + +// Manager is an interface exposing the NetControlIntent functionality +type NetControlIntentManager interface { + CreateNetControlIntent(nci NetControlIntent, project, compositeapp, compositeappversion string, exists bool) (NetControlIntent, error) + 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 +// It will also be used to maintain some localized state +type NetControlIntentClient struct { + db ClientDbInfo +} + +// NewNetControlIntentClient returns an instance of the NetControlIntentClient +// which implements the Manager +func NewNetControlIntentClient() *NetControlIntentClient { + return &NetControlIntentClient{ + db: ClientDbInfo{ + storeName: "orchestrator", + tagMeta: "netcontrolintentmetadata", + }, + } +} + +// CreateNetControlIntent - create a new NetControlIntent +func (v *NetControlIntentClient) CreateNetControlIntent(nci NetControlIntent, project, compositeapp, compositeappversion string, exists bool) (NetControlIntent, error) { + + //Construct key and tag to select the entry + key := NetControlIntentKey{ + NetControlIntent: nci.Metadata.Name, + Project: project, + CompositeApp: compositeapp, + CompositeAppVersion: compositeappversion, + } + + //Check if this NetControlIntent already exists + _, err := v.GetNetControlIntent(nci.Metadata.Name, project, compositeapp, compositeappversion) + if err == nil && !exists { + return NetControlIntent{}, pkgerrors.New("NetControlIntent already exists") + } + + err = db.DBconn.Insert(v.db.storeName, key, nil, v.db.tagMeta, nci) + if err != nil { + return NetControlIntent{}, pkgerrors.Wrap(err, "Creating DB Entry") + } + + return nci, nil +} + +// GetNetControlIntent returns the NetControlIntent for corresponding name +func (v *NetControlIntentClient) GetNetControlIntent(name, project, compositeapp, compositeappversion string) (NetControlIntent, error) { + + //Construct key and tag to select the entry + key := NetControlIntentKey{ + NetControlIntent: name, + Project: project, + CompositeApp: compositeapp, + CompositeAppVersion: compositeappversion, + } + + value, err := db.DBconn.Find(v.db.storeName, key, v.db.tagMeta) + if err != nil { + return NetControlIntent{}, pkgerrors.Wrap(err, "Get NetControlIntent") + } + + //value is a byte array + if value != nil { + nci := NetControlIntent{} + err = db.DBconn.Unmarshal(value[0], &nci) + if err != nil { + return NetControlIntent{}, pkgerrors.Wrap(err, "Unmarshalling Value") + } + return nci, nil + } + + return NetControlIntent{}, pkgerrors.New("Error getting NetControlIntent") +} + +// GetNetControlIntentList returns all of the NetControlIntent for corresponding name +func (v *NetControlIntentClient) GetNetControlIntents(project, compositeapp, compositeappversion string) ([]NetControlIntent, error) { + + //Construct key and tag to select the entry + key := NetControlIntentKey{ + NetControlIntent: "", + Project: project, + CompositeApp: compositeapp, + CompositeAppVersion: compositeappversion, + } + + var resp []NetControlIntent + values, err := db.DBconn.Find(v.db.storeName, key, v.db.tagMeta) + if err != nil { + return []NetControlIntent{}, pkgerrors.Wrap(err, "Get NetControlIntents") + } + + for _, value := range values { + nci := NetControlIntent{} + err = db.DBconn.Unmarshal(value, &nci) + if err != nil { + return []NetControlIntent{}, pkgerrors.Wrap(err, "Unmarshalling Value") + } + resp = append(resp, nci) + } + + return resp, nil +} + +// Delete the NetControlIntent from database +func (v *NetControlIntentClient) DeleteNetControlIntent(name, project, compositeapp, compositeappversion string) error { + + //Construct key and tag to select the entry + key := NetControlIntentKey{ + NetControlIntent: name, + Project: project, + CompositeApp: compositeapp, + CompositeAppVersion: compositeappversion, + } + + err := db.DBconn.Remove(v.db.storeName, key) + if err != nil { + return pkgerrors.Wrap(err, "Delete NetControlIntent Entry;") + } + + 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/ovnaction/pkg/module/resources.go b/src/ovnaction/pkg/module/resources.go new file mode 100644 index 00000000..24c9833e --- /dev/null +++ b/src/ovnaction/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, + }) + } +} diff --git a/src/ovnaction/pkg/module/workloadifintent.go b/src/ovnaction/pkg/module/workloadifintent.go new file mode 100644 index 00000000..55062564 --- /dev/null +++ b/src/ovnaction/pkg/module/workloadifintent.go @@ -0,0 +1,188 @@ +/* + * 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 ( + "github.com/onap/multicloud-k8s/src/orchestrator/pkg/infra/db" + + pkgerrors "github.com/pkg/errors" +) + +// WorkloadIfIntent contains the parameters needed for dynamic networks +type WorkloadIfIntent struct { + Metadata Metadata `json:"metadata"` + Spec WorkloadIfIntentSpec `json:"spec"` +} + +type WorkloadIfIntentSpec struct { + IfName string `json:"interface"` + NetworkName string `json:"name"` + DefaultGateway string `json:"defaultGateway"` // optional, default value is "false" + IpAddr string `json:"ipAddress,omitempty"` // optional, if not provided then will be dynamically allocated + MacAddr string `json:"macAddress,omitempty"` // optional, if not provided then will be dynamically allocated +} + +// WorkloadIfIntentKey is the key structure that is used in the database +type WorkloadIfIntentKey struct { + Project string `json:"provider"` + CompositeApp string `json:"compositeapp"` + CompositeAppVersion string `json:"compositeappversion"` + NetControlIntent string `json:"netcontrolintent"` + WorkloadIntent string `json:"workloadintent"` + WorkloadIfIntent string `json:"workloadifintent"` +} + +// Manager is an interface exposing the WorkloadIfIntent functionality +type WorkloadIfIntentManager interface { + CreateWorkloadIfIntent(wi WorkloadIfIntent, project, compositeapp, compositeappversion, netcontrolintent, workloadintent string, exists bool) (WorkloadIfIntent, error) + GetWorkloadIfIntent(name, project, compositeapp, compositeappversion, netcontrolintent, workloadintent string) (WorkloadIfIntent, error) + GetWorkloadIfIntents(project, compositeapp, compositeappversion, netcontrolintent, workloadintent string) ([]WorkloadIfIntent, error) + DeleteWorkloadIfIntent(name, project, compositeapp, compositeappversion, netcontrolintent, workloadintent string) error +} + +// WorkloadIfIntentClient implements the Manager +// It will also be used to maintain some localized state +type WorkloadIfIntentClient struct { + db ClientDbInfo +} + +// NewWorkloadIfIntentClient returns an instance of the WorkloadIfIntentClient +// which implements the Manager +func NewWorkloadIfIntentClient() *WorkloadIfIntentClient { + return &WorkloadIfIntentClient{ + db: ClientDbInfo{ + storeName: "orchestrator", + tagMeta: "workloadifintentmetadata", + }, + } +} + +// CreateWorkloadIfIntent - create a new WorkloadIfIntent +func (v *WorkloadIfIntentClient) CreateWorkloadIfIntent(wif WorkloadIfIntent, project, compositeapp, compositeappversion, netcontrolintent, workloadintent string, exists bool) (WorkloadIfIntent, error) { + + //Construct key and tag to select the entry + key := WorkloadIfIntentKey{ + Project: project, + CompositeApp: compositeapp, + CompositeAppVersion: compositeappversion, + NetControlIntent: netcontrolintent, + WorkloadIntent: workloadintent, + WorkloadIfIntent: wif.Metadata.Name, + } + + //Check if the Workload Intent exists + _, err := NewWorkloadIntentClient().GetWorkloadIntent(workloadintent, project, compositeapp, compositeappversion, netcontrolintent) + if err != nil { + return WorkloadIfIntent{}, pkgerrors.Errorf("Workload Intent %v does not exist", workloadintent) + } + + //Check if this WorkloadIfIntent already exists + _, err = v.GetWorkloadIfIntent(wif.Metadata.Name, project, compositeapp, compositeappversion, netcontrolintent, workloadintent) + if err == nil && !exists { + return WorkloadIfIntent{}, pkgerrors.New("WorkloadIfIntent already exists") + } + + err = db.DBconn.Insert(v.db.storeName, key, nil, v.db.tagMeta, wif) + if err != nil { + return WorkloadIfIntent{}, pkgerrors.Wrap(err, "Creating DB Entry") + } + + return wif, nil +} + +// GetWorkloadIfIntent returns the WorkloadIfIntent for corresponding name +func (v *WorkloadIfIntentClient) GetWorkloadIfIntent(name, project, compositeapp, compositeappversion, netcontrolintent, workloadintent string) (WorkloadIfIntent, error) { + + //Construct key and tag to select the entry + key := WorkloadIfIntentKey{ + Project: project, + CompositeApp: compositeapp, + CompositeAppVersion: compositeappversion, + NetControlIntent: netcontrolintent, + WorkloadIntent: workloadintent, + WorkloadIfIntent: name, + } + + value, err := db.DBconn.Find(v.db.storeName, key, v.db.tagMeta) + if err != nil { + return WorkloadIfIntent{}, pkgerrors.Wrap(err, "Get WorkloadIfIntent") + } + + //value is a byte array + if value != nil { + wif := WorkloadIfIntent{} + err = db.DBconn.Unmarshal(value[0], &wif) + if err != nil { + return WorkloadIfIntent{}, pkgerrors.Wrap(err, "Unmarshalling Value") + } + return wif, nil + } + + return WorkloadIfIntent{}, pkgerrors.New("Error getting WorkloadIfIntent") +} + +// GetWorkloadIfIntentList returns all of the WorkloadIfIntent for corresponding name +func (v *WorkloadIfIntentClient) GetWorkloadIfIntents(project, compositeapp, compositeappversion, netcontrolintent, workloadintent string) ([]WorkloadIfIntent, error) { + + //Construct key and tag to select the entry + key := WorkloadIfIntentKey{ + Project: project, + CompositeApp: compositeapp, + CompositeAppVersion: compositeappversion, + NetControlIntent: netcontrolintent, + WorkloadIntent: workloadintent, + WorkloadIfIntent: "", + } + + var resp []WorkloadIfIntent + values, err := db.DBconn.Find(v.db.storeName, key, v.db.tagMeta) + if err != nil { + return []WorkloadIfIntent{}, pkgerrors.Wrap(err, "Get WorkloadIfIntents") + } + + for _, value := range values { + wif := WorkloadIfIntent{} + err = db.DBconn.Unmarshal(value, &wif) + if err != nil { + return []WorkloadIfIntent{}, pkgerrors.Wrap(err, "Unmarshalling Value") + } + resp = append(resp, wif) + } + + return resp, nil +} + +// Delete the WorkloadIfIntent from database +func (v *WorkloadIfIntentClient) DeleteWorkloadIfIntent(name, project, compositeapp, compositeappversion, netcontrolintent, workloadintent string) error { + + //Construct key and tag to select the entry + key := WorkloadIfIntentKey{ + Project: project, + CompositeApp: compositeapp, + CompositeAppVersion: compositeappversion, + NetControlIntent: netcontrolintent, + WorkloadIntent: workloadintent, + WorkloadIfIntent: name, + } + + err := db.DBconn.Remove(v.db.storeName, key) + if err != nil { + return pkgerrors.Wrap(err, "Delete WorkloadIfIntent Entry;") + } + + return nil +} diff --git a/src/ovnaction/pkg/module/workloadintent.go b/src/ovnaction/pkg/module/workloadintent.go new file mode 100644 index 00000000..e6916954 --- /dev/null +++ b/src/ovnaction/pkg/module/workloadintent.go @@ -0,0 +1,181 @@ +/* + * 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 ( + "github.com/onap/multicloud-k8s/src/orchestrator/pkg/infra/db" + + pkgerrors "github.com/pkg/errors" +) + +// WorkloadIntent contains the parameters needed for dynamic networks +type WorkloadIntent struct { + Metadata Metadata `json:"metadata"` + Spec WorkloadIntentSpec `json:"spec"` +} + +type WorkloadIntentSpec struct { + AppName string `json:"application-name"` + WorkloadResource string `json:"workload-resource"` + Type string `json:"type"` +} + +// WorkloadIntentKey is the key structure that is used in the database +type WorkloadIntentKey struct { + Project string `json:"provider"` + CompositeApp string `json:"compositeapp"` + CompositeAppVersion string `json:"compositeappversion"` + NetControlIntent string `json:"netcontrolintent"` + WorkloadIntent string `json:"workloadintent"` +} + +// Manager is an interface exposing the WorkloadIntent functionality +type WorkloadIntentManager interface { + CreateWorkloadIntent(wi WorkloadIntent, project, compositeapp, compositeappversion, netcontrolintent string, exists bool) (WorkloadIntent, error) + GetWorkloadIntent(name, project, compositeapp, compositeappversion, netcontrolintent string) (WorkloadIntent, error) + GetWorkloadIntents(project, compositeapp, compositeappversion, netcontrolintent string) ([]WorkloadIntent, error) + DeleteWorkloadIntent(name, project, compositeapp, compositeappversion, netcontrolintent string) error +} + +// WorkloadIntentClient implements the Manager +// It will also be used to maintain some localized state +type WorkloadIntentClient struct { + db ClientDbInfo +} + +// NewWorkloadIntentClient returns an instance of the WorkloadIntentClient +// which implements the Manager +func NewWorkloadIntentClient() *WorkloadIntentClient { + return &WorkloadIntentClient{ + db: ClientDbInfo{ + storeName: "orchestrator", + tagMeta: "workloadintentmetadata", + }, + } +} + +// CreateWorkloadIntent - create a new WorkloadIntent +func (v *WorkloadIntentClient) CreateWorkloadIntent(wi WorkloadIntent, project, compositeapp, compositeappversion, netcontrolintent string, exists bool) (WorkloadIntent, error) { + + //Construct key and tag to select the entry + key := WorkloadIntentKey{ + Project: project, + CompositeApp: compositeapp, + CompositeAppVersion: compositeappversion, + NetControlIntent: netcontrolintent, + WorkloadIntent: wi.Metadata.Name, + } + + //Check if the Network Control Intent exists + _, err := NewNetControlIntentClient().GetNetControlIntent(netcontrolintent, project, compositeapp, compositeappversion) + if err != nil { + return WorkloadIntent{}, pkgerrors.Errorf("Network Control Intent %v does not exist", netcontrolintent) + } + + //Check if this WorkloadIntent already exists + _, err = v.GetWorkloadIntent(wi.Metadata.Name, project, compositeapp, compositeappversion, netcontrolintent) + if err == nil && !exists { + return WorkloadIntent{}, pkgerrors.New("WorkloadIntent already exists") + } + + err = db.DBconn.Insert(v.db.storeName, key, nil, v.db.tagMeta, wi) + if err != nil { + return WorkloadIntent{}, pkgerrors.Wrap(err, "Creating DB Entry") + } + + return wi, nil +} + +// GetWorkloadIntent returns the WorkloadIntent for corresponding name +func (v *WorkloadIntentClient) GetWorkloadIntent(name, project, compositeapp, compositeappversion, netcontrolintent string) (WorkloadIntent, error) { + + //Construct key and tag to select the entry + key := WorkloadIntentKey{ + Project: project, + CompositeApp: compositeapp, + CompositeAppVersion: compositeappversion, + NetControlIntent: netcontrolintent, + WorkloadIntent: name, + } + + value, err := db.DBconn.Find(v.db.storeName, key, v.db.tagMeta) + if err != nil { + return WorkloadIntent{}, pkgerrors.Wrap(err, "Get WorkloadIntent") + } + + //value is a byte array + if value != nil { + wi := WorkloadIntent{} + err = db.DBconn.Unmarshal(value[0], &wi) + if err != nil { + return WorkloadIntent{}, pkgerrors.Wrap(err, "Unmarshalling Value") + } + return wi, nil + } + + return WorkloadIntent{}, pkgerrors.New("Error getting WorkloadIntent") +} + +// GetWorkloadIntentList returns all of the WorkloadIntent for corresponding name +func (v *WorkloadIntentClient) GetWorkloadIntents(project, compositeapp, compositeappversion, netcontrolintent string) ([]WorkloadIntent, error) { + + //Construct key and tag to select the entry + key := WorkloadIntentKey{ + Project: project, + CompositeApp: compositeapp, + CompositeAppVersion: compositeappversion, + NetControlIntent: netcontrolintent, + WorkloadIntent: "", + } + + var resp []WorkloadIntent + values, err := db.DBconn.Find(v.db.storeName, key, v.db.tagMeta) + if err != nil { + return []WorkloadIntent{}, pkgerrors.Wrap(err, "Get WorkloadIntents") + } + + for _, value := range values { + wi := WorkloadIntent{} + err = db.DBconn.Unmarshal(value, &wi) + if err != nil { + return []WorkloadIntent{}, pkgerrors.Wrap(err, "Unmarshalling Value") + } + resp = append(resp, wi) + } + + return resp, nil +} + +// Delete the WorkloadIntent from database +func (v *WorkloadIntentClient) DeleteWorkloadIntent(name, project, compositeapp, compositeappversion, netcontrolintent string) error { + + //Construct key and tag to select the entry + key := WorkloadIntentKey{ + Project: project, + CompositeApp: compositeapp, + CompositeAppVersion: compositeappversion, + NetControlIntent: netcontrolintent, + WorkloadIntent: name, + } + + err := db.DBconn.Remove(v.db.storeName, key) + if err != nil { + return pkgerrors.Wrap(err, "Delete WorkloadIntent Entry;") + } + + return nil +} |