diff options
Diffstat (limited to 'src/k8splugin/internal/app/config.go')
-rw-r--r-- | src/k8splugin/internal/app/config.go | 438 |
1 files changed, 438 insertions, 0 deletions
diff --git a/src/k8splugin/internal/app/config.go b/src/k8splugin/internal/app/config.go new file mode 100644 index 00000000..f7e81358 --- /dev/null +++ b/src/k8splugin/internal/app/config.go @@ -0,0 +1,438 @@ +/* + * 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 app + +import ( + "strconv" + "strings" + + "k8splugin/internal/db" + + 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"` +} + +//ConfigResult output for Create, Update and delete +type ConfigResult struct { + DefinitionName string `json:"rb-name"` + DefinitionVersion string `json:"rb-version"` + ProfileName string `json:"profile-name"` + ConfigName string `json:"config-name"` + TemplateName string `json:"template-name"` + ConfigVersion uint `json:"config-verion"` +} + +//ConfigRollback input +type ConfigRollback struct { + AnyOf struct { + ConfigVersion string `json:"config-version,omitempty"` + ConfigTag string `json:"config-tag,omitempty"` + } `json:"anyOf"` +} + +//ConfigTagit for Tagging configurations +type ConfigTagit struct { + TagName string `json:"tag-name"` +} + +// ConfigManager is an interface exposes the config functionality +type ConfigManager interface { + Create(rbName, rbVersion, profileName string, p Config) (ConfigResult, error) + Get(rbName, rbVersion, profileName, configName string) (Config, error) + Help() map[string]string + Update(rbName, rbVersion, profileName, configName string, p Config) (ConfigResult, error) + Delete(rbName, rbVersion, profileName, configName string) (ConfigResult, error) + Rollback(rbName, rbVersion, profileName string, p ConfigRollback) error + Tagit(rbName, rbVersion, profileName string, p ConfigTagit) error +} + +// ConfigClient implements the ConfigManager +// It will also be used to maintain some localized state +type ConfigClient struct { + tagTag string +} + +// NewConfigClient returns an instance of the ConfigClient +// which implements the ConfigManager +func NewConfigClient() *ConfigClient { + return &ConfigClient{ + tagTag: "tag", + } +} + +// Help returns some information on how to create the content +// for the config in the form of html formatted page +func (v *ConfigClient) Help() map[string]string { + ret := make(map[string]string) + + return ret +} + +// Create an entry for the config in the database +func (v *ConfigClient) Create(rbName, rbVersion, profileName string, p Config) (ConfigResult, error) { + + // Check required fields + if p.ConfigName == "" || p.TemplateName == "" || len(p.Values) == 0 { + return ConfigResult{}, pkgerrors.New("Incomplete Configuration Provided") + } + cs := ConfigStore{ + rbName: rbName, + rbVersion: rbVersion, + profileName: profileName, + configName: p.ConfigName, + } + _, err := cs.getConfig() + if err == nil { + return ConfigResult{}, pkgerrors.Wrap(err, "Create Error - Config exists") + } else { + if strings.Contains(err.Error(), "Key doesn't exist") == false { + return ConfigResult{}, pkgerrors.Wrap(err, "Create Error") + } + } + lock, profileChannel := getProfileData(rbName + rbVersion + profileName) + // Acquire per profile Mutex + lock.Lock() + defer lock.Unlock() + err = applyConfig(rbName, rbVersion, profileName, p, profileChannel, "POST") + if err != nil { + return ConfigResult{}, pkgerrors.Wrap(err, "Apply Config failed") + } + // Create Config DB Entry + err = cs.createConfig(p) + if err != nil { + return ConfigResult{}, pkgerrors.Wrap(err, "Create Config DB Entry") + } + // Create Version Entry in DB for Config + cvs := ConfigVersionStore{ + rbName: rbName, + rbVersion: rbVersion, + profileName: profileName, + } + version, err := cvs.createConfigVersion(p, Config{}, "POST") + if err != nil { + return ConfigResult{}, pkgerrors.Wrap(err, "Create Config Version DB Entry") + } + // Create Result structure + cfgRes := ConfigResult{ + DefinitionName: rbName, + DefinitionVersion: rbVersion, + ProfileName: profileName, + ConfigName: p.ConfigName, + TemplateName: p.TemplateName, + ConfigVersion: version, + } + return cfgRes, nil +} + +// Update an entry for the config in the database +func (v *ConfigClient) Update(rbName, rbVersion, profileName, configName string, p Config) (ConfigResult, error) { + + // Check required fields + if len(p.Values) == 0 { + return ConfigResult{}, pkgerrors.New("Incomplete Configuration Provided") + } + // Check if Config exists + cs := ConfigStore{ + rbName: rbName, + rbVersion: rbVersion, + profileName: profileName, + configName: configName, + } + _, err := cs.getConfig() + if err != nil { + return ConfigResult{}, pkgerrors.Wrap(err, "Update Error - Config doesn't exist") + } + lock, profileChannel := getProfileData(rbName + rbVersion + profileName) + // Acquire per profile Mutex + lock.Lock() + defer lock.Unlock() + err = applyConfig(rbName, rbVersion, profileName, p, profileChannel, "PUT") + if err != nil { + return ConfigResult{}, pkgerrors.Wrap(err, "Apply Config failed") + } + // Update Config DB Entry + configPrev, err := cs.updateConfig(p) + if err != nil { + return ConfigResult{}, pkgerrors.Wrap(err, "Update Config DB Entry") + } + // Create Version Entry in DB for Config + cvs := ConfigVersionStore{ + rbName: rbName, + rbVersion: rbVersion, + profileName: profileName, + } + version, err := cvs.createConfigVersion(p, configPrev, "PUT") + if err != nil { + return ConfigResult{}, pkgerrors.Wrap(err, "Create Config Version DB Entry") + } + // Create Result structure + cfgRes := ConfigResult{ + DefinitionName: rbName, + DefinitionVersion: rbVersion, + ProfileName: profileName, + ConfigName: p.ConfigName, + TemplateName: p.TemplateName, + ConfigVersion: version, + } + return cfgRes, nil +} + +// Get config entry in the database +func (v *ConfigClient) Get(rbName, rbVersion, profileName, configName string) (Config, error) { + + // Acquire per profile Mutex + lock, _ := getProfileData(rbName + rbVersion + profileName) + lock.Lock() + defer lock.Unlock() + // Read Config DB + cs := ConfigStore{ + rbName: rbName, + rbVersion: rbVersion, + profileName: profileName, + configName: configName, + } + cfg, err := cs.getConfig() + if err != nil { + return Config{}, pkgerrors.Wrap(err, "Get Config DB Entry") + } + return cfg, nil +} + +// Delete the Config from database +func (v *ConfigClient) Delete(rbName, rbVersion, profileName, configName string) (ConfigResult, error) { + + // Check if Config exists + cs := ConfigStore{ + rbName: rbName, + rbVersion: rbVersion, + profileName: profileName, + configName: configName, + } + p, err := cs.getConfig() + if err != nil { + return ConfigResult{}, pkgerrors.Wrap(err, "Update Error - Config doesn't exist") + } + lock, profileChannel := getProfileData(rbName + rbVersion + profileName) + // Acquire per profile Mutex + lock.Lock() + defer lock.Unlock() + err = applyConfig(rbName, rbVersion, profileName, p, profileChannel, "DELETE") + if err != nil { + return ConfigResult{}, pkgerrors.Wrap(err, "Apply Config failed") + } + // Delete Config from DB + configPrev, err := cs.deleteConfig() + if err != nil { + return ConfigResult{}, pkgerrors.Wrap(err, "Delete Config DB Entry") + } + // Create Version Entry in DB for Config + cvs := ConfigVersionStore{ + rbName: rbName, + rbVersion: rbVersion, + profileName: profileName, + } + version, err := cvs.createConfigVersion(Config{}, configPrev, "DELETE") + if err != nil { + return ConfigResult{}, pkgerrors.Wrap(err, "Delete Config Version DB Entry") + } + // Create Result structure + cfgRes := ConfigResult{ + DefinitionName: rbName, + DefinitionVersion: rbVersion, + ProfileName: profileName, + ConfigName: configName, + TemplateName: configPrev.TemplateName, + ConfigVersion: version, + } + return cfgRes, nil +} + +// Rollback starts from current version and rollbacks to the version desired +func (v *ConfigClient) Rollback(rbName, rbVersion, profileName string, rback ConfigRollback) error { + + var reqVersion string + var err error + + if rback.AnyOf.ConfigTag != "" { + reqVersion, err = v.GetTagVersion(rbName, rbVersion, profileName, rback.AnyOf.ConfigTag) + if err != nil { + return pkgerrors.Wrap(err, "Rollback Invalid tag") + } + } else if rback.AnyOf.ConfigVersion != "" { + reqVersion = rback.AnyOf.ConfigVersion + } else { + return pkgerrors.Errorf("No valid Index for Rollback") + } + + index, err := strconv.Atoi(reqVersion) + if err != nil { + return pkgerrors.Wrap(err, "Rollback Invalid Index") + } + rollbackIndex := uint(index) + + lock, profileChannel := getProfileData(rbName + rbVersion + profileName) + // Acquire per profile Mutex + lock.Lock() + defer lock.Unlock() + + cvs := ConfigVersionStore{ + rbName: rbName, + rbVersion: rbVersion, + profileName: profileName, + } + currentVersion, err := cvs.getCurrentVersion() + if err != nil { + return pkgerrors.Wrap(err, "Rollback Get Current Config Version ") + } + + if rollbackIndex < 1 && rollbackIndex >= currentVersion { + return pkgerrors.Wrap(err, "Rollback Invalid Config Version") + } + + //Rollback all the intermettinent configurations + for i := currentVersion; i > rollbackIndex; i-- { + configNew, configPrev, action, err := cvs.getConfigVersion(i) + if err != nil { + return pkgerrors.Wrap(err, "Rollback Get Config Version") + } + cs := ConfigStore{ + rbName: rbName, + rbVersion: rbVersion, + profileName: profileName, + configName: configNew.ConfigName, + } + if action == "PUT" { + // PUT is proceeded by PUT or POST + err = applyConfig(rbName, rbVersion, profileName, configPrev, profileChannel, "PUT") + if err != nil { + return pkgerrors.Wrap(err, "Apply Config failed") + } + _, err = cs.updateConfig(configPrev) + if err != nil { + return pkgerrors.Wrap(err, "Update Config DB Entry") + } + } else if action == "POST" { + // POST is always preceeded by Config not existing + err = applyConfig(rbName, rbVersion, profileName, configNew, profileChannel, "DELETE") + if err != nil { + return pkgerrors.Wrap(err, "Delete Config failed") + } + _, err = cs.deleteConfig() + if err != nil { + return pkgerrors.Wrap(err, "Delete Config DB Entry") + } + } else if action == "DELETE" { + // DELETE is proceeded by PUT or POST + err = applyConfig(rbName, rbVersion, profileName, configPrev, profileChannel, "PUT") + if err != nil { + return pkgerrors.Wrap(err, "Delete Config failed") + } + _, err = cs.updateConfig(configPrev) + if err != nil { + return pkgerrors.Wrap(err, "Update Config DB Entry") + } + } + } + for i := currentVersion; i > rollbackIndex; i-- { + // Delete rolled back items + err = cvs.deleteConfigVersion() + if err != nil { + return pkgerrors.Wrap(err, "Delete Config Version ") + } + } + return nil +} + +// Tagit tags the current version with the tag provided +func (v *ConfigClient) Tagit(rbName, rbVersion, profileName string, tag ConfigTagit) error { + + lock, _ := getProfileData(rbName + rbVersion + profileName) + // Acquire per profile Mutex + lock.Lock() + defer lock.Unlock() + + cvs := ConfigVersionStore{ + rbName: rbName, + rbVersion: rbVersion, + profileName: profileName, + } + currentVersion, err := cvs.getCurrentVersion() + if err != nil { + return pkgerrors.Wrap(err, "Get Current Config Version ") + } + tagKey := constructKey(rbName, rbVersion, profileName, v.tagTag, tag.TagName) + + err = db.Etcd.Put(tagKey, strconv.Itoa(int(currentVersion))) + if err != nil { + return pkgerrors.Wrap(err, "TagIt store DB") + } + return nil +} + +// GetTagVersion returns the version associated with the tag +func (v *ConfigClient) GetTagVersion(rbName, rbVersion, profileName, tagName string) (string, error) { + + tagKey := constructKey(rbName, rbVersion, profileName, v.tagTag, tagName) + + value, err := db.Etcd.Get(tagKey) + if err != nil { + return "", pkgerrors.Wrap(err, "Config DB Entry Not found") + } + return string(value), nil +} + +// ApplyAllConfig starts from first configuration version and applies all versions in sequence +func (v *ConfigClient) ApplyAllConfig(rbName, rbVersion, profileName string) error { + + lock, profileChannel := getProfileData(rbName + rbVersion + profileName) + // Acquire per profile Mutex + lock.Lock() + defer lock.Unlock() + + cvs := ConfigVersionStore{ + rbName: rbName, + rbVersion: rbVersion, + profileName: profileName, + } + currentVersion, err := cvs.getCurrentVersion() + if err != nil { + return pkgerrors.Wrap(err, "Get Current Config Version ") + } + if currentVersion < 1 { + return pkgerrors.Wrap(err, "No Config Version to Apply") + } + //Apply all configurations + var i uint + for i = 1; i <= currentVersion; i++ { + configNew, _, action, err := cvs.getConfigVersion(i) + if err != nil { + return pkgerrors.Wrap(err, "Get Config Version") + } + err = applyConfig(rbName, rbVersion, profileName, configNew, profileChannel, action) + if err != nil { + return pkgerrors.Wrap(err, "Apply Config failed") + } + } + return nil +} |