diff options
Diffstat (limited to 'src/orchestrator/pkg')
-rw-r--r-- | src/orchestrator/pkg/infra/db/mock.go | 21 | ||||
-rw-r--r-- | src/orchestrator/pkg/infra/db/mongo_test.go | 3 | ||||
-rw-r--r-- | src/orchestrator/pkg/module/add_intents.go | 184 | ||||
-rw-r--r-- | src/orchestrator/pkg/module/app_intent.go | 195 | ||||
-rw-r--r-- | src/orchestrator/pkg/module/app_intent_test.go | 271 | ||||
-rw-r--r-- | src/orchestrator/pkg/module/app_profile.go | 296 | ||||
-rw-r--r-- | src/orchestrator/pkg/module/composite_profile.go | 192 | ||||
-rw-r--r-- | src/orchestrator/pkg/module/composite_profile_test.go | 175 | ||||
-rw-r--r-- | src/orchestrator/pkg/module/deployment_intent_groups.go | 177 | ||||
-rw-r--r-- | src/orchestrator/pkg/module/deployment_intent_groups_test.go | 230 | ||||
-rw-r--r-- | src/orchestrator/pkg/module/generic_placement_intent.go | 165 | ||||
-rw-r--r-- | src/orchestrator/pkg/module/generic_placement_intent_test.go | 184 | ||||
-rw-r--r-- | src/orchestrator/pkg/module/module.go | 20 |
13 files changed, 2106 insertions, 7 deletions
diff --git a/src/orchestrator/pkg/infra/db/mock.go b/src/orchestrator/pkg/infra/db/mock.go index 6b46a524..79366d10 100644 --- a/src/orchestrator/pkg/infra/db/mock.go +++ b/src/orchestrator/pkg/infra/db/mock.go @@ -45,6 +45,10 @@ func (m *MockDB) Create(table string, key Key, tag string, data interface{}) err return m.Err } +func (m *MockDB) Insert(table string, key Key, query interface{}, tag string, data interface{}) error { + return m.Err +} + func (m *MockDB) Update(table string, key Key, tag string, data interface{}) error { return m.Err } @@ -73,7 +77,22 @@ func (m *MockDB) Read(table string, key Key, tag string) ([]byte, error) { return nil, m.Err } +func (m *MockDB) Find(table string, key Key, tag string) ([][]byte, error) { + if m.Err != nil { + return nil, m.Err + } + + str := fmt.Sprintf("%v", key) + for k, v := range m.Items { + if k == str { + + return [][]byte{v[tag]}, nil + } + } + + return nil, m.Err +} + func (m *MockDB) Delete(table string, key Key, tag string) error { return m.Err } - diff --git a/src/orchestrator/pkg/infra/db/mongo_test.go b/src/orchestrator/pkg/infra/db/mongo_test.go index d506dbda..9f813ca4 100644 --- a/src/orchestrator/pkg/infra/db/mongo_test.go +++ b/src/orchestrator/pkg/infra/db/mongo_test.go @@ -76,7 +76,7 @@ func (c *mockCollection) DeleteMany(ctx context.Context, filter interface{}, func (c *mockCollection) UpdateOne(ctx context.Context, filter interface{}, update interface{}, opts ...*options.UpdateOptions) (*mongo.UpdateResult, error) { - return nil, c.Err + return nil, c.Err } func TestCreate(t *testing.T) { @@ -463,4 +463,3 @@ func TestDelete(t *testing.T) { }) } } - diff --git a/src/orchestrator/pkg/module/add_intents.go b/src/orchestrator/pkg/module/add_intents.go new file mode 100644 index 00000000..a657cce7 --- /dev/null +++ b/src/orchestrator/pkg/module/add_intents.go @@ -0,0 +1,184 @@ +/* + * 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 Addlicable 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" + "reflect" + + "github.com/onap/multicloud-k8s/src/orchestrator/pkg/infra/db" + + pkgerrors "github.com/pkg/errors" +) + +// Intent shall have 2 fields - MetaData and Spec +type Intent struct { + MetaData IntentMetaData `json:"metadata"` + Spec IntentSpecData `json:"spec"` +} + +// IntentMetaData has Name, Description, userdata1, userdata2 +type IntentMetaData struct { + Name string `json:"name"` + Description string `json:"description"` + UserData1 string `json:"userData1"` + UserData2 string `json:"userData2"` +} + +// IntentSpecData has Intent +type IntentSpecData struct { + Intent IntentObj `json:"intent"` +} + +// IntentObj has name of the generic placement intent +type IntentObj struct { + Generic string `json:"generic"` +} + +// ListOfIntents is a list of intents +type ListOfIntents struct { + ListOfIntents []map[string]string `json:"intent"` +} + +// IntentManager is an interface which exposes the IntentManager functionality +type IntentManager interface { + AddIntent(a Intent, p string, ca string, v string, di string) (Intent, error) + GetIntent(i string, p string, ca string, v string, di string) (Intent, error) + DeleteIntent(i string, p string, ca string, v string, di string) error +} + +// IntentKey consists of Name if the intent, Project name, CompositeApp name, +// CompositeApp version +type IntentKey struct { + Name string `json:"name"` + Project string `json:"project"` + CompositeApp string `json:"compositeapp"` + Version string `json:"version"` + DeploymentIntentGroup string `json:"deployment-intent-group-name"` +} + +// We will use json marshalling to convert to string to +// preserve the underlying structure. +func (ik IntentKey) String() string { + out, err := json.Marshal(ik) + if err != nil { + return "" + } + return string(out) +} + +// IntentClient implements the AddIntentManager interface +type IntentClient struct { + storeName string + tagMetaData string +} + +// NewIntentClient returns an instance of AddIntentClient +func NewIntentClient() *IntentClient { + return &IntentClient{ + storeName: "orchestrator", + tagMetaData: "addintent", + } +} + +// AddIntent adds a given intent to the deployment-intent-group and stores in the db. Other input parameters for it - projectName, compositeAppName, version, DeploymentIntentgroupName +func (c *IntentClient) AddIntent(a Intent, p string, ca string, v string, di string) (Intent, error) { + + //Check for the AddIntent already exists here. + res, err := c.GetIntent(a.MetaData.Name, p, ca, v, di) + if !reflect.DeepEqual(res, Intent{}) { + return Intent{}, pkgerrors.New("AppIntent already exists") + } + + //Check if project exists + _, err = NewProjectClient().GetProject(p) + if err != nil { + return Intent{}, pkgerrors.New("Unable to find the project") + } + + //check if compositeApp exists + _, err = NewCompositeAppClient().GetCompositeApp(ca, v, p) + if err != nil { + return Intent{}, pkgerrors.New("Unable to find the composite-app") + } + + //check if DeploymentIntentGroup exists + _, err = NewDeploymentIntentGroupClient().GetDeploymentIntentGroup(di, p, ca, v) + if err != nil { + return Intent{}, pkgerrors.New("Unable to find the intent") + } + + akey := IntentKey{ + Name: a.MetaData.Name, + Project: p, + CompositeApp: ca, + Version: v, + DeploymentIntentGroup: di, + } + + err = db.DBconn.Insert(c.storeName, akey, nil, c.tagMetaData, a) + if err != nil { + return Intent{}, pkgerrors.Wrap(err, "Create DB entry error") + } + return a, nil +} + +// GetIntent returns an Intent +func (c *IntentClient) GetIntent(i string, p string, ca string, v string, di string) (Intent, error) { + + k := IntentKey{ + Name: i, + Project: p, + CompositeApp: ca, + Version: v, + DeploymentIntentGroup: di, + } + + result, err := db.DBconn.Find(c.storeName, k, c.tagMetaData) + if err != nil { + return Intent{}, pkgerrors.Wrap(err, "Get AppIntent error") + } + + if result != nil { + a := Intent{} + err = db.DBconn.Unmarshal(result[0], &a) + if err != nil { + return Intent{}, pkgerrors.Wrap(err, "Unmarshalling AppIntent") + } + return a, nil + + } + return Intent{}, pkgerrors.New("Error getting AppIntent") +} + +// DeleteIntent deletes a given intent tied to project, composite app and deployment intent group +func (c IntentClient) DeleteIntent(i string, p string, ca string, v string, di string) error { + k := IntentKey{ + Name: i, + Project: p, + CompositeApp: ca, + Version: v, + DeploymentIntentGroup: di, + } + + err := db.DBconn.Remove(c.storeName, k) + if err != nil { + return pkgerrors.Wrap(err, "Delete Project entry;") + } + return nil + +} diff --git a/src/orchestrator/pkg/module/app_intent.go b/src/orchestrator/pkg/module/app_intent.go new file mode 100644 index 00000000..70c80dac --- /dev/null +++ b/src/orchestrator/pkg/module/app_intent.go @@ -0,0 +1,195 @@ +/* + * 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" + "reflect" + + "github.com/onap/multicloud-k8s/src/orchestrator/pkg/infra/db" + + pkgerrors "github.com/pkg/errors" +) + +// AppIntent has two components - metadata, spec +type AppIntent struct { + MetaData MetaData `json:"metadata"` + Spec SpecData `json:"spec"` +} + +// MetaData has - name, description, userdata1, userdata2 +type MetaData struct { + Name string `json:"name"` + Description string `json:"description"` + UserData1 string `json:"userData1"` + UserData2 string `json:"userData2"` +} + +// AllOf consists of AnyOfArray and ClusterNames array +type AllOf struct { + ClusterName string `json:"cluster-name,omitempty"` + ClusterLabelName string `json:"cluster-label-name,omitempty"` + AnyOfArray []AnyOf `json:"anyOf,omitempty"` +} + +// AnyOf consists of Array of ClusterLabelNames +type AnyOf struct { + ClusterName string `json:"cluster-name,omitempty"` + ClusterLabelName string `json:"cluster-label-name,omitempty"` +} + +// IntentStruc consists of AllOfArray and AnyOfArray +type IntentStruc struct { + AllOfArray []AllOf `json:"allOf,omitempty"` + AnyOfArray []AnyOf `json:"anyOf,omitempty"` +} + +// SpecData consists of appName and intent +type SpecData struct { + AppName string `json:"app-name"` + Intent IntentStruc `json:"intent"` +} + +// AppIntentManager is an interface which exposes the +// AppIntentManager functionalities +type AppIntentManager interface { + CreateAppIntent(a AppIntent, p string, ca string, v string, i string) (AppIntent, error) + GetAppIntent(ai string, p string, ca string, v string, i string) (AppIntent, error) + DeleteAppIntent(ai string, p string, ca string, v string, i string) error +} + +// AppIntentKey is used as primary key +type AppIntentKey struct { + Name string `json:"name"` + Project string `json:"project"` + CompositeApp string `json:"compositeapp"` + Version string `json:"version"` + Intent string `json:"intent-name"` +} + +// We will use json marshalling to convert to string to +// preserve the underlying structure. +func (ak AppIntentKey) String() string { + out, err := json.Marshal(ak) + if err != nil { + return "" + } + return string(out) +} + +// AppIntentClient implements the AppIntentManager interface +type AppIntentClient struct { + storeName string + tagMetaData string +} + +// NewAppIntentClient returns an instance of AppIntentClient +func NewAppIntentClient() *AppIntentClient { + return &AppIntentClient{ + storeName: "orchestrator", + tagMetaData: "appintent", + } +} + +// CreateAppIntent creates an entry for AppIntent in the db. Other input parameters for it - projectName, compositeAppName, version, intentName. +func (c *AppIntentClient) CreateAppIntent(a AppIntent, p string, ca string, v string, i string) (AppIntent, error) { + + //Check for the AppIntent already exists here. + res, err := c.GetAppIntent(a.MetaData.Name, p, ca, v, i) + if !reflect.DeepEqual(res, AppIntent{}) { + return AppIntent{}, pkgerrors.New("AppIntent already exists") + } + + //Check if project exists + _, err = NewProjectClient().GetProject(p) + if err != nil { + return AppIntent{}, pkgerrors.New("Unable to find the project") + } + + // check if compositeApp exists + _, err = NewCompositeAppClient().GetCompositeApp(ca, v, p) + if err != nil { + return AppIntent{}, pkgerrors.New("Unable to find the composite-app") + } + + // check if Intent exists + _, err = NewGenericPlacementIntentClient().GetGenericPlacementIntent(i, p, ca, v) + if err != nil { + return AppIntent{}, pkgerrors.New("Unable to find the intent") + } + + akey := AppIntentKey{ + Name: a.MetaData.Name, + Project: p, + CompositeApp: ca, + Version: v, + Intent: i, + } + + err = db.DBconn.Insert(c.storeName, akey, nil, c.tagMetaData, a) + if err != nil { + return AppIntent{}, pkgerrors.Wrap(err, "Create DB entry error") + } + + return a, nil +} + +// GetAppIntent shall take arguments - name of the app intent, name of the project, name of the composite app, version of the composite app and intent name. It shall return the AppIntent +func (c *AppIntentClient) GetAppIntent(ai string, p string, ca string, v string, i string) (AppIntent, error) { + + k := AppIntentKey{ + Name: ai, + Project: p, + CompositeApp: ca, + Version: v, + Intent: i, + } + + result, err := db.DBconn.Find(c.storeName, k, c.tagMetaData) + if err != nil { + return AppIntent{}, pkgerrors.Wrap(err, "Get AppIntent error") + } + + if result != nil { + a := AppIntent{} + err = db.DBconn.Unmarshal(result[0], &a) + if err != nil { + return AppIntent{}, pkgerrors.Wrap(err, "Unmarshalling AppIntent") + } + return a, nil + + } + return AppIntent{}, pkgerrors.New("Error getting AppIntent") +} + +// DeleteAppIntent delete an AppIntent +func (c *AppIntentClient) DeleteAppIntent(ai string, p string, ca string, v string, i string) error { + k := AppIntentKey{ + Name: ai, + Project: p, + CompositeApp: ca, + Version: v, + Intent: i, + } + + err := db.DBconn.Remove(c.storeName, k) + if err != nil { + return pkgerrors.Wrap(err, "Delete Project entry;") + } + return nil + +} diff --git a/src/orchestrator/pkg/module/app_intent_test.go b/src/orchestrator/pkg/module/app_intent_test.go new file mode 100644 index 00000000..5a4f7693 --- /dev/null +++ b/src/orchestrator/pkg/module/app_intent_test.go @@ -0,0 +1,271 @@ +/* + * 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 ( + "reflect" + "strings" + "testing" + + "github.com/onap/multicloud-k8s/src/orchestrator/pkg/infra/db" +) + +func TestCreateAppIntent(t *testing.T) { + testCases := []struct { + label string + inputAppIntent AppIntent + inputProject string + inputCompositeApp string + inputCompositeAppVersion string + inputGenericPlacementIntent string + expectedError string + mockdb *db.MockDB + expected AppIntent + }{ + { + label: "Create AppIntent", + inputAppIntent: AppIntent{ + MetaData: MetaData{ + Name: "testAppIntent", + Description: "A sample AppIntent", + UserData1: "userData1", + UserData2: "userData2", + }, + Spec: SpecData{ + AppName: "SampleApp", + Intent: IntentStruc{ + AllOfArray: []AllOf{ + { + ClusterName: "edge1", + //ClusterLabelName: "edge1", + }, + { + ClusterName: "edge2", + //ClusterLabelName: "edge2", + }, + { + AnyOfArray: []AnyOf{ + {ClusterLabelName: "east-us1"}, + {ClusterLabelName: "east-us2"}, + //{ClusterName: "east-us1"}, + //{ClusterName: "east-us2"}, + }, + }, + }, + + AnyOfArray: []AnyOf{}, + }, + }, + }, + + inputProject: "testProject", + inputCompositeApp: "testCompositeApp", + inputCompositeAppVersion: "testCompositeAppVersion", + inputGenericPlacementIntent: "testIntent", + expected: AppIntent{ + MetaData: MetaData{ + Name: "testAppIntent", + Description: "A sample AppIntent", + UserData1: "userData1", + UserData2: "userData2", + }, + Spec: SpecData{ + AppName: "SampleApp", + Intent: IntentStruc{ + AllOfArray: []AllOf{ + { + ClusterName: "edge1", + //ClusterLabelName: "edge1", + }, + { + ClusterName: "edge2", + //ClusterLabelName: "edge2", + }, + { + AnyOfArray: []AnyOf{ + {ClusterLabelName: "east-us1"}, + {ClusterLabelName: "east-us2"}, + //{ClusterName: "east-us1"}, + //{ClusterName: "east-us2"}, + }, + }, + }, + AnyOfArray: []AnyOf{}, + }, + }, + }, + expectedError: "", + mockdb: &db.MockDB{ + Items: map[string]map[string][]byte{ + ProjectKey{ProjectName: "testProject"}.String(): { + "projectmetadata": []byte( + "{\"project-name\":\"testProject\"," + + "\"description\":\"Test project for unit testing\"}"), + }, + CompositeAppKey{CompositeAppName: "testCompositeApp", + Version: "testCompositeAppVersion", Project: "testProject"}.String(): { + "compositeAppmetadata": []byte( + "{\"metadata\":{" + + "\"name\":\"testCompositeApp\"," + + "\"description\":\"description\"," + + "\"userData1\":\"user data\"," + + "\"userData2\":\"user data\"" + + "}," + + "\"spec\":{" + + "\"version\":\"version of the composite app\"}}"), + }, + GenericPlacementIntentKey{ + Name: "testIntent", + Project: "testProject", + CompositeApp: "testCompositeApp", + Version: "testCompositeAppVersion", + }.String(): { + "genericplacementintent": []byte( + "{\"metadata\":{\"Name\":\"testIntent\"," + + "\"Description\":\"A sample intent for testing\"," + + "\"UserData1\": \"userData1\"," + + "\"UserData2\": \"userData2\"}," + + "\"spec\":{\"Logical-Cloud\": \"logicalCloud1\"}}"), + }, + }, + }, + }, + } + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + db.DBconn = testCase.mockdb + appIntentCli := NewAppIntentClient() + got, err := appIntentCli.CreateAppIntent(testCase.inputAppIntent, testCase.inputProject, testCase.inputCompositeApp, testCase.inputCompositeAppVersion, testCase.inputGenericPlacementIntent) + if err != nil { + if testCase.expectedError == "" { + t.Fatalf("CreateAppIntent returned an unexpected error %s, ", err) + } + if strings.Contains(err.Error(), testCase.expectedError) == false { + t.Fatalf("CreateAppIntent returned an unexpected error %s", err) + } + } else { + if reflect.DeepEqual(testCase.expected, got) == false { + t.Errorf("CreateAppIntent returned unexpected body: got %v; "+" expected %v", got, testCase.expected) + } + } + }) + } +} + +func TestGetAppIntent(t *testing.T) { + testCases := []struct { + label string + expectedError string + expected AppIntent + mockdb *db.MockDB + appIntentName string + projectName string + compositeAppName string + compositeAppVersion string + genericPlacementIntent string + }{ + { + label: "Get Intent", + appIntentName: "testAppIntent", + projectName: "testProject", + compositeAppName: "testCompositeApp", + compositeAppVersion: "testCompositeAppVersion", + genericPlacementIntent: "testIntent", + expected: AppIntent{ + MetaData: MetaData{ + Name: "testAppIntent", + Description: "testAppIntent", + UserData1: "userData1", + UserData2: "userData2", + }, + Spec: SpecData{ + AppName: "SampleApp", + Intent: IntentStruc{ + AllOfArray: []AllOf{ + { + ClusterName: "edge1", + }, + { + ClusterName: "edge2", + }, + { + AnyOfArray: []AnyOf{ + {ClusterLabelName: "east-us1"}, + {ClusterLabelName: "east-us2"}, + }, + }, + }, + }, + }, + }, + expectedError: "", + mockdb: &db.MockDB{ + Items: map[string]map[string][]byte{ + AppIntentKey{ + Name: "testAppIntent", + Project: "testProject", + CompositeApp: "testCompositeApp", + Version: "testCompositeAppVersion", + Intent: "testIntent", + }.String(): { + "appintent": []byte( + "{\"metadata\":{\"Name\":\"testAppIntent\"," + + "\"Description\":\"testAppIntent\"," + + "\"UserData1\": \"userData1\"," + + "\"UserData2\": \"userData2\"}," + + "\"spec\":{\"app-name\": \"SampleApp\"," + + "\"intent\": {" + + "\"allOf\":[" + + "{\"cluster-name\":\"edge1\"}," + + "{\"cluster-name\":\"edge2\"}," + + "{" + + "\"anyOf\":[" + + "{" + + "\"cluster-label-name\":\"east-us1\"}," + + "{" + + "\"cluster-label-name\":\"east-us2\"}" + + "]}]" + + "}}}"), + }, + }, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + db.DBconn = testCase.mockdb + appIntentCli := NewAppIntentClient() + got, err := appIntentCli.GetAppIntent(testCase.appIntentName, testCase.projectName, testCase.compositeAppName, testCase.compositeAppVersion, + testCase.genericPlacementIntent) + if err != nil { + if testCase.expectedError == "" { + t.Fatalf("GetAppIntent returned an unexpected error: %s", err) + } + if strings.Contains(err.Error(), testCase.expectedError) == false { + t.Fatalf("GetAppIntent returned an unexpected error: %s", err) + } + } else { + if reflect.DeepEqual(testCase.expected, got) == false { + t.Errorf("GetAppIntent returned unexpected body: got %v;"+ + " expected %v", got, testCase.expected) + } + } + + }) + } +} diff --git a/src/orchestrator/pkg/module/app_profile.go b/src/orchestrator/pkg/module/app_profile.go new file mode 100644 index 00000000..77835fb4 --- /dev/null +++ b/src/orchestrator/pkg/module/app_profile.go @@ -0,0 +1,296 @@ +/* + * 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" +) + +// AppProfile contains the parameters needed for AppProfiles +// It implements the interface for managing the AppProfiles +type AppProfile struct { + Metadata AppProfileMetadata `json:"metadata"` + Spec AppProfileSpec `json:"spec"` +} + +type AppProfileContent struct { + Profile string `json:"profile"` +} + +// AppProfileMetadata contains the metadata for AppProfiles +type AppProfileMetadata struct { + Name string `json:"name"` + Description string `json:"description"` + UserData1 string `json:"userData1"` + UserData2 string `json:"userData2"` +} + +// AppProfileSpec contains the Spec for AppProfiles +type AppProfileSpec struct { + AppName string `json:"app-name"` +} + +// AppProfileKey is the key structure that is used in the database +type AppProfileKey struct { + Project string `json:"project"` + CompositeApp string `json:"compositeapp"` + CompositeAppVersion string `json:"compositeappversion"` + CompositeProfile string `json:"compositeprofile"` + Profile string `json:"profile"` +} + +type AppProfileQueryKey struct { + AppName string `json:"app-name"` +} + +type AppProfileFindByAppKey struct { + Project string `json:"project"` + CompositeApp string `json:"compositeapp"` + CompositeAppVersion string `json:"compositeappversion"` + CompositeProfile string `json:"compositeprofile"` + AppName string `json:"app-name"` +} + +// AppProfileManager exposes the AppProfile functionality +type AppProfileManager interface { + CreateAppProfile(provider, compositeApp, compositeAppVersion, compositeProfile string, ap AppProfile, ac AppProfileContent) (AppProfile, error) + GetAppProfile(project, compositeApp, compositeAppVersion, compositeProfile, profile string) (AppProfile, error) + GetAppProfiles(project, compositeApp, compositeAppVersion, compositeProfile string) ([]AppProfile, error) + GetAppProfileByApp(project, compositeApp, compositeAppVersion, compositeProfile, appName string) (AppProfile, error) + GetAppProfileContent(project, compositeApp, compositeAppVersion, compositeProfile, profile string) (AppProfileContent, error) + GetAppProfileContentByApp(project, compositeApp, compositeAppVersion, compositeProfile, appName string) (AppProfileContent, error) + DeleteAppProfile(project, compositeApp, compositeAppVersion, compositeProfile, profile string) error +} + +// AppProfileClient implements the Manager +// It will also be used to maintain some localized state +type AppProfileClient struct { + storeName string + tagMeta string + tagContent string +} + +// NewAppProfileClient returns an instance of the AppProfileClient +// which implements the Manager +func NewAppProfileClient() *AppProfileClient { + return &AppProfileClient{ + storeName: "orchestrator", + tagMeta: "profilemetadata", + tagContent: "profilecontent", + } +} + +// CreateAppProfile creates an entry for AppProfile in the database. +func (c *AppProfileClient) CreateAppProfile(project, compositeApp, compositeAppVersion, compositeProfile string, ap AppProfile, ac AppProfileContent) (AppProfile, error) { + key := AppProfileKey{ + Project: project, + CompositeApp: compositeApp, + CompositeAppVersion: compositeAppVersion, + CompositeProfile: compositeProfile, + Profile: ap.Metadata.Name, + } + qkey := AppProfileQueryKey{ + AppName: ap.Spec.AppName, + } + + res, err := c.GetAppProfile(project, compositeApp, compositeAppVersion, compositeProfile, ap.Metadata.Name) + if res != (AppProfile{}) { + return AppProfile{}, pkgerrors.New("AppProfile already exists") + } + + res, err = c.GetAppProfileByApp(project, compositeApp, compositeAppVersion, compositeProfile, ap.Spec.AppName) + if res != (AppProfile{}) { + return AppProfile{}, pkgerrors.New("App already has an AppProfile") + } + + //Check if composite profile exists (success assumes existance of all higher level 'parent' objects) + _, err = NewCompositeProfileClient().GetCompositeProfile(compositeProfile, project, compositeApp, compositeAppVersion) + if err != nil { + return AppProfile{}, pkgerrors.New("Unable to find the project") + } + + // TODO: (after app api is ready) check that the app Spec.AppName exists as part of the composite app + + err = db.DBconn.Insert(c.storeName, key, qkey, c.tagMeta, ap) + if err != nil { + return AppProfile{}, pkgerrors.Wrap(err, "Creating DB Entry") + } + err = db.DBconn.Insert(c.storeName, key, qkey, c.tagContent, ac) + if err != nil { + return AppProfile{}, pkgerrors.Wrap(err, "Creating DB Entry") + } + + return ap, nil +} + +// GetAppProfile - return specified App Profile +func (c *AppProfileClient) GetAppProfile(project, compositeApp, compositeAppVersion, compositeProfile, profile string) (AppProfile, error) { + key := AppProfileKey{ + Project: project, + CompositeApp: compositeApp, + CompositeAppVersion: compositeAppVersion, + CompositeProfile: compositeProfile, + Profile: profile, + } + + value, err := db.DBconn.Find(c.storeName, key, c.tagMeta) + if err != nil { + return AppProfile{}, pkgerrors.Wrap(err, "Get App Profile error") + } + + if value != nil { + ap := AppProfile{} + err = db.DBconn.Unmarshal(value[0], &ap) + if err != nil { + return AppProfile{}, pkgerrors.Wrap(err, "Unmarshalling AppProfile") + } + return ap, nil + } + + return AppProfile{}, pkgerrors.New("Error getting AppProfile") + +} + +// GetAppProfile - return all App Profiles for given composite profile +func (c *AppProfileClient) GetAppProfiles(project, compositeApp, compositeAppVersion, compositeProfile string) ([]AppProfile, error) { + + key := AppProfileKey{ + Project: project, + CompositeApp: compositeApp, + CompositeAppVersion: compositeAppVersion, + CompositeProfile: compositeProfile, + Profile: "", + } + + var resp []AppProfile + values, err := db.DBconn.Find(c.storeName, key, c.tagMeta) + if err != nil { + return []AppProfile{}, pkgerrors.Wrap(err, "Get AppProfiles") + } + + for _, value := range values { + ap := AppProfile{} + err = db.DBconn.Unmarshal(value, &ap) + if err != nil { + return []AppProfile{}, pkgerrors.Wrap(err, "Unmarshaling Value") + } + resp = append(resp, ap) + } + + return resp, nil +} + +// GetAppProfileByApp - return all App Profiles for given composite profile +func (c *AppProfileClient) GetAppProfileByApp(project, compositeApp, compositeAppVersion, compositeProfile, appName string) (AppProfile, error) { + + key := AppProfileFindByAppKey{ + Project: project, + CompositeApp: compositeApp, + CompositeAppVersion: compositeAppVersion, + CompositeProfile: compositeProfile, + AppName: appName, + } + + value, err := db.DBconn.Find(c.storeName, key, c.tagMeta) + if err != nil { + return AppProfile{}, pkgerrors.Wrap(err, "Get AppProfile by App") + } + + if value != nil { + ap := AppProfile{} + err = db.DBconn.Unmarshal(value[0], &ap) + if err != nil { + return AppProfile{}, pkgerrors.Wrap(err, "Unmarshalling AppProfile") + } + return ap, nil + } + + return AppProfile{}, pkgerrors.New("Error getting AppProfile by App") +} + +func (c *AppProfileClient) GetAppProfileContent(project, compositeApp, compositeAppVersion, compositeProfile, profile string) (AppProfileContent, error) { + key := AppProfileKey{ + Project: project, + CompositeApp: compositeApp, + CompositeAppVersion: compositeAppVersion, + CompositeProfile: compositeProfile, + Profile: profile, + } + + value, err := db.DBconn.Find(c.storeName, key, c.tagContent) + if err != nil { + return AppProfileContent{}, pkgerrors.Wrap(err, "Get Cluster Content") + } + + //value is a byte array + if value != nil { + ac := AppProfileContent{} + err = db.DBconn.Unmarshal(value[0], &ac) + if err != nil { + return AppProfileContent{}, pkgerrors.Wrap(err, "Unmarshaling Value") + } + return ac, nil + } + + return AppProfileContent{}, pkgerrors.New("Error getting App Profile Content") +} + +func (c *AppProfileClient) GetAppProfileContentByApp(project, compositeApp, compositeAppVersion, compositeProfile, appName string) (AppProfileContent, error) { + key := AppProfileFindByAppKey{ + Project: project, + CompositeApp: compositeApp, + CompositeAppVersion: compositeAppVersion, + CompositeProfile: compositeProfile, + AppName: appName, + } + + value, err := db.DBconn.Find(c.storeName, key, c.tagContent) + if err != nil { + return AppProfileContent{}, pkgerrors.Wrap(err, "Get Cluster Content") + } + + //value is a byte array + if value != nil { + ac := AppProfileContent{} + err = db.DBconn.Unmarshal(value[0], &ac) + if err != nil { + return AppProfileContent{}, pkgerrors.Wrap(err, "Unmarshaling Value") + } + return ac, nil + } + + return AppProfileContent{}, pkgerrors.New("Error getting App Profile Content") +} + +// Delete AppProfile from the database +func (c *AppProfileClient) DeleteAppProfile(project, compositeApp, compositeAppVersion, compositeProfile, profile string) error { + key := AppProfileKey{ + Project: project, + CompositeApp: compositeApp, + CompositeAppVersion: compositeAppVersion, + CompositeProfile: compositeProfile, + Profile: profile, + } + + err := db.DBconn.Remove(c.storeName, key) + if err != nil { + return pkgerrors.Wrap(err, "Delete AppProfile entry;") + } + return nil +} diff --git a/src/orchestrator/pkg/module/composite_profile.go b/src/orchestrator/pkg/module/composite_profile.go new file mode 100644 index 00000000..dca2116a --- /dev/null +++ b/src/orchestrator/pkg/module/composite_profile.go @@ -0,0 +1,192 @@ +/* + * 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" + + "github.com/onap/multicloud-k8s/src/orchestrator/pkg/infra/db" + + pkgerrors "github.com/pkg/errors" +) + +// CompositeProfile contains the parameters needed for CompositeProfiles +// It implements the interface for managing the CompositeProfiles +type CompositeProfile struct { + Metadata CompositeProfileMetadata `json:"metadata"` +} + +// CompositeProfileMetadata contains the metadata for CompositeProfiles +type CompositeProfileMetadata struct { + Name string `json:"name"` + Description string `json:"description"` + UserData1 string `json:"userData1"` + UserData2 string `json:"userData2"` +} + +// CompositeProfileKey is the key structure that is used in the database +type CompositeProfileKey struct { + Name string `json:"compositeprofile"` + Project string `json:"project"` + CompositeApp string `json:"compositeapp"` + Version string `json:"compositeappversion"` +} + +// We will use json marshalling to convert to string to +// preserve the underlying structure. +func (cpk CompositeProfileKey) String() string { + out, err := json.Marshal(cpk) + if err != nil { + return "" + } + + return string(out) +} + +// CompositeProfileManager exposes the CompositeProfile functionality +type CompositeProfileManager interface { + CreateCompositeProfile(cpf CompositeProfile, p string, ca string, + v string) (CompositeProfile, error) + GetCompositeProfile(compositeProfileName string, projectName string, + compositeAppName string, version string) (CompositeProfile, error) + GetCompositeProfiles(projectName string, compositeAppName string, + version string) ([]CompositeProfile, error) + DeleteCompositeProfile(compositeProfileName string, projectName string, + compositeAppName string, version string) error +} + +// CompositeProfileClient implements the Manager +// It will also be used to maintain some localized state +type CompositeProfileClient struct { + storeName string + tagMeta string +} + +// NewCompositeProfileClient returns an instance of the CompositeProfileClient +// which implements the Manager +func NewCompositeProfileClient() *CompositeProfileClient { + return &CompositeProfileClient{ + storeName: "orchestrator", + tagMeta: "compositeprofilemetadata", + } +} + +// CreateCompositeProfile creates an entry for CompositeProfile in the database. Other Input parameters for it - projectName, compositeAppName, version +func (c *CompositeProfileClient) CreateCompositeProfile(cpf CompositeProfile, p string, ca string, + v string) (CompositeProfile, error) { + + res, err := c.GetCompositeProfile(cpf.Metadata.Name, p, ca, v) + if res != (CompositeProfile{}) { + return CompositeProfile{}, pkgerrors.New("CompositeProfile already exists") + } + + //Check if project exists + _, err = NewProjectClient().GetProject(p) + if err != nil { + return CompositeProfile{}, pkgerrors.New("Unable to find the project") + } + + // check if compositeApp exists + _, err = NewCompositeAppClient().GetCompositeApp(ca, v, p) + if err != nil { + return CompositeProfile{}, pkgerrors.New("Unable to find the composite-app") + } + + cProfkey := CompositeProfileKey{ + Name: cpf.Metadata.Name, + Project: p, + CompositeApp: ca, + Version: v, + } + + err = db.DBconn.Insert(c.storeName, cProfkey, nil, c.tagMeta, cpf) + if err != nil { + return CompositeProfile{}, pkgerrors.Wrap(err, "Create DB entry error") + } + + return cpf, nil +} + +// GetCompositeProfile shall take arguments - name of the composite profile, name of //// the project, name of the composite app and version of the composite app. It shall return the CompositeProfile if its present. +func (c *CompositeProfileClient) GetCompositeProfile(cpf string, p string, ca string, v string) (CompositeProfile, error) { + key := CompositeProfileKey{ + Name: cpf, + Project: p, + CompositeApp: ca, + Version: v, + } + + result, err := db.DBconn.Find(c.storeName, key, c.tagMeta) + if err != nil { + return CompositeProfile{}, pkgerrors.Wrap(err, "Get Composite Profile error") + } + + if result != nil { + cProf := CompositeProfile{} + err = db.DBconn.Unmarshal(result[0], &cProf) + if err != nil { + return CompositeProfile{}, pkgerrors.Wrap(err, "Unmarshalling CompositeProfile") + } + return cProf, nil + } + + return CompositeProfile{}, pkgerrors.New("Error getting CompositeProfile") +} + +// GetCompositeProfile shall take arguments - name of the composite profile, name of //// the project, name of the composite app and version of the composite app. It shall return the CompositeProfile if its present. +func (c *CompositeProfileClient) GetCompositeProfiles(p string, ca string, v string) ([]CompositeProfile, error) { + key := CompositeProfileKey{ + Name: "", + Project: p, + CompositeApp: ca, + Version: v, + } + + values, err := db.DBconn.Find(c.storeName, key, c.tagMeta) + if err != nil { + return []CompositeProfile{}, pkgerrors.Wrap(err, "Get Composite Profiles error") + } + + var resp []CompositeProfile + + for _, value := range values { + cp := CompositeProfile{} + err = db.DBconn.Unmarshal(value, &cp) + if err != nil { + return []CompositeProfile{}, pkgerrors.Wrap(err, "Get Composite Profiles unmarshalling error") + } + resp = append(resp, cp) + } + + return resp, nil +} + +// DeleteCompositeProfile the intent from the database +func (c *CompositeProfileClient) DeleteCompositeProfile(cpf string, p string, ca string, v string) error { + key := CompositeProfileKey{ + Name: cpf, + Project: p, + CompositeApp: ca, + Version: v, + } + + err := db.DBconn.Remove(c.storeName, key) + if err != nil { + return pkgerrors.Wrap(err, "Delete CompositeProfile entry;") + } + return nil +} diff --git a/src/orchestrator/pkg/module/composite_profile_test.go b/src/orchestrator/pkg/module/composite_profile_test.go new file mode 100644 index 00000000..af0dd7b7 --- /dev/null +++ b/src/orchestrator/pkg/module/composite_profile_test.go @@ -0,0 +1,175 @@ +/* + * 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 + +//pkgerrors "github.com/pkg/errors" + +/* TODO - db.MockDB needs to be enhanced and then these can be fixed up +func TestCreateCompositeProfile(t *testing.T) { + testCases := []struct { + label string + compositeProfile CompositeProfile + projectName string + compositeApp string + compositeAppVersion string + expectedError string + mockdb *db.MockDB + expected CompositeProfile + }{ + { + label: "Create CompositeProfile", + compositeProfile: CompositeProfile{ + Metadata: CompositeProfileMetadata{ + Name: "testCompositeProfile", + Description: "A sample Composite Profile for testing", + UserData1: "userData1", + UserData2: "userData2", + }, + }, + projectName: "testProject", + compositeApp: "testCompositeApp", + compositeAppVersion: "v1", + expected: CompositeProfile{ + Metadata: CompositeProfileMetadata{ + Name: "testCompositeProfile", + Description: "A sample Composite Profile for testing", + UserData1: "userData1", + UserData2: "userData2", + }, + }, + expectedError: "", + mockdb: &db.MockDB{ + Items: map[string]map[string][]byte{ + ProjectKey{ProjectName: "testProject"}.String(): { + "projectmetadata": []byte( + "{" + + "\"metadata\" : {" + + "\"Name\":\"testProject\"," + + "\"Description\":\"Test project for unit testing\"," + + "\"UserData1\": \"userData1\"," + + "\"UserData2\":\"userData2\"}" + + "}"), + }, + CompositeAppKey{CompositeAppName: "testCompositeApp", Project: "testProject", Version: "v1"}.String(): { + "compositeAppmetadata": []byte( + "{" + + "\"metadata\" : {" + + "\"Name\":\"testCompositeApp\"," + + "\"Description\":\"Test Composite App for unit testing\"," + + "\"UserData1\": \"userData1\"," + + "\"UserData2\":\"userData2\"}," + + "\"spec\": {" + + "\"Version\": \"v1\"}" + + "}"), + }, + }, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + db.DBconn = testCase.mockdb + cprofCli := NewCompositeProfileClient() + got, err := cprofCli.CreateCompositeProfile(testCase.compositeProfile, testCase.projectName, testCase.compositeApp, testCase.compositeAppVersion) + if err != nil { + if testCase.expectedError == "" { + t.Fatalf("CreateCompositeProfile returned an unexpected error %s", err) + } + if strings.Contains(err.Error(), testCase.expectedError) == false { + t.Fatalf("CreateCompositeProfile returned an unexpected error %s", err) + } + } else { + if reflect.DeepEqual(testCase.expected, got) == false { + t.Errorf("CreateCompositeProfile returned unexpected body: got %v; "+" expected %v", got, testCase.expected) + } + } + }) + + } +} + +func TestGetCompositeProfile(t *testing.T) { + + testCases := []struct { + label string + expectedError string + expected CompositeProfile + mockdb *db.MockDB + compositeProfileName string + projectName string + compositeAppName string + compositeAppVersion string + }{ + { + label: "Get CompositeProfile", + compositeProfileName: "testCompositeProfile", + projectName: "testProject", + compositeAppName: "testCompositeApp", + compositeAppVersion: "v1", + expected: CompositeProfile{ + Metadata: CompositeProfileMetadata{ + Name: "testCompositeProfile", + Description: "A sample CompositeProfile for testing", + UserData1: "userData1", + UserData2: "userData2", + }, + }, + expectedError: "", + mockdb: &db.MockDB{ + Items: map[string]map[string][]byte{ + CompositeProfileKey{ + Name: "testCompositeProfile", + Project: "testProject", + CompositeApp: "testCompositeApp", + Version: "v1", + }.String(): { + "compositeprofile": []byte( + "{\"metadata\":{\"Name\":\"testCompositeProfile\"," + + "\"Description\":\"A sample CompositeProfile for testing\"," + + "\"UserData1\": \"userData1\"," + + "\"UserData2\": \"userData2\"}}"), + }, + }, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + db.DBconn = testCase.mockdb + cprofCli := NewCompositeProfileClient() + got, err := cprofCli.GetCompositeProfile(testCase.compositeProfileName, testCase.projectName, testCase.compositeAppName, testCase.compositeAppVersion) + if err != nil { + if testCase.expectedError == "" { + t.Fatalf("GetCompositeProfile returned an unexpected error: %s", err) + } + if strings.Contains(err.Error(), testCase.expectedError) == false { + t.Fatalf("GetCompositeProfile returned an unexpected error: %s", err) + } + } else { + if reflect.DeepEqual(testCase.expected, got) == false { + t.Errorf("GetCompositeProfile returned unexpected body: got %v;"+ + " expected %v", got, testCase.expected) + } + } + + }) + } + +} +*/ diff --git a/src/orchestrator/pkg/module/deployment_intent_groups.go b/src/orchestrator/pkg/module/deployment_intent_groups.go new file mode 100644 index 00000000..fe622771 --- /dev/null +++ b/src/orchestrator/pkg/module/deployment_intent_groups.go @@ -0,0 +1,177 @@ +/* + * 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" + "github.com/onap/multicloud-k8s/src/orchestrator/pkg/infra/db" + "reflect" + + pkgerrors "github.com/pkg/errors" +) + +// DeploymentIntentGroup shall have 2 fields - MetaData and Spec +type DeploymentIntentGroup struct { + MetaData DepMetaData `json:"metadata"` + Spec DepSpecData `json:"spec"` +} + +// DepMetaData has Name, description, userdata1, userdata2 +type DepMetaData struct { + Name string `json:"name"` + Description string `json:"description"` + UserData1 string `json:"userData1"` + UserData2 string `json:"userData2"` +} + +// DepSpecData has profile, version, OverrideValuesObj +type DepSpecData struct { + Profile string `json:"profile"` + Version string `json:"version"` + OverrideValuesObj []OverrideValues `json:"override-values"` +} + +// OverrideValues has appName and ValuesObj +type OverrideValues struct { + AppName string `json:"app-name"` + ValuesObj map[string]string `json:"values"` +} + +// Values has ImageRepository +// type Values struct { +// ImageRepository string `json:"imageRepository"` +// } + +// DeploymentIntentGroupManager is an interface which exposes the DeploymentIntentGroupManager functionality +type DeploymentIntentGroupManager interface { + CreateDeploymentIntentGroup(d DeploymentIntentGroup, p string, ca string, v string) (DeploymentIntentGroup, error) + GetDeploymentIntentGroup(di string, p string, ca string, v string) (DeploymentIntentGroup, error) + DeleteDeploymentIntentGroup(di string, p string, ca string, v string) error +} + +// DeploymentIntentGroupKey consists of Name of the deployment group, project name, CompositeApp name, CompositeApp version +type DeploymentIntentGroupKey struct { + Name string `json:"name"` + Project string `json:"project"` + CompositeApp string `json:"compositeapp"` + Version string `json:"version"` +} + +// We will use json marshalling to convert to string to +// preserve the underlying structure. +func (dk DeploymentIntentGroupKey) String() string { + out, err := json.Marshal(dk) + if err != nil { + return "" + } + return string(out) +} + +// DeploymentIntentGroupClient implements the DeploymentIntentGroupManager interface +type DeploymentIntentGroupClient struct { + storeName string + tagMetaData string +} + +// NewDeploymentIntentGroupClient return an instance of DeploymentIntentGroupClient which implements DeploymentIntentGroupManager +func NewDeploymentIntentGroupClient() *DeploymentIntentGroupClient { + return &DeploymentIntentGroupClient{ + storeName: "orchestrator", + tagMetaData: "deploymentintentgroup", + } +} + +// CreateDeploymentIntentGroup creates an entry for a given DeploymentIntentGroup in the database. Other Input parameters for it - projectName, compositeAppName, version +func (c *DeploymentIntentGroupClient) CreateDeploymentIntentGroup(d DeploymentIntentGroup, p string, ca string, + v string) (DeploymentIntentGroup, error) { + + res, err := c.GetDeploymentIntentGroup(d.MetaData.Name, p, ca, v) + if !reflect.DeepEqual(res, DeploymentIntentGroup{}) { + return DeploymentIntentGroup{}, pkgerrors.New("AppIntent already exists") + } + + //Check if project exists + _, err = NewProjectClient().GetProject(p) + if err != nil { + return DeploymentIntentGroup{}, pkgerrors.New("Unable to find the project") + } + + //check if compositeApp exists + _, err = NewCompositeAppClient().GetCompositeApp(ca, v, p) + if err != nil { + return DeploymentIntentGroup{}, pkgerrors.New("Unable to find the composite-app") + } + + gkey := DeploymentIntentGroupKey{ + Name: d.MetaData.Name, + Project: p, + CompositeApp: ca, + Version: v, + } + + err = db.DBconn.Insert(c.storeName, gkey, nil, c.tagMetaData, d) + if err != nil { + return DeploymentIntentGroup{}, pkgerrors.Wrap(err, "Create DB entry error") + } + + return d, nil +} + +// GetDeploymentIntentGroup returns the DeploymentIntentGroup with a given name, project, compositeApp and version of compositeApp +func (c *DeploymentIntentGroupClient) GetDeploymentIntentGroup(di string, p string, ca string, v string) (DeploymentIntentGroup, error) { + + key := DeploymentIntentGroupKey{ + Name: di, + Project: p, + CompositeApp: ca, + Version: v, + } + + result, err := db.DBconn.Find(c.storeName, key, c.tagMetaData) + if err != nil { + return DeploymentIntentGroup{}, pkgerrors.Wrap(err, "Get DeploymentIntentGroup error") + } + + if result != nil { + d := DeploymentIntentGroup{} + err = db.DBconn.Unmarshal(result[0], &d) + if err != nil { + return DeploymentIntentGroup{}, pkgerrors.Wrap(err, "Unmarshalling DeploymentIntentGroup") + } + return d, nil + } + + return DeploymentIntentGroup{}, pkgerrors.New("Error getting DeploymentIntentGroup") + +} + +// DeleteDeploymentIntentGroup deletes a DeploymentIntentGroup +func (c *DeploymentIntentGroupClient) DeleteDeploymentIntentGroup(di string, p string, ca string, v string) error { + k := DeploymentIntentGroupKey{ + Name: di, + Project: p, + CompositeApp: ca, + Version: v, + } + + err := db.DBconn.Remove(c.storeName, k) + if err != nil { + return pkgerrors.Wrap(err, "Delete DeploymentIntentGroup entry;") + } + return nil + +} diff --git a/src/orchestrator/pkg/module/deployment_intent_groups_test.go b/src/orchestrator/pkg/module/deployment_intent_groups_test.go new file mode 100644 index 00000000..c0876ceb --- /dev/null +++ b/src/orchestrator/pkg/module/deployment_intent_groups_test.go @@ -0,0 +1,230 @@ +/* + * 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 ( + "reflect" + "strings" + "testing" + + "github.com/onap/multicloud-k8s/src/orchestrator/pkg/infra/db" +) + +func TestCreateDeploymentIntentGroup(t *testing.T) { + testCases := []struct { + label string + inputDeploymentIntentGrp DeploymentIntentGroup + inputProject string + inputCompositeApp string + inputCompositeAppVersion string + expectedError string + mockdb *db.MockDB + expected DeploymentIntentGroup + }{ + { + label: "Create DeploymentIntentGroup", + inputDeploymentIntentGrp: DeploymentIntentGroup{ + MetaData: DepMetaData{ + Name: "testDeploymentIntentGroup", + Description: "DescriptionTestDeploymentIntentGroup", + UserData1: "userData1", + UserData2: "userData2", + }, + Spec: DepSpecData{ + Profile: "Testprofile", + Version: "version of deployment", + OverrideValuesObj: []OverrideValues{ + {AppName: "TestAppName", + ValuesObj: map[string]string{ + "imageRepository": "registry.hub.docker.com", + }}, + {AppName: "TestAppName", + ValuesObj: map[string]string{ + "imageRepository": "registry.hub.docker.com", + }}, + }, + }, + }, + inputProject: "testProject", + inputCompositeApp: "testCompositeApp", + inputCompositeAppVersion: "testCompositeAppVersion", + expected: DeploymentIntentGroup{ + MetaData: DepMetaData{ + Name: "testDeploymentIntentGroup", + Description: "DescriptionTestDeploymentIntentGroup", + UserData1: "userData1", + UserData2: "userData2", + }, + Spec: DepSpecData{ + Profile: "Testprofile", + Version: "version of deployment", + OverrideValuesObj: []OverrideValues{ + {AppName: "TestAppName", + ValuesObj: map[string]string{ + "imageRepository": "registry.hub.docker.com", + }}, + {AppName: "TestAppName", + ValuesObj: map[string]string{ + "imageRepository": "registry.hub.docker.com", + }}, + }, + }, + }, + expectedError: "", + mockdb: &db.MockDB{ + Items: map[string]map[string][]byte{ + ProjectKey{ProjectName: "testProject"}.String(): { + "projectmetadata": []byte( + "{\"project-name\":\"testProject\"," + + "\"description\":\"Test project for unit testing\"}"), + }, + CompositeAppKey{CompositeAppName: "testCompositeApp", + Version: "testCompositeAppVersion", Project: "testProject"}.String(): { + "compositeAppmetadata": []byte( + "{\"metadata\":{" + + "\"name\":\"testCompositeApp\"," + + "\"description\":\"description\"," + + "\"userData1\":\"user data\"," + + "\"userData2\":\"user data\"" + + "}," + + "\"spec\":{" + + "\"version\":\"version of the composite app\"}}"), + }, + }, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + db.DBconn = testCase.mockdb + depIntentCli := NewDeploymentIntentGroupClient() + got, err := depIntentCli.CreateDeploymentIntentGroup(testCase.inputDeploymentIntentGrp, testCase.inputProject, testCase.inputCompositeApp, testCase.inputCompositeAppVersion) + if err != nil { + if testCase.expectedError == "" { + t.Fatalf("CreateDeploymentIntentGroup returned an unexpected error %s, ", err) + } + if strings.Contains(err.Error(), testCase.expectedError) == false { + t.Fatalf("CreateDeploymentIntentGroup returned an unexpected error %s", err) + } + } else { + if reflect.DeepEqual(testCase.expected, got) == false { + t.Errorf("CreateDeploymentIntentGroup returned unexpected body: got %v; "+" expected %v", got, testCase.expected) + } + } + }) + } +} + +func TestGetDeploymentIntentGroup(t *testing.T) { + testCases := []struct { + label string + inputDeploymentIntentGrp string + inputProject string + inputCompositeApp string + inputCompositeAppVersion string + expected DeploymentIntentGroup + expectedError string + mockdb *db.MockDB + }{ + { + label: "Get DeploymentIntentGroup", + inputDeploymentIntentGrp: "testDeploymentIntentGroup", + inputProject: "testProject", + inputCompositeApp: "testCompositeApp", + inputCompositeAppVersion: "testCompositeAppVersion", + expected: DeploymentIntentGroup{ + MetaData: DepMetaData{ + Name: "testDeploymentIntentGroup", + Description: "DescriptionTestDeploymentIntentGroup", + UserData1: "userData1", + UserData2: "userData2", + }, + Spec: DepSpecData{ + Profile: "Testprofile", + Version: "version of deployment", + OverrideValuesObj: []OverrideValues{ + {AppName: "TestAppName", + ValuesObj: map[string]string{ + "imageRepository": "registry.hub.docker.com", + }}, + {AppName: "TestAppName", + ValuesObj: map[string]string{ + "imageRepository": "registry.hub.docker.com", + }}, + }, + }, + }, + expectedError: "", + mockdb: &db.MockDB{ + Items: map[string]map[string][]byte{ + DeploymentIntentGroupKey{ + Name: "testDeploymentIntentGroup", + Project: "testProject", + CompositeApp: "testCompositeApp", + Version: "testCompositeAppVersion", + }.String(): { + "deploymentintentgroup": []byte( + "{\"metadata\":{\"name\":\"testDeploymentIntentGroup\"," + + "\"description\":\"DescriptionTestDeploymentIntentGroup\"," + + "\"userData1\": \"userData1\"," + + "\"userData2\": \"userData2\"}," + + "\"spec\":{\"profile\": \"Testprofile\"," + + "\"version\": \"version of deployment\"," + + "\"override-values\":[" + + "{" + + "\"app-name\": \"TestAppName\"," + + "\"values\": " + + "{" + + "\"imageRepository\":\"registry.hub.docker.com\"" + + "}" + + "}," + + "{" + + "\"app-name\": \"TestAppName\"," + + "\"values\": " + + "{" + + "\"imageRepository\":\"registry.hub.docker.com\"" + + "}" + + "}" + + "]}}"), + }, + }, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + db.DBconn = testCase.mockdb + depIntentCli := NewDeploymentIntentGroupClient() + got, err := depIntentCli.GetDeploymentIntentGroup(testCase.inputDeploymentIntentGrp, testCase.inputProject, testCase.inputCompositeApp, testCase.inputCompositeAppVersion) + if err != nil { + if testCase.expectedError == "" { + t.Fatalf("GetDeploymentIntentGroup returned an unexpected error: %s", err) + } + if strings.Contains(err.Error(), testCase.expectedError) == false { + t.Fatalf("GetDeploymentIntentGroup returned an unexpected error: %s", err) + } + } else { + if reflect.DeepEqual(testCase.expected, got) == false { + t.Errorf("GetDeploymentIntentGroup returned unexpected body: got %v;"+ + " expected %v", got, testCase.expected) + } + } + }) + } +} diff --git a/src/orchestrator/pkg/module/generic_placement_intent.go b/src/orchestrator/pkg/module/generic_placement_intent.go new file mode 100644 index 00000000..8e739fc8 --- /dev/null +++ b/src/orchestrator/pkg/module/generic_placement_intent.go @@ -0,0 +1,165 @@ +/* + * 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" + "github.com/onap/multicloud-k8s/src/orchestrator/pkg/infra/db" + + pkgerrors "github.com/pkg/errors" +) + +// GenericPlacementIntent shall have 2 fields - metadata and spec +type GenericPlacementIntent struct { + MetaData GenIntentMetaData `json:"metadata"` + Spec GenIntentSpecData `json:"spec"` +} + +// GenIntentMetaData has name, description, userdata1, userdata2 +type GenIntentMetaData struct { + Name string `json:"name"` + Description string `json:"description"` + UserData1 string `json:"userData1"` + UserData2 string `json:"userData2"` +} + +// GenIntentSpecData has logical-cloud-name +type GenIntentSpecData struct { + LogicalCloud string `json:"logical-cloud"` +} + +// GenericPlacementIntentManager is an interface which exposes the GenericPlacementIntentManager functionality +type GenericPlacementIntentManager interface { + CreateGenericPlacementIntent(g GenericPlacementIntent, p string, ca string, + v string) (GenericPlacementIntent, error) + GetGenericPlacementIntent(intentName string, projectName string, + compositeAppName string, version string) (GenericPlacementIntent, error) + DeleteGenericPlacementIntent(intentName string, projectName string, + compositeAppName string, version string) error +} + +// GenericPlacementIntentKey is used as the primary key +type GenericPlacementIntentKey struct { + Name string `json:"name"` + Project string `json:"project"` + CompositeApp string `json:"compositeapp"` + Version string `json:"version"` +} + +// We will use json marshalling to convert to string to +// preserve the underlying structure. +func (gk GenericPlacementIntentKey) String() string { + out, err := json.Marshal(gk) + if err != nil { + return "" + } + return string(out) +} + +// GenericPlacementIntentClient implements the GenericPlacementIntentManager interface +type GenericPlacementIntentClient struct { + storeName string + tagMetaData string +} + +// NewGenericPlacementIntentClient return an instance of GenericPlacementIntentClient which implements GenericPlacementIntentManager +func NewGenericPlacementIntentClient() *GenericPlacementIntentClient { + return &GenericPlacementIntentClient{ + storeName: "orchestrator", + tagMetaData: "genericplacementintent", + } +} + +// CreateGenericPlacementIntent creates an entry for GenericPlacementIntent in the database. Other Input parameters for it - projectName, compositeAppName, version +func (c *GenericPlacementIntentClient) CreateGenericPlacementIntent(g GenericPlacementIntent, p string, ca string, + v string) (GenericPlacementIntent, error) { + + // check if the genericPlacement already exists. + res, err := c.GetGenericPlacementIntent(g.MetaData.Name, p, ca, v) + if res != (GenericPlacementIntent{}) { + return GenericPlacementIntent{}, pkgerrors.New("Intent already exists") + } + + //Check if project exists + _, err = NewProjectClient().GetProject(p) + if err != nil { + return GenericPlacementIntent{}, pkgerrors.New("Unable to find the project") + } + + // check if compositeApp exists + _, err = NewCompositeAppClient().GetCompositeApp(ca, v, p) + if err != nil { + return GenericPlacementIntent{}, pkgerrors.New("Unable to find the composite-app") + } + + gkey := GenericPlacementIntentKey{ + Name: g.MetaData.Name, + Project: p, + CompositeApp: ca, + Version: v, + } + + err = db.DBconn.Insert(c.storeName, gkey, nil, c.tagMetaData, g) + if err != nil { + return GenericPlacementIntent{}, pkgerrors.Wrap(err, "Create DB entry error") + } + + return g, nil +} + +// GetGenericPlacementIntent shall take arguments - name of the intent, name of the project, name of the composite app and version of the composite app. It shall return the genericPlacementIntent if its present. +func (c *GenericPlacementIntentClient) GetGenericPlacementIntent(i string, p string, ca string, v string) (GenericPlacementIntent, error) { + key := GenericPlacementIntentKey{ + Name: i, + Project: p, + CompositeApp: ca, + Version: v, + } + + result, err := db.DBconn.Find(c.storeName, key, c.tagMetaData) + if err != nil { + return GenericPlacementIntent{}, pkgerrors.Wrap(err, "Get Intent error") + } + + if result != nil { + g := GenericPlacementIntent{} + err = db.DBconn.Unmarshal(result[0], &g) + if err != nil { + return GenericPlacementIntent{}, pkgerrors.Wrap(err, "Unmarshalling GenericPlacement Intent") + } + return g, nil + } + + return GenericPlacementIntent{}, pkgerrors.New("Error getting GenericPlacementIntent") + +} + +// DeleteGenericPlacementIntent the intent from the database +func (c *GenericPlacementIntentClient) DeleteGenericPlacementIntent(i string, p string, ca string, v string) error { + key := GenericPlacementIntentKey{ + Name: i, + Project: p, + CompositeApp: ca, + Version: v, + } + + err := db.DBconn.Remove(c.storeName, key) + if err != nil { + return pkgerrors.Wrap(err, "Delete Project entry;") + } + return nil +} diff --git a/src/orchestrator/pkg/module/generic_placement_intent_test.go b/src/orchestrator/pkg/module/generic_placement_intent_test.go new file mode 100644 index 00000000..c87f9ddc --- /dev/null +++ b/src/orchestrator/pkg/module/generic_placement_intent_test.go @@ -0,0 +1,184 @@ +/* + * 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 ( + "reflect" + "strings" + "testing" + + "github.com/onap/multicloud-k8s/src/orchestrator/pkg/infra/db" +) + +func TestCreateGenericPlacementIntent(t *testing.T) { + testCases := []struct { + label string + inputIntent GenericPlacementIntent + inputProject string + inputCompositeApp string + inputCompositeAppVersion string + expectedError string + mockdb *db.MockDB + expected GenericPlacementIntent + }{ + { + label: "Create GenericPlacementIntent", + inputIntent: GenericPlacementIntent{ + MetaData: GenIntentMetaData{ + Name: "testGenericPlacement", + Description: " A sample intent for testing", + UserData1: "userData1", + UserData2: "userData2", + }, + Spec: GenIntentSpecData{ + LogicalCloud: "logicalCloud1", + }, + }, + inputProject: "testProject", + inputCompositeApp: "testCompositeApp", + inputCompositeAppVersion: "testCompositeAppVersion", + expected: GenericPlacementIntent{ + MetaData: GenIntentMetaData{ + Name: "testGenericPlacement", + Description: " A sample intent for testing", + UserData1: "userData1", + UserData2: "userData2", + }, + Spec: GenIntentSpecData{ + LogicalCloud: "logicalCloud1", + }, + }, + expectedError: "", + mockdb: &db.MockDB{ + Items: map[string]map[string][]byte{ + ProjectKey{ProjectName: "testProject"}.String(): { + "projectmetadata": []byte( + "{\"project-name\":\"testProject\"," + + "\"description\":\"Test project for unit testing\"}"), + }, + CompositeAppKey{CompositeAppName: "testCompositeApp", + Version: "testCompositeAppVersion", Project: "testProject"}.String(): { + "compositeAppmetadata": []byte( + "{\"metadata\":{" + + "\"name\":\"testCompositeApp\"," + + "\"description\":\"description\"," + + "\"userData1\":\"user data\"," + + "\"userData2\":\"user data\"" + + "}," + + "\"spec\":{" + + "\"version\":\"version of the composite app\"}}"), + }, + }, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + db.DBconn = testCase.mockdb + intentCli := NewGenericPlacementIntentClient() + got, err := intentCli.CreateGenericPlacementIntent(testCase.inputIntent, testCase.inputProject, testCase.inputCompositeApp, testCase.inputCompositeAppVersion) + if err != nil { + if testCase.expectedError == "" { + t.Fatalf("CreateGenericPlacementIntent returned an unexpected error %s", err) + } + if strings.Contains(err.Error(), testCase.expectedError) == false { + t.Fatalf("CreateGenericPlacementIntent returned an unexpected error %s", err) + } + } else { + if reflect.DeepEqual(testCase.expected, got) == false { + t.Errorf("CreateGenericPlacementIntent returned unexpected body: got %v; "+" expected %v", got, testCase.expected) + } + } + }) + + } +} + +func TestGetGenericPlacementIntent(t *testing.T) { + + testCases := []struct { + label string + expectedError string + expected GenericPlacementIntent + mockdb *db.MockDB + intentName string + projectName string + compositeAppName string + compositeAppVersion string + }{ + { + label: "Get Intent", + intentName: "testIntent", + projectName: "testProject", + compositeAppName: "testCompositeApp", + compositeAppVersion: "testVersion", + expected: GenericPlacementIntent{ + MetaData: GenIntentMetaData{ + Name: "testIntent", + Description: "A sample intent for testing", + UserData1: "userData1", + UserData2: "userData2", + }, + Spec: GenIntentSpecData{ + LogicalCloud: "logicalCloud1", + }, + }, + expectedError: "", + mockdb: &db.MockDB{ + Items: map[string]map[string][]byte{ + GenericPlacementIntentKey{ + Name: "testIntent", + Project: "testProject", + CompositeApp: "testCompositeApp", + Version: "testVersion", + }.String(): { + "genericplacementintent": []byte( + "{\"metadata\":{\"Name\":\"testIntent\"," + + "\"Description\":\"A sample intent for testing\"," + + "\"UserData1\": \"userData1\"," + + "\"UserData2\": \"userData2\"}," + + "\"spec\":{\"Logical-Cloud\": \"logicalCloud1\"}}"), + }, + }, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + db.DBconn = testCase.mockdb + intentCli := NewGenericPlacementIntentClient() + got, err := intentCli.GetGenericPlacementIntent(testCase.intentName, testCase.projectName, testCase.compositeAppName, testCase.compositeAppVersion) + if err != nil { + if testCase.expectedError == "" { + t.Fatalf("GetGenericPlacementIntent returned an unexpected error: %s", err) + } + if strings.Contains(err.Error(), testCase.expectedError) == false { + t.Fatalf("GetGenericPlacementIntent returned an unexpected error: %s", err) + } + } else { + if reflect.DeepEqual(testCase.expected, got) == false { + t.Errorf("GetGenericPlacementIntent returned unexpected body: got %v;"+ + " expected %v", got, testCase.expected) + } + } + + }) + } + +} diff --git a/src/orchestrator/pkg/module/module.go b/src/orchestrator/pkg/module/module.go index f80ff55b..8f2948dd 100644 --- a/src/orchestrator/pkg/module/module.go +++ b/src/orchestrator/pkg/module/module.go @@ -18,10 +18,16 @@ package module // Client for using the services in the orchestrator type Client struct { - Project *ProjectClient - CompositeApp *CompositeAppClient - Controller *ControllerClient - Cluster *ClusterClient + Project *ProjectClient + CompositeApp *CompositeAppClient + Controller *ControllerClient + Cluster *ClusterClient + GenericPlacementIntent *GenericPlacementIntentClient + AppIntent *AppIntentClient + DeploymentIntentGroup *DeploymentIntentGroupClient + Intent *IntentClient + CompositeProfile *CompositeProfileClient + AppProfile *AppProfileClient // Add Clients for API's here } @@ -32,6 +38,12 @@ func NewClient() *Client { c.CompositeApp = NewCompositeAppClient() c.Controller = NewControllerClient() c.Cluster = NewClusterClient() + c.GenericPlacementIntent = NewGenericPlacementIntentClient() + c.AppIntent = NewAppIntentClient() + c.DeploymentIntentGroup = NewDeploymentIntentGroupClient() + c.Intent = NewIntentClient() + c.CompositeProfile = NewCompositeProfileClient() + c.AppProfile = NewAppProfileClient() // Add Client API handlers here return c } |