blob: 4b1c8413b5607dfdd5cf6d784b9352395dd4f5de [file] [log] [blame]
divyadesai8bf96862020-02-07 12:24:26 +00001/*
2 * Copyright 2018-present Open Networking Foundation
3
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7
8 * http://www.apache.org/licenses/LICENSE-2.0
9
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package config
17
18import (
19 "context"
20 "fmt"
serkant.uluderyae1e24732020-05-20 00:35:21 -070021 "os"
22 "strings"
23 "time"
24
Girish Gowdra89c985b2020-10-14 15:02:09 -070025 "github.com/opencord/voltha-lib-go/v4/pkg/db"
26 "github.com/opencord/voltha-lib-go/v4/pkg/db/kvstore"
27 "github.com/opencord/voltha-lib-go/v4/pkg/log"
divyadesai8bf96862020-02-07 12:24:26 +000028)
29
30const (
serkant.uluderyae1e24732020-05-20 00:35:21 -070031 defaultkvStoreConfigPath = "config"
32 defaultkvStoreDataPathPrefix = "service/voltha"
33 kvStorePathSeparator = "/"
divyadesai8bf96862020-02-07 12:24:26 +000034)
35
36// ConfigType represents the type for which config is created inside the kvstore
37// For example, loglevel
38type ConfigType int
39
40const (
41 ConfigTypeLogLevel ConfigType = iota
Girish Kumar472a5c92020-04-14 09:14:18 +000042 ConfigTypeMetadata
divyadesai8bf96862020-02-07 12:24:26 +000043 ConfigTypeKafka
Girish Kumare9d35bb2020-08-18 06:47:59 +000044 ConfigTypeLogFeatures
divyadesai8bf96862020-02-07 12:24:26 +000045)
46
47func (c ConfigType) String() string {
Girish Kumare9d35bb2020-08-18 06:47:59 +000048 return [...]string{"loglevel", "metadata", "kafka", "logfeatures"}[c]
divyadesai8bf96862020-02-07 12:24:26 +000049}
50
51// ChangeEvent represents the event recieved from watch
52// For example, Put Event
53type ChangeEvent int
54
55const (
56 Put ChangeEvent = iota
57 Delete
58)
59
Girish Kumar21972fb2020-03-13 13:27:47 +000060func (ce ChangeEvent) String() string {
61 return [...]string{"Put", "Delete"}[ce]
62}
63
divyadesai8bf96862020-02-07 12:24:26 +000064// ConfigChangeEvent represents config for the events recieved from watch
65// For example,ChangeType is Put ,ConfigAttribute default
66type ConfigChangeEvent struct {
67 ChangeType ChangeEvent
68 ConfigAttribute string
69}
70
Matteo Scandolo2e677482020-04-09 11:56:27 -070071// ConfigManager is a wrapper over Backend to maintain Configuration of voltha components
divyadesai8bf96862020-02-07 12:24:26 +000072// in kvstore based persistent storage
73type ConfigManager struct {
serkant.uluderyae1e24732020-05-20 00:35:21 -070074 Backend *db.Backend
75 KVStoreConfigPrefix string
76 KVStoreDataPathPrefix string
divyadesai8bf96862020-02-07 12:24:26 +000077}
78
79// ComponentConfig represents a category of configuration for a specific VOLTHA component type
80// stored in a persistent storage pointed to by Config Manager
81// For example, one ComponentConfig instance will be created for loglevel config type for rw-core
82// component while another ComponentConfig instance will refer to connection config type for same
83// rw-core component. So, there can be multiple ComponentConfig instance created per component
84// pointing to different category of configuration.
85//
86// Configuration pointed to be by ComponentConfig is stored in kvstore as a list of key/value pairs
87// under the hierarchical tree with following base path
88// <Backend Prefix Path>/<Config Prefix>/<Component Name>/<Config Type>/
89//
90// For example, rw-core ComponentConfig for loglevel config entries will be stored under following path
91// /voltha/service/config/rw-core/loglevel/
92type ComponentConfig struct {
93 cManager *ConfigManager
94 componentLabel string
95 configType ConfigType
96 changeEventChan chan *ConfigChangeEvent
97 kvStoreEventChan chan *kvstore.Event
98}
99
Neha Sharma94f16a92020-06-26 04:17:55 +0000100func NewConfigManager(ctx context.Context, kvClient kvstore.Client, kvStoreType, kvStoreAddress string, kvStoreTimeout time.Duration) *ConfigManager {
serkant.uluderyae1e24732020-05-20 00:35:21 -0700101 var kvStorePrefix string
102 if prefix, present := os.LookupEnv("KV_STORE_DATAPATH_PREFIX"); present {
103 kvStorePrefix = prefix
Neha Sharma94f16a92020-06-26 04:17:55 +0000104 logger.Infow(ctx, "KV_STORE_DATAPATH_PREFIX env variable is set, ", log.Fields{"kvStoreDataPathPrefix": kvStorePrefix})
serkant.uluderyae1e24732020-05-20 00:35:21 -0700105 } else {
106 kvStorePrefix = defaultkvStoreDataPathPrefix
Neha Sharma94f16a92020-06-26 04:17:55 +0000107 logger.Infow(ctx, "KV_STORE_DATAPATH_PREFIX env variable is not set, using default", log.Fields{"kvStoreDataPathPrefix": defaultkvStoreDataPathPrefix})
serkant.uluderyae1e24732020-05-20 00:35:21 -0700108 }
divyadesai8bf96862020-02-07 12:24:26 +0000109 return &ConfigManager{
serkant.uluderyae1e24732020-05-20 00:35:21 -0700110 KVStoreConfigPrefix: defaultkvStoreConfigPath,
111 KVStoreDataPathPrefix: kvStorePrefix,
Matteo Scandolo2e677482020-04-09 11:56:27 -0700112 Backend: &db.Backend{
divyadesai8bf96862020-02-07 12:24:26 +0000113 Client: kvClient,
114 StoreType: kvStoreType,
Neha Sharmadd9af392020-04-28 09:03:57 +0000115 Address: kvStoreAddress,
divyadesai8bf96862020-02-07 12:24:26 +0000116 Timeout: kvStoreTimeout,
serkant.uluderyae1e24732020-05-20 00:35:21 -0700117 PathPrefix: kvStorePrefix,
divyadesai8bf96862020-02-07 12:24:26 +0000118 },
119 }
120}
121
divyadesai42bcb672020-03-04 11:40:53 +0000122// RetrieveComponentList list the component Names for which loglevel is stored in kvstore
123func (c *ConfigManager) RetrieveComponentList(ctx context.Context, configType ConfigType) ([]string, error) {
serkant.uluderyae1e24732020-05-20 00:35:21 -0700124 data, err := c.Backend.List(ctx, c.KVStoreConfigPrefix)
divyadesai42bcb672020-03-04 11:40:53 +0000125 if err != nil {
126 return nil, err
127 }
128
Matteo Scandolo2e677482020-04-09 11:56:27 -0700129 // Looping through the data recieved from the Backend for config
divyadesai42bcb672020-03-04 11:40:53 +0000130 // Trimming and Splitting the required key and value from data and storing as componentName,PackageName and Level
131 // For Example, recieved key would be <Backend Prefix Path>/<Config Prefix>/<Component Name>/<Config Type>/default and value \"DEBUG\"
132 // Then in default will be stored as PackageName,componentName as <Component Name> and DEBUG will be stored as value in List struct
133 ccPathPrefix := kvStorePathSeparator + configType.String() + kvStorePathSeparator
serkant.uluderyae1e24732020-05-20 00:35:21 -0700134 pathPrefix := c.KVStoreDataPathPrefix + kvStorePathSeparator + c.KVStoreConfigPrefix + kvStorePathSeparator
divyadesai42bcb672020-03-04 11:40:53 +0000135 var list []string
136 keys := make(map[string]interface{})
137 for attr := range data {
138 cname := strings.TrimPrefix(attr, pathPrefix)
139 cName := strings.SplitN(cname, ccPathPrefix, 2)
140 if len(cName) != 2 {
141 continue
142 }
143 if _, exist := keys[cName[0]]; !exist {
144 keys[cName[0]] = nil
145 list = append(list, cName[0])
146 }
147 }
148 return list, nil
149}
150
divyadesai8bf96862020-02-07 12:24:26 +0000151// Initialize the component config
152func (cm *ConfigManager) InitComponentConfig(componentLabel string, configType ConfigType) *ComponentConfig {
153
154 return &ComponentConfig{
155 componentLabel: componentLabel,
156 configType: configType,
157 cManager: cm,
158 changeEventChan: nil,
159 kvStoreEventChan: nil,
160 }
161
162}
163
164func (c *ComponentConfig) makeConfigPath() string {
165
166 cType := c.configType.String()
serkant.uluderyae1e24732020-05-20 00:35:21 -0700167 return c.cManager.KVStoreConfigPrefix + kvStorePathSeparator +
divyadesai8bf96862020-02-07 12:24:26 +0000168 c.componentLabel + kvStorePathSeparator + cType
169}
170
171// MonitorForConfigChange watch on the subkeys for the given key
172// Any changes to the subkeys for the given key will return an event channel
173// Then Event channel will be processed and new event channel with required values will be created and return
174// For example, rw-core will be watching on <Backend Prefix Path>/<Config Prefix>/<Component Name>/<Config Type>/
175// will return an event channel for PUT,DELETE eventType.
176// Then values from event channel will be processed and stored in kvStoreEventChan.
177func (c *ComponentConfig) MonitorForConfigChange(ctx context.Context) chan *ConfigChangeEvent {
178 key := c.makeConfigPath()
179
Neha Sharma94f16a92020-06-26 04:17:55 +0000180 logger.Debugw(ctx, "monitoring-for-config-change", log.Fields{"key": key})
divyadesai8bf96862020-02-07 12:24:26 +0000181
182 c.changeEventChan = make(chan *ConfigChangeEvent, 1)
183
Matteo Scandolo2e677482020-04-09 11:56:27 -0700184 c.kvStoreEventChan = c.cManager.Backend.CreateWatch(ctx, key, true)
divyadesai8bf96862020-02-07 12:24:26 +0000185
Neha Sharma94f16a92020-06-26 04:17:55 +0000186 go c.processKVStoreWatchEvents(ctx)
divyadesai8bf96862020-02-07 12:24:26 +0000187
188 return c.changeEventChan
189}
190
Matteo Scandolo2e677482020-04-09 11:56:27 -0700191// processKVStoreWatchEvents process event channel recieved from the Backend for any ChangeType
divyadesai8bf96862020-02-07 12:24:26 +0000192// It checks for the EventType is valid or not.For the valid EventTypes creates ConfigChangeEvent and send it on channel
Neha Sharma94f16a92020-06-26 04:17:55 +0000193func (c *ComponentConfig) processKVStoreWatchEvents(ctx context.Context) {
divyadesai8bf96862020-02-07 12:24:26 +0000194
195 ccKeyPrefix := c.makeConfigPath()
divyadesai42bcb672020-03-04 11:40:53 +0000196
Neha Sharma94f16a92020-06-26 04:17:55 +0000197 logger.Debugw(ctx, "processing-kvstore-event-change", log.Fields{"key-prefix": ccKeyPrefix})
divyadesai42bcb672020-03-04 11:40:53 +0000198
Matteo Scandolo2e677482020-04-09 11:56:27 -0700199 ccPathPrefix := c.cManager.Backend.PathPrefix + ccKeyPrefix + kvStorePathSeparator
divyadesai42bcb672020-03-04 11:40:53 +0000200
divyadesai8bf96862020-02-07 12:24:26 +0000201 for watchResp := range c.kvStoreEventChan {
202
203 if watchResp.EventType == kvstore.CONNECTIONDOWN || watchResp.EventType == kvstore.UNKNOWN {
Neha Sharma94f16a92020-06-26 04:17:55 +0000204 logger.Warnw(ctx, "received-invalid-change-type-in-watch-channel-from-kvstore", log.Fields{"change-type": watchResp.EventType})
divyadesai8bf96862020-02-07 12:24:26 +0000205 continue
206 }
207
208 // populating the configAttribute from the received Key
209 // For Example, Key received would be <Backend Prefix Path>/<Config Prefix>/<Component Name>/<Config Type>/default
210 // Storing default in configAttribute variable
211 ky := fmt.Sprintf("%s", watchResp.Key)
212
213 c.changeEventChan <- &ConfigChangeEvent{
214 ChangeType: ChangeEvent(watchResp.EventType),
215 ConfigAttribute: strings.TrimPrefix(ky, ccPathPrefix),
216 }
217 }
218}
219
Girish Kumar78cdb4f2020-03-16 13:39:42 +0000220// Retrieves value of a specific config key. Value of key is returned in String format
221func (c *ComponentConfig) Retrieve(ctx context.Context, configKey string) (string, error) {
222 key := c.makeConfigPath() + "/" + configKey
223
Neha Sharma94f16a92020-06-26 04:17:55 +0000224 logger.Debugw(ctx, "retrieving-config", log.Fields{"key": key})
Girish Kumar78cdb4f2020-03-16 13:39:42 +0000225
Matteo Scandolo2e677482020-04-09 11:56:27 -0700226 if kvpair, err := c.cManager.Backend.Get(ctx, key); err != nil {
Girish Kumar78cdb4f2020-03-16 13:39:42 +0000227 return "", err
228 } else {
229 if kvpair == nil {
230 return "", fmt.Errorf("config-key-does-not-exist : %s", key)
231 }
232
233 value := strings.Trim(fmt.Sprintf("%s", kvpair.Value), "\"")
Neha Sharma94f16a92020-06-26 04:17:55 +0000234 logger.Debugw(ctx, "retrieved-config", log.Fields{"key": key, "value": value})
Girish Kumar78cdb4f2020-03-16 13:39:42 +0000235 return value, nil
236 }
237}
238
divyadesai8bf96862020-02-07 12:24:26 +0000239func (c *ComponentConfig) RetrieveAll(ctx context.Context) (map[string]string, error) {
240 key := c.makeConfigPath()
241
Neha Sharma94f16a92020-06-26 04:17:55 +0000242 logger.Debugw(ctx, "retreiving-list", log.Fields{"key": key})
divyadesai42bcb672020-03-04 11:40:53 +0000243
Matteo Scandolo2e677482020-04-09 11:56:27 -0700244 data, err := c.cManager.Backend.List(ctx, key)
divyadesai8bf96862020-02-07 12:24:26 +0000245 if err != nil {
246 return nil, err
247 }
248
Matteo Scandolo2e677482020-04-09 11:56:27 -0700249 // Looping through the data recieved from the Backend for the given key
divyadesai8bf96862020-02-07 12:24:26 +0000250 // Trimming the required key and value from data and storing as key/value pair
251 // For Example, recieved key would be <Backend Prefix Path>/<Config Prefix>/<Component Name>/<Config Type>/default and value \"DEBUG\"
252 // Then in default will be stored as key and DEBUG will be stored as value in map[string]string
253 res := make(map[string]string)
Matteo Scandolo2e677482020-04-09 11:56:27 -0700254 ccPathPrefix := c.cManager.Backend.PathPrefix + kvStorePathSeparator + key + kvStorePathSeparator
divyadesai8bf96862020-02-07 12:24:26 +0000255 for attr, val := range data {
256 res[strings.TrimPrefix(attr, ccPathPrefix)] = strings.Trim(fmt.Sprintf("%s", val.Value), "\"")
257 }
258
259 return res, nil
260}
261
divyadesai42bcb672020-03-04 11:40:53 +0000262func (c *ComponentConfig) Save(ctx context.Context, configKey string, configValue string) error {
divyadesai8bf96862020-02-07 12:24:26 +0000263 key := c.makeConfigPath() + "/" + configKey
264
Neha Sharma94f16a92020-06-26 04:17:55 +0000265 logger.Debugw(ctx, "saving-config", log.Fields{"key": key, "value": configValue})
divyadesai8bf96862020-02-07 12:24:26 +0000266
267 //save the data for update config
Matteo Scandolo2e677482020-04-09 11:56:27 -0700268 if err := c.cManager.Backend.Put(ctx, key, configValue); err != nil {
divyadesai8bf96862020-02-07 12:24:26 +0000269 return err
270 }
271 return nil
272}
273
divyadesai42bcb672020-03-04 11:40:53 +0000274func (c *ComponentConfig) Delete(ctx context.Context, configKey string) error {
divyadesai8bf96862020-02-07 12:24:26 +0000275 //construct key using makeConfigPath
276 key := c.makeConfigPath() + "/" + configKey
277
Neha Sharma94f16a92020-06-26 04:17:55 +0000278 logger.Debugw(ctx, "deleting-config", log.Fields{"key": key})
divyadesai8bf96862020-02-07 12:24:26 +0000279 //delete the config
Matteo Scandolo2e677482020-04-09 11:56:27 -0700280 if err := c.cManager.Backend.Delete(ctx, key); err != nil {
divyadesai8bf96862020-02-07 12:24:26 +0000281 return err
282 }
283 return nil
284}