Move Day2 Config Values API to new endpoint
[multicloud/k8s.git] / src / k8splugin / internal / app / config.go
1 /*
2  * Copyright 2018 Intel Corporation, Inc
3  * Copyright © 2021 Samsung Electronics
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18 package app
19
20 import (
21         "strconv"
22         "strings"
23
24         "github.com/onap/multicloud-k8s/src/k8splugin/internal/db"
25
26         pkgerrors "github.com/pkg/errors"
27 )
28
29 // Config contains the parameters needed for configuration
30 type Config struct {
31         ConfigName   string                 `json:"config-name"`
32         TemplateName string                 `json:"template-name"`
33         Description  string                 `json:"description"`
34         Values       map[string]interface{} `json:"values"`
35 }
36
37 //ConfigResult output for Create, Update and delete
38 type ConfigResult struct {
39         InstanceName      string `json:"instance-id"`
40         DefinitionName    string `json:"rb-name"`
41         DefinitionVersion string `json:"rb-version"`
42         ProfileName       string `json:"profile-name"`
43         ConfigName        string `json:"config-name"`
44         TemplateName      string `json:"template-name"`
45         ConfigVersion     uint   `json:"config-verion"`
46 }
47
48 //ConfigRollback input
49 type ConfigRollback struct {
50         AnyOf struct {
51                 ConfigVersion string `json:"config-version,omitempty"`
52                 ConfigTag     string `json:"config-tag,omitempty"`
53         } `json:"anyOf"`
54 }
55
56 //ConfigTagit for Tagging configurations
57 type ConfigTagit struct {
58         TagName string `json:"tag-name"`
59 }
60
61 // ConfigManager is an interface exposes the config functionality
62 type ConfigManager interface {
63         Create(instanceID string, p Config) (ConfigResult, error)
64         Get(instanceID, configName string) (Config, error)
65         Help() map[string]string
66         Update(instanceID, configName string, p Config) (ConfigResult, error)
67         Delete(instanceID, configName string) (ConfigResult, error)
68         Rollback(instanceID string, p ConfigRollback) error
69         Tagit(instanceID string, p ConfigTagit) error
70 }
71
72 // ConfigClient implements the ConfigManager
73 // It will also be used to maintain some localized state
74 type ConfigClient struct {
75         tagTag string
76 }
77
78 // NewConfigClient returns an instance of the ConfigClient
79 // which implements the ConfigManager
80 func NewConfigClient() *ConfigClient {
81         return &ConfigClient{
82                 tagTag: "tag",
83         }
84 }
85
86 // Help returns some information on how to create the content
87 // for the config in the form of html formatted page
88 func (v *ConfigClient) Help() map[string]string {
89         ret := make(map[string]string)
90
91         return ret
92 }
93
94 // Create an entry for the config in the database
95 func (v *ConfigClient) Create(instanceID string, p Config) (ConfigResult, error) {
96
97         // Check required fields
98         if p.ConfigName == "" || p.TemplateName == "" || len(p.Values) == 0 {
99                 return ConfigResult{}, pkgerrors.New("Incomplete Configuration Provided")
100         }
101         // Resolving rbName, Version, etc. not to break response
102         rbName, rbVersion, profileName, _, err := resolveModelFromInstance(instanceID)
103         if err != nil {
104                 return ConfigResult{}, pkgerrors.Wrap(err, "Retrieving model info")
105         }
106         cs := ConfigStore{
107                 instanceID: instanceID,
108                 configName: p.ConfigName,
109         }
110         _, err = cs.getConfig()
111         if err == nil {
112                 return ConfigResult{}, pkgerrors.Wrap(err, "Create Error - Config exists")
113         } else {
114                 if strings.Contains(err.Error(), "Key doesn't exist") == false {
115                         return ConfigResult{}, pkgerrors.Wrap(err, "Create Error")
116                 }
117         }
118         lock, profileChannel := getProfileData(instanceID)
119         // Acquire per profile Mutex
120         lock.Lock()
121         defer lock.Unlock()
122         err = applyConfig(instanceID, p, profileChannel, "POST")
123         if err != nil {
124                 return ConfigResult{}, pkgerrors.Wrap(err, "Apply Config failed")
125         }
126         // Create Config DB Entry
127         err = cs.createConfig(p)
128         if err != nil {
129                 return ConfigResult{}, pkgerrors.Wrap(err, "Create Config DB Entry")
130         }
131         // Create Version Entry in DB for Config
132         cvs := ConfigVersionStore{
133                 instanceID: instanceID,
134         }
135         version, err := cvs.createConfigVersion(p, Config{}, "POST")
136         if err != nil {
137                 return ConfigResult{}, pkgerrors.Wrap(err, "Create Config Version DB Entry")
138         }
139
140         // Create Result structure
141         cfgRes := ConfigResult{
142                 InstanceName:      instanceID,
143                 DefinitionName:    rbName,
144                 DefinitionVersion: rbVersion,
145                 ProfileName:       profileName,
146                 ConfigName:        p.ConfigName,
147                 TemplateName:      p.TemplateName,
148                 ConfigVersion:     version,
149         }
150         return cfgRes, nil
151 }
152
153 // Update an entry for the config in the database
154 func (v *ConfigClient) Update(instanceID, configName string, p Config) (ConfigResult, error) {
155
156         // Check required fields
157         if len(p.Values) == 0 {
158                 return ConfigResult{}, pkgerrors.New("Incomplete Configuration Provided")
159         }
160         // Resolving rbName, Version, etc. not to break response
161         rbName, rbVersion, profileName, _, err := resolveModelFromInstance(instanceID)
162         if err != nil {
163                 return ConfigResult{}, pkgerrors.Wrap(err, "Retrieving model info")
164         }
165         // Check if Config exists
166         cs := ConfigStore{
167                 instanceID: instanceID,
168                 configName: configName,
169         }
170         _, err = cs.getConfig()
171         if err != nil {
172                 return ConfigResult{}, pkgerrors.Wrap(err, "Update Error - Config doesn't exist")
173         }
174         lock, profileChannel := getProfileData(instanceID)
175         // Acquire per profile Mutex
176         lock.Lock()
177         defer lock.Unlock()
178         err = applyConfig(instanceID, p, profileChannel, "PUT")
179         if err != nil {
180                 return ConfigResult{}, pkgerrors.Wrap(err, "Apply Config  failed")
181         }
182         // Update Config DB Entry
183         configPrev, err := cs.updateConfig(p)
184         if err != nil {
185                 return ConfigResult{}, pkgerrors.Wrap(err, "Update Config DB Entry")
186         }
187         // Create Version Entry in DB for Config
188         cvs := ConfigVersionStore{
189                 instanceID: instanceID,
190         }
191         version, err := cvs.createConfigVersion(p, configPrev, "PUT")
192         if err != nil {
193                 return ConfigResult{}, pkgerrors.Wrap(err, "Create Config Version DB Entry")
194         }
195
196         // Create Result structure
197         cfgRes := ConfigResult{
198                 InstanceName:      instanceID,
199                 DefinitionName:    rbName,
200                 DefinitionVersion: rbVersion,
201                 ProfileName:       profileName,
202                 ConfigName:        p.ConfigName,
203                 TemplateName:      p.TemplateName,
204                 ConfigVersion:     version,
205         }
206         return cfgRes, nil
207 }
208
209 // Get config entry in the database
210 func (v *ConfigClient) Get(instanceID, configName string) (Config, error) {
211
212         // Acquire per profile Mutex
213         lock, _ := getProfileData(instanceID)
214         lock.Lock()
215         defer lock.Unlock()
216         // Read Config DB
217         cs := ConfigStore{
218                 instanceID: instanceID,
219                 configName: configName,
220         }
221         cfg, err := cs.getConfig()
222         if err != nil {
223                 return Config{}, pkgerrors.Wrap(err, "Get Config DB Entry")
224         }
225         return cfg, nil
226 }
227
228 // Delete the Config from database
229 func (v *ConfigClient) Delete(instanceID, configName string) (ConfigResult, error) {
230
231         // Resolving rbName, Version, etc. not to break response
232         rbName, rbVersion, profileName, _, err := resolveModelFromInstance(instanceID)
233         if err != nil {
234                 return ConfigResult{}, pkgerrors.Wrap(err, "Retrieving model info")
235         }
236         // Check if Config exists
237         cs := ConfigStore{
238                 instanceID: instanceID,
239                 configName: configName,
240         }
241         p, err := cs.getConfig()
242         if err != nil {
243                 return ConfigResult{}, pkgerrors.Wrap(err, "Update Error - Config doesn't exist")
244         }
245         lock, profileChannel := getProfileData(instanceID)
246         // Acquire per profile Mutex
247         lock.Lock()
248         defer lock.Unlock()
249         err = applyConfig(instanceID, p, profileChannel, "DELETE")
250         if err != nil {
251                 return ConfigResult{}, pkgerrors.Wrap(err, "Apply Config  failed")
252         }
253         // Delete Config from DB
254         configPrev, err := cs.deleteConfig()
255         if err != nil {
256                 return ConfigResult{}, pkgerrors.Wrap(err, "Delete Config DB Entry")
257         }
258         // Create Version Entry in DB for Config
259         cvs := ConfigVersionStore{
260                 instanceID: instanceID,
261         }
262         version, err := cvs.createConfigVersion(Config{}, configPrev, "DELETE")
263         if err != nil {
264                 return ConfigResult{}, pkgerrors.Wrap(err, "Delete Config Version DB Entry")
265         }
266
267         // Create Result structure
268         cfgRes := ConfigResult{
269                 InstanceName:      instanceID,
270                 DefinitionName:    rbName,
271                 DefinitionVersion: rbVersion,
272                 ProfileName:       profileName,
273                 ConfigName:        configName,
274                 TemplateName:      configPrev.TemplateName,
275                 ConfigVersion:     version,
276         }
277         return cfgRes, nil
278 }
279
280 // Rollback starts from current version and rollbacks to the version desired
281 func (v *ConfigClient) Rollback(instanceID string, rback ConfigRollback) error {
282
283         var reqVersion string
284         var err error
285
286         if rback.AnyOf.ConfigTag != "" {
287                 reqVersion, err = v.GetTagVersion(instanceID, rback.AnyOf.ConfigTag)
288                 if err != nil {
289                         return pkgerrors.Wrap(err, "Rollback Invalid tag")
290                 }
291         } else if rback.AnyOf.ConfigVersion != "" {
292                 reqVersion = rback.AnyOf.ConfigVersion
293         } else {
294                 return pkgerrors.Errorf("No valid Index for Rollback")
295         }
296
297         index, err := strconv.Atoi(reqVersion)
298         if err != nil {
299                 return pkgerrors.Wrap(err, "Rollback Invalid Index")
300         }
301         rollbackIndex := uint(index)
302
303         lock, profileChannel := getProfileData(instanceID)
304         // Acquire per profile Mutex
305         lock.Lock()
306         defer lock.Unlock()
307
308         cvs := ConfigVersionStore{
309                 instanceID: instanceID,
310         }
311         currentVersion, err := cvs.getCurrentVersion()
312         if err != nil {
313                 return pkgerrors.Wrap(err, "Rollback Get Current Config Version ")
314         }
315
316         if rollbackIndex < 1 && rollbackIndex >= currentVersion {
317                 return pkgerrors.Wrap(err, "Rollback Invalid Config Version")
318         }
319
320         //Rollback all the intermettinent configurations
321         for i := currentVersion; i > rollbackIndex; i-- {
322                 configNew, configPrev, action, err := cvs.getConfigVersion(i)
323                 if err != nil {
324                         return pkgerrors.Wrap(err, "Rollback Get Config Version")
325                 }
326                 cs := ConfigStore{
327                         instanceID: instanceID,
328                         configName: configNew.ConfigName,
329                 }
330                 if action == "PUT" {
331                         // PUT is proceeded by PUT or POST
332                         err = applyConfig(instanceID, configPrev, profileChannel, "PUT")
333                         if err != nil {
334                                 return pkgerrors.Wrap(err, "Apply Config  failed")
335                         }
336                         _, err = cs.updateConfig(configPrev)
337                         if err != nil {
338                                 return pkgerrors.Wrap(err, "Update Config DB Entry")
339                         }
340                 } else if action == "POST" {
341                         // POST is always preceeded by Config not existing
342                         err = applyConfig(instanceID, configNew, profileChannel, "DELETE")
343                         if err != nil {
344                                 return pkgerrors.Wrap(err, "Delete Config  failed")
345                         }
346                         _, err = cs.deleteConfig()
347                         if err != nil {
348                                 return pkgerrors.Wrap(err, "Delete Config DB Entry")
349                         }
350                 } else if action == "DELETE" {
351                         // DELETE is proceeded by PUT or POST
352                         err = applyConfig(instanceID, configPrev, profileChannel, "PUT")
353                         if err != nil {
354                                 return pkgerrors.Wrap(err, "Delete Config  failed")
355                         }
356                         _, err = cs.updateConfig(configPrev)
357                         if err != nil {
358                                 return pkgerrors.Wrap(err, "Update Config DB Entry")
359                         }
360                 }
361         }
362         for i := currentVersion; i > rollbackIndex; i-- {
363                 // Delete rolled back items
364                 err = cvs.deleteConfigVersion()
365                 if err != nil {
366                         return pkgerrors.Wrap(err, "Delete Config Version ")
367                 }
368         }
369         return nil
370 }
371
372 // Tagit tags the current version with the tag provided
373 func (v *ConfigClient) Tagit(instanceID string, tag ConfigTagit) error {
374
375         rbName, rbVersion, profileName, _, err := resolveModelFromInstance(instanceID)
376         if err != nil {
377                 return pkgerrors.Wrap(err, "Retrieving model info")
378         }
379         lock, _ := getProfileData(instanceID)
380         // Acquire per profile Mutex
381         lock.Lock()
382         defer lock.Unlock()
383
384         cvs := ConfigVersionStore{
385                 instanceID: instanceID,
386         }
387         currentVersion, err := cvs.getCurrentVersion()
388         if err != nil {
389                 return pkgerrors.Wrap(err, "Get Current Config Version ")
390         }
391         tagKey := constructKey(rbName, rbVersion, profileName, instanceID, v.tagTag, tag.TagName)
392
393         err = db.Etcd.Put(tagKey, strconv.Itoa(int(currentVersion)))
394         if err != nil {
395                 return pkgerrors.Wrap(err, "TagIt store DB")
396         }
397         return nil
398 }
399
400 // GetTagVersion returns the version associated with the tag
401 func (v *ConfigClient) GetTagVersion(instanceID, tagName string) (string, error) {
402
403         rbName, rbVersion, profileName, _, err := resolveModelFromInstance(instanceID)
404         if err != nil {
405                 return "", pkgerrors.Wrap(err, "Retrieving model info")
406         }
407         tagKey := constructKey(rbName, rbVersion, profileName, instanceID, v.tagTag, tagName)
408
409         value, err := db.Etcd.Get(tagKey)
410         if err != nil {
411                 return "", pkgerrors.Wrap(err, "Config DB Entry Not found")
412         }
413         return string(value), nil
414 }
415
416 // ApplyAllConfig starts from first configuration version and applies all versions in sequence
417 func (v *ConfigClient) ApplyAllConfig(instanceID string) error {
418
419         lock, profileChannel := getProfileData(instanceID)
420         // Acquire per profile Mutex
421         lock.Lock()
422         defer lock.Unlock()
423
424         cvs := ConfigVersionStore{
425                 instanceID: instanceID,
426         }
427         currentVersion, err := cvs.getCurrentVersion()
428         if err != nil {
429                 return pkgerrors.Wrap(err, "Get Current Config Version ")
430         }
431         if currentVersion < 1 {
432                 return pkgerrors.Wrap(err, "No Config Version to Apply")
433         }
434         //Apply all configurations
435         var i uint
436         for i = 1; i <= currentVersion; i++ {
437                 configNew, _, action, err := cvs.getConfigVersion(i)
438                 if err != nil {
439                         return pkgerrors.Wrap(err, "Get Config Version")
440                 }
441                 err = applyConfig(instanceID, configNew, profileChannel, action)
442                 if err != nil {
443                         return pkgerrors.Wrap(err, "Apply Config  failed")
444                 }
445         }
446         return nil
447 }