aboutsummaryrefslogtreecommitdiffstats
path: root/src/orchestrator/pkg
diff options
context:
space:
mode:
Diffstat (limited to 'src/orchestrator/pkg')
-rw-r--r--src/orchestrator/pkg/infra/contextdb/contextdb.go2
-rw-r--r--src/orchestrator/pkg/infra/contextdb/etcd.go15
-rw-r--r--src/orchestrator/pkg/infra/db/README.md12
-rw-r--r--src/orchestrator/pkg/infra/db/mock.go27
-rw-r--r--src/orchestrator/pkg/infra/db/mongo.go32
-rw-r--r--src/orchestrator/pkg/infra/db/mongo_test.go8
-rw-r--r--src/orchestrator/pkg/infra/db/store.go14
-rw-r--r--src/orchestrator/pkg/infra/db/store_test.go4
-rw-r--r--src/orchestrator/pkg/infra/validation/validation.go264
-rw-r--r--src/orchestrator/pkg/infra/validation/validation_test.go466
-rw-r--r--src/orchestrator/pkg/module/add_intents.go184
-rw-r--r--src/orchestrator/pkg/module/app.go231
-rw-r--r--src/orchestrator/pkg/module/app_intent.go195
-rw-r--r--src/orchestrator/pkg/module/app_intent_test.go271
-rw-r--r--src/orchestrator/pkg/module/app_profile.go296
-rw-r--r--src/orchestrator/pkg/module/app_test.go327
-rw-r--r--src/orchestrator/pkg/module/composite_profile.go192
-rw-r--r--src/orchestrator/pkg/module/composite_profile_test.go175
-rw-r--r--src/orchestrator/pkg/module/compositeapp.go18
-rw-r--r--src/orchestrator/pkg/module/compositeapp_test.go236
-rw-r--r--src/orchestrator/pkg/module/deployment_intent_groups.go177
-rw-r--r--src/orchestrator/pkg/module/deployment_intent_groups_test.go230
-rw-r--r--src/orchestrator/pkg/module/generic_placement_intent.go165
-rw-r--r--src/orchestrator/pkg/module/generic_placement_intent_test.go184
-rw-r--r--src/orchestrator/pkg/module/module.go20
-rw-r--r--src/orchestrator/pkg/module/project.go8
-rw-r--r--src/orchestrator/pkg/rtcontext/rtcontext.go238
-rw-r--r--src/orchestrator/pkg/rtcontext/rtcontext_test.go596
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)
+ }
+ }
+ })
+ }
+}