blob: c0915afd187f7ccb6e6d3f0a2a37c021cdf970fd [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"
21 "github.com/opencord/voltha-lib-go/v3/pkg/db"
22 "github.com/opencord/voltha-lib-go/v3/pkg/db/kvstore"
23 "github.com/opencord/voltha-lib-go/v3/pkg/log"
24 "strings"
Neha Sharma87d43d72020-04-08 14:10:40 +000025 "time"
divyadesai81bb7ba2020-03-11 11:45:23 +000026)
27
divyadesai81bb7ba2020-03-11 11:45:23 +000028const (
29 defaultkvStoreConfigPath = "config"
divyadesai52dc0882020-03-19 06:38:11 +000030 kvStoreDataPathPrefix = "service/voltha"
divyadesai81bb7ba2020-03-11 11:45:23 +000031 kvStorePathSeparator = "/"
32)
33
34// ConfigType represents the type for which config is created inside the kvstore
35// For example, loglevel
36type ConfigType int
37
38const (
39 ConfigTypeLogLevel ConfigType = iota
Rohan Agrawal00d3a412020-04-22 10:51:39 +000040 ConfigTypeMetadata
divyadesai81bb7ba2020-03-11 11:45:23 +000041 ConfigTypeKafka
42)
43
44func (c ConfigType) String() string {
Rohan Agrawal00d3a412020-04-22 10:51:39 +000045 return [...]string{"loglevel", "metadata", "kafka"}[c]
divyadesai81bb7ba2020-03-11 11:45:23 +000046}
47
48// ChangeEvent represents the event recieved from watch
49// For example, Put Event
50type ChangeEvent int
51
52const (
53 Put ChangeEvent = iota
54 Delete
55)
56
divyadesai52dc0882020-03-19 06:38:11 +000057func (ce ChangeEvent) String() string {
58 return [...]string{"Put", "Delete"}[ce]
59}
60
divyadesai81bb7ba2020-03-11 11:45:23 +000061// ConfigChangeEvent represents config for the events recieved from watch
62// For example,ChangeType is Put ,ConfigAttribute default
63type ConfigChangeEvent struct {
64 ChangeType ChangeEvent
65 ConfigAttribute string
66}
67
Rohan Agrawal00d3a412020-04-22 10:51:39 +000068// ConfigManager is a wrapper over Backend to maintain Configuration of voltha components
divyadesai81bb7ba2020-03-11 11:45:23 +000069// in kvstore based persistent storage
70type ConfigManager struct {
Rohan Agrawal00d3a412020-04-22 10:51:39 +000071 Backend *db.Backend
divyadesai81bb7ba2020-03-11 11:45:23 +000072 KvStoreConfigPrefix string
73}
74
75// ComponentConfig represents a category of configuration for a specific VOLTHA component type
76// stored in a persistent storage pointed to by Config Manager
77// For example, one ComponentConfig instance will be created for loglevel config type for rw-core
78// component while another ComponentConfig instance will refer to connection config type for same
79// rw-core component. So, there can be multiple ComponentConfig instance created per component
80// pointing to different category of configuration.
81//
82// Configuration pointed to be by ComponentConfig is stored in kvstore as a list of key/value pairs
83// under the hierarchical tree with following base path
84// <Backend Prefix Path>/<Config Prefix>/<Component Name>/<Config Type>/
85//
86// For example, rw-core ComponentConfig for loglevel config entries will be stored under following path
87// /voltha/service/config/rw-core/loglevel/
88type ComponentConfig struct {
89 cManager *ConfigManager
90 componentLabel string
91 configType ConfigType
92 changeEventChan chan *ConfigChangeEvent
93 kvStoreEventChan chan *kvstore.Event
94}
95
Neha Sharma87d43d72020-04-08 14:10:40 +000096func NewConfigManager(kvClient kvstore.Client, kvStoreType, kvStoreHost string, kvStorePort int, kvStoreTimeout time.Duration) *ConfigManager {
divyadesai81bb7ba2020-03-11 11:45:23 +000097
98 return &ConfigManager{
99 KvStoreConfigPrefix: defaultkvStoreConfigPath,
Rohan Agrawal00d3a412020-04-22 10:51:39 +0000100 Backend: &db.Backend{
divyadesai81bb7ba2020-03-11 11:45:23 +0000101 Client: kvClient,
102 StoreType: kvStoreType,
103 Host: kvStoreHost,
104 Port: kvStorePort,
105 Timeout: kvStoreTimeout,
106 PathPrefix: kvStoreDataPathPrefix,
107 },
108 }
109}
110
111// RetrieveComponentList list the component Names for which loglevel is stored in kvstore
112func (c *ConfigManager) RetrieveComponentList(ctx context.Context, configType ConfigType) ([]string, error) {
Rohan Agrawal00d3a412020-04-22 10:51:39 +0000113 data, err := c.Backend.List(ctx, c.KvStoreConfigPrefix)
divyadesai81bb7ba2020-03-11 11:45:23 +0000114 if err != nil {
115 return nil, err
116 }
117
Rohan Agrawal00d3a412020-04-22 10:51:39 +0000118 // Looping through the data recieved from the Backend for config
divyadesai81bb7ba2020-03-11 11:45:23 +0000119 // Trimming and Splitting the required key and value from data and storing as componentName,PackageName and Level
120 // For Example, recieved key would be <Backend Prefix Path>/<Config Prefix>/<Component Name>/<Config Type>/default and value \"DEBUG\"
121 // Then in default will be stored as PackageName,componentName as <Component Name> and DEBUG will be stored as value in List struct
122 ccPathPrefix := kvStorePathSeparator + configType.String() + kvStorePathSeparator
123 pathPrefix := kvStoreDataPathPrefix + kvStorePathSeparator + c.KvStoreConfigPrefix + kvStorePathSeparator
124 var list []string
125 keys := make(map[string]interface{})
126 for attr := range data {
127 cname := strings.TrimPrefix(attr, pathPrefix)
128 cName := strings.SplitN(cname, ccPathPrefix, 2)
129 if len(cName) != 2 {
130 continue
131 }
132 if _, exist := keys[cName[0]]; !exist {
133 keys[cName[0]] = nil
134 list = append(list, cName[0])
135 }
136 }
137 return list, nil
138}
139
140// Initialize the component config
141func (cm *ConfigManager) InitComponentConfig(componentLabel string, configType ConfigType) *ComponentConfig {
142
143 return &ComponentConfig{
144 componentLabel: componentLabel,
145 configType: configType,
146 cManager: cm,
147 changeEventChan: nil,
148 kvStoreEventChan: nil,
149 }
150
151}
152
153func (c *ComponentConfig) makeConfigPath() string {
154
155 cType := c.configType.String()
156 return c.cManager.KvStoreConfigPrefix + kvStorePathSeparator +
157 c.componentLabel + kvStorePathSeparator + cType
158}
159
160// MonitorForConfigChange watch on the subkeys for the given key
161// Any changes to the subkeys for the given key will return an event channel
162// Then Event channel will be processed and new event channel with required values will be created and return
163// For example, rw-core will be watching on <Backend Prefix Path>/<Config Prefix>/<Component Name>/<Config Type>/
164// will return an event channel for PUT,DELETE eventType.
165// Then values from event channel will be processed and stored in kvStoreEventChan.
166func (c *ComponentConfig) MonitorForConfigChange(ctx context.Context) chan *ConfigChangeEvent {
167 key := c.makeConfigPath()
168
Rohan Agrawal00d3a412020-04-22 10:51:39 +0000169 logger.Debugw("monitoring-for-config-change", log.Fields{"key": key})
divyadesai81bb7ba2020-03-11 11:45:23 +0000170
171 c.changeEventChan = make(chan *ConfigChangeEvent, 1)
172
Rohan Agrawal00d3a412020-04-22 10:51:39 +0000173 c.kvStoreEventChan = c.cManager.Backend.CreateWatch(ctx, key, true)
divyadesai81bb7ba2020-03-11 11:45:23 +0000174
175 go c.processKVStoreWatchEvents()
176
177 return c.changeEventChan
178}
179
Rohan Agrawal00d3a412020-04-22 10:51:39 +0000180// processKVStoreWatchEvents process event channel recieved from the Backend for any ChangeType
divyadesai81bb7ba2020-03-11 11:45:23 +0000181// It checks for the EventType is valid or not.For the valid EventTypes creates ConfigChangeEvent and send it on channel
182func (c *ComponentConfig) processKVStoreWatchEvents() {
183
184 ccKeyPrefix := c.makeConfigPath()
185
Rohan Agrawal00d3a412020-04-22 10:51:39 +0000186 logger.Debugw("processing-kvstore-event-change", log.Fields{"key-prefix": ccKeyPrefix})
divyadesai81bb7ba2020-03-11 11:45:23 +0000187
Rohan Agrawal00d3a412020-04-22 10:51:39 +0000188 ccPathPrefix := c.cManager.Backend.PathPrefix + ccKeyPrefix + kvStorePathSeparator
divyadesai81bb7ba2020-03-11 11:45:23 +0000189
190 for watchResp := range c.kvStoreEventChan {
191
192 if watchResp.EventType == kvstore.CONNECTIONDOWN || watchResp.EventType == kvstore.UNKNOWN {
Rohan Agrawal00d3a412020-04-22 10:51:39 +0000193 logger.Warnw("received-invalid-change-type-in-watch-channel-from-kvstore", log.Fields{"change-type": watchResp.EventType})
divyadesai81bb7ba2020-03-11 11:45:23 +0000194 continue
195 }
196
197 // populating the configAttribute from the received Key
198 // For Example, Key received would be <Backend Prefix Path>/<Config Prefix>/<Component Name>/<Config Type>/default
199 // Storing default in configAttribute variable
200 ky := fmt.Sprintf("%s", watchResp.Key)
201
202 c.changeEventChan <- &ConfigChangeEvent{
203 ChangeType: ChangeEvent(watchResp.EventType),
204 ConfigAttribute: strings.TrimPrefix(ky, ccPathPrefix),
205 }
206 }
207}
208
divyadesai52dc0882020-03-19 06:38:11 +0000209// Retrieves value of a specific config key. Value of key is returned in String format
210func (c *ComponentConfig) Retrieve(ctx context.Context, configKey string) (string, error) {
211 key := c.makeConfigPath() + "/" + configKey
212
Rohan Agrawal00d3a412020-04-22 10:51:39 +0000213 logger.Debugw("retrieving-config", log.Fields{"key": key})
divyadesai52dc0882020-03-19 06:38:11 +0000214
Rohan Agrawal00d3a412020-04-22 10:51:39 +0000215 if kvpair, err := c.cManager.Backend.Get(ctx, key); err != nil {
divyadesai52dc0882020-03-19 06:38:11 +0000216 return "", err
217 } else {
218 if kvpair == nil {
219 return "", fmt.Errorf("config-key-does-not-exist : %s", key)
220 }
221
222 value := strings.Trim(fmt.Sprintf("%s", kvpair.Value), "\"")
Rohan Agrawal00d3a412020-04-22 10:51:39 +0000223 logger.Debugw("retrieved-config", log.Fields{"key": key, "value": value})
divyadesai52dc0882020-03-19 06:38:11 +0000224 return value, nil
225 }
226}
227
divyadesai81bb7ba2020-03-11 11:45:23 +0000228func (c *ComponentConfig) RetrieveAll(ctx context.Context) (map[string]string, error) {
229 key := c.makeConfigPath()
230
Rohan Agrawal00d3a412020-04-22 10:51:39 +0000231 logger.Debugw("retreiving-list", log.Fields{"key": key})
divyadesai81bb7ba2020-03-11 11:45:23 +0000232
Rohan Agrawal00d3a412020-04-22 10:51:39 +0000233 data, err := c.cManager.Backend.List(ctx, key)
divyadesai81bb7ba2020-03-11 11:45:23 +0000234 if err != nil {
235 return nil, err
236 }
237
Rohan Agrawal00d3a412020-04-22 10:51:39 +0000238 // Looping through the data recieved from the Backend for the given key
divyadesai81bb7ba2020-03-11 11:45:23 +0000239 // Trimming the required key and value from data and storing as key/value pair
240 // For Example, recieved key would be <Backend Prefix Path>/<Config Prefix>/<Component Name>/<Config Type>/default and value \"DEBUG\"
241 // Then in default will be stored as key and DEBUG will be stored as value in map[string]string
242 res := make(map[string]string)
Rohan Agrawal00d3a412020-04-22 10:51:39 +0000243 ccPathPrefix := c.cManager.Backend.PathPrefix + kvStorePathSeparator + key + kvStorePathSeparator
divyadesai81bb7ba2020-03-11 11:45:23 +0000244 for attr, val := range data {
245 res[strings.TrimPrefix(attr, ccPathPrefix)] = strings.Trim(fmt.Sprintf("%s", val.Value), "\"")
246 }
247
248 return res, nil
249}
250
251func (c *ComponentConfig) Save(ctx context.Context, configKey string, configValue string) error {
252 key := c.makeConfigPath() + "/" + configKey
253
Rohan Agrawal00d3a412020-04-22 10:51:39 +0000254 logger.Debugw("saving-config", log.Fields{"key": key, "value": configValue})
divyadesai81bb7ba2020-03-11 11:45:23 +0000255
256 //save the data for update config
Rohan Agrawal00d3a412020-04-22 10:51:39 +0000257 if err := c.cManager.Backend.Put(ctx, key, configValue); err != nil {
divyadesai81bb7ba2020-03-11 11:45:23 +0000258 return err
259 }
260 return nil
261}
262
263func (c *ComponentConfig) Delete(ctx context.Context, configKey string) error {
264 //construct key using makeConfigPath
265 key := c.makeConfigPath() + "/" + configKey
266
Rohan Agrawal00d3a412020-04-22 10:51:39 +0000267 logger.Debugw("deleting-config", log.Fields{"key": key})
divyadesai81bb7ba2020-03-11 11:45:23 +0000268 //delete the config
Rohan Agrawal00d3a412020-04-22 10:51:39 +0000269 if err := c.cManager.Backend.Delete(ctx, key); err != nil {
divyadesai81bb7ba2020-03-11 11:45:23 +0000270 return err
271 }
272 return nil
273}