diff options
Diffstat (limited to 'src/orchestrator/pkg')
28 files changed, 4556 insertions, 31 deletions
diff --git a/src/orchestrator/pkg/infra/contextdb/contextdb.go b/src/orchestrator/pkg/infra/contextdb/contextdb.go index d18af227..58832a19 100644 --- a/src/orchestrator/pkg/infra/contextdb/contextdb.go +++ b/src/orchestrator/pkg/infra/contextdb/contextdb.go @@ -29,6 +29,8 @@ type ContextDb interface { Put(key string, value interface{}) error // Delete k,v Delete(key string) error + // Delete all keys in heirarchy + DeleteAll(key string) error // Gets Json Struct from db Get(key string, value interface{}) error // Returns all keys with a prefix diff --git a/src/orchestrator/pkg/infra/contextdb/etcd.go b/src/orchestrator/pkg/infra/contextdb/etcd.go index a1922d3b..44f8ab48 100644 --- a/src/orchestrator/pkg/infra/contextdb/etcd.go +++ b/src/orchestrator/pkg/infra/contextdb/etcd.go @@ -156,13 +156,26 @@ func (e *EtcdClient) GetAllKeys(key string) ([]string, error) { return keys, nil } +// DeleteAll keys from Etcd DB +func (e *EtcdClient) DeleteAll(key string) error { + cli := getEtcd(e) + if cli == nil { + return pkgerrors.Errorf("Etcd Client not initialized") + } + _, err := cli.Delete(context.Background(), key, clientv3.WithPrefix()) + if err != nil { + return pkgerrors.Errorf("Delete failed etcd entry: %s", err.Error()) + } + return nil +} + // Delete values from Etcd DB func (e *EtcdClient) Delete(key string) error { cli := getEtcd(e) if cli == nil { return pkgerrors.Errorf("Etcd Client not initialized") } - _, err := cli.Delete(context.Background(), key, clientv3.WithPrefix()) + _, err := cli.Delete(context.Background(), key) if err != nil { return pkgerrors.Errorf("Delete failed etcd entry: %s", err.Error()) } diff --git a/src/orchestrator/pkg/infra/db/README.md b/src/orchestrator/pkg/infra/db/README.md index ff482b98..71da1e0a 100644 --- a/src/orchestrator/pkg/infra/db/README.md +++ b/src/orchestrator/pkg/infra/db/README.md @@ -130,7 +130,7 @@ key := CompositeAppKey{ NOTE: Key structure can be different from the original key and can include Query fields also. ANY operation is not supported for Query fields. -### Remove +### RemoveAll Arguments: ```go @@ -139,6 +139,15 @@ key interface ``` Similar to find. This will remove one or more documents based on the key structure. +### Remove + +Arguments: +```go +collection string +key interface +``` +This will remove one document based on the key structure. If child refrences exist for the key then the document will not be removed. + ### Unmarshal Data in mongo is stored as `bson` which is a compressed form of `json`. We need mongo to convert the stored `bson` data to regular `json` @@ -147,3 +156,4 @@ that we can use in our code when returned. `bson.Unmarshal` API is used to achieve this. + diff --git a/src/orchestrator/pkg/infra/db/mock.go b/src/orchestrator/pkg/infra/db/mock.go index afa4b024..e43be8fb 100644 --- a/src/orchestrator/pkg/infra/db/mock.go +++ b/src/orchestrator/pkg/infra/db/mock.go @@ -15,6 +15,7 @@ package db import ( "encoding/json" + "fmt" pkgerrors "github.com/pkg/errors" ) @@ -44,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 } @@ -62,8 +67,9 @@ func (m *MockDB) Read(table string, key Key, tag string) ([]byte, error) { return nil, m.Err } + str := fmt.Sprintf("%v", key) for k, v := range m.Items { - if k == key.String() { + if k == str { return v[tag], nil } } @@ -71,7 +77,26 @@ 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 } +func (m *MockDB) Remove(table string, key Key) error { + return m.Err +} diff --git a/src/orchestrator/pkg/infra/db/mongo.go b/src/orchestrator/pkg/infra/db/mongo.go index 30eb899f..a344aa1c 100644 --- a/src/orchestrator/pkg/infra/db/mongo.go +++ b/src/orchestrator/pkg/infra/db/mongo.go @@ -49,6 +49,8 @@ type MongoCollection interface { opts ...*options.FindOptions) (*mongo.Cursor, error) UpdateOne(ctx context.Context, filter interface{}, update interface{}, opts ...*options.UpdateOptions) (*mongo.UpdateResult, error) + CountDocuments(ctx context.Context, filter interface{}, + opts ...*options.CountOptions) (int64, error) } // MongoStore is an implementation of the db.Store interface @@ -543,8 +545,8 @@ func (m *MongoStore) Find(coll string, key Key, tag string) ([][]byte, error) { return result, nil } -// Remove method to remove the documet by key -func (m *MongoStore) Remove(coll string, key Key) error { +// RemoveAll method to removes all the documet matching key +func (m *MongoStore) RemoveAll(coll string, key Key) error { if !m.validateParams(coll, key) { return pkgerrors.New("Mandatory fields are missing") } @@ -560,3 +562,29 @@ func (m *MongoStore) Remove(coll string, key Key) error { } return nil } + +// Remove method to remove the documet by key if no child references +func (m *MongoStore) Remove(coll string, key Key) error { + if !m.validateParams(coll, key) { + return pkgerrors.New("Mandatory fields are missing") + } + c := getCollection(coll, m) + ctx := context.Background() + filter, err := m.findFilter(key) + if err != nil { + return err + } + count, err := c.CountDocuments(context.Background(), filter) + if err != nil { + return pkgerrors.Errorf("Error finding: %s", err.Error()) + } + if count > 1 { + return pkgerrors.Errorf("Can't delete parent without deleting child references first") + } + _, err = c.DeleteOne(ctx, filter) + if err != nil { + return pkgerrors.Errorf("Error Deleting from database: %s", err.Error()) + } + return nil +} + diff --git a/src/orchestrator/pkg/infra/db/mongo_test.go b/src/orchestrator/pkg/infra/db/mongo_test.go index d506dbda..d57c19dd 100644 --- a/src/orchestrator/pkg/infra/db/mongo_test.go +++ b/src/orchestrator/pkg/infra/db/mongo_test.go @@ -76,7 +76,12 @@ 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 (c *mockCollection) CountDocuments(ctx context.Context, filter interface{}, + opts ...*options.CountOptions) (int64, error) { + return 1, c.Err } func TestCreate(t *testing.T) { @@ -463,4 +468,3 @@ func TestDelete(t *testing.T) { }) } } - diff --git a/src/orchestrator/pkg/infra/db/store.go b/src/orchestrator/pkg/infra/db/store.go index c8a8f744..e87722cd 100644 --- a/src/orchestrator/pkg/infra/db/store.go +++ b/src/orchestrator/pkg/infra/db/store.go @@ -29,7 +29,6 @@ var DBconn Store // that wants to use the Store interface. This allows various // db backends and key types. type Key interface { - String() string } // Store is an interface for accessing the database @@ -60,17 +59,20 @@ type Store interface { // Find the document(s) with key and get the tag values from the document(s) Find(coll string, key Key, tag string) ([][]byte, error) - // Removes the document(s) matching the key + // Removes the document(s) matching the key if no child reference in collection Remove(coll string, key Key) error + + // Remove all the document(s) matching the key + RemoveAll(coll string, key Key) error } // CreateDBClient creates the DB client -func createDBClient(dbType string) error { +func createDBClient(dbType string, dbName string) error { var err error switch dbType { case "mongo": // create a mongodb database with orchestrator as the name - DBconn, err = NewMongoStore("orchestrator", nil) + DBconn, err = NewMongoStore(dbName, nil) default: return pkgerrors.New(dbType + "DB not supported") } @@ -97,8 +99,8 @@ func DeSerialize(str string, v interface{}) error { // InitializeDatabaseConnection sets up the connection to the // configured database to allow the application to talk to it. -func InitializeDatabaseConnection() error { - err := createDBClient(config.GetConfiguration().DatabaseType) +func InitializeDatabaseConnection(dbName string) error { + err := createDBClient(config.GetConfiguration().DatabaseType, dbName) if err != nil { return pkgerrors.Cause(err) } diff --git a/src/orchestrator/pkg/infra/db/store_test.go b/src/orchestrator/pkg/infra/db/store_test.go index 42a41787..fb23e232 100644 --- a/src/orchestrator/pkg/infra/db/store_test.go +++ b/src/orchestrator/pkg/infra/db/store_test.go @@ -23,7 +23,7 @@ func TestCreateDBClient(t *testing.T) { t.Run("Successfully create DB client", func(t *testing.T) { expected := &MongoStore{} - err := createDBClient("mongo") + err := createDBClient("mongo", "testdb") if err != nil { t.Fatalf("CreateDBClient returned an error (%s)", err) } @@ -32,7 +32,7 @@ func TestCreateDBClient(t *testing.T) { } }) t.Run("Fail to create client for unsupported DB", func(t *testing.T) { - err := createDBClient("fakeDB") + err := createDBClient("fakeDB", "testdb2") if err == nil { t.Fatal("CreateDBClient didn't return an error") } diff --git a/src/orchestrator/pkg/infra/validation/validation.go b/src/orchestrator/pkg/infra/validation/validation.go new file mode 100644 index 00000000..d744dc3d --- /dev/null +++ b/src/orchestrator/pkg/infra/validation/validation.go @@ -0,0 +1,264 @@ +/* + * 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 validation + +import ( + "archive/tar" + "compress/gzip" + "io" + "net" + "regexp" + "strings" + + pkgerrors "github.com/pkg/errors" + "k8s.io/apimachinery/pkg/util/validation" +) + +func IsTarGz(r io.Reader) error { + //Check if it is a valid gz + gzf, err := gzip.NewReader(r) + if err != nil { + return pkgerrors.Wrap(err, "Invalid gzip format") + } + + //Check if it is a valid tar file + //Unfortunately this can only be done by inspecting all the tar contents + tarR := tar.NewReader(gzf) + first := true + + for true { + header, err := tarR.Next() + + if err == io.EOF { + //Check if we have just a gzip file without a tar archive inside + if first { + return pkgerrors.New("Empty or non-existant Tar file found") + } + //End of archive + break + } + + if err != nil { + return pkgerrors.Errorf("Error reading tar file %s", err.Error()) + } + + //Check if files are of type directory and regular file + if header.Typeflag != tar.TypeDir && + header.Typeflag != tar.TypeReg { + return pkgerrors.Errorf("Unknown header in tar %s, %s", + header.Name, string(header.Typeflag)) + } + + first = false + } + + return nil +} + +func IsIpv4Cidr(cidr string) error { + _, _, err := net.ParseCIDR(cidr) + if err != nil { + return pkgerrors.Wrapf(err, "could not parse subnet %v", cidr) + } + return nil +} + +func IsIpv4(ip string) error { + addr := net.ParseIP(ip) + if addr == nil { + return pkgerrors.Errorf("invalid ipv4 address %v", ip) + } + return nil +} + +// default name check - matches valid label value with addtion that length > 0 +func IsValidName(name string) []string { + var errs []string + + errs = validation.IsValidLabelValue(name) + if len(name) == 0 { + errs = append(errs, "name must have non-zero length") + } + return errs +} + +const VALID_NAME_STR string = "NAME" + +var validNameRegEx = regexp.MustCompile("^([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]$") + +const VALID_ALPHA_STR string = "ALPHA" + +var validAlphaStrRegEx = regexp.MustCompile("^[A-Za-z]*$") + +const VALID_ALPHANUM_STR string = "ALPHANUM" + +var validAlphaNumStrRegEx = regexp.MustCompile("^[A-Za-z0-9]*$") + +// doesn't verify valid base64 length - just checks for proper base64 characters +const VALID_BASE64_STR string = "BASE64" + +var validBase64StrRegEx = regexp.MustCompile("^[A-Za-z0-9+/]+={0,2}$") + +const VALID_ANY_STR string = "ANY" + +var validAnyStrRegEx = regexp.MustCompile("(?s)^.*$") + +// string check - validates for conformance to provided lengths and specified content +// min and max - the string +// if format string provided - check against matching predefined +func IsValidString(str string, min, max int, format string) []string { + var errs []string + + if min > max { + errs = append(errs, "Invalid string length constraints - min is greater than max") + return errs + } + + if len(str) < min { + errs = append(errs, "string length is less than the minimum constraint") + return errs + } + if len(str) > max { + errs = append(errs, "string length is greater than the maximum constraint") + return errs + } + + switch format { + case VALID_ALPHA_STR: + if !validAlphaStrRegEx.MatchString(str) { + errs = append(errs, "string does not match the alpha only constraint") + } + case VALID_ALPHANUM_STR: + if !validAlphaNumStrRegEx.MatchString(str) { + errs = append(errs, "string does not match the alphanumeric only constraint") + } + case VALID_NAME_STR: + if !validNameRegEx.MatchString(str) { + errs = append(errs, "string does not match the valid k8s name constraint") + } + case VALID_BASE64_STR: + if !validBase64StrRegEx.MatchString(str) { + errs = append(errs, "string does not match the valid base64 characters constraint") + } + if len(str)%4 != 0 { + errs = append(errs, "base64 string length should be a multiple of 4") + } + case VALID_ANY_STR: + if !validAnyStrRegEx.MatchString(str) { + errs = append(errs, "string does not match the any characters constraint") + } + default: + // invalid string format supplied + errs = append(errs, "an invalid string constraint was supplied") + } + + return errs +} + +// validate that label conforms to kubernetes label conventions +// general label format expected is: +// "<labelprefix>/<labelname>=<Labelvalue>" +// where labelprefix matches DNS1123Subdomain format +// labelname matches DNS1123Label format +// +// Input labels are allowed to match following formats: +// "<DNS1123Subdomain>/<DNS1123Label>=<Labelvalue>" +// "<DNS1123Label>=<LabelValue>" +// "<LabelValue>" +func IsValidLabel(label string) []string { + var labelerrs []string + + expectLabelName := false + expectLabelPrefix := false + + // split label up into prefix, name and value + // format: prefix/name=value + var labelprefix, labelname, labelvalue string + + kv := strings.SplitN(label, "=", 2) + if len(kv) == 1 { + labelprefix = "" + labelname = "" + labelvalue = kv[0] + } else { + pn := strings.SplitN(kv[0], "/", 2) + if len(pn) == 1 { + labelprefix = "" + labelname = pn[0] + } else { + labelprefix = pn[0] + labelname = pn[1] + expectLabelPrefix = true + } + labelvalue = kv[1] + // if "=" was in the label input, then expect a non-zero length name + expectLabelName = true + } + + // check label prefix validity - prefix is optional + if len(labelprefix) > 0 { + errs := validation.IsDNS1123Subdomain(labelprefix) + if len(errs) > 0 { + labelerrs = append(labelerrs, "Invalid label prefix - label=["+label+"%], labelprefix=["+labelprefix+"], errors: ") + for _, err := range errs { + labelerrs = append(labelerrs, err) + } + } + } else if expectLabelPrefix { + labelerrs = append(labelerrs, "Invalid label prefix - label=["+label+"%], labelprefix=["+labelprefix+"]") + } + if expectLabelName { + errs := validation.IsDNS1123Label(labelname) + if len(errs) > 0 { + labelerrs = append(labelerrs, "Invalid label name - label=["+label+"%], labelname=["+labelname+"], errors: ") + for _, err := range errs { + labelerrs = append(labelerrs, err) + } + } + } + if len(labelvalue) > 0 { + errs := validation.IsValidLabelValue(labelvalue) + if len(errs) > 0 { + labelerrs = append(labelerrs, "Invalid label value - label=["+label+"%], labelvalue=["+labelvalue+"], errors: ") + for _, err := range errs { + labelerrs = append(labelerrs, err) + } + } + } else { + // expect a non-zero value + labelerrs = append(labelerrs, "Invalid label value - label=["+label+"%], labelvalue=["+labelvalue+"]") + } + + return labelerrs +} + +func IsValidNumber(value, min, max int) []string { + var errs []string + + if min > max { + errs = append(errs, "invalid constraints") + return errs + } + + if value < min { + errs = append(errs, "value less than minimum") + } + if value > max { + errs = append(errs, "value greater than maximum") + } + return errs +} diff --git a/src/orchestrator/pkg/infra/validation/validation_test.go b/src/orchestrator/pkg/infra/validation/validation_test.go new file mode 100644 index 00000000..5109b6c7 --- /dev/null +++ b/src/orchestrator/pkg/infra/validation/validation_test.go @@ -0,0 +1,466 @@ +/* + * Copyright 2018 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 validation + +import ( + "bytes" + "testing" +) + +func TestIsTarGz(t *testing.T) { + + t.Run("Valid tar.gz", func(t *testing.T) { + content := []byte{ + 0x1f, 0x8b, 0x08, 0x08, 0xb0, 0x6b, 0xf4, 0x5b, + 0x00, 0x03, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x74, + 0x61, 0x72, 0x00, 0xed, 0xce, 0x41, 0x0a, 0xc2, + 0x30, 0x10, 0x85, 0xe1, 0xac, 0x3d, 0x45, 0x4e, + 0x50, 0x12, 0xd2, 0xc4, 0xe3, 0x48, 0xa0, 0x01, + 0x4b, 0x52, 0x0b, 0xed, 0x88, 0x1e, 0xdf, 0x48, + 0x11, 0x5c, 0x08, 0xa5, 0x8b, 0x52, 0x84, 0xff, + 0xdb, 0xbc, 0x61, 0x66, 0x16, 0x4f, 0xd2, 0x2c, + 0x8d, 0x3c, 0x45, 0xed, 0xc8, 0x54, 0x21, 0xb4, + 0xef, 0xb4, 0x67, 0x6f, 0xbe, 0x73, 0x61, 0x9d, + 0xb2, 0xce, 0xd5, 0x55, 0xf0, 0xde, 0xd7, 0x3f, + 0xdb, 0xd6, 0x49, 0x69, 0xb3, 0x67, 0xa9, 0x8f, + 0xfb, 0x2c, 0x71, 0xd2, 0x5a, 0xc5, 0xee, 0x92, + 0x73, 0x8e, 0x43, 0x7f, 0x4b, 0x3f, 0xff, 0xd6, + 0xee, 0x7f, 0xea, 0x9a, 0x4a, 0x19, 0x1f, 0xe3, + 0x54, 0xba, 0xd3, 0xd1, 0x55, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x1b, 0xbc, 0x00, 0xb5, 0xe8, + 0x4a, 0xf9, 0x00, 0x28, 0x00, 0x00, + } + + err := IsTarGz(bytes.NewBuffer(content)) + if err != nil { + t.Errorf("Error reading valid tar.gz file %s", err.Error()) + } + }) + + t.Run("Invalid tar.gz", func(t *testing.T) { + content := []byte{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xff, 0xf2, 0x48, 0xcd, + } + + err := IsTarGz(bytes.NewBuffer(content)) + if err == nil { + t.Errorf("Error should NOT be nil") + } + }) + + t.Run("Empty tar.gz", func(t *testing.T) { + content := []byte{} + err := IsTarGz(bytes.NewBuffer(content)) + if err == nil { + t.Errorf("Error should NOT be nil") + } + }) +} + +func TestIsValidName(t *testing.T) { + t.Run("Valid Names", func(t *testing.T) { + validnames := []string{ + "abc123", + "1_abc123.ONE", + "0abcABC_-.5", + "123456789012345678901234567890123456789012345678901234567890123", // max of 63 characters + } + for _, name := range validnames { + errs := IsValidName(name) + if len(errs) > 0 { + t.Errorf("Valid name failed to pass: %v", name) + } + } + }) + + t.Run("Invalid Names", func(t *testing.T) { + invalidnames := []string{ + "", // empty + "_abc123", // starts with non-alphanum + "-abc123", // starts with non-alphanum + ".abc123", // starts with non-alphanum + "abc123-", // ends with non-alphanum + "abc123_", // ends with non-alphanum + "abc123.", // ends with non-alphanum + "1_abc-123.O=NE", // contains not allowed character + "1_a/bc-123.ONE", // contains not allowed character + "1234567890123456789012345678901234567890123456789012345678901234", // longer than 63 characters + } + for _, name := range invalidnames { + errs := IsValidName(name) + if len(errs) == 0 { + t.Errorf("Invalid name passed: %v", name) + } + } + }) +} + +func TestIsIpv4Cidr(t *testing.T) { + t.Run("Valid IPv4 Cidr", func(t *testing.T) { + validipv4cidr := []string{ + "1.2.3.4/32", + "10.11.12.0/24", + "192.168.1.2/8", + "255.0.0.0/16", + } + for _, ip := range validipv4cidr { + err := IsIpv4Cidr(ip) + if err != nil { + t.Errorf("Valid IPv4 CIDR string failed to pass: %v", ip) + } + } + }) + + t.Run("Invalid IPv4 Cidr", func(t *testing.T) { + invalidipv4cidr := []string{ + "", + "1.2.3.4.5/32", + "1.2.3.415/16", + "1.2.3.4/33", + "2.3.4/24", + "1.2.3.4", + "1.2.3.4/", + } + for _, ip := range invalidipv4cidr { + err := IsIpv4Cidr(ip) + if err == nil { + t.Errorf("Invalid IPv4 Cidr passed: %v", ip) + } + } + }) +} + +func TestIsIpv4(t *testing.T) { + t.Run("Valid IPv4", func(t *testing.T) { + validipv4 := []string{ + "1.2.3.42", + "10.11.12.0", + "192.168.1.2", + "255.0.0.0", + "255.255.255.255", + "0.0.0.0", + } + for _, ip := range validipv4 { + err := IsIpv4(ip) + if err != nil { + t.Errorf("Valid IPv4 string failed to pass: %v", ip) + } + } + }) + + t.Run("Invalid IPv4", func(t *testing.T) { + invalidipv4 := []string{ + "", + "1.2.3.4.5", + "1.2.3.45/32", + "1.2.3.4a", + "2.3.4", + "1.2.3.400", + "256.255.255.255", + "10,11,12,13", + "1.2.3.4/", + } + for _, ip := range invalidipv4 { + err := IsIpv4(ip) + if err == nil { + t.Errorf("Invalid IPv4 passed: %v", ip) + } + } + }) +} + +func TestIsValidString(t *testing.T) { + t.Run("Valid Strings", func(t *testing.T) { + validStrings := []struct { + str string + min int + max int + format string + }{ + { + str: "abc123", + min: 0, + max: 16, + format: VALID_NAME_STR, + }, + { + str: "ab-c1_2.3", + min: 0, + max: 16, + format: VALID_NAME_STR, + }, + { + str: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", + min: 0, + max: 62, + format: VALID_ALPHANUM_STR, + }, + { + str: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", + min: 0, + max: 52, + format: VALID_ALPHA_STR, + }, + { + str: "", + min: 0, + max: 52, + format: VALID_ALPHA_STR, + }, + { + str: "", + min: 0, + max: 52, + format: VALID_ALPHANUM_STR, + }, + { + str: "dGhpcyBpcyBhCnRlc3Qgc3RyaW5nCg==", + min: 0, + max: 52, + format: VALID_BASE64_STR, + }, + { + str: "\t\n \n0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+-=,.<>/?'\"\\[]{}\n", + min: 0, + max: 256, + format: VALID_ANY_STR, + }, + } + for _, test := range validStrings { + errs := IsValidString(test.str, test.min, test.max, test.format) + if len(errs) > 0 { + t.Errorf("Valid string failed to pass: str:%v, min:%v, max:%v, format:%v", test.str, test.min, test.max, test.format) + } + } + }) + + t.Run("Invalid Strings", func(t *testing.T) { + inValidStrings := []struct { + str string + min int + max int + format string + }{ + { + str: "abc123", + min: 0, + max: 5, + format: VALID_NAME_STR, + }, + { + str: "", + min: 0, + max: 5, + format: VALID_NAME_STR, + }, + { + str: "-ab-c1_2.3", + min: 0, + max: 16, + format: VALID_NAME_STR, + }, + { + str: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ=", + min: 0, + max: 100, + format: VALID_ALPHANUM_STR, + }, + { + str: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456", + min: 0, + max: 62, + format: VALID_ALPHA_STR, + }, + { + str: "", + min: 1, + max: 52, + format: VALID_ALPHA_STR, + }, + { + str: "abc123", + min: 1, + max: 3, + format: VALID_ALPHA_STR, + }, + { + str: "", + min: 1, + max: 52, + format: VALID_ALPHANUM_STR, + }, + { + str: "dGhpcyBpcyBhCnRlc3Qgc3RyaW5nCg===", + min: 0, + max: 52, + format: VALID_BASE64_STR, + }, + { + str: "dGhpcyBpcyBhCnRlc3=Qgc3RyaW5nCg==", + min: 0, + max: 52, + format: VALID_BASE64_STR, + }, + { + str: "dGhpcyBpcyBhCnRlc3#Qgc3RyaW5nCg==", + min: 0, + max: 52, + format: VALID_BASE64_STR, + }, + { + str: "\t\n \n0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+-=,.<>/?'\"\\[]{}\n", + min: 0, + max: 10, + format: VALID_ANY_STR, + }, + { + str: "abc123", + min: 0, + max: 10, + format: "unknownformat", + }, + } + for _, test := range inValidStrings { + errs := IsValidString(test.str, test.min, test.max, test.format) + if len(errs) == 0 { + t.Errorf("Invalid string passed: str:%v, min:%v, max:%v, format:%v", test.str, test.min, test.max, test.format) + } + } + }) +} + +func TestIsValidLabel(t *testing.T) { + t.Run("Valid Labels", func(t *testing.T) { + validlabels := []string{ + "kubernetes.io/hostname=localhost", + "hostname=localhost", + "localhost", + } + for _, label := range validlabels { + errs := IsValidLabel(label) + if len(errs) > 0 { + t.Errorf("Valid label failed to pass: %v %v", label, errs) + } + } + }) + + t.Run("Invalid Labels", func(t *testing.T) { + invalidlabels := []string{ + "", + "kubernetes$.io/hostname=localhost", + "hostname==localhost", + "=localhost", + "/hostname=localhost", + ".a.b/hostname=localhost", + "kubernetes.io/hostname", + "kubernetes.io/hostname=", + "kubernetes.io/1234567890123456789012345678901234567890123456789012345678901234=localhost", // too long name + "kubernetes.io/hostname=localhost1234567890123456789012345678901234567890123456789012345678901234", // too long value + "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234/hostname=localhost", // too long prefix + } + for _, label := range invalidlabels { + errs := IsValidLabel(label) + if len(errs) == 0 { + t.Errorf("Invalid label passed: %v", label) + } + } + }) +} + +func TestIsValidNumber(t *testing.T) { + t.Run("Valid Number", func(t *testing.T) { + validNumbers := []struct { + value int + min int + max int + }{ + { + value: 0, + min: 0, + max: 5, + }, + { + value: 1000, + min: 0, + max: 4095, + }, + { + value: 0, + min: 0, + max: 0, + }, + { + value: -100, + min: -200, + max: -99, + }, + { + value: 123, + min: 123, + max: 123, + }, + } + for _, test := range validNumbers { + err := IsValidNumber(test.value, test.min, test.max) + if len(err) > 0 { + t.Errorf("Valid number failed to pass - value:%v, min:%v, max:%v", test.value, test.min, test.max) + } + } + }) + + t.Run("Invalid Number", func(t *testing.T) { + inValidNumbers := []struct { + value int + min int + max int + }{ + { + value: 6, + min: 0, + max: 5, + }, + { + value: 4096, + min: 0, + max: 4095, + }, + { + value: 11, + min: 10, + max: 10, + }, + { + value: -100, + min: -99, + max: -200, + }, + { + value: 123, + min: 223, + max: 123, + }, + } + for _, test := range inValidNumbers { + err := IsValidNumber(test.value, test.min, test.max) + if len(err) == 0 { + t.Errorf("Invalid number passed - value:%v, min:%v, max:%v", test.value, test.min, test.max) + } + } + }) +} diff --git a/src/orchestrator/pkg/module/add_intents.go b/src/orchestrator/pkg/module/add_intents.go new file mode 100644 index 00000000..20fba189 --- /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:"intentname"` + Project string `json:"project"` + CompositeApp string `json:"compositeapp"` + Version string `json:"compositeappversion"` + DeploymentIntentGroup string `json:"deploymentintentgroup"` +} + +// 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.go b/src/orchestrator/pkg/module/app.go new file mode 100644 index 00000000..1e1a5974 --- /dev/null +++ b/src/orchestrator/pkg/module/app.go @@ -0,0 +1,231 @@ +/* + * 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) + GetApps(p string, cN string, cV string) ([]App, 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") +} + +// GetApps returns all Apps for given composite App +func (v *AppClient) GetApps(project, compositeApp, compositeAppVersion string) ([]App, error) { + + key := AppKey{ + App: "", + Project: project, + CompositeApp: compositeApp, + CompositeAppVersion: compositeAppVersion, + } + + var resp []App + values, err := db.DBconn.Find(v.storeName, key, v.tagMeta) + if err != nil { + return []App{}, pkgerrors.Wrap(err, "Get Apps") + } + + for _, value := range values { + a := App{} + err = db.DBconn.Unmarshal(value, &a) + if err != nil { + return []App{}, pkgerrors.Wrap(err, "Unmarshaling Value") + } + resp = append(resp, a) + } + + return resp, nil +} + +// 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_intent.go b/src/orchestrator/pkg/module/app_intent.go new file mode 100644 index 00000000..a3f4b832 --- /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:"appintent"` + Project string `json:"project"` + CompositeApp string `json:"compositeapp"` + Version string `json:"compositeappversion"` + Intent string `json:"genericplacement"` +} + +// 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: "appintentmetadata", + } +} + +// 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..6cbdf15f --- /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(): { + "genericplacementintentmetadata": []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(): { + "appintentmetadata": []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/app_test.go b/src/orchestrator/pkg/module/app_test.go new file mode 100644 index 00000000..3bbbaf2f --- /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(): { + "compositeappmetadata": []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/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/compositeapp.go b/src/orchestrator/pkg/module/compositeapp.go index 0a4e158c..70502367 100644 --- a/src/orchestrator/pkg/module/compositeapp.go +++ b/src/orchestrator/pkg/module/compositeapp.go @@ -45,8 +45,8 @@ type CompositeAppSpec struct { // CompositeAppKey is the key structure that is used in the database type CompositeAppKey struct { - CompositeAppName string `json:"compositeappname"` - Version string `json:"version"` + CompositeAppName string `json:"compositeapp"` + Version string `json:"compositeappversion"` Project string `json:"project"` } @@ -70,8 +70,8 @@ type CompositeAppManager interface { // CompositeAppClient implements the CompositeAppManager // It will also be used to maintain some localized state type CompositeAppClient struct { - storeName string - tagMeta, tagContent string + storeName string + tagMeta string } // NewCompositeAppClient returns an instance of the CompositeAppClient @@ -79,7 +79,7 @@ type CompositeAppClient struct { func NewCompositeAppClient() *CompositeAppClient { return &CompositeAppClient{ storeName: "orchestrator", - tagMeta: "compositeAppmetadata", + tagMeta: "compositeappmetadata", } } @@ -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/compositeapp_test.go b/src/orchestrator/pkg/module/compositeapp_test.go new file mode 100644 index 00000000..1fc5f5d2 --- /dev/null +++ b/src/orchestrator/pkg/module/compositeapp_test.go @@ -0,0 +1,236 @@ +/* + * 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" +) + +func TestCreateCompositeApp(t *testing.T) { + testCases := []struct { + label string + inpCompApp CompositeApp + inpProject string + expectedError string + mockdb *db.MockDB + expected CompositeApp + }{ + { + label: "Create Composite App", + inpCompApp: CompositeApp{ + Metadata: CompositeAppMetaData{ + Name: "testCompositeApp", + Description: "A sample composite app used for unit testing", + UserData1: "userData1", + UserData2: "userData2", + }, + Spec: CompositeAppSpec{ + Version: "v1", + }, + }, + + inpProject: "testProject", + expected: CompositeApp{ + Metadata: CompositeAppMetaData{ + Name: "testCompositeApp", + Description: "A sample composite app used for unit testing", + UserData1: "userData1", + UserData2: "userData2", + }, + Spec: CompositeAppSpec{ + Version: "v1", + }, + }, + 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\"}" + + "}"), + }, + }, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + db.DBconn = testCase.mockdb + impl := NewCompositeAppClient() + got, err := impl.CreateCompositeApp(testCase.inpCompApp, testCase.inpProject) + 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 TestGetCompositeApp(t *testing.T) { + + testCases := []struct { + label string + inpName string + inpVersion string + inpProject string + expectedError string + mockdb *db.MockDB + expected CompositeApp + }{ + { + label: "Get Composite App", + inpName: "testCompositeApp", + inpVersion: "v1", + inpProject: "testProject", + expected: CompositeApp{ + Metadata: CompositeAppMetaData{ + Name: "testCompositeApp", + Description: "Test CompositeApp for unit testing", + UserData1: "userData1", + UserData2: "userData2", + }, + Spec: CompositeAppSpec{ + Version: "v1", + }, + }, + expectedError: "", + mockdb: &db.MockDB{ + Items: map[string]map[string][]byte{ + CompositeAppKey{CompositeAppName: "testCompositeApp", Version: "v1", Project: "testProject"}.String(): { + "compositeappmetadata": []byte( + "{" + + "\"metadata\":{" + + "\"Name\":\"testCompositeApp\"," + + "\"Description\":\"Test CompositeApp for unit testing\"," + + "\"UserData1\":\"userData1\"," + + "\"UserData2\":\"userData2\"}," + + "\"spec\":{" + + "\"Version\":\"v1\"}" + + "}"), + }, + }, + }, + }, + { + 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 := NewCompositeAppClient() + got, err := impl.GetCompositeApp(testCase.inpName, testCase.inpVersion, testCase.inpProject) + 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 TestDeleteCompositeApp(t *testing.T) { + + testCases := []struct { + label string + inpName string + inpVersion string + inpProject string + expectedError string + mockdb *db.MockDB + }{ + { + label: "Delete Composite app", + inpName: "testCompositeApp", + inpVersion: "v1", + inpProject: "testProject", + mockdb: &db.MockDB{ + Items: map[string]map[string][]byte{ + CompositeAppKey{CompositeAppName: "testCompositeApp", Version: "v1", Project: "testProject"}.String(): { + "compositeappmetadata": []byte( + "{" + + "\"metadata\":{" + + "\"Name\":\"testCompositeApp\"," + + "\"Description\":\"Test CompositeApp for unit testing\"," + + "\"UserData1\":\"userData1\"," + + "\"UserData2\":\"userData2\"}," + + "\"spec\":{" + + "\"Version\":\"v1\"}" + + "}"), + }, + }, + }, + }, + { + 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 := NewCompositeAppClient() + err := impl.DeleteCompositeApp(testCase.inpName, testCase.inpVersion, testCase.inpProject) + 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/deployment_intent_groups.go b/src/orchestrator/pkg/module/deployment_intent_groups.go new file mode 100644 index 00000000..cfbf53e2 --- /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:"deploymentintentgroup"` + 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 (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: "deploymentintentgroupmetadata", + } +} + +// 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..0fdeb4a1 --- /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(): { + "deploymentintentgroupmetadata": []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..73849474 --- /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:"genericplacement"` + 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 (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: "genericplacementintentmetadata", + } +} + +// 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..d779e81f --- /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(): { + "genericplacementintentmetadata": []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 a94a4207..c697bbff 100644 --- a/src/orchestrator/pkg/module/module.go +++ b/src/orchestrator/pkg/module/module.go @@ -18,9 +18,16 @@ package module // Client for using the services in the orchestrator type Client struct { - Project *ProjectClient - CompositeApp *CompositeAppClient - Controller *ControllerClient + Project *ProjectClient + CompositeApp *CompositeAppClient + App *AppClient + Controller *ControllerClient + GenericPlacementIntent *GenericPlacementIntentClient + AppIntent *AppIntentClient + DeploymentIntentGroup *DeploymentIntentGroupClient + Intent *IntentClient + CompositeProfile *CompositeProfileClient + AppProfile *AppProfileClient // Add Clients for API's here } @@ -29,7 +36,14 @@ func NewClient() *Client { c := &Client{} c.Project = NewProjectClient() c.CompositeApp = NewCompositeAppClient() + c.App = NewAppClient() c.Controller = NewControllerClient() + 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 } diff --git a/src/orchestrator/pkg/module/project.go b/src/orchestrator/pkg/module/project.go index a95251b5..a6f59254 100644 --- a/src/orchestrator/pkg/module/project.go +++ b/src/orchestrator/pkg/module/project.go @@ -90,7 +90,7 @@ func (v *ProjectClient) CreateProject(p Project) (Project, error) { return Project{}, pkgerrors.New("Project already exists") } - err = db.DBconn.Create(v.storeName, key, v.tagMeta, p) + err = db.DBconn.Insert(v.storeName, key, nil, v.tagMeta, p) if err != nil { return Project{}, pkgerrors.Wrap(err, "Creating DB Entry") } @@ -105,7 +105,7 @@ func (v *ProjectClient) GetProject(name string) (Project, error) { key := ProjectKey{ ProjectName: name, } - value, err := db.DBconn.Read(v.storeName, key, v.tagMeta) + value, err := db.DBconn.Find(v.storeName, key, v.tagMeta) if err != nil { return Project{}, pkgerrors.Wrap(err, "Get Project") } @@ -113,7 +113,7 @@ func (v *ProjectClient) GetProject(name string) (Project, error) { //value is a byte array if value != nil { proj := Project{} - err = db.DBconn.Unmarshal(value, &proj) + err = db.DBconn.Unmarshal(value[0], &proj) if err != nil { return Project{}, pkgerrors.Wrap(err, "Unmarshaling Value") } @@ -130,7 +130,7 @@ func (v *ProjectClient) DeleteProject(name string) error { key := ProjectKey{ ProjectName: name, } - err := db.DBconn.Delete(v.storeName, key, v.tagMeta) + err := db.DBconn.Remove(v.storeName, key) if err != nil { return pkgerrors.Wrap(err, "Delete Project Entry;") } diff --git a/src/orchestrator/pkg/rtcontext/rtcontext.go b/src/orchestrator/pkg/rtcontext/rtcontext.go new file mode 100644 index 00000000..e1f1c03b --- /dev/null +++ b/src/orchestrator/pkg/rtcontext/rtcontext.go @@ -0,0 +1,238 @@ +/* + * 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 rtcontext + +import ( + "fmt" + "math/rand" + "time" + "strings" + "github.com/onap/multicloud-k8s/src/orchestrator/pkg/infra/contextdb" + pkgerrors "github.com/pkg/errors" +) + +const maxrand = 0x7fffffffffffffff +const prefix string = "/context/" + +type RunTimeContext struct { + cid interface{} +} + +type Rtcontext interface { + RtcCreate() (interface{}, error) + RtcGet() (interface{}, error) + RtcAddLevel(handle interface{}, level string, value string) (interface{}, error) + RtcAddResource(handle interface{}, resname string, value interface{}) (interface{}, error) + RtcAddInstruction(handle interface{}, level string, insttype string, value interface{}) (interface{}, error) + RtcDeletePair(handle interface{}) (error) + RtcDeletePrefix(handle interface{}) (error) + RtcGetHandles(handle interface{}) ([]interface{}, error) + RtcGetValue(handle interface{}, value interface{}) (error) + RtcUpdateValue(handle interface{}, value interface{}) (error) +} + +//Create context by assiging a new id +func (rtc *RunTimeContext) RtcCreate() (interface{}, error) { + + ra := rand.New(rand.NewSource(time.Now().UnixNano())) + rn := ra.Int63n(maxrand) + id := fmt.Sprintf("%v", rn) + cid := (prefix + id + "/") + rtc.cid = interface{}(cid) + + err := contextdb.Db.Put(cid, id) + if err != nil { + return nil, pkgerrors.Errorf("Error creating run time context: %s", err.Error()) + } + + return rtc.cid, nil +} + +//Get the root handle +func (rtc *RunTimeContext) RtcGet() (interface{}, error) { + str := fmt.Sprintf("%v", rtc.cid) + if !strings.HasPrefix(str, prefix) { + return nil, pkgerrors.Errorf("Not a valid run time context") + } + + var value string + err := contextdb.Db.Get(str, &value) + if err != nil { + return nil, pkgerrors.Errorf("Error getting run time context metadata: %s", err.Error()) + } + if !strings.Contains(str, value) { + return nil, pkgerrors.Errorf("Error matching run time context metadata") + } + + return rtc.cid, nil +} + +//Add a new level at a given handle and return the new handle +func (rtc *RunTimeContext) RtcAddLevel(handle interface{}, level string, value string) (interface{}, error) { + str := fmt.Sprintf("%v", handle) + sid := fmt.Sprintf("%v", rtc.cid) + if !strings.HasPrefix(str, sid) { + return nil, pkgerrors.Errorf("Not a valid run time context handle") + } + + if level == "" { + return nil, pkgerrors.Errorf("Not a valid run time context level") + } + if value == "" { + return nil, pkgerrors.Errorf("Not a valid run time context level value") + } + + key := str + level + "/" + value + "/" + err := contextdb.Db.Put(key, value) + if err != nil { + return nil, pkgerrors.Errorf("Error adding run time context level: %s", err.Error()) + } + + return (interface{})(key), nil +} + +// Add a resource under the given level and return new handle +func (rtc *RunTimeContext) RtcAddResource(handle interface{}, resname string, value interface{}) (interface{}, error) { + + str := fmt.Sprintf("%v", handle) + sid := fmt.Sprintf("%v", rtc.cid) + if !strings.HasPrefix(str, sid) { + return nil, pkgerrors.Errorf("Not a valid run time context handle") + } + if resname == "" { + return nil, pkgerrors.Errorf("Not a valid run time context resource name") + } + if value == nil { + return nil, pkgerrors.Errorf("Not a valid run time context resource value") + } + + k := str + "resource" + "/" + resname + "/" + err := contextdb.Db.Put(k, value) + if err != nil { + return nil, pkgerrors.Errorf("Error adding run time context resource: %s", err.Error()) + } + return (interface{})(k), nil +} + +// Add instruction at a given level and type, return the new handle +func (rtc *RunTimeContext) RtcAddInstruction(handle interface{}, level string, insttype string, value interface{}) (interface{}, error) { + str := fmt.Sprintf("%v", handle) + sid := fmt.Sprintf("%v", rtc.cid) + if !strings.HasPrefix(str, sid) { + return nil, pkgerrors.Errorf("Not a valid run time context handle") + } + + if level == "" { + return nil, pkgerrors.Errorf("Not a valid run time context level") + } + if insttype == "" { + return nil, pkgerrors.Errorf("Not a valid run time context instruction type") + } + if value == nil { + return nil, pkgerrors.Errorf("Not a valid run time context instruction value") + } + + k := str + level + "/" + "instruction" + "/" + insttype +"/" + err := contextdb.Db.Put(k, fmt.Sprintf("%v", value)) + if err != nil { + return nil, pkgerrors.Errorf("Error adding run time context instruction: %s", err.Error()) + } + + return (interface{})(k), nil +} + +//Delete the key value pair using given handle +func (rtc *RunTimeContext) RtcDeletePair(handle interface{}) (error) { + str := fmt.Sprintf("%v", handle) + sid := fmt.Sprintf("%v", rtc.cid) + if !strings.HasPrefix(str, sid) { + return pkgerrors.Errorf("Not a valid run time context handle") + } + + err := contextdb.Db.Delete(str) + if err != nil { + return pkgerrors.Errorf("Error deleting run time context pair: %s", err.Error()) + } + + return nil +} + +// Delete all handles underneath the given handle +func (rtc *RunTimeContext) RtcDeletePrefix(handle interface{}) (error) { + str := fmt.Sprintf("%v", handle) + sid := fmt.Sprintf("%v", rtc.cid) + if !strings.HasPrefix(str, sid) { + return pkgerrors.Errorf("Not a valid run time context handle") + } + + err := contextdb.Db.DeleteAll(str) + if err != nil { + return pkgerrors.Errorf("Error deleting run time context with prefix: %s", err.Error()) + } + + return nil +} + +// Return the list of handles under the given handle +func (rtc *RunTimeContext) RtcGetHandles(handle interface{}) ([]interface{}, error) { + str := fmt.Sprintf("%v", handle) + sid := fmt.Sprintf("%v", rtc.cid) + if !strings.HasPrefix(str, sid) { + return nil, pkgerrors.Errorf("Not a valid run time context handle") + } + + s, err := contextdb.Db.GetAllKeys(str) + if err != nil { + return nil, pkgerrors.Errorf("Error getting run time context handles: %s", err.Error()) + } + r := make([]interface{}, len(s)) + for i, v := range s { + r[i] = v + } + return r, nil +} + +// Get the value for a given handle +func (rtc *RunTimeContext) RtcGetValue(handle interface{}, value interface{}) (error) { + str := fmt.Sprintf("%v", handle) + sid := fmt.Sprintf("%v", rtc.cid) + if !strings.HasPrefix(str, sid) { + return pkgerrors.Errorf("Not a valid run time context handle") + } + + err := contextdb.Db.Get(str, value) + if err != nil { + return pkgerrors.Errorf("Error getting run time context value: %s", err.Error()) + } + + return nil +} + +// Update the value of a given handle +func (rtc *RunTimeContext) RtcUpdateValue(handle interface{}, value interface{}) (error) { + str := fmt.Sprintf("%v", handle) + sid := fmt.Sprintf("%v", rtc.cid) + if !strings.HasPrefix(str, sid) { + return pkgerrors.Errorf("Not a valid run time context handle") + } + err := contextdb.Db.Put(str, value) + if err != nil { + return pkgerrors.Errorf("Error updating run time context value: %s", err.Error()) + } + return nil + +} diff --git a/src/orchestrator/pkg/rtcontext/rtcontext_test.go b/src/orchestrator/pkg/rtcontext/rtcontext_test.go new file mode 100644 index 00000000..29df9a25 --- /dev/null +++ b/src/orchestrator/pkg/rtcontext/rtcontext_test.go @@ -0,0 +1,596 @@ +/* + * 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 rtcontext + +import ( + "testing" + "strings" + "github.com/onap/multicloud-k8s/src/orchestrator/pkg/infra/contextdb" + pkgerrors "github.com/pkg/errors" +) + +// MockContextDb for mocking contextdb +type MockContextDb struct { + + Items map[string]interface{} + Err error +} + +// Put function +func (c *MockContextDb) Put(key string, val interface{}) (error) { + if c.Items == nil { + c.Items = make(map[string]interface{}) + } + c.Items[key] = val + return c.Err +} + +// Get function +func (c *MockContextDb) Get(key string, val interface{}) (error) { + var s *string + s = val.(*string) + for kvKey, kvValue := range c.Items { + if kvKey == key { + *s = kvValue.(string) + return c.Err + } + } + return c.Err +} + +// Delete function +func (c *MockContextDb) Delete(key string) (error) { + delete(c.Items, key) + return c.Err +} + +// Delete all function +func (c *MockContextDb) DeleteAll(key string) (error) { + for kvKey, _ := range c.Items { + delete(c.Items, kvKey) + } + return c.Err +} + +// GetAllKeys function +func (c *MockContextDb) GetAllKeys(path string) ([]string, error) { + var keys []string + + for k, _ := range c.Items { + keys = append(keys, string(k)) + } + return keys, c.Err +} + +func (c *MockContextDb) HealthCheck() error { + return nil +} + +func TestRtcCreate(t *testing.T) { + var rtc = RunTimeContext{} + testCases := []struct { + label string + mockContextDb *MockContextDb + expectedError string + }{ + { + label: "Success case", + mockContextDb: &MockContextDb{}, + }, + { + label: "Create returns error case", + mockContextDb: &MockContextDb{Err: pkgerrors.Errorf("Client not intialized")}, + expectedError: "Error creating run time context:", + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + contextdb.Db = testCase.mockContextDb + _, err := rtc.RtcCreate() + if err != nil { + if !strings.Contains(string(err.Error()), testCase.expectedError) { + t.Fatalf("Method returned an error (%s)", err) + } + } + + }) + } +} + +func TestRtcGet(t *testing.T) { + var rtc = RunTimeContext{} + var rtc1 = RunTimeContext{"/context/5345674458787728/"} + testCases := []struct { + label string + mockContextDb *MockContextDb + expectedError string + }{ + { + label: "Success case", + mockContextDb: &MockContextDb{}, + }, + { + label: "Get returns error case", + mockContextDb: &MockContextDb{Err: pkgerrors.Errorf("Client not intialized")}, + expectedError: "Error getting run time context metadata:", + }, + { + label: "Context handle does not match", + mockContextDb: &MockContextDb{Err: nil}, + expectedError: "Error matching run time context metadata", + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + switch testCase.label { + case "Success case": + contextdb.Db = testCase.mockContextDb + chandle, err := rtc.RtcCreate() + if err != nil { + t.Fatalf("Create returned an error (%s)", err) + } + ghandle, err := rtc.RtcGet() + if err != nil { + t.Fatalf("Get returned an error (%s)", err) + } + if ( chandle != ghandle ) { + t.Fatalf("Create and Get does not match") + } + case "Get returns error case": + contextdb.Db = testCase.mockContextDb + _, err := rtc.RtcGet() + if err != nil { + if !strings.Contains(string(err.Error()), testCase.expectedError) { + t.Fatalf("Method returned an error (%s)", err) + } + } + case "Context handle does not match": + contextdb.Db = testCase.mockContextDb + contextdb.Db.Put("/context/5345674458787728/", "6345674458787728") + _, err := rtc1.RtcGet() + if err != nil { + if !strings.Contains(string(err.Error()), testCase.expectedError) { + t.Fatalf("Method returned an error (%s)", err) + } + } + } + }) + } +} + +func TestRtcAddLevel(t *testing.T) { + var rtc = RunTimeContext{"/context/3528435435454354/"} + testCases := []struct { + label string + mockContextDb *MockContextDb + handle interface{} + level string + value string + expectedError string + }{ + { + label: "Success case", + mockContextDb: &MockContextDb{}, + handle: "/context/3528435435454354/", + level: "app", + value: "testapp1", + }, + { + label: "Not a valid rtc handle", + mockContextDb: &MockContextDb{}, + handle: "/context/9528435435454354/", + level: "app", + value: "testapp1", + expectedError: "Not a valid run time context handle", + }, + { + label: "Not a valid rtc level", + mockContextDb: &MockContextDb{}, + handle: "/context/3528435435454354/", + level: "", + value: "testapp1", + expectedError: "Not a valid run time context level", + }, + { + label: "Not a valid rtc value", + mockContextDb: &MockContextDb{}, + handle: "/context/3528435435454354/", + level: "app", + value: "", + expectedError: "Not a valid run time context level value", + }, + { + label: "Put returns error", + mockContextDb: &MockContextDb{Err: pkgerrors.Errorf("Client not intialized")}, + handle: "/context/3528435435454354/", + level: "app", + value: "testapp1", + expectedError: "Error adding run time context level:", + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + contextdb.Db = testCase.mockContextDb + _, err := rtc.RtcAddLevel(testCase.handle, testCase.level, testCase.value) + if err != nil { + if !strings.Contains(string(err.Error()), testCase.expectedError) { + t.Fatalf("Method returned an error (%s)", err) + } + } + }) + } +} + +func TestRtcAddResource(t *testing.T) { + var rtc = RunTimeContext{"/context/3528435435454354/"} + testCases := []struct { + label string + mockContextDb *MockContextDb + handle interface{} + resname string + value interface{} + expectedError string + }{ + { + label: "Success case", + mockContextDb: &MockContextDb{}, + handle: "/context/3528435435454354/app/apptest1/cluster/cluster1/", + resname: "R1", + value: "res1", + }, + { + label: "Not a valid rtc handle", + mockContextDb: &MockContextDb{}, + handle: "/context/9528435435454354/app/apptest1/cluster/cluster1/", + resname: "R1", + value: "res1", + expectedError: "Not a valid run time context handle", + }, + { + label: "Not a valid rtc resource name", + mockContextDb: &MockContextDb{}, + handle: "/context/3528435435454354/app/apptest1/cluster/cluster1/", + resname: "", + value: "res1", + expectedError: "Not a valid run time context resource name", + }, + { + label: "Not a valid rtc value", + mockContextDb: &MockContextDb{}, + handle: "/context/3528435435454354/app/apptest1/cluster/cluster1/", + resname: "R1", + value: nil, + expectedError: "Not a valid run time context resource value", + }, + { + label: "Put returns error", + mockContextDb: &MockContextDb{Err: pkgerrors.Errorf("Client not intialized")}, + handle: "/context/3528435435454354/app/apptest1/cluster/cluster1/", + resname: "R1", + value: "res1", + expectedError: "Error adding run time context resource:", + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + contextdb.Db = testCase.mockContextDb + _, err := rtc.RtcAddResource(testCase.handle, testCase.resname, testCase.value) + if err != nil { + if !strings.Contains(string(err.Error()), testCase.expectedError) { + t.Fatalf("Method returned an error (%s)", err) + } + } + }) + } +} + +func TestRtcAddInstruction(t *testing.T) { + var rtc = RunTimeContext{"/context/3528435435454354/"} + testCases := []struct { + label string + mockContextDb *MockContextDb + handle interface{} + level string + insttype string + value interface{} + expectedError string + }{ + { + label: "Success case", + mockContextDb: &MockContextDb{}, + handle: "/context/3528435435454354/app/apptest1/cluster/cluster1/", + level: "resource", + insttype: "order", + value: "{resorder: [R3, R1, R2]}", + }, + { + label: "Not a valid rtc handle", + mockContextDb: &MockContextDb{}, + handle: "/context/9528435435454354/app/apptest1/cluster/cluster1/", + level: "resource", + insttype: "order", + value: "{resorder: [R3, R1, R2]}", + expectedError: "Not a valid run time context handle", + }, + { + label: "Not a valid rtc level name", + mockContextDb: &MockContextDb{}, + handle: "/context/3528435435454354/app/apptest1/cluster/cluster1/", + level: "", + insttype: "order", + value: "{resorder: [R3, R1, R2]}", + expectedError: "Not a valid run time context level", + }, + { + label: "Not a valid rtc instruction type", + mockContextDb: &MockContextDb{}, + handle: "/context/3528435435454354/app/apptest1/cluster/cluster1/", + level: "resource", + insttype: "", + value: "{resorder: [R3, R1, R2]}", + expectedError: "Not a valid run time context instruction type", + }, + { + label: "Not a valid rtc value", + mockContextDb: &MockContextDb{}, + handle: "/context/3528435435454354/app/apptest1/cluster/cluster1/", + level: "resource", + insttype: "order", + value: nil, + expectedError: "Not a valid run time context instruction value", + }, + { + label: "Put returns error", + mockContextDb: &MockContextDb{Err: pkgerrors.Errorf("Client not intialized")}, + handle: "/context/3528435435454354/app/apptest1/cluster/cluster1/", + level: "resource", + insttype: "order", + value: "{resorder: [R3, R1, R2]}", + expectedError: "Error adding run time context instruction:", + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + contextdb.Db = testCase.mockContextDb + _, err := rtc.RtcAddInstruction(testCase.handle, testCase.level, testCase.insttype, testCase.value) + if err != nil { + if !strings.Contains(string(err.Error()), testCase.expectedError) { + t.Fatalf("Method returned an error (%s)", err) + } + } + }) + } +} + +func TestRtcGetHandles(t *testing.T) { + var rtc = RunTimeContext{"/context/5345674458787728/"} + testCases := []struct { + label string + mockContextDb *MockContextDb + key interface{} + expectedError string + }{ + { + label: "Not valid input handle case", + mockContextDb: &MockContextDb{}, + key: "/context/3528435435454354/", + expectedError: "Not a valid run time context handle", + }, + { + label: "Contextdb call returns error case", + mockContextDb: &MockContextDb{Err: pkgerrors.Errorf("Key does not exist")}, + key: "/context/5345674458787728/", + expectedError: "Error getting run time context handles:", + }, + { + label: "Success case", + mockContextDb: &MockContextDb{}, + key: "/context/5345674458787728/", + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + contextdb.Db = testCase.mockContextDb + if testCase.label == "Success case" { + contextdb.Db.Put("/context/5345674458787728/", 5345674458787728) + } + _, err := rtc.RtcGetHandles(testCase.key) + if err != nil { + if !strings.Contains(string(err.Error()), testCase.expectedError) { + t.Fatalf("Method returned an error (%s)", err) + } + } + }) + } +} + +func TestRtcGetValue(t *testing.T) { + var rtc = RunTimeContext{"/context/5345674458787728/"} + testCases := []struct { + label string + mockContextDb *MockContextDb + key interface{} + expectedError string + }{ + { + label: "Not valid input handle case", + mockContextDb: &MockContextDb{}, + key: "/context/3528435435454354/", + expectedError: "Not a valid run time context handle", + }, + { + label: "Contextdb call returns error case", + mockContextDb: &MockContextDb{Err: pkgerrors.Errorf("Key does not exist")}, + key: "/context/5345674458787728/", + expectedError: "Error getting run time context value:", + }, + { + label: "Success case", + mockContextDb: &MockContextDb{}, + key: "/context/5345674458787728/", + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + contextdb.Db = testCase.mockContextDb + if testCase.label == "Success case" { + contextdb.Db.Put("/context/5345674458787728/", "5345674458787728") + } + var val string + err := rtc.RtcGetValue(testCase.key, &val) + if err != nil { + if !strings.Contains(string(err.Error()), testCase.expectedError) { + t.Fatalf("Method returned an error (%s)", err) + } + } + }) + } +} + +func TestRtcUpdateValue(t *testing.T) { + var rtc = RunTimeContext{"/context/5345674458787728/"} + testCases := []struct { + label string + mockContextDb *MockContextDb + key interface{} + value interface{} + expectedError string + }{ + { + label: "Not valid input handle case", + mockContextDb: &MockContextDb{}, + key: "/context/3528435435454354/", + value: "{apporder: [app1, app2, app3]}", + expectedError: "Not a valid run time context handle", + }, + { + label: "Contextdb call returns error case", + mockContextDb: &MockContextDb{Err: pkgerrors.Errorf("Key does not exist")}, + key: "/context/5345674458787728/", + value: "{apporder: [app1, app2, app3]}", + expectedError: "Error updating run time context value:", + }, + { + label: "Success case", + mockContextDb: &MockContextDb{}, + key: "/context/5345674458787728/", + value: "{apporder: [app2, app3, app1]}", + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + contextdb.Db = testCase.mockContextDb + if testCase.label == "Success case" { + contextdb.Db.Put("/context/5345674458787728/", "5345674458787728") + } + err := rtc.RtcUpdateValue(testCase.key, testCase.value) + if err != nil { + if !strings.Contains(string(err.Error()), testCase.expectedError) { + t.Fatalf("Method returned an error (%s)", err) + } + } + }) + } +} + +func TestRtcDeletePair(t *testing.T) { + var rtc = RunTimeContext{"/context/5345674458787728/"} + testCases := []struct { + label string + mockContextDb *MockContextDb + key interface{} + expectedError string + }{ + { + label: "Not valid input handle case", + mockContextDb: &MockContextDb{}, + key: "/context/3528435435454354/", + expectedError: "Not a valid run time context handle", + }, + { + label: "Contextdb call returns error case", + mockContextDb: &MockContextDb{Err: pkgerrors.Errorf("Key does not exist")}, + key: "/context/5345674458787728/", + expectedError: "Error deleting run time context pair:", + }, + { + label: "Success case", + mockContextDb: &MockContextDb{}, + key: "/context/5345674458787728/", + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + contextdb.Db = testCase.mockContextDb + err := rtc.RtcDeletePair(testCase.key) + if err != nil { + if !strings.Contains(string(err.Error()), testCase.expectedError) { + t.Fatalf("Method returned an error (%s)", err) + } + } + }) + } +} + +func TestRtcDeletePrefix(t *testing.T) { + var rtc = RunTimeContext{"/context/5345674458787728/"} + testCases := []struct { + label string + mockContextDb *MockContextDb + key interface{} + expectedError string + }{ + { + label: "Not valid input handle case", + mockContextDb: &MockContextDb{}, + key: "/context/3528435435454354/", + expectedError: "Not a valid run time context handle", + }, + { + label: "Contextdb call returns error case", + mockContextDb: &MockContextDb{Err: pkgerrors.Errorf("Key does not exist")}, + key: "/context/5345674458787728/", + expectedError: "Error deleting run time context with prefix:", + }, + { + label: "Success case", + mockContextDb: &MockContextDb{}, + key: "/context/5345674458787728/", + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + contextdb.Db = testCase.mockContextDb + err := rtc.RtcDeletePrefix(testCase.key) + if err != nil { + if !strings.Contains(string(err.Error()), testCase.expectedError) { + t.Fatalf("Method returned an error (%s)", err) + } + } + }) + } +} |