diff options
author | Lukasz Rajewski <lukasz.rajewski@orange.com> | 2022-02-03 19:18:07 +0100 |
---|---|---|
committer | Lukasz Rajewski <lukasz.rajewski@orange.com> | 2022-02-23 14:27:01 +0100 |
commit | 88ecb1f9dfeded36e7fd74c776daefcaf67f8ae2 (patch) | |
tree | 321eb34967dd32a43d7e3ac62b5fd664e5b2231b /src/k8splugin/internal | |
parent | 1f92a0ec4ea037089b82e3f80bb030f34fab64f0 (diff) |
ConfigAPI and Query API improvements
- Config Template create from the definition content
- Missing CRUD Config handlers added
- Improved Rollback and Config delete
- Query API name filtering improved
Issue-ID: MULTICLOUD-1437
Signed-off-by: Lukasz Rajewski <lukasz.rajewski@orange.com>
Change-Id: Iec8ec6d03746085f294d9318a252f1ae45d3b9c8
Diffstat (limited to 'src/k8splugin/internal')
-rw-r--r-- | src/k8splugin/internal/app/config.go | 312 | ||||
-rw-r--r-- | src/k8splugin/internal/app/config_backend.go | 194 | ||||
-rw-r--r-- | src/k8splugin/internal/app/config_test.go | 17 | ||||
-rw-r--r-- | src/k8splugin/internal/app/query.go | 16 | ||||
-rw-r--r-- | src/k8splugin/internal/db/etcd.go | 23 | ||||
-rw-r--r-- | src/k8splugin/internal/db/etcd_testing.go | 12 | ||||
-rw-r--r-- | src/k8splugin/internal/rb/config_template.go | 31 |
7 files changed, 454 insertions, 151 deletions
diff --git a/src/k8splugin/internal/app/config.go b/src/k8splugin/internal/app/config.go index 8952c16d..fce163fc 100644 --- a/src/k8splugin/internal/app/config.go +++ b/src/k8splugin/internal/app/config.go @@ -22,17 +22,17 @@ import ( "strconv" "strings" - "github.com/onap/multicloud-k8s/src/k8splugin/internal/helm" - pkgerrors "github.com/pkg/errors" ) // Config contains the parameters needed for configuration type Config struct { - ConfigName string `json:"config-name"` - TemplateName string `json:"template-name"` - Description string `json:"description"` - Values map[string]interface{} `json:"values"` + ConfigName string `json:"config-name"` + TemplateName string `json:"template-name"` + Description string `json:"description"` + Values map[string]interface{} `json:"values"` + ConfigVersion uint `json:"config-version"` + ConfigTag string `json:"config-tag"` } //ConfigResult output for Create, Update and delete @@ -54,6 +54,12 @@ type ConfigRollback struct { } `json:"anyOf"` } +//ConfigRollback input +type ConfigTag struct { + ConfigVersion uint `json:"config-version"` + ConfigTag string `json:"config-tag"` +} + //ConfigTagit for Tagging configurations type ConfigTagit struct { TagName string `json:"tag-name"` @@ -63,14 +69,18 @@ type ConfigTagit struct { type ConfigManager interface { Create(instanceID string, p Config) (ConfigResult, error) Get(instanceID, configName string) (Config, error) + GetVersion(instanceID, configName, version string) (Config, error) + GetTag(instanceID, configName, tagName string) (Config, error) List(instanceID string) ([]Config, error) + VersionList(instanceID, configName string) ([]Config, error) Help() map[string]string Update(instanceID, configName string, p Config) (ConfigResult, error) Delete(instanceID, configName string) (ConfigResult, error) - DeleteAll(instanceID, configName string) error - Rollback(instanceID string, configName string, p ConfigRollback) error + DeleteAll(instanceID, configName string, deleteConfigOnly bool) error + Rollback(instanceID string, configName string, p ConfigRollback, acceptRevert bool) (ConfigResult, error) Cleanup(instanceID string) error - Tagit(instanceID string, configName string, p ConfigTagit) error + Tagit(instanceID string, configName string, p ConfigTagit) (ConfigTag, error) + TagList(instanceID, configName string) ([]ConfigTag, error) } // ConfigClient implements the ConfigManager @@ -99,7 +109,7 @@ func (v *ConfigClient) Help() map[string]string { func (v *ConfigClient) Create(instanceID string, p Config) (ConfigResult, error) { log.Printf("[Config Create] Instance %s", instanceID) // Check required fields - if p.ConfigName == "" || p.TemplateName == "" || len(p.Values) == 0 { + if p.ConfigName == "" || p.TemplateName == "" { return ConfigResult{}, pkgerrors.New("Incomplete Configuration Provided") } // Resolving rbName, Version, etc. not to break response @@ -123,7 +133,7 @@ func (v *ConfigClient) Create(instanceID string, p Config) (ConfigResult, error) // Acquire per profile Mutex lock.Lock() defer lock.Unlock() - var appliedResources ([]helm.KubernetesResource) + var appliedResources ([]KubernetesConfigResource) appliedResources, err = applyConfig(instanceID, p, profileChannel, "POST", nil) if err != nil { return ConfigResult{}, pkgerrors.Wrap(err, "Apply Config failed") @@ -160,10 +170,6 @@ func (v *ConfigClient) Create(instanceID string, p Config) (ConfigResult, error) // Update an entry for the config in the database func (v *ConfigClient) Update(instanceID, configName string, p Config) (ConfigResult, error) { log.Printf("[Config Update] Instance %s Config %s", instanceID, configName) - // Check required fields - if len(p.Values) == 0 { - return ConfigResult{}, pkgerrors.New("Incomplete Configuration Provided") - } // Resolving rbName, Version, etc. not to break response rbName, rbVersion, profileName, _, err := resolveModelFromInstance(instanceID) if err != nil { @@ -182,7 +188,7 @@ func (v *ConfigClient) Update(instanceID, configName string, p Config) (ConfigRe // Acquire per profile Mutex lock.Lock() defer lock.Unlock() - var appliedResources ([]helm.KubernetesResource) + var appliedResources ([]KubernetesConfigResource) appliedResources, err = applyConfig(instanceID, p, profileChannel, "PUT", nil) if err != nil { return ConfigResult{}, pkgerrors.Wrap(err, "Apply Config failed") @@ -232,6 +238,59 @@ func (v *ConfigClient) Get(instanceID, configName string) (Config, error) { if err != nil { return Config{}, pkgerrors.Wrap(err, "Get Config DB Entry") } + + cvs := ConfigVersionStore{ + instanceID: instanceID, + configName: configName, + } + currentVersion, err := cvs.getCurrentVersion(configName) + if err != nil { + return Config{}, pkgerrors.Wrap(err, "Get Config Version Entry") + } + cfg.ConfigVersion = currentVersion + return cfg, nil +} + +// Get version config entry in the database +func (v *ConfigClient) GetTag(instanceID, configName, tagName string) (Config, error) { + cvs := ConfigVersionStore{ + instanceID: instanceID, + configName: configName, + } + version, err := cvs.getTagVersion(configName, tagName) + if err != nil { + return Config{}, pkgerrors.Wrap(err, "Get Config Tag Version Entry") + } + return v.GetVersion(instanceID, configName, version) +} + +// Get version config entry in the database +func (v *ConfigClient) GetVersion(instanceID, configName, version string) (Config, error) { + + // Acquire per profile Mutex + lock, _ := getProfileData(instanceID) + lock.Lock() + defer lock.Unlock() + // Read Config DB + cs := ConfigStore{ + instanceID: instanceID, + configName: configName, + } + cfg, err := cs.getConfig() + + cvs := ConfigVersionStore{ + instanceID: instanceID, + configName: configName, + } + versionInt, err := strconv.ParseUint(version, 0, 32) + if err != nil { + return Config{}, pkgerrors.Wrap(err, "Parsint version string") + } + _, _, _, _, err = cvs.getConfigVersion(configName, uint(versionInt)) + if err != nil { + return Config{}, pkgerrors.Wrap(err, "Get Config Version Entry") + } + cfg.ConfigVersion = uint(versionInt) return cfg, nil } @@ -247,14 +306,100 @@ func (v *ConfigClient) List(instanceID string) ([]Config, error) { instanceID: instanceID, } cfg, err := cs.getConfigList() + result := make([]Config, 0) + for _, config := range cfg { + cvs := ConfigVersionStore{ + instanceID: instanceID, + configName: config.ConfigName, + } + currentVersion, err := cvs.getCurrentVersion(config.ConfigName) + if err != nil { + return []Config{}, pkgerrors.Wrap(err, "Get Current Config Version ") + } + config.ConfigVersion = currentVersion + result = append(result, config) + } if err != nil { return []Config{}, pkgerrors.Wrap(err, "Get Config DB Entry") } - return cfg, nil + return result, nil +} + +// Version List config entry in the database +func (v *ConfigClient) VersionList(instanceID string, configName string) ([]Config, error) { + + // Acquire per profile Mutex + lock, _ := getProfileData(instanceID) + lock.Lock() + defer lock.Unlock() + + cvs := ConfigVersionStore{ + instanceID: instanceID, + configName: configName, + } + currentVersion, err := cvs.getCurrentVersion(configName) + if err != nil { + return []Config{}, pkgerrors.Wrap(err, "Get Current Config Version ") + } + //Get all configurations + var i uint + cfgList := make([]Config, 0) + for i = 1; i <= currentVersion; i++ { + config, _, _, _, err := cvs.getConfigVersion(configName, i) + config.ConfigVersion = i + if err != nil { + return []Config{}, pkgerrors.Wrap(err, "Get Config Version") + } + cfgList = append(cfgList, config) + } + + return cfgList, nil +} + +func (v *ConfigClient) TagList(instanceID, configName string) ([]ConfigTag, error) { + + // Acquire per profile Mutex + lock, _ := getProfileData(instanceID) + lock.Lock() + defer lock.Unlock() + // Read Config DB + cs := ConfigStore{ + instanceID: instanceID, + configName: configName, + } + _, err := cs.getConfig() + if err != nil { + return []ConfigTag{}, pkgerrors.Wrap(err, "Get Config DB Entry") + } + cvs := ConfigVersionStore{ + instanceID: instanceID, + configName: configName, + } + + tagList, err := cvs.getTagList(configName) + if err != nil { + return []ConfigTag{}, pkgerrors.Wrap(err, "Get Tag list") + } + result := make([]ConfigTag, 0) + for _, tag := range tagList { + tagData := ConfigTag{} + version, err := cvs.getTagVersion(configName, tag) + if err != nil { + return []ConfigTag{}, pkgerrors.Wrap(err, "Get Tag version") + } + versionInt, err := strconv.ParseUint(version, 0, 32) + if err != nil { + return []ConfigTag{}, pkgerrors.Wrap(err, "Parsint version string") + } + tagData.ConfigTag = tag + tagData.ConfigVersion = uint(versionInt) + result = append(result, tagData) + } + return result, nil } // Delete the Config from database -func (v *ConfigClient) DeleteAll(instanceID, configName string) error { +func (v *ConfigClient) DeleteAll(instanceID, configName string, deleteConfigOnly bool) error { log.Printf("[Config Delete All] Instance %s Config %s", instanceID, configName) // Check if Config exists cs := ConfigStore{ @@ -270,19 +415,13 @@ func (v *ConfigClient) DeleteAll(instanceID, configName string) error { instanceID: instanceID, configName: configName, } - currentVersion, err := cvs.getCurrentVersion(configName) - if err != nil { - return pkgerrors.Wrap(err, "Current version get failed") - } - _, _, action, _, err := cvs.getConfigVersion(configName, currentVersion) - if err != nil { - return pkgerrors.Wrap(err, "Config version get failed") - } - if action != "DELETE" { - _, err = v.Delete(instanceID, configName) + if !deleteConfigOnly { + var rollbackConfig = ConfigRollback{} + rollbackConfig.AnyOf.ConfigVersion = "0" + _, err = v.Rollback(instanceID, configName, rollbackConfig, true) if err != nil { - return pkgerrors.Wrap(err, "Config DELETE version failed") + return pkgerrors.Wrap(err, "Rollback to base version") } } // Delete Config from DB @@ -339,7 +478,7 @@ func (v *ConfigClient) Delete(instanceID, configName string) (ConfigResult, erro if err != nil { return ConfigResult{}, pkgerrors.Wrap(err, "Update Config DB Entry") } - version, err := cvs.createConfigVersion(p, configPrev, "DELETE", []helm.KubernetesResource{}) + version, err := cvs.createConfigVersion(p, configPrev, "DELETE", []KubernetesConfigResource{}) if err != nil { return ConfigResult{}, pkgerrors.Wrap(err, "Create Delete Config Version DB Entry") } @@ -358,25 +497,28 @@ func (v *ConfigClient) Delete(instanceID, configName string) (ConfigResult, erro } // Rollback starts from current version and rollbacks to the version desired -func (v *ConfigClient) Rollback(instanceID string, configName string, rback ConfigRollback) error { +func (v *ConfigClient) Rollback(instanceID string, configName string, rback ConfigRollback, acceptRevert bool) (ConfigResult, error) { log.Printf("[Config Rollback] Instance %s Config %s", instanceID, configName) var reqVersion string var err error - + rbName, rbVersion, profileName, _, err := resolveModelFromInstance(instanceID) + if err != nil { + return ConfigResult{}, pkgerrors.Wrap(err, "Retrieving model info") + } if rback.AnyOf.ConfigTag != "" { reqVersion, err = v.GetTagVersion(instanceID, configName, rback.AnyOf.ConfigTag) if err != nil { - return pkgerrors.Wrap(err, "Rollback Invalid tag") + return ConfigResult{}, pkgerrors.Wrap(err, "Rollback Invalid tag") } } else if rback.AnyOf.ConfigVersion != "" { reqVersion = rback.AnyOf.ConfigVersion } else { - return pkgerrors.Errorf("No valid Index for Rollback") + return ConfigResult{}, pkgerrors.Errorf("No valid Index for Rollback") } index, err := strconv.Atoi(reqVersion) if err != nil { - return pkgerrors.Wrap(err, "Rollback Invalid Index") + return ConfigResult{}, pkgerrors.Wrap(err, "Rollback Invalid Index") } rollbackIndex := uint(index) @@ -391,62 +533,114 @@ func (v *ConfigClient) Rollback(instanceID string, configName string, rback Conf } currentVersion, err := cvs.getCurrentVersion(configName) if err != nil { - return pkgerrors.Wrap(err, "Rollback Get Current Config Version ") + return ConfigResult{}, pkgerrors.Wrap(err, "Rollback Get Current Config Version ") + } + + if (rollbackIndex < 1 && !acceptRevert) || rollbackIndex >= currentVersion { + return ConfigResult{}, pkgerrors.Wrap(err, "Rollback Invalid Config Version") } - if rollbackIndex < 1 && rollbackIndex >= currentVersion { - return pkgerrors.Wrap(err, "Rollback Invalid Config Version") + if rollbackIndex < 1 && acceptRevert { + rollbackIndex = 0 } //Rollback all the intermettinent configurations for i := currentVersion; i > rollbackIndex; i-- { configNew, configPrev, _, resources, err := cvs.getConfigVersion(configName, i) if err != nil { - return pkgerrors.Wrap(err, "Rollback Get Config Version") + return ConfigResult{}, pkgerrors.Wrap(err, "Rollback Get Config Version") + } + var prevAction string + if i == 1 { + prevAction = "POST" + configPrev.ConfigName = "" + configPrev.TemplateName = "" + configPrev.Values = make(map[string]interface{}) + } else { + _, _, prevAction, _, err = cvs.getConfigVersion(configName, i-1) } - _, _, prevAction, _, err := cvs.getConfigVersion(configName, i-1) + log.Printf("ROLLBACK to version: %d", i-1) if err != nil { - return pkgerrors.Wrap(err, "Rollback Get Prev Config Version") + return ConfigResult{}, pkgerrors.Wrap(err, "Rollback Get Prev Config Version") } cs := ConfigStore{ instanceID: instanceID, configName: configNew.ConfigName, } if prevAction != "DELETE" { + var resourcesToDelete = make([]KubernetesConfigResource, 0) + for _, res := range resources { + if res.Status == "CREATED" { + resourcesToDelete = append(resourcesToDelete, res) + } + } + if len(resourcesToDelete) > 0 { + _, err := applyConfig(instanceID, configPrev, profileChannel, "DELETE", resources) + if err != nil { + return ConfigResult{}, pkgerrors.Wrap(err, "Apply Config failed") + } + } appliedResources, err := applyConfig(instanceID, configPrev, profileChannel, prevAction, nil) if err != nil { - return pkgerrors.Wrap(err, "Apply Config failed") + return ConfigResult{}, pkgerrors.Wrap(err, "Apply Config failed") } log.Printf("%s result: %s", prevAction, appliedResources) _, err = cs.updateConfig(configPrev) if err != nil { - return pkgerrors.Wrap(err, "Update Config DB Entry") + return ConfigResult{}, pkgerrors.Wrap(err, "Update Config DB Entry") } } else { // POST is always preceeded by Config not existing - _, err := applyConfig(instanceID, configNew, profileChannel, prevAction, resources) + _, err := applyConfig(instanceID, configPrev, profileChannel, prevAction, resources) if err != nil { - return pkgerrors.Wrap(err, "Delete Config failed") + return ConfigResult{}, pkgerrors.Wrap(err, "Delete Config failed") } log.Printf("DELETE resources: %s", resources) _, err = cs.updateConfig(configPrev) if err != nil { - return pkgerrors.Wrap(err, "Update Config DB Entry") + return ConfigResult{}, pkgerrors.Wrap(err, "Update Config DB Entry") } } } + if rollbackIndex == 0 { + //this is used only for delete config and remianing configuration 1 will be removed there + rollbackIndex = 1 + } for i := currentVersion; i > rollbackIndex; i-- { // Delete rolled back items err = cvs.deleteConfigVersion(configName) if err != nil { - return pkgerrors.Wrap(err, "Delete Config Version ") + return ConfigResult{}, pkgerrors.Wrap(err, "Delete Config Version ") } } - return nil + currentVersion, err = cvs.getCurrentVersion(configName) + if err != nil { + return ConfigResult{}, pkgerrors.Wrap(err, "Rollback Get Current Config Version ") + } + // Check if Config exists + cs := ConfigStore{ + instanceID: instanceID, + configName: configName, + } + currentConfig, err := cs.getConfig() + if err != nil { + return ConfigResult{}, pkgerrors.Wrap(err, "Update Error - Config doesn't exist") + } + // Create Result structure + cfgRes := ConfigResult{ + InstanceName: instanceID, + DefinitionName: rbName, + DefinitionVersion: rbVersion, + ProfileName: profileName, + ConfigName: configName, + TemplateName: currentConfig.TemplateName, + ConfigVersion: currentVersion, + } + return cfgRes, nil } // Tagit tags the current version with the tag provided -func (v *ConfigClient) Tagit(instanceID string, configName string, tag ConfigTagit) error { +func (v *ConfigClient) Tagit(instanceID string, configName string, tag ConfigTagit) (ConfigTag, error) { log.Printf("[Config Tag It] Instance %s Config %s", instanceID, configName) lock, _ := getProfileData(instanceID) // Acquire per profile Mutex @@ -459,9 +653,17 @@ func (v *ConfigClient) Tagit(instanceID string, configName string, tag ConfigTag } err := cvs.tagCurrentVersion(configName, tag.TagName) if err != nil { - return pkgerrors.Wrap(err, "Tag of current version failed") + return ConfigTag{}, pkgerrors.Wrap(err, "Tag of current version failed") } - return nil + currentVersion, err := cvs.getCurrentVersion(configName) + if err != nil { + return ConfigTag{}, pkgerrors.Wrap(err, "Rollback Get Current Config Version ") + } + + var tagResult = ConfigTag{} + tagResult.ConfigVersion = currentVersion + tagResult.ConfigTag = tag.TagName + return tagResult, nil } // GetTagVersion returns the version associated with the tag @@ -489,7 +691,11 @@ func (v *ConfigClient) Cleanup(instanceID string) error { } for _, config := range configs { - err = v.DeleteAll(instanceID, config.ConfigName) + _, err = v.Delete(instanceID, config.ConfigName) + if err != nil { + log.Printf("Config %s delete failed: %s", config.ConfigName, err.Error()) + } + err = v.DeleteAll(instanceID, config.ConfigName, true) if err != nil { log.Printf("Config %s delete failed: %s", config.ConfigName, err.Error()) } @@ -529,7 +735,7 @@ func (v *ConfigClient) ApplyAllConfig(instanceID string, configName string) erro if action != "DELETE" { resources = nil } - var appliedResources ([]helm.KubernetesResource) + var appliedResources ([]KubernetesConfigResource) appliedResources, err = applyConfig(instanceID, configNew, profileChannel, action, resources) if err != nil { return pkgerrors.Wrap(err, "Apply Config failed") diff --git a/src/k8splugin/internal/app/config_backend.go b/src/k8splugin/internal/app/config_backend.go index c365363f..4dcbeb57 100644 --- a/src/k8splugin/internal/app/config_backend.go +++ b/src/k8splugin/internal/app/config_backend.go @@ -38,10 +38,10 @@ import ( //ConfigStore contains the values that will be stored in the database type configVersionDBContent struct { - ConfigNew Config `json:"config-new"` - ConfigPrev Config `json:"config-prev"` - Action string `json:"action"` // CRUD opration for this config - Resources []helm.KubernetesResource `json:"resources"` + ConfigNew Config `json:"config-new"` + ConfigPrev Config `json:"config-prev"` + Action string `json:"action"` // CRUD opration for this config + Resources []KubernetesConfigResource `json:"resources"` } //ConfigStore to Store the Config @@ -56,10 +56,15 @@ type ConfigVersionStore struct { configName string } +type KubernetesConfigResource struct { + Resource helm.KubernetesResource `json:"resource"` + Status string `json:"status"` +} + type configResourceList struct { resourceTemplates []helm.KubernetesResourceTemplate - resources []helm.KubernetesResource - updatedResources chan []helm.KubernetesResource + resources []KubernetesConfigResource + updatedResources chan []KubernetesConfigResource profile rb.Profile action string } @@ -181,7 +186,7 @@ func (c ConfigStore) getConfigList() ([]Config, error) { return []Config{}, pkgerrors.Wrap(err, "Retrieving model info") } cfgKey := constructKey(rbName, rbVersion, profileName, c.instanceID, tagConfig) - values, err := db.Etcd.GetAll(cfgKey) + values, err := db.Etcd.GetValues(cfgKey) if err != nil { return []Config{}, pkgerrors.Wrap(err, "Get Config DB List") } @@ -194,6 +199,9 @@ func (c ConfigStore) getConfigList() ([]Config, error) { if err != nil { return []Config{}, pkgerrors.Wrap(err, "Unmarshaling Config Value") } + if cfg.ConfigName == "" { + continue + } result = append(result, cfg) } return result, nil @@ -256,7 +264,7 @@ func (c ConfigVersionStore) cleanupIstanceTags(configName string) error { } // Create a version for the configuration. If previous config provided that is also stored -func (c ConfigVersionStore) createConfigVersion(configNew, configPrev Config, action string, resources []helm.KubernetesResource) (uint, error) { +func (c ConfigVersionStore) createConfigVersion(configNew, configPrev Config, action string, resources []KubernetesConfigResource) (uint, error) { configName := "" if configNew.ConfigName != "" { @@ -281,7 +289,7 @@ func (c ConfigVersionStore) createConfigVersion(configNew, configPrev Config, ac cs.Action = action cs.ConfigNew = configNew cs.ConfigPrev = configPrev - cs.Resources = resources //[]helm.KubernetesResource{} + cs.Resources = resources //[]KubernetesConfigResource{} configValue, err := db.Serialize(cs) if err != nil { @@ -321,27 +329,27 @@ func (c ConfigVersionStore) deleteConfigVersion(configName string) error { // Read the specified version of the configuration and return its prev and current value. // Also returns the action for the config version -func (c ConfigVersionStore) getConfigVersion(configName string, version uint) (Config, Config, string, []helm.KubernetesResource, error) { +func (c ConfigVersionStore) getConfigVersion(configName string, version uint) (Config, Config, string, []KubernetesConfigResource, error) { rbName, rbVersion, profileName, _, err := resolveModelFromInstance(c.instanceID) if err != nil { - return Config{}, Config{}, "", []helm.KubernetesResource{}, pkgerrors.Wrap(err, "Retrieving model info") + return Config{}, Config{}, "", []KubernetesConfigResource{}, pkgerrors.Wrap(err, "Retrieving model info") } versionKey := constructKey(rbName, rbVersion, profileName, c.instanceID, tagVersion, configName, strconv.Itoa(int(version))) configBytes, err := db.Etcd.Get(versionKey) if err != nil { - return Config{}, Config{}, "", []helm.KubernetesResource{}, pkgerrors.Wrap(err, "Get Config Version ") + return Config{}, Config{}, "", []KubernetesConfigResource{}, pkgerrors.Wrap(err, "Get Config Version ") } if configBytes != nil { pr := configVersionDBContent{} err = db.DeSerialize(string(configBytes), &pr) if err != nil { - return Config{}, Config{}, "", []helm.KubernetesResource{}, pkgerrors.Wrap(err, "DeSerialize Config Version") + return Config{}, Config{}, "", []KubernetesConfigResource{}, pkgerrors.Wrap(err, "DeSerialize Config Version") } return pr.ConfigNew, pr.ConfigPrev, pr.Action, pr.Resources, nil } - return Config{}, Config{}, "", []helm.KubernetesResource{}, pkgerrors.Wrap(err, "Invalid data ") + return Config{}, Config{}, "", []KubernetesConfigResource{}, pkgerrors.Wrap(err, "Invalid data ") } // Get the counter for the version @@ -419,6 +427,25 @@ func (c ConfigVersionStore) decrementVersion(configName string) error { return nil } +// Get tag list +func (c ConfigVersionStore) getTagList(configName string) ([]string, error) { + rbName, rbVersion, profileName, _, err := resolveModelFromInstance(c.instanceID) + if err != nil { + return []string{}, pkgerrors.Wrap(err, "Retrieving model info") + } + tagKey := constructKey(rbName, rbVersion, profileName, c.instanceID, tagName, configName) + + tagKeyList, err := db.Etcd.GetKeys(tagKey) + if err != nil { + return []string{}, pkgerrors.Wrap(err, "Config DB Entry Not found") + } + result := make([]string, 0) + for _, tag := range tagKeyList { + result = append(result, tag[len(tagKey):len(tag)-1]) + } + return result, nil +} + // Get tag version func (c ConfigVersionStore) getTagVersion(configName, tagNameValue string) (string, error) { rbName, rbVersion, profileName, _, err := resolveModelFromInstance(c.instanceID) @@ -444,28 +471,44 @@ func (c ConfigVersionStore) tagCurrentVersion(configName, tagNameValue string) e if err != nil { return pkgerrors.Wrap(err, "Retrieving model info") } + currentConfig, _, _, _, err := c.getConfigVersion(configName, currentVersion) + if err != nil { + return pkgerrors.Wrap(err, "Retrieving current configuration") + } + tagKey := constructKey(rbName, rbVersion, profileName, c.instanceID, tagName, configName, tagNameValue) err = db.Etcd.Put(tagKey, strconv.Itoa(int(currentVersion))) if err != nil { return pkgerrors.Wrap(err, "TagIt store DB") } + + currentConfig.ConfigTag = tagNameValue + configValue, err := db.Serialize(currentConfig) + if err != nil { + return pkgerrors.Wrap(err, "Serialize Config Value") + } + cfgKey := constructKey(rbName, rbVersion, profileName, c.instanceID, tagConfig, configName) + err = db.Etcd.Put(cfgKey, configValue) + if err != nil { + return pkgerrors.Wrap(err, "Config DB Entry") + } return nil } // Apply Config -func applyConfig(instanceID string, p Config, pChannel chan configResourceList, action string, resources []helm.KubernetesResource) ([]helm.KubernetesResource, error) { +func applyConfig(instanceID string, p Config, pChannel chan configResourceList, action string, resources []KubernetesConfigResource) ([]KubernetesConfigResource, error) { rbName, rbVersion, profileName, releaseName, err := resolveModelFromInstance(instanceID) if err != nil { - return []helm.KubernetesResource{}, pkgerrors.Wrap(err, "Retrieving model info") + return []KubernetesConfigResource{}, pkgerrors.Wrap(err, "Retrieving model info") } // Get Template and Resolve the template with values crl, err := resolve(rbName, rbVersion, profileName, instanceID, p, releaseName) if err != nil { - return []helm.KubernetesResource{}, pkgerrors.Wrap(err, "Resolve Config") + return []KubernetesConfigResource{}, pkgerrors.Wrap(err, "Resolve Config") } - var updatedResources (chan []helm.KubernetesResource) = make(chan []helm.KubernetesResource) + var updatedResources (chan []KubernetesConfigResource) = make(chan []KubernetesConfigResource) crl.action = action crl.resources = resources crl.updatedResources = updatedResources @@ -477,7 +520,7 @@ func applyConfig(instanceID string, p Config, pChannel chan configResourceList, default: } - var resultResources []helm.KubernetesResource = <-updatedResources + var resultResources []KubernetesConfigResource = <-updatedResources return resultResources, nil } @@ -492,14 +535,14 @@ func scheduleResources(c chan configResourceList) { resp, err := ic.Find(data.profile.RBName, data.profile.RBVersion, data.profile.ProfileName, nil) if (err != nil || len(resp) == 0) && data.action != "STOP" { log.Println("Error finding a running instance. Retrying later...") - data.updatedResources <- []helm.KubernetesResource{} + data.updatedResources <- []KubernetesConfigResource{} continue } breakThread := false switch { - case data.action == "POST": - log.Printf("[scheduleResources]: POST %v %v", data.profile, data.resourceTemplates) - var resources []helm.KubernetesResource + case data.action == "PUT" || data.action == "POST": + log.Printf("[scheduleResources]: %v %v %v", data.action, data.profile, data.resourceTemplates) + resources := []KubernetesConfigResource{} for _, inst := range resp { k8sClient := KubernetesClient{} err = k8sClient.Init(inst.Request.CloudRegion, inst.ID) @@ -508,36 +551,30 @@ func scheduleResources(c chan configResourceList) { //Move onto the next cloud region continue } - //assuming - the resource is not exist already - resources, err = k8sClient.createResources(data.resourceTemplates, inst.Namespace) - errCreate := err - if err != nil { - // assuming - the err represent the resource is already exist, so going for update - resources, err = k8sClient.updateResources(data.resourceTemplates, inst.Namespace) + for _, res := range data.resourceTemplates { + var resToCreateOrUpdate = []helm.KubernetesResourceTemplate{res} + resProceeded, err := k8sClient.createResources(resToCreateOrUpdate, inst.Namespace) + errCreate := err + var status string = "" if err != nil { - log.Printf("Error Creating resources: %s", errCreate.Error()) - log.Printf("Error Updating resources: %s", err.Error()) - continue + // assuming - the err represent the resource already exist, so going for update + resProceeded, err = k8sClient.updateResources(resToCreateOrUpdate, inst.Namespace) + if err != nil { + log.Printf("Error Creating resources: %s", errCreate.Error()) + log.Printf("Error Updating resources: %s", err.Error()) + break + } else { + status = "UPDATED" + } + } else { + status = "CREATED" + } + for _, resCreated := range resProceeded { + resource := KubernetesConfigResource{} + resource.Resource = resCreated + resource.Status = status + resources = append(resources, resource) } - } - } - data.updatedResources <- resources - case data.action == "PUT": - log.Printf("[scheduleResources]: PUT %v %v", data.profile, data.resourceTemplates) - var resources []helm.KubernetesResource - for _, inst := range resp { - k8sClient := KubernetesClient{} - err = k8sClient.Init(inst.Request.CloudRegion, inst.ID) - if err != nil { - log.Printf("Getting CloudRegion Information: %s", err.Error()) - //Move onto the next cloud region - continue - } - - resources, err = k8sClient.updateResources(data.resourceTemplates, inst.Namespace) - if err != nil { - log.Printf("Error Updating resources: %s", err.Error()) - continue } } data.updatedResources <- resources @@ -551,13 +588,17 @@ func scheduleResources(c chan configResourceList) { //Move onto the next cloud region continue } - err = k8sClient.deleteResources(helm.GetReverseK8sResources(data.resources), inst.Namespace) + var tmpResources []helm.KubernetesResource = []helm.KubernetesResource{} + for _, res := range data.resources { + tmpResources = append(tmpResources, res.Resource) + } + err = k8sClient.deleteResources(helm.GetReverseK8sResources(tmpResources), inst.Namespace) if err != nil { log.Printf("Error Deleting resources: %s", err.Error()) continue } } - data.updatedResources <- []helm.KubernetesResource{} + data.updatedResources <- []KubernetesConfigResource{} case data.action == "STOP": breakThread = true @@ -581,17 +622,40 @@ var resolve = func(rbName, rbVersion, profileName, instanceId string, p Config, return configResourceList{}, pkgerrors.Wrap(err, "Reading Profile Data") } - t, err := rb.NewConfigTemplateClient().Get(rbName, rbVersion, p.TemplateName) - if err != nil { - return configResourceList{}, pkgerrors.Wrap(err, "Getting Template") - } - if t.ChartName == "" { - return configResourceList{}, pkgerrors.New("Invalid template no Chart.yaml file found") + var t rb.ConfigTemplate + if p.TemplateName == "" && p.ConfigName == "" { + //for rollback to base definition + t = rb.ConfigTemplate{} + t.HasContent = false + } else { + t, err = rb.NewConfigTemplateClient().Get(rbName, rbVersion, p.TemplateName) + if err != nil { + return configResourceList{}, pkgerrors.Wrap(err, "Getting Template") + } + if t.ChartName == "" { + return configResourceList{}, pkgerrors.New("Invalid template no Chart.yaml file found") + } } - def, err := rb.NewConfigTemplateClient().Download(rbName, rbVersion, p.TemplateName) - if err != nil { - return configResourceList{}, pkgerrors.Wrap(err, "Downloading Template") + var def []byte + + if t.HasContent { + def, err = rb.NewConfigTemplateClient().Download(rbName, rbVersion, p.TemplateName) + if err != nil { + return configResourceList{}, pkgerrors.Wrap(err, "Downloading Template") + } + } else { + log.Printf("Using Definition Template as a Configuration Template") + defClient := rb.NewDefinitionClient() + definition, err := defClient.Get(rbName, rbVersion) + if err != nil { + return configResourceList{}, pkgerrors.Wrap(err, "Get RB Definition") + } + def, err = defClient.Download(rbName, rbVersion) + if err != nil { + return configResourceList{}, pkgerrors.Wrap(err, "Downloading RB Definition Template") + } + t.ChartName = definition.ChartName } ic := NewInstanceClient() @@ -625,8 +689,10 @@ var resolve = func(rbName, rbVersion, profileName, instanceId string, p Config, return configResourceList{}, pkgerrors.Wrap(err, "Processing values") } - for k, v := range p.Values { - rawValues[k] = v + if p.Values != nil { + for k, v := range p.Values { + rawValues[k] = v + } } //Create a temp file in the system temp folder for values input b, err := json.Marshal(rawValues) diff --git a/src/k8splugin/internal/app/config_test.go b/src/k8splugin/internal/app/config_test.go index 1aef6656..3b673b86 100644 --- a/src/k8splugin/internal/app/config_test.go +++ b/src/k8splugin/internal/app/config_test.go @@ -138,20 +138,23 @@ func TestRollbackConfig(t *testing.T) { profileName: "testprofile1", instanceID: "testinstance1", inp: Config{ - ConfigName: "testconfig1", - TemplateName: "testtemplate1", + ConfigName: "testconfig1", + TemplateName: "testtemplate1", + ConfigVersion: 1, Values: map[string]interface{}{ "values": "{\"namespace\": \"kafka\", \"topic\": {\"name\":\"orders\", \"cluster\":\"my-cluster\", \"partitions\": 10,\"replicas\": 2, }}"}, }, inpUpdate1: Config{ - ConfigName: "testconfig1", - TemplateName: "testtemplate1", + ConfigName: "testconfig1", + TemplateName: "testtemplate1", + ConfigVersion: 2, Values: map[string]interface{}{ "values": "{\"namespace\": \"kafka\", \"topic\": {\"name\":\"orders\", \"cluster\":\"my-cluster\", \"partitions\": 20,\"replicas\": 2, }}"}, }, inpUpdate2: Config{ - ConfigName: "testconfig1", - TemplateName: "testtemplate1", + ConfigName: "testconfig1", + TemplateName: "testtemplate1", + ConfigVersion: 3, Values: map[string]interface{}{ "values": "{\"namespace\": \"kafka\", \"topic\": {\"name\":\"orders\", \"cluster\":\"my-cluster\", \"partitions\": 30,\"replicas\": 2, }}"}, }, @@ -293,7 +296,7 @@ func TestRollbackConfig(t *testing.T) { } } testCase.rollbackConfig.AnyOf.ConfigVersion = "2" - err = impl.Rollback(testCase.instanceID, testCase.inp.ConfigName, testCase.rollbackConfig) + _, err = impl.Rollback(testCase.instanceID, testCase.inp.ConfigName, testCase.rollbackConfig, false) if err != nil { if testCase.expectedError == "" { t.Fatalf("Create returned an unexpected error %s", err) diff --git a/src/k8splugin/internal/app/query.go b/src/k8splugin/internal/app/query.go index 251b14e6..6a1b3516 100644 --- a/src/k8splugin/internal/app/query.go +++ b/src/k8splugin/internal/app/query.go @@ -19,9 +19,6 @@ package app import ( - "github.com/onap/multicloud-k8s/src/k8splugin/internal/helm" - "k8s.io/apimachinery/pkg/runtime/schema" - pkgerrors "github.com/pkg/errors" ) @@ -64,7 +61,7 @@ func (v *QueryClient) Query(namespace, cloudRegion, apiVersion, kind, name, labe } var resourcesStatus []ResourceStatus - if labels != "" { + if name != "" { resList, err := k8sClient.queryResources(apiVersion, kind, labels, namespace) if err != nil { return QueryStatus{}, pkgerrors.Wrap(err, "Querying Resources") @@ -76,22 +73,11 @@ func (v *QueryClient) Query(namespace, cloudRegion, apiVersion, kind, name, labe for _, res := range resList { if res.Name == name { resourcesStatus = append(resourcesStatus, res) - break } } } else { resourcesStatus = resList } - } else if name != "" { - resIdentifier := helm.KubernetesResource{ - Name: name, - GVK: schema.FromAPIVersionAndKind(apiVersion, kind), - } - res, err := k8sClient.GetResourceStatus(resIdentifier, namespace) - if err != nil { - return QueryStatus{}, pkgerrors.Wrap(err, "Querying Resource") - } - resourcesStatus = []ResourceStatus{res} } else { resList, err := k8sClient.queryResources(apiVersion, kind, labels, namespace) if err != nil { diff --git a/src/k8splugin/internal/db/etcd.go b/src/k8splugin/internal/db/etcd.go index e455cc1a..5ce8135a 100644 --- a/src/k8splugin/internal/db/etcd.go +++ b/src/k8splugin/internal/db/etcd.go @@ -36,7 +36,8 @@ type EtcdConfig struct { // EtcdStore Interface needed for mocking type EtcdStore interface { Get(key string) ([]byte, error) - GetAll(key string) ([][]byte, error) + GetKeys(key string) ([]string, error) + GetValues(key string) ([][]byte, error) Put(key, value string) error Delete(key string) error DeletePrefix(keyPrefix string) error @@ -124,8 +125,24 @@ func (e EtcdClient) Get(key string) ([]byte, error) { return getResp.Kvs[0].Value, nil } -// GetAll sub values from Etcd DB -func (e EtcdClient) GetAll(key string) ([][]byte, error) { +// GetKeys sub values from Etcd DB +func (e EtcdClient) GetKeys(key string) ([]string, error) { + if e.cli == nil { + return nil, pkgerrors.Errorf("Etcd Client not initialized") + } + getResp, err := e.cli.Get(context.Background(), key, clientv3.WithPrefix()) + if err != nil { + return nil, pkgerrors.Errorf("Error getting etcd entry: %s", err.Error()) + } + result := make([]string, 0) + for _, v := range getResp.Kvs { + result = append(result, string(v.Key)) + } + return result, nil +} + +// GetValues sub values from Etcd DB +func (e EtcdClient) GetValues(key string) ([][]byte, error) { if e.cli == nil { return nil, pkgerrors.Errorf("Etcd Client not initialized") } diff --git a/src/k8splugin/internal/db/etcd_testing.go b/src/k8splugin/internal/db/etcd_testing.go index 4b4dfe3e..2f62d365 100644 --- a/src/k8splugin/internal/db/etcd_testing.go +++ b/src/k8splugin/internal/db/etcd_testing.go @@ -41,7 +41,17 @@ func (c *MockEtcdClient) Get(key string) ([]byte, error) { return nil, pkgerrors.Errorf("Key doesn't exist") } -func (c *MockEtcdClient) GetAll(key string) ([][]byte, error) { +func (c *MockEtcdClient) GetKeys(key string) ([]string, error) { + result := make([]string, 0) + for kvKey := range c.Items { + if strings.HasPrefix(kvKey, key) { + result = append(result, kvKey) + } + } + return result, nil +} + +func (c *MockEtcdClient) GetValues(key string) ([][]byte, error) { result := make([][]byte, 0) for kvKey, kvValue := range c.Items { if strings.HasPrefix(kvKey, key) { diff --git a/src/k8splugin/internal/rb/config_template.go b/src/k8splugin/internal/rb/config_template.go index 97fe0fb4..06576e5d 100644 --- a/src/k8splugin/internal/rb/config_template.go +++ b/src/k8splugin/internal/rb/config_template.go @@ -36,14 +36,20 @@ import ( type ConfigTemplate struct { TemplateName string `json:"template-name"` Description string `json:"description"` - ChartName string + ChartName string `json:"chart-name"` + HasContent bool `json:"has-content"` +} + +type ConfigTemplateList struct { + TemplateName string `json:"template-name"` + Description string `json:"description"` } // ConfigTemplateManager is an interface exposes the resource bundle ConfigTemplate functionality type ConfigTemplateManager interface { CreateOrUpdate(rbName, rbVersion string, p ConfigTemplate, update bool) error Get(rbName, rbVersion, templateName string) (ConfigTemplate, error) - List(rbName, rbVersion string) ([]ConfigTemplate, error) + List(rbName, rbVersion string) ([]ConfigTemplateList, error) Delete(rbName, rbVersion, templateName string) error Upload(rbName, rbVersion, templateName string, inp []byte) error } @@ -94,7 +100,7 @@ func (v *ConfigTemplateClient) CreateOrUpdate(rbName, rbVersion string, p Config } //Check if ConfigTemplate already exists - _, err := v.Get(rbName, rbVersion, p.TemplateName) + prev, err := v.Get(rbName, rbVersion, p.TemplateName) if err == nil && !update { return pkgerrors.New(" ConfigTemplate already exists for this Definition") } @@ -103,7 +109,7 @@ func (v *ConfigTemplateClient) CreateOrUpdate(rbName, rbVersion string, p Config } //Check if provided resource bundle information is valid - _, err = NewDefinitionClient().Get(rbName, rbVersion) + rbDef, err := NewDefinitionClient().Get(rbName, rbVersion) if err != nil { return pkgerrors.Errorf("Invalid Resource Bundle ID provided: %s", err.Error()) } @@ -115,11 +121,15 @@ func (v *ConfigTemplateClient) CreateOrUpdate(rbName, rbVersion string, p Config } if update { + p.ChartName = prev.ChartName + p.HasContent = prev.HasContent err = db.DBconn.Update(v.storeName, key, v.tagMeta, p) if err != nil { return pkgerrors.Wrap(err, "Updating ConfigTemplate DB Entry") } } else { + p.ChartName = rbDef.ChartName + p.HasContent = false err = db.DBconn.Create(v.storeName, key, v.tagMeta, p) if err != nil { return pkgerrors.Wrap(err, "Creating ConfigTemplate DB Entry") @@ -155,19 +165,19 @@ func (v *ConfigTemplateClient) Get(rbName, rbVersion, templateName string) (Conf } // List returns the Resource Bundle ConfigTemplate for corresponding ID -func (v *ConfigTemplateClient) List(rbName, rbVersion string) ([]ConfigTemplate, error) { +func (v *ConfigTemplateClient) List(rbName, rbVersion string) ([]ConfigTemplateList, error) { //Get all config templates dbres, err := db.DBconn.ReadAll(v.storeName, v.tagMeta) if err != nil || len(dbres) == 0 { - return []ConfigTemplate{}, pkgerrors.Wrap(err, "No Config Templates Found") + return []ConfigTemplateList{}, pkgerrors.Wrap(err, "No Config Templates Found") } - var results []ConfigTemplate + var results []ConfigTemplateList for key, value := range dbres { //value is a byte array if value != nil { - tmp := ConfigTemplate{} + tmp := ConfigTemplateList{} err = db.DBconn.Unmarshal(value, &tmp) if err != nil { log.Printf("[ConfigTemplate] Error: %s Unmarshaling value for: %s", err.Error(), key) @@ -267,6 +277,11 @@ func (v *ConfigTemplateClient) Upload(rbName, rbVersion, templateName string, in if err != nil { return pkgerrors.Errorf("Error uploading data to db %s", err.Error()) } + t.HasContent = true + err = db.DBconn.Update(v.storeName, key, v.tagMeta, t) + if err != nil { + return pkgerrors.Wrap(err, "Updating ConfigTemplate DB Entry") + } return nil } |