blob: bb999702ff7c9c973b686cc7b8e7b04b6c187c95 [file] [log] [blame]
Scott Baker2c1c4822019-10-16 11:02:41 -07001/*
Joey Armstrong9cdee9f2024-01-03 04:56:14 -05002 * Copyright 2018-2024 Open Networking Foundation (ONF) and the ONF Contributors
Scott Baker2c1c4822019-10-16 11:02:41 -07003
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 */
16
sbarbari1e3e29c2019-11-05 10:06:50 -050017package db
Scott Baker2c1c4822019-10-16 11:02:41 -070018
19import (
Girish Kumarca522102019-11-08 11:26:35 +000020 "context"
Scott Baker2c1c4822019-10-16 11:02:41 -070021 "errors"
22 "fmt"
David K. Bainbridge5edd7fb2020-07-29 19:30:48 -070023 "sync"
Girish Kumarca522102019-11-08 11:26:35 +000024 "time"
serkant.uluderyab38671c2019-11-01 09:35:38 -070025
khenaidoo26721882021-08-11 17:42:52 -040026 "github.com/opencord/voltha-lib-go/v7/pkg/db/kvstore"
27 "github.com/opencord/voltha-lib-go/v7/pkg/log"
serkant.uluderyab38671c2019-11-01 09:35:38 -070028 "google.golang.org/grpc/codes"
29 "google.golang.org/grpc/status"
Girish Kumarca522102019-11-08 11:26:35 +000030)
31
32const (
33 // Default Minimal Interval for posting alive state of backend kvstore on Liveness Channel
34 DefaultLivenessChannelInterval = time.Second * 30
Scott Baker2c1c4822019-10-16 11:02:41 -070035)
36
Scott Baker2c1c4822019-10-16 11:02:41 -070037// Backend structure holds details for accessing the kv store
38type Backend struct {
Girish Kumarca522102019-11-08 11:26:35 +000039 Client kvstore.Client
40 StoreType string
Neha Sharma130ac6d2020-04-08 08:46:32 +000041 Timeout time.Duration
Neha Sharmadd9af392020-04-28 09:03:57 +000042 Address string
Girish Kumarca522102019-11-08 11:26:35 +000043 PathPrefix string
David K. Bainbridge5edd7fb2020-07-29 19:30:48 -070044 alive bool // Is this backend connection alive?
45 livenessMutex sync.Mutex
Girish Kumarca522102019-11-08 11:26:35 +000046 liveness chan bool // channel to post alive state
47 LivenessChannelInterval time.Duration // regularly push alive state beyond this interval
48 lastLivenessTime time.Time // Instant of last alive state push
Scott Baker2c1c4822019-10-16 11:02:41 -070049}
50
51// NewBackend creates a new instance of a Backend structure
Neha Sharma94f16a92020-06-26 04:17:55 +000052func NewBackend(ctx context.Context, storeType string, address string, timeout time.Duration, pathPrefix string) *Backend {
Scott Baker2c1c4822019-10-16 11:02:41 -070053 var err error
54
55 b := &Backend{
Girish Kumarca522102019-11-08 11:26:35 +000056 StoreType: storeType,
Neha Sharmadd9af392020-04-28 09:03:57 +000057 Address: address,
Girish Kumarca522102019-11-08 11:26:35 +000058 Timeout: timeout,
59 LivenessChannelInterval: DefaultLivenessChannelInterval,
60 PathPrefix: pathPrefix,
61 alive: false, // connection considered down at start
Scott Baker2c1c4822019-10-16 11:02:41 -070062 }
63
Neha Sharma94f16a92020-06-26 04:17:55 +000064 if b.Client, err = b.newClient(ctx, address, timeout); err != nil {
65 logger.Errorw(ctx, "failed-to-create-kv-client",
Scott Baker2c1c4822019-10-16 11:02:41 -070066 log.Fields{
Neha Sharmadd9af392020-04-28 09:03:57 +000067 "type": storeType, "address": address,
Scott Baker2c1c4822019-10-16 11:02:41 -070068 "timeout": timeout, "prefix": pathPrefix,
69 "error": err.Error(),
70 })
71 }
72
73 return b
74}
75
Neha Sharma94f16a92020-06-26 04:17:55 +000076func (b *Backend) newClient(ctx context.Context, address string, timeout time.Duration) (kvstore.Client, error) {
Scott Baker2c1c4822019-10-16 11:02:41 -070077 switch b.StoreType {
serkant.uluderyae5afeff2021-02-23 18:00:23 +030078 case "redis":
79 return kvstore.NewRedisClient(address, timeout, false)
80 case "redis-sentinel":
81 return kvstore.NewRedisClient(address, timeout, true)
Scott Baker2c1c4822019-10-16 11:02:41 -070082 case "etcd":
Neha Sharma94f16a92020-06-26 04:17:55 +000083 return kvstore.NewEtcdClient(ctx, address, timeout, log.WarnLevel)
Scott Baker2c1c4822019-10-16 11:02:41 -070084 }
85 return nil, errors.New("unsupported-kv-store")
86}
87
Neha Sharma94f16a92020-06-26 04:17:55 +000088func (b *Backend) makePath(ctx context.Context, key string) string {
Scott Baker2c1c4822019-10-16 11:02:41 -070089 path := fmt.Sprintf("%s/%s", b.PathPrefix, key)
pnalmas37560752025-01-11 22:05:35 +053090 logger.Debugw(ctx, "make-path", log.Fields{"key": key, "path": path})
Scott Baker2c1c4822019-10-16 11:02:41 -070091 return path
92}
93
Neha Sharma94f16a92020-06-26 04:17:55 +000094func (b *Backend) updateLiveness(ctx context.Context, alive bool) {
Girish Kumarca522102019-11-08 11:26:35 +000095 // Periodically push stream of liveness data to the channel,
96 // so that in a live state, the core does not timeout and
97 // send a forced liveness message. Push alive state if the
98 // last push to channel was beyond livenessChannelInterval
David K. Bainbridge5edd7fb2020-07-29 19:30:48 -070099 b.livenessMutex.Lock()
100 defer b.livenessMutex.Unlock()
Girish Kumarca522102019-11-08 11:26:35 +0000101 if b.liveness != nil {
Girish Kumarca522102019-11-08 11:26:35 +0000102 if b.alive != alive {
Neha Sharma94f16a92020-06-26 04:17:55 +0000103 logger.Debug(ctx, "update-liveness-channel-reason-change")
Girish Kumarca522102019-11-08 11:26:35 +0000104 b.liveness <- alive
105 b.lastLivenessTime = time.Now()
David K. Bainbridge7c75cac2020-02-19 08:53:46 -0800106 } else if time.Since(b.lastLivenessTime) > b.LivenessChannelInterval {
Neha Sharma94f16a92020-06-26 04:17:55 +0000107 logger.Debug(ctx, "update-liveness-channel-reason-interval")
Girish Kumarca522102019-11-08 11:26:35 +0000108 b.liveness <- alive
109 b.lastLivenessTime = time.Now()
110 }
111 }
112
113 // Emit log message only for alive state change
114 if b.alive != alive {
Neha Sharma94f16a92020-06-26 04:17:55 +0000115 logger.Debugw(ctx, "change-kvstore-alive-status", log.Fields{"alive": alive})
Girish Kumarca522102019-11-08 11:26:35 +0000116 b.alive = alive
117 }
118}
119
120// Perform a dummy Key Lookup on kvstore to test Connection Liveness and
121// post on Liveness channel
npujar5bf737f2020-01-16 19:35:25 +0530122func (b *Backend) PerformLivenessCheck(ctx context.Context) bool {
123 alive := b.Client.IsConnectionUp(ctx)
Neha Sharma94f16a92020-06-26 04:17:55 +0000124 logger.Debugw(ctx, "kvstore-liveness-check-result", log.Fields{"alive": alive})
Girish Kumarca522102019-11-08 11:26:35 +0000125
Neha Sharma94f16a92020-06-26 04:17:55 +0000126 b.updateLiveness(ctx, alive)
Girish Kumarca522102019-11-08 11:26:35 +0000127 return alive
128}
129
130// Enable the liveness monitor channel. This channel will report
131// a "true" or "false" on every kvstore operation which indicates whether
132// or not the connection is still Live. This channel is then picked up
133// by the service (i.e. rw_core / ro_core) to update readiness status
134// and/or take other actions.
Neha Sharma94f16a92020-06-26 04:17:55 +0000135func (b *Backend) EnableLivenessChannel(ctx context.Context) chan bool {
136 logger.Debug(ctx, "enable-kvstore-liveness-channel")
David K. Bainbridge5edd7fb2020-07-29 19:30:48 -0700137 b.livenessMutex.Lock()
138 defer b.livenessMutex.Unlock()
Girish Kumarca522102019-11-08 11:26:35 +0000139 if b.liveness == nil {
Girish Kumarca522102019-11-08 11:26:35 +0000140 b.liveness = make(chan bool, 10)
Girish Kumarca522102019-11-08 11:26:35 +0000141 b.liveness <- b.alive
142 b.lastLivenessTime = time.Now()
143 }
144
145 return b.liveness
146}
147
148// Extract Alive status of Kvstore based on type of error
pnalmas37560752025-01-11 22:05:35 +0530149func (b *Backend) isErrorIndicatingAliveKvstore(err error) bool {
Girish Kumarca522102019-11-08 11:26:35 +0000150 // Alive unless observed an error indicating so
151 alive := true
152
153 if err != nil {
154
155 // timeout indicates kvstore not reachable/alive
156 if err == context.DeadlineExceeded {
157 alive = false
158 }
159
160 // Need to analyze client-specific errors based on backend type
161 if b.StoreType == "etcd" {
162
163 // For etcd backend, consider not-alive only for errors indicating
164 // timedout request or unavailable/corrupted cluster. For all remaining
165 // error codes listed in https://godoc.org/google.golang.org/grpc/codes#Code,
166 // we would not infer a not-alive backend because such a error may also
167 // occur due to bad client requests or sequence of operations
168 switch status.Code(err) {
169 case codes.DeadlineExceeded:
170 fallthrough
171 case codes.Unavailable:
172 fallthrough
173 case codes.DataLoss:
174 alive = false
175 }
Girish Kumarca522102019-11-08 11:26:35 +0000176 }
177 }
178
179 return alive
180}
181
Scott Baker2c1c4822019-10-16 11:02:41 -0700182// List retrieves one or more items that match the specified key
npujar5bf737f2020-01-16 19:35:25 +0530183func (b *Backend) List(ctx context.Context, key string) (map[string]*kvstore.KVPair, error) {
serkant.uluderyae5afeff2021-02-23 18:00:23 +0300184 span, ctx := log.CreateChildSpan(ctx, "kvs-list")
Rohan Agrawalfb1cb352020-08-03 04:59:32 +0000185 defer span.Finish()
186
Neha Sharma94f16a92020-06-26 04:17:55 +0000187 formattedPath := b.makePath(ctx, key)
188 logger.Debugw(ctx, "listing-key", log.Fields{"key": key, "path": formattedPath})
Scott Baker2c1c4822019-10-16 11:02:41 -0700189
npujar5bf737f2020-01-16 19:35:25 +0530190 pair, err := b.Client.List(ctx, formattedPath)
Girish Kumarca522102019-11-08 11:26:35 +0000191
pnalmas37560752025-01-11 22:05:35 +0530192 b.updateLiveness(ctx, b.isErrorIndicatingAliveKvstore(err))
Girish Kumarca522102019-11-08 11:26:35 +0000193
194 return pair, err
Scott Baker2c1c4822019-10-16 11:02:41 -0700195}
196
197// Get retrieves an item that matches the specified key
npujar5bf737f2020-01-16 19:35:25 +0530198func (b *Backend) Get(ctx context.Context, key string) (*kvstore.KVPair, error) {
serkant.uluderyae5afeff2021-02-23 18:00:23 +0300199 span, ctx := log.CreateChildSpan(ctx, "kvs-get")
Rohan Agrawalfb1cb352020-08-03 04:59:32 +0000200 defer span.Finish()
201
Neha Sharma94f16a92020-06-26 04:17:55 +0000202 formattedPath := b.makePath(ctx, key)
203 logger.Debugw(ctx, "getting-key", log.Fields{"key": key, "path": formattedPath})
Scott Baker2c1c4822019-10-16 11:02:41 -0700204
npujar5bf737f2020-01-16 19:35:25 +0530205 pair, err := b.Client.Get(ctx, formattedPath)
Girish Kumarca522102019-11-08 11:26:35 +0000206
pnalmas37560752025-01-11 22:05:35 +0530207 b.updateLiveness(ctx, b.isErrorIndicatingAliveKvstore(err))
208
209 return pair, err
210}
211
212// GetWithPrefix retrieves one or more items that match the specified key prefix
213func (b *Backend) GetWithPrefix(ctx context.Context, prefixKey string) (map[string]*kvstore.KVPair, error) {
214 span, ctx := log.CreateChildSpan(ctx, "kvs-get-with-prefix")
215 defer span.Finish()
216
217 formattedPath := b.makePath(ctx, prefixKey)
218 logger.Debugw(ctx, "get-entries-matching-prefix-key", log.Fields{"key": prefixKey, "path": formattedPath})
219
220 pair, err := b.Client.GetWithPrefix(ctx, formattedPath)
221
222 b.updateLiveness(ctx, b.isErrorIndicatingAliveKvstore(err))
223
224 return pair, err
225}
226
227// GetWithPrefixKeysOnly retrieves one or more keys that match the specified key prefix
228func (b *Backend) GetWithPrefixKeysOnly(ctx context.Context, prefixKey string) ([]string, error) {
229 span, ctx := log.CreateChildSpan(ctx, "kvs-get-with-prefix")
230 defer span.Finish()
231
232 formattedPath := b.makePath(ctx, prefixKey)
233 logger.Debugw(ctx, "get-keys-entries-matching-prefix-key", log.Fields{"key": prefixKey, "path": formattedPath})
234
235 pair, err := b.Client.GetWithPrefixKeysOnly(ctx, formattedPath)
236
237 b.updateLiveness(ctx, b.isErrorIndicatingAliveKvstore(err))
Girish Kumarca522102019-11-08 11:26:35 +0000238
239 return pair, err
Scott Baker2c1c4822019-10-16 11:02:41 -0700240}
241
242// Put stores an item value under the specifed key
npujar5bf737f2020-01-16 19:35:25 +0530243func (b *Backend) Put(ctx context.Context, key string, value interface{}) error {
serkant.uluderyae5afeff2021-02-23 18:00:23 +0300244 span, ctx := log.CreateChildSpan(ctx, "kvs-put")
Rohan Agrawalfb1cb352020-08-03 04:59:32 +0000245 defer span.Finish()
246
Neha Sharma94f16a92020-06-26 04:17:55 +0000247 formattedPath := b.makePath(ctx, key)
248 logger.Debugw(ctx, "putting-key", log.Fields{"key": key, "path": formattedPath})
Scott Baker2c1c4822019-10-16 11:02:41 -0700249
npujar5bf737f2020-01-16 19:35:25 +0530250 err := b.Client.Put(ctx, formattedPath, value)
Girish Kumarca522102019-11-08 11:26:35 +0000251
pnalmas37560752025-01-11 22:05:35 +0530252 b.updateLiveness(ctx, b.isErrorIndicatingAliveKvstore(err))
Girish Kumarca522102019-11-08 11:26:35 +0000253
254 return err
Scott Baker2c1c4822019-10-16 11:02:41 -0700255}
256
257// Delete removes an item under the specified key
npujar5bf737f2020-01-16 19:35:25 +0530258func (b *Backend) Delete(ctx context.Context, key string) error {
serkant.uluderyae5afeff2021-02-23 18:00:23 +0300259 span, ctx := log.CreateChildSpan(ctx, "kvs-delete")
Rohan Agrawalfb1cb352020-08-03 04:59:32 +0000260 defer span.Finish()
261
Neha Sharma94f16a92020-06-26 04:17:55 +0000262 formattedPath := b.makePath(ctx, key)
263 logger.Debugw(ctx, "deleting-key", log.Fields{"key": key, "path": formattedPath})
Scott Baker2c1c4822019-10-16 11:02:41 -0700264
npujar5bf737f2020-01-16 19:35:25 +0530265 err := b.Client.Delete(ctx, formattedPath)
Girish Kumarca522102019-11-08 11:26:35 +0000266
pnalmas37560752025-01-11 22:05:35 +0530267 b.updateLiveness(ctx, b.isErrorIndicatingAliveKvstore(err))
Girish Kumarca522102019-11-08 11:26:35 +0000268
269 return err
Scott Baker2c1c4822019-10-16 11:02:41 -0700270}
271
Serkant Uluderya198de902020-11-16 20:29:17 +0300272func (b *Backend) DeleteWithPrefix(ctx context.Context, prefixKey string) error {
serkant.uluderyae5afeff2021-02-23 18:00:23 +0300273 span, ctx := log.CreateChildSpan(ctx, "kvs-delete-with-prefix")
Serkant Uluderya198de902020-11-16 20:29:17 +0300274 defer span.Finish()
275
276 formattedPath := b.makePath(ctx, prefixKey)
277 logger.Debugw(ctx, "deleting-prefix-key", log.Fields{"key": prefixKey, "path": formattedPath})
278
279 err := b.Client.DeleteWithPrefix(ctx, formattedPath)
280
pnalmas37560752025-01-11 22:05:35 +0530281 b.updateLiveness(ctx, b.isErrorIndicatingAliveKvstore(err))
Serkant Uluderya198de902020-11-16 20:29:17 +0300282
283 return err
284}
285
Scott Baker2c1c4822019-10-16 11:02:41 -0700286// CreateWatch starts watching events for the specified key
divyadesai8bf96862020-02-07 12:24:26 +0000287func (b *Backend) CreateWatch(ctx context.Context, key string, withPrefix bool) chan *kvstore.Event {
serkant.uluderyae5afeff2021-02-23 18:00:23 +0300288 span, ctx := log.CreateChildSpan(ctx, "kvs-create-watch")
Rohan Agrawalfb1cb352020-08-03 04:59:32 +0000289 defer span.Finish()
290
Neha Sharma94f16a92020-06-26 04:17:55 +0000291 formattedPath := b.makePath(ctx, key)
292 logger.Debugw(ctx, "creating-key-watch", log.Fields{"key": key, "path": formattedPath})
Scott Baker2c1c4822019-10-16 11:02:41 -0700293
divyadesai8bf96862020-02-07 12:24:26 +0000294 return b.Client.Watch(ctx, formattedPath, withPrefix)
Scott Baker2c1c4822019-10-16 11:02:41 -0700295}
296
297// DeleteWatch stops watching events for the specified key
Neha Sharma94f16a92020-06-26 04:17:55 +0000298func (b *Backend) DeleteWatch(ctx context.Context, key string, ch chan *kvstore.Event) {
serkant.uluderyae5afeff2021-02-23 18:00:23 +0300299 span, ctx := log.CreateChildSpan(ctx, "kvs-delete-watch")
Rohan Agrawalfb1cb352020-08-03 04:59:32 +0000300 defer span.Finish()
301
Neha Sharma94f16a92020-06-26 04:17:55 +0000302 formattedPath := b.makePath(ctx, key)
303 logger.Debugw(ctx, "deleting-key-watch", log.Fields{"key": key, "path": formattedPath})
Scott Baker2c1c4822019-10-16 11:02:41 -0700304
Neha Sharma94f16a92020-06-26 04:17:55 +0000305 b.Client.CloseWatch(ctx, formattedPath, ch)
Scott Baker2c1c4822019-10-16 11:02:41 -0700306}