From def02dfebada38bde91dc98db85eff3d8923a923 Mon Sep 17 00:00:00 2001 From: Srivahni Chivukula Date: Fri, 21 Feb 2020 10:08:04 -0800 Subject: Add apps under composite app API Implemented create, get and delete handlers for the apps under composite app. Added unit tests Added remove function to mockdb Handled multipart POST request to upload file along with app data. Issue-ID: MULTICLOUD-998 Signed-off-by: Srivahni Chivukula Change-Id: I25c1faba1212c0cc881c2cd599e8e66a7b93033e --- src/orchestrator/pkg/module/app.go | 202 +++++++++++++++++ src/orchestrator/pkg/module/app_test.go | 327 ++++++++++++++++++++++++++++ src/orchestrator/pkg/module/compositeapp.go | 8 +- src/orchestrator/pkg/module/module.go | 2 + 4 files changed, 535 insertions(+), 4 deletions(-) create mode 100644 src/orchestrator/pkg/module/app.go create mode 100644 src/orchestrator/pkg/module/app_test.go (limited to 'src/orchestrator/pkg/module') diff --git a/src/orchestrator/pkg/module/app.go b/src/orchestrator/pkg/module/app.go new file mode 100644 index 00000000..c25a1b51 --- /dev/null +++ b/src/orchestrator/pkg/module/app.go @@ -0,0 +1,202 @@ +/* + * 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 governinog 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" +) + +// App contains metadata for Apps +type App struct { + Metadata AppMetaData `json:"metadata"` +} + +//AppMetaData contains the parameters needed for Apps +type AppMetaData struct { + Name string `json:"name"` + Description string `json:"description"` + UserData1 string `json:"userData1"` + UserData2 string `json:"userData2"` +} + +//AppContent contains fileContent +type AppContent struct { + FileContent string +} + +// AppKey is the key structure that is used in the database +type AppKey struct { + App string `json:"app"` + Project string `json:"project"` + CompositeApp string `json:"compositeapp"` + CompositeAppVersion string `json:"compositeappversion"` +} + +// We will use json marshalling to convert to string to +// preserve the underlying structure. +func (aK AppKey) String() string { + out, err := json.Marshal(aK) + if err != nil { + return "" + } + return string(out) +} + +// AppManager is an interface exposes the App functionality +type AppManager interface { + CreateApp(a App, ac AppContent, p string, cN string, cV string) (App, error) + GetApp(name string, p string, cN string, cV string) (App, error) + GetAppContent(name string, p string, cN string, cV string) (AppContent, error) + DeleteApp(name string, p string, cN string, cV string) error +} + +// AppClient implements the AppManager +// It will also be used to maintain some localized state +type AppClient struct { + storeName string + tagMeta, tagContent string +} + +// NewAppClient returns an instance of the AppClient +// which implements the AppManager +func NewAppClient() *AppClient { + return &AppClient{ + storeName: "orchestrator", + tagMeta: "appmetadata", + tagContent: "appcontent", + } +} + +// CreateApp creates a new collection based on the App +func (v *AppClient) CreateApp(a App, ac AppContent, p string, cN string, cV string) (App, error) { + + //Construct the composite key to select the entry + key := AppKey{ + App: a.Metadata.Name, + Project: p, + CompositeApp: cN, + CompositeAppVersion: cV, + } + + //Check if this App already exists + _, err := v.GetApp(a.Metadata.Name, p, cN, cV) + if err == nil { + return App{}, pkgerrors.New("App already exists") + } + + //Check if Project exists + _, err = NewProjectClient().GetProject(p) + if err != nil { + return App{}, pkgerrors.New("Unable to find the project") + } + + //check if CompositeApp with version exists + _, err = NewCompositeAppClient().GetCompositeApp(cN, cV, p) + if err != nil { + return App{}, pkgerrors.New("Unable to find the composite app with version") + } + + err = db.DBconn.Insert(v.storeName, key, nil, v.tagMeta, a) + if err != nil { + return App{}, pkgerrors.Wrap(err, "Creating DB Entry") + } + + err = db.DBconn.Insert(v.storeName, key, nil, v.tagContent, ac) + if err != nil { + return App{}, pkgerrors.Wrap(err, "Creating DB Entry") + } + + return a, nil +} + +// GetApp returns the App for corresponding name +func (v *AppClient) GetApp(name string, p string, cN string, cV string) (App, error) { + + //Construct the composite key to select the entry + key := AppKey{ + App: name, + Project: p, + CompositeApp: cN, + CompositeAppVersion: cV, + } + value, err := db.DBconn.Find(v.storeName, key, v.tagMeta) + if err != nil { + return App{}, pkgerrors.Wrap(err, "Get app") + } + + //value is a byte array + if value != nil { + app := App{} + err = db.DBconn.Unmarshal(value[0], &app) + if err != nil { + return App{}, pkgerrors.Wrap(err, "Unmarshaling Value") + } + return app, nil + } + + return App{}, pkgerrors.New("Error getting app") +} + +// GetAppContent returns content for corresponding app +func (v *AppClient) GetAppContent(name string, p string, cN string, cV string) (AppContent, error) { + + //Construct the composite key to select the entry + key := AppKey{ + App: name, + Project: p, + CompositeApp: cN, + CompositeAppVersion: cV, + } + value, err := db.DBconn.Find(v.storeName, key, v.tagContent) + if err != nil { + return AppContent{}, pkgerrors.Wrap(err, "Get app content") + } + + //value is a byte array + if value != nil { + ac := AppContent{} + err = db.DBconn.Unmarshal(value[0], &ac) + if err != nil { + return AppContent{}, pkgerrors.Wrap(err, "Unmarshaling Value") + } + return ac, nil + } + + return AppContent{}, pkgerrors.New("Error getting app content") +} + +// DeleteApp deletes the App from database +func (v *AppClient) DeleteApp(name string, p string, cN string, cV string) error { + + //Construct the composite key to select the entry + key := AppKey{ + App: name, + Project: p, + CompositeApp: cN, + CompositeAppVersion: cV, + } + err := db.DBconn.Remove(v.storeName, key) + if err != nil { + return pkgerrors.Wrap(err, "Delete App Entry;") + } + + return nil +} diff --git a/src/orchestrator/pkg/module/app_test.go b/src/orchestrator/pkg/module/app_test.go new file mode 100644 index 00000000..42c08ef6 --- /dev/null +++ b/src/orchestrator/pkg/module/app_test.go @@ -0,0 +1,327 @@ +/* + * 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" + pkgerrors "github.com/pkg/errors" + // pkgerrors "github.com/pkg/errors" +) + +func TestCreateApp(t *testing.T) { + testCases := []struct { + label string + inpApp App + inpAppContent AppContent + inpProject string + inpCompositeAppName string + inpCompositeAppVersion string + expectedError string + mockdb *db.MockDB + expected App + }{ + { + label: "Create App", + inpApp: App{ + Metadata: AppMetaData{ + Name: "testApp", + Description: "A sample app used for unit testing", + UserData1: "userData1", + UserData2: "userData2", + }, + }, + + inpAppContent: AppContent{ + FileContent: "Sample file content", + }, + inpProject: "testProject", + inpCompositeAppName: "testCompositeApp", + inpCompositeAppVersion: "v1", + expected: App{ + Metadata: AppMetaData{ + Name: "testApp", + Description: "A sample app used for unit 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", Version: "v1", Project: "testProject"}.String(): { + "compositeapp": []byte( + "{" + + "\"metadata\":{" + + "\"Name\":\"testCompositeApp\"," + + "\"Description\":\"Test CompositeApp 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 + impl := NewAppClient() + got, err := impl.CreateApp(testCase.inpApp, testCase.inpAppContent, testCase.inpProject, testCase.inpCompositeAppName, testCase.inpCompositeAppVersion) + if err != nil { + if testCase.expectedError == "" { + t.Fatalf("Create returned an unexpected error %s", err) + } + if strings.Contains(err.Error(), testCase.expectedError) == false { + t.Fatalf("Create returned an unexpected error %s", err) + } + } else { + if reflect.DeepEqual(testCase.expected, got) == false { + t.Errorf("Create returned unexpected body: got %v;"+ + " expected %v", got, testCase.expected) + } + } + }) + } +} + +func TestGetApp(t *testing.T) { + + testCases := []struct { + label string + inpApp string + inpProject string + inpCompositeAppName string + inpCompositeAppVersion string + expectedError string + mockdb *db.MockDB + expected App + }{ + { + label: "Get Composite App", + inpApp: "testApp", + inpProject: "testProject", + inpCompositeAppName: "testCompositeApp", + inpCompositeAppVersion: "v1", + expected: App{ + Metadata: AppMetaData{ + Name: "testApp", + Description: "Test App for unit testing", + UserData1: "userData1", + UserData2: "userData2", + }, + }, + expectedError: "", + mockdb: &db.MockDB{ + Items: map[string]map[string][]byte{ + AppKey{App: "testApp", Project: "testProject", CompositeApp: "testCompositeApp", CompositeAppVersion: "v1"}.String(): { + "appmetadata": []byte( + "{" + + "\"metadata\": {" + + "\"Name\": \"testApp\"," + + "\"Description\": \"Test App for unit testing\"," + + "\"UserData1\": \"userData1\"," + + "\"UserData2\": \"userData2\"}" + + "}"), + "appcontent": []byte( + "{" + + "\"FileContent\": \"sample file content\"" + + "}"), + }, + }, + }, + }, + { + label: "Get Error", + expectedError: "DB Error", + mockdb: &db.MockDB{ + Err: pkgerrors.New("DB Error"), + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + db.DBconn = testCase.mockdb + impl := NewAppClient() + got, err := impl.GetApp(testCase.inpApp, testCase.inpProject, testCase.inpCompositeAppName, testCase.inpCompositeAppVersion) + if err != nil { + if testCase.expectedError == "" { + t.Fatalf("Get returned an unexpected error: %s", err) + } + if strings.Contains(err.Error(), testCase.expectedError) == false { + t.Fatalf("Get returned an unexpected error: %s", err) + } + } else { + if reflect.DeepEqual(testCase.expected, got) == false { + t.Errorf("Get returned unexpected body: got %v;"+ + " expected %v", got, testCase.expected) + } + } + }) + } +} + +func TestGetAppContent(t *testing.T) { + + testCases := []struct { + label string + inpApp string + inpProject string + inpCompositeAppName string + inpCompositeAppVersion string + expectedError string + mockdb *db.MockDB + expected AppContent + }{ + { + label: "Get App content", + inpApp: "testApp", + inpProject: "testProject", + inpCompositeAppName: "testCompositeApp", + inpCompositeAppVersion: "v1", + expected: AppContent{ + FileContent: "Samplefilecontent", + }, + expectedError: "", + mockdb: &db.MockDB{ + Items: map[string]map[string][]byte{ + AppKey{App: "testApp", Project: "testProject", CompositeApp: "testCompositeApp", CompositeAppVersion: "v1"}.String(): { + "appmetadata": []byte( + "{" + + "\"metadata\": {" + + "\"Name\": \"testApp\"," + + "\"Description\": \"Test App for unit testing\"," + + "\"UserData1\": \"userData1\"," + + "\"UserData2\": \"userData2\"}" + + "}"), + "appcontent": []byte( + "{" + + "\"FileContent\": \"Samplefilecontent\"" + + "}"), + }, + }, + }, + }, + { + label: "Get Error", + expectedError: "DB Error", + mockdb: &db.MockDB{ + Err: pkgerrors.New("DB Error"), + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + db.DBconn = testCase.mockdb + impl := NewAppClient() + got, err := impl.GetAppContent(testCase.inpApp, testCase.inpProject, testCase.inpCompositeAppName, testCase.inpCompositeAppVersion) + if err != nil { + if testCase.expectedError == "" { + t.Fatalf("Get returned an unexpected error: %s", err) + } + if strings.Contains(err.Error(), testCase.expectedError) == false { + t.Fatalf("Get returned an unexpected error: %s", err) + } + } else { + if reflect.DeepEqual(testCase.expected, got) == false { + t.Errorf("Get returned unexpected body: got %v;"+ + " expected %v", got, testCase.expected) + } + } + }) + } +} + +func TestDeleteApp(t *testing.T) { + + testCases := []struct { + label string + inpApp string + inpProject string + inpCompositeAppName string + inpCompositeAppVersion string + expectedError string + mockdb *db.MockDB + }{ + { + label: "Delete App", + inpApp: "testApp", + inpProject: "testProject", + inpCompositeAppName: "testCompositeApp", + inpCompositeAppVersion: "v1", + mockdb: &db.MockDB{ + Items: map[string]map[string][]byte{ + AppKey{App: "testApp", Project: "testProject", CompositeApp: "testCompositeApp", CompositeAppVersion: "v1"}.String(): { + "appmetadata": []byte( + "{" + + "\"metadata\": {" + + "\"Name\": \"testApp\"," + + "\"Description\": \"Test App for unit testing\"," + + "\"UserData1\": \"userData1\"," + + "\"UserData2\": \"userData2\"}" + + "}"), + "appcontent": []byte( + "{" + + "\"FileContent\": \"Samplefilecontent\"" + + "}"), + }, + }, + }, + }, + { + label: "Delete Error", + expectedError: "DB Error", + mockdb: &db.MockDB{ + Err: pkgerrors.New("DB Error"), + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + db.DBconn = testCase.mockdb + impl := NewAppClient() + err := impl.DeleteApp(testCase.inpApp, testCase.inpProject, testCase.inpCompositeAppName, testCase.inpCompositeAppVersion) + if err != nil { + if testCase.expectedError == "" { + t.Fatalf("Delete returned an unexpected error %s", err) + } + if strings.Contains(err.Error(), testCase.expectedError) == false { + t.Fatalf("Delete returned an unexpected error %s", err) + } + } + }) + } +} diff --git a/src/orchestrator/pkg/module/compositeapp.go b/src/orchestrator/pkg/module/compositeapp.go index 74fbe0d5..59fbbab5 100644 --- a/src/orchestrator/pkg/module/compositeapp.go +++ b/src/orchestrator/pkg/module/compositeapp.go @@ -105,7 +105,7 @@ func (v *CompositeAppClient) CreateCompositeApp(c CompositeApp, p string) (Compo return CompositeApp{}, pkgerrors.New("Unable to find the project") } - err = db.DBconn.Create(v.storeName, key, v.tagMeta, c) + err = db.DBconn.Insert(v.storeName, key, nil, v.tagMeta, c) if err != nil { return CompositeApp{}, pkgerrors.Wrap(err, "Creating DB Entry") } @@ -122,7 +122,7 @@ func (v *CompositeAppClient) GetCompositeApp(name string, version string, p stri Version: version, Project: p, } - value, err := db.DBconn.Read(v.storeName, key, v.tagMeta) + value, err := db.DBconn.Find(v.storeName, key, v.tagMeta) if err != nil { return CompositeApp{}, pkgerrors.Wrap(err, "Get composite application") } @@ -130,7 +130,7 @@ func (v *CompositeAppClient) GetCompositeApp(name string, version string, p stri //value is a byte array if value != nil { compApp := CompositeApp{} - err = db.DBconn.Unmarshal(value, &compApp) + err = db.DBconn.Unmarshal(value[0], &compApp) if err != nil { return CompositeApp{}, pkgerrors.Wrap(err, "Unmarshaling Value") } @@ -149,7 +149,7 @@ func (v *CompositeAppClient) DeleteCompositeApp(name string, version string, p s Version: version, Project: p, } - err := db.DBconn.Delete(v.storeName, key, v.tagMeta) + err := db.DBconn.Remove(v.storeName, key) if err != nil { return pkgerrors.Wrap(err, "Delete CompositeApp Entry;") } diff --git a/src/orchestrator/pkg/module/module.go b/src/orchestrator/pkg/module/module.go index 8f2948dd..77a2f5bf 100644 --- a/src/orchestrator/pkg/module/module.go +++ b/src/orchestrator/pkg/module/module.go @@ -20,6 +20,7 @@ package module type Client struct { Project *ProjectClient CompositeApp *CompositeAppClient + App *AppClient Controller *ControllerClient Cluster *ClusterClient GenericPlacementIntent *GenericPlacementIntentClient @@ -36,6 +37,7 @@ func NewClient() *Client { c := &Client{} c.Project = NewProjectClient() c.CompositeApp = NewCompositeAppClient() + c.App = NewAppClient() c.Controller = NewControllerClient() c.Cluster = NewClusterClient() c.GenericPlacementIntent = NewGenericPlacementIntentClient() -- cgit 1.2.3-korg