blob: 11aa8e62a8ac653456c0132bea75c707e478cfb4 [file] [log] [blame]
divyadesai81bb7ba2020-03-11 11:45:23 +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"
Neha Sharma318a1292020-05-14 19:53:26 +000021 "os"
22 "strings"
23 "time"
24
divyadesai81bb7ba2020-03-11 11:45:23 +000025 "github.com/opencord/voltha-lib-go/v3/pkg/db"
26 "github.com/opencord/voltha-lib-go/v3/pkg/db/kvstore"
27 "github.com/opencord/voltha-lib-go/v3/pkg/log"
divyadesai81bb7ba2020-03-11 11:45:23 +000028)
29
divyadesai81bb7ba2020-03-11 11:45:23 +000030const (
Neha Sharma318a1292020-05-14 19:53:26 +000031 defaultkvStoreConfigPath = "config"
32 defaultkvStoreDataPathPrefix = "service/voltha"
33 kvStorePathSeparator = "/"
divyadesai81bb7ba2020-03-11 11:45:23 +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
Rohan Agrawal00d3a412020-04-22 10:51:39 +000042 ConfigTypeMetadata
divyadesai81bb7ba2020-03-11 11:45:23 +000043 ConfigTypeKafka
44)
45
46func (c ConfigType) String() string {
Rohan Agrawal00d3a412020-04-22 10:51:39 +000047 return [...]string{"loglevel", "metadata", "kafka"}[c]
divyadesai81bb7ba2020-03-11 11:45:23 +000048}
49
50// ChangeEvent represents the event recieved from watch
51// For example, Put Event
52type ChangeEvent int
53
54const (
55 Put ChangeEvent = iota
56 Delete
57)
58
divyadesai52dc0882020-03-19 06:38:11 +000059func (ce ChangeEvent) String() string {
60 return [...]string{"Put", "Delete"}[ce]
61}
62
divyadesai81bb7ba2020-03-11 11:45:23 +000063// ConfigChangeEvent represents config for the events recieved from watch
64// For example,ChangeType is Put ,ConfigAttribute default
65type ConfigChangeEvent struct {
66 ChangeType ChangeEvent
67 ConfigAttribute string
68}
69
Rohan Agrawal00d3a412020-04-22 10:51:39 +000070// ConfigManager is a wrapper over Backend to maintain Configuration of voltha components
divyadesai81bb7ba2020-03-11 11:45:23 +000071// in kvstore based persistent storage
72type ConfigManager struct {
Neha Sharma318a1292020-05-14 19:53:26 +000073 Backend *db.Backend
74 KVStoreConfigPrefix string
75 KVStoreDataPathPrefix string
divyadesai81bb7ba2020-03-11 11:45:23 +000076}
77
78// ComponentConfig represents a category of configuration for a specific VOLTHA component type
79// stored in a persistent storage pointed to by Config Manager
80// For example, one ComponentConfig instance will be created for loglevel config type for rw-core
81// component while another ComponentConfig instance will refer to connection config type for same
82// rw-core component. So, there can be multiple ComponentConfig instance created per component
83// pointing to different category of configuration.
84//
85// Configuration pointed to be by ComponentConfig is stored in kvstore as a list of key/value pairs
86// under the hierarchical tree with following base path
87// <Backend Prefix Path>/<Config Prefix>/<Component Name>/<Config Type>/
88//
89// For example, rw-core ComponentConfig for loglevel config entries will be stored under following path
90// /voltha/service/config/rw-core/loglevel/
91type ComponentConfig struct {
92 cManager *ConfigManager
93 componentLabel string
94 configType ConfigType
95 changeEventChan chan *ConfigChangeEvent
96 kvStoreEventChan chan *kvstore.Event
97}
98
Rohan Agrawalc32d9932020-06-15 11:01:47 +000099func NewConfigManager(ctx context.Context, kvClient kvstore.Client, kvStoreType, kvStoreAddress string, kvStoreTimeout time.Duration) *ConfigManager {
Neha Sharma318a1292020-05-14 19:53:26 +0000100 var kvStorePrefix string
101 if prefix, present := os.LookupEnv("KV_STORE_DATAPATH_PREFIX"); present {
102 kvStorePrefix = prefix
Rohan Agrawalc32d9932020-06-15 11:01:47 +0000103 logger.Infow(ctx, "KV_STORE_DATAPATH_PREFIX env variable is set, ", log.Fields{"kvStoreDataPathPrefix": kvStorePrefix})
Neha Sharma318a1292020-05-14 19:53:26 +0000104 } else {
105 kvStorePrefix = defaultkvStoreDataPathPrefix
Rohan Agrawalc32d9932020-06-15 11:01:47 +0000106 logger.Infow(ctx, "KV_STORE_DATAPATH_PREFIX env variable is not set, using default", log.Fields{"kvStoreDataPathPrefix": defaultkvStoreDataPathPrefix})
Neha Sharma318a1292020-05-14 19:53:26 +0000107 }
divyadesai81bb7ba2020-03-11 11:45:23 +0000108 return &ConfigManager{
Neha Sharma318a1292020-05-14 19:53:26 +0000109 KVStoreConfigPrefix: defaultkvStoreConfigPath,
110 KVStoreDataPathPrefix: kvStorePrefix,
Rohan Agrawal00d3a412020-04-22 10:51:39 +0000111 Backend: &db.Backend{
divyadesai81bb7ba2020-03-11 11:45:23 +0000112 Client: kvClient,
113 StoreType: kvStoreType,
Neha Sharma318a1292020-05-14 19:53:26 +0000114 Address: kvStoreAddress,
divyadesai81bb7ba2020-03-11 11:45:23 +0000115 Timeout: kvStoreTimeout,
Neha Sharma318a1292020-05-14 19:53:26 +0000116 PathPrefix: kvStorePrefix,
divyadesai81bb7ba2020-03-11 11:45:23 +0000117 },
118 }
119}
120
121// RetrieveComponentList list the component Names for which loglevel is stored in kvstore
122func (c *ConfigManager) RetrieveComponentList(ctx context.Context, configType ConfigType) ([]string, error) {
Neha Sharma318a1292020-05-14 19:53:26 +0000123 data, err := c.Backend.List(ctx, c.KVStoreConfigPrefix)
divyadesai81bb7ba2020-03-11 11:45:23 +0000124 if err != nil {
125 return nil, err
126 }
127
Rohan Agrawal00d3a412020-04-22 10:51:39 +0000128 // Looping through the data recieved from the Backend for config
divyadesai81bb7ba2020-03-11 11:45:23 +0000129 // Trimming and Splitting the required key and value from data and storing as componentName,PackageName and Level
130 // For Example, recieved key would be <Backend Prefix Path>/<Config Prefix>/<Component Name>/<Config Type>/default and value \"DEBUG\"
131 // Then in default will be stored as PackageName,componentName as <Component Name> and DEBUG will be stored as value in List struct
132 ccPathPrefix := kvStorePathSeparator + configType.String() + kvStorePathSeparator
Neha Sharma318a1292020-05-14 19:53:26 +0000133 pathPrefix := c.KVStoreDataPathPrefix + kvStorePathSeparator + c.KVStoreConfigPrefix + kvStorePathSeparator
divyadesai81bb7ba2020-03-11 11:45:23 +0000134 var list []string
135 keys := make(map[string]interface{})
136 for attr := range data {
137 cname := strings.TrimPrefix(attr, pathPrefix)
138 cName := strings.SplitN(cname, ccPathPrefix, 2)
139 if len(cName) != 2 {
140 continue
141 }
142 if _, exist := keys[cName[0]]; !exist {
143 keys[cName[0]] = nil
144 list = append(list, cName[0])
145 }
146 }
147 return list, nil
148}
149
150// Initialize the component config
151func (cm *ConfigManager) InitComponentConfig(componentLabel string, configType ConfigType) *ComponentConfig {
152
153 return &ComponentConfig{
154 componentLabel: componentLabel,
155 configType: configType,
156 cManager: cm,
157 changeEventChan: nil,
158 kvStoreEventChan: nil,
159 }
160
161}
162
163func (c *ComponentConfig) makeConfigPath() string {
164
165 cType := c.configType.String()
Neha Sharma318a1292020-05-14 19:53:26 +0000166 return c.cManager.KVStoreConfigPrefix + kvStorePathSeparator +
divyadesai81bb7ba2020-03-11 11:45:23 +0000167 c.componentLabel + kvStorePathSeparator + cType
168}
169
170// MonitorForConfigChange watch on the subkeys for the given key
171// Any changes to the subkeys for the given key will return an event channel
172// Then Event channel will be processed and new event channel with required values will be created and return
173// For example, rw-core will be watching on <Backend Prefix Path>/<Config Prefix>/<Component Name>/<Config Type>/
174// will return an event channel for PUT,DELETE eventType.
175// Then values from event channel will be processed and stored in kvStoreEventChan.
176func (c *ComponentConfig) MonitorForConfigChange(ctx context.Context) chan *ConfigChangeEvent {
177 key := c.makeConfigPath()
178
Rohan Agrawalc32d9932020-06-15 11:01:47 +0000179 logger.Debugw(ctx, "monitoring-for-config-change", log.Fields{"key": key})
divyadesai81bb7ba2020-03-11 11:45:23 +0000180
181 c.changeEventChan = make(chan *ConfigChangeEvent, 1)
182
Rohan Agrawal00d3a412020-04-22 10:51:39 +0000183 c.kvStoreEventChan = c.cManager.Backend.CreateWatch(ctx, key, true)
divyadesai81bb7ba2020-03-11 11:45:23 +0000184
Rohan Agrawalc32d9932020-06-15 11:01:47 +0000185 go c.processKVStoreWatchEvents(ctx)
divyadesai81bb7ba2020-03-11 11:45:23 +0000186
187 return c.changeEventChan
188}
189
Rohan Agrawal00d3a412020-04-22 10:51:39 +0000190// processKVStoreWatchEvents process event channel recieved from the Backend for any ChangeType
divyadesai81bb7ba2020-03-11 11:45:23 +0000191// It checks for the EventType is valid or not.For the valid EventTypes creates ConfigChangeEvent and send it on channel
Rohan Agrawalc32d9932020-06-15 11:01:47 +0000192func (c *ComponentConfig) processKVStoreWatchEvents(ctx context.Context) {
divyadesai81bb7ba2020-03-11 11:45:23 +0000193
194 ccKeyPrefix := c.makeConfigPath()
195
Rohan Agrawalc32d9932020-06-15 11:01:47 +0000196 logger.Debugw(ctx, "processing-kvstore-event-change", log.Fields{"key-prefix": ccKeyPrefix})
divyadesai81bb7ba2020-03-11 11:45:23 +0000197
Rohan Agrawal00d3a412020-04-22 10:51:39 +0000198 ccPathPrefix := c.cManager.Backend.PathPrefix + ccKeyPrefix + kvStorePathSeparator
divyadesai81bb7ba2020-03-11 11:45:23 +0000199
200 for watchResp := range c.kvStoreEventChan {
201
202 if watchResp.EventType == kvstore.CONNECTIONDOWN || watchResp.EventType == kvstore.UNKNOWN {
Rohan Agrawalc32d9932020-06-15 11:01:47 +0000203 logger.Warnw(ctx, "received-invalid-change-type-in-watch-channel-from-kvstore", log.Fields{"change-type": watchResp.EventType})
divyadesai81bb7ba2020-03-11 11:45:23 +0000204 continue
205 }
206
207 // populating the configAttribute from the received Key
208 // For Example, Key received would be <Backend Prefix Path>/<Config Prefix>/<Component Name>/<Config Type>/default
209 // Storing default in configAttribute variable
210 ky := fmt.Sprintf("%s", watchResp.Key)
211
212 c.changeEventChan <- &ConfigChangeEvent{
213 ChangeType: ChangeEvent(watchResp.EventType),
214 ConfigAttribute: strings.TrimPrefix(ky, ccPathPrefix),
215 }
216 }
217}
218
divyadesai52dc0882020-03-19 06:38:11 +0000219// Retrieves value of a specific config key. Value of key is returned in String format
220func (c *ComponentConfig) Retrieve(ctx context.Context, configKey string) (string, error) {
221 key := c.makeConfigPath() + "/" + configKey
222
Rohan Agrawalc32d9932020-06-15 11:01:47 +0000223 logger.Debugw(ctx, "retrieving-config", log.Fields{"key": key})
divyadesai52dc0882020-03-19 06:38:11 +0000224
Rohan Agrawal00d3a412020-04-22 10:51:39 +0000225 if kvpair, err := c.cManager.Backend.Get(ctx, key); err != nil {
divyadesai52dc0882020-03-19 06:38:11 +0000226 return "", err
227 } else {
228 if kvpair == nil {
229 return "", fmt.Errorf("config-key-does-not-exist : %s", key)
230 }
231
232 value := strings.Trim(fmt.Sprintf("%s", kvpair.Value), "\"")
Rohan Agrawalc32d9932020-06-15 11:01:47 +0000233 logger.Debugw(ctx, "retrieved-config", log.Fields{"key": key, "value": value})
divyadesai52dc0882020-03-19 06:38:11 +0000234 return value, nil
235 }
236}
237
divyadesai81bb7ba2020-03-11 11:45:23 +0000238func (c *ComponentConfig) RetrieveAll(ctx context.Context) (map[string]string, error) {
239 key := c.makeConfigPath()
240
Rohan Agrawalc32d9932020-06-15 11:01:47 +0000241 logger.Debugw(ctx, "retreiving-list", log.Fields{"key": key})
divyadesai81bb7ba2020-03-11 11:45:23 +0000242
Rohan Agrawal00d3a412020-04-22 10:51:39 +0000243 data, err := c.cManager.Backend.List(ctx, key)
divyadesai81bb7ba2020-03-11 11:45:23 +0000244 if err != nil {
245 return nil, err
246 }
247
Rohan Agrawal00d3a412020-04-22 10:51:39 +0000248 // Looping through the data recieved from the Backend for the given key
divyadesai81bb7ba2020-03-11 11:45:23 +0000249 // Trimming the required key and value from data and storing as key/value pair
250 // For Example, recieved key would be <Backend Prefix Path>/<Config Prefix>/<Component Name>/<Config Type>/default and value \"DEBUG\"
251 // Then in default will be stored as key and DEBUG will be stored as value in map[string]string
252 res := make(map[string]string)
Rohan Agrawal00d3a412020-04-22 10:51:39 +0000253 ccPathPrefix := c.cManager.Backend.PathPrefix + kvStorePathSeparator + key + kvStorePathSeparator
divyadesai81bb7ba2020-03-11 11:45:23 +0000254 for attr, val := range data {
255 res[strings.TrimPrefix(attr, ccPathPrefix)] = strings.Trim(fmt.Sprintf("%s", val.Value), "\"")
256 }
257
258 return res, nil
259}
260
261func (c *ComponentConfig) Save(ctx context.Context, configKey string, configValue string) error {
262 key := c.makeConfigPath() + "/" + configKey
263
Rohan Agrawalc32d9932020-06-15 11:01:47 +0000264 logger.Debugw(ctx, "saving-config", log.Fields{"key": key, "value": configValue})
divyadesai81bb7ba2020-03-11 11:45:23 +0000265
266 //save the data for update config
Rohan Agrawal00d3a412020-04-22 10:51:39 +0000267 if err := c.cManager.Backend.Put(ctx, key, configValue); err != nil {
divyadesai81bb7ba2020-03-11 11:45:23 +0000268 return err
269 }
270 return nil
271}
272
273func (c *ComponentConfig) Delete(ctx context.Context, configKey string) error {
274 //construct key using makeConfigPath
275 key := c.makeConfigPath() + "/" + configKey
276
Rohan Agrawalc32d9932020-06-15 11:01:47 +0000277 logger.Debugw(ctx, "deleting-config", log.Fields{"key": key})
divyadesai81bb7ba2020-03-11 11:45:23 +0000278 //delete the config
Rohan Agrawal00d3a412020-04-22 10:51:39 +0000279 if err := c.cManager.Backend.Delete(ctx, key); err != nil {
divyadesai81bb7ba2020-03-11 11:45:23 +0000280 return err
281 }
282 return nil
283}