blob: 431970d19d0e7248427b092b4c4ad8bffc32ebac [file] [log] [blame]
Matt Jeanneretcab955f2019-04-10 15:45:57 -04001/*
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 */
16
sbarbaria8910ba2019-11-05 10:12:23 -050017package db
Matt Jeanneretcab955f2019-04-10 15:45:57 -040018
19import (
Girish Gowdra54934262019-11-13 14:19:55 +053020 "context"
Matt Jeanneretcab955f2019-04-10 15:45:57 -040021 "errors"
22 "fmt"
Girish Kumar935f7af2020-08-18 11:59:42 +000023 "sync"
Girish Gowdra54934262019-11-13 14:19:55 +053024 "time"
Esin Karamanccb714b2019-11-29 15:02:06 +000025
Girish Gowdra4c3d4602021-07-22 16:33:37 -070026 "github.com/opencord/voltha-lib-go/v6/pkg/db/kvstore"
27 "github.com/opencord/voltha-lib-go/v6/pkg/log"
Esin Karamanccb714b2019-11-29 15:02:06 +000028 "google.golang.org/grpc/codes"
29 "google.golang.org/grpc/status"
Girish Gowdra54934262019-11-13 14:19:55 +053030)
31
32const (
33 // Default Minimal Interval for posting alive state of backend kvstore on Liveness Channel
34 DefaultLivenessChannelInterval = time.Second * 30
Matt Jeanneretcab955f2019-04-10 15:45:57 -040035)
36
Matt Jeanneretcab955f2019-04-10 15:45:57 -040037// Backend structure holds details for accessing the kv store
38type Backend struct {
Girish Gowdra54934262019-11-13 14:19:55 +053039 Client kvstore.Client
40 StoreType string
Neha Sharmacc656962020-04-14 14:26:11 +000041 Timeout time.Duration
Neha Sharma3f221ae2020-04-29 19:02:12 +000042 Address string
Girish Gowdra54934262019-11-13 14:19:55 +053043 PathPrefix string
Girish Kumar935f7af2020-08-18 11:59:42 +000044 alive bool // Is this backend connection alive?
45 livenessMutex sync.Mutex
Girish Gowdra54934262019-11-13 14:19:55 +053046 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
Matt Jeanneretcab955f2019-04-10 15:45:57 -040049}
50
51// NewBackend creates a new instance of a Backend structure
Neha Sharma96b7bf22020-06-15 10:37:32 +000052func NewBackend(ctx context.Context, storeType string, address string, timeout time.Duration, pathPrefix string) *Backend {
Matt Jeanneretcab955f2019-04-10 15:45:57 -040053 var err error
54
55 b := &Backend{
Girish Gowdra54934262019-11-13 14:19:55 +053056 StoreType: storeType,
Neha Sharma3f221ae2020-04-29 19:02:12 +000057 Address: address,
Girish Gowdra54934262019-11-13 14:19:55 +053058 Timeout: timeout,
59 LivenessChannelInterval: DefaultLivenessChannelInterval,
60 PathPrefix: pathPrefix,
61 alive: false, // connection considered down at start
Matt Jeanneretcab955f2019-04-10 15:45:57 -040062 }
63
Neha Sharma96b7bf22020-06-15 10:37:32 +000064 if b.Client, err = b.newClient(ctx, address, timeout); err != nil {
65 logger.Errorw(ctx, "failed-to-create-kv-client",
Matt Jeanneretcab955f2019-04-10 15:45:57 -040066 log.Fields{
Neha Sharma3f221ae2020-04-29 19:02:12 +000067 "type": storeType, "address": address,
Matt Jeanneretcab955f2019-04-10 15:45:57 -040068 "timeout": timeout, "prefix": pathPrefix,
69 "error": err.Error(),
70 })
71 }
72
73 return b
74}
75
Neha Sharma96b7bf22020-06-15 10:37:32 +000076func (b *Backend) newClient(ctx context.Context, address string, timeout time.Duration) (kvstore.Client, error) {
Matt Jeanneretcab955f2019-04-10 15:45:57 -040077 switch b.StoreType {
Matt Jeanneretcab955f2019-04-10 15:45:57 -040078 case "etcd":
Neha Sharma96b7bf22020-06-15 10:37:32 +000079 return kvstore.NewEtcdClient(ctx, address, timeout, log.WarnLevel)
Matt Jeanneretcab955f2019-04-10 15:45:57 -040080 }
81 return nil, errors.New("unsupported-kv-store")
82}
83
Neha Sharma96b7bf22020-06-15 10:37:32 +000084func (b *Backend) makePath(ctx context.Context, key string) string {
Matt Jeanneretcab955f2019-04-10 15:45:57 -040085 path := fmt.Sprintf("%s/%s", b.PathPrefix, key)
86 return path
87}
88
Neha Sharma96b7bf22020-06-15 10:37:32 +000089func (b *Backend) updateLiveness(ctx context.Context, alive bool) {
Girish Gowdra54934262019-11-13 14:19:55 +053090 // Periodically push stream of liveness data to the channel,
91 // so that in a live state, the core does not timeout and
92 // send a forced liveness message. Push alive state if the
93 // last push to channel was beyond livenessChannelInterval
Girish Kumar935f7af2020-08-18 11:59:42 +000094 b.livenessMutex.Lock()
95 defer b.livenessMutex.Unlock()
Girish Gowdra54934262019-11-13 14:19:55 +053096 if b.liveness != nil {
Girish Gowdra54934262019-11-13 14:19:55 +053097 if b.alive != alive {
Neha Sharma96b7bf22020-06-15 10:37:32 +000098 logger.Debug(ctx, "update-liveness-channel-reason-change")
Girish Gowdra54934262019-11-13 14:19:55 +053099 b.liveness <- alive
100 b.lastLivenessTime = time.Now()
Rohan Agrawal02f784d2020-02-14 09:34:02 +0000101 } else if time.Since(b.lastLivenessTime) > b.LivenessChannelInterval {
Neha Sharma96b7bf22020-06-15 10:37:32 +0000102 logger.Debug(ctx, "update-liveness-channel-reason-interval")
Girish Gowdra54934262019-11-13 14:19:55 +0530103 b.liveness <- alive
104 b.lastLivenessTime = time.Now()
105 }
106 }
107
108 // Emit log message only for alive state change
109 if b.alive != alive {
Neha Sharma96b7bf22020-06-15 10:37:32 +0000110 logger.Debugw(ctx, "change-kvstore-alive-status", log.Fields{"alive": alive})
Girish Gowdra54934262019-11-13 14:19:55 +0530111 b.alive = alive
112 }
113}
114
115// Perform a dummy Key Lookup on kvstore to test Connection Liveness and
116// post on Liveness channel
npujarec5762e2020-01-01 14:08:48 +0530117func (b *Backend) PerformLivenessCheck(ctx context.Context) bool {
118 alive := b.Client.IsConnectionUp(ctx)
Neha Sharma96b7bf22020-06-15 10:37:32 +0000119 logger.Debugw(ctx, "kvstore-liveness-check-result", log.Fields{"alive": alive})
Girish Gowdra54934262019-11-13 14:19:55 +0530120
Neha Sharma96b7bf22020-06-15 10:37:32 +0000121 b.updateLiveness(ctx, alive)
Girish Gowdra54934262019-11-13 14:19:55 +0530122 return alive
123}
124
125// Enable the liveness monitor channel. This channel will report
126// a "true" or "false" on every kvstore operation which indicates whether
127// or not the connection is still Live. This channel is then picked up
128// by the service (i.e. rw_core / ro_core) to update readiness status
129// and/or take other actions.
Neha Sharma96b7bf22020-06-15 10:37:32 +0000130func (b *Backend) EnableLivenessChannel(ctx context.Context) chan bool {
131 logger.Debug(ctx, "enable-kvstore-liveness-channel")
Girish Kumar935f7af2020-08-18 11:59:42 +0000132 b.livenessMutex.Lock()
133 defer b.livenessMutex.Unlock()
Girish Gowdra54934262019-11-13 14:19:55 +0530134 if b.liveness == nil {
Girish Gowdra54934262019-11-13 14:19:55 +0530135 b.liveness = make(chan bool, 10)
Girish Gowdra54934262019-11-13 14:19:55 +0530136 b.liveness <- b.alive
137 b.lastLivenessTime = time.Now()
138 }
139
140 return b.liveness
141}
142
143// Extract Alive status of Kvstore based on type of error
Neha Sharma96b7bf22020-06-15 10:37:32 +0000144func (b *Backend) isErrorIndicatingAliveKvstore(ctx context.Context, err error) bool {
Girish Gowdra54934262019-11-13 14:19:55 +0530145 // Alive unless observed an error indicating so
146 alive := true
147
148 if err != nil {
149
150 // timeout indicates kvstore not reachable/alive
151 if err == context.DeadlineExceeded {
152 alive = false
153 }
154
155 // Need to analyze client-specific errors based on backend type
156 if b.StoreType == "etcd" {
157
158 // For etcd backend, consider not-alive only for errors indicating
159 // timedout request or unavailable/corrupted cluster. For all remaining
160 // error codes listed in https://godoc.org/google.golang.org/grpc/codes#Code,
161 // we would not infer a not-alive backend because such a error may also
162 // occur due to bad client requests or sequence of operations
163 switch status.Code(err) {
164 case codes.DeadlineExceeded:
165 fallthrough
166 case codes.Unavailable:
167 fallthrough
168 case codes.DataLoss:
169 alive = false
170 }
Girish Gowdra54934262019-11-13 14:19:55 +0530171 }
172 }
173
174 return alive
175}
176
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400177// List retrieves one or more items that match the specified key
npujarec5762e2020-01-01 14:08:48 +0530178func (b *Backend) List(ctx context.Context, key string) (map[string]*kvstore.KVPair, error) {
Girish Kumar935f7af2020-08-18 11:59:42 +0000179 span, ctx := log.CreateChildSpan(ctx, "etcd-list")
180 defer span.Finish()
181
Neha Sharma96b7bf22020-06-15 10:37:32 +0000182 formattedPath := b.makePath(ctx, key)
183 logger.Debugw(ctx, "listing-key", log.Fields{"key": key, "path": formattedPath})
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400184
npujarec5762e2020-01-01 14:08:48 +0530185 pair, err := b.Client.List(ctx, formattedPath)
Girish Gowdra54934262019-11-13 14:19:55 +0530186
Neha Sharma96b7bf22020-06-15 10:37:32 +0000187 b.updateLiveness(ctx, b.isErrorIndicatingAliveKvstore(ctx, err))
Girish Gowdra54934262019-11-13 14:19:55 +0530188
189 return pair, err
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400190}
191
192// Get retrieves an item that matches the specified key
npujarec5762e2020-01-01 14:08:48 +0530193func (b *Backend) Get(ctx context.Context, key string) (*kvstore.KVPair, error) {
Girish Kumar935f7af2020-08-18 11:59:42 +0000194 span, ctx := log.CreateChildSpan(ctx, "etcd-get")
195 defer span.Finish()
196
Neha Sharma96b7bf22020-06-15 10:37:32 +0000197 formattedPath := b.makePath(ctx, key)
198 logger.Debugw(ctx, "getting-key", log.Fields{"key": key, "path": formattedPath})
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400199
npujarec5762e2020-01-01 14:08:48 +0530200 pair, err := b.Client.Get(ctx, formattedPath)
Girish Gowdra54934262019-11-13 14:19:55 +0530201
Neha Sharma96b7bf22020-06-15 10:37:32 +0000202 b.updateLiveness(ctx, b.isErrorIndicatingAliveKvstore(ctx, err))
Girish Gowdra54934262019-11-13 14:19:55 +0530203
204 return pair, err
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400205}
206
207// Put stores an item value under the specifed key
npujarec5762e2020-01-01 14:08:48 +0530208func (b *Backend) Put(ctx context.Context, key string, value interface{}) error {
Girish Kumar935f7af2020-08-18 11:59:42 +0000209 span, ctx := log.CreateChildSpan(ctx, "etcd-put")
210 defer span.Finish()
211
Neha Sharma96b7bf22020-06-15 10:37:32 +0000212 formattedPath := b.makePath(ctx, key)
213 logger.Debugw(ctx, "putting-key", log.Fields{"key": key, "path": formattedPath})
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400214
npujarec5762e2020-01-01 14:08:48 +0530215 err := b.Client.Put(ctx, formattedPath, value)
Girish Gowdra54934262019-11-13 14:19:55 +0530216
Neha Sharma96b7bf22020-06-15 10:37:32 +0000217 b.updateLiveness(ctx, b.isErrorIndicatingAliveKvstore(ctx, err))
Girish Gowdra54934262019-11-13 14:19:55 +0530218
219 return err
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400220}
221
222// Delete removes an item under the specified key
npujarec5762e2020-01-01 14:08:48 +0530223func (b *Backend) Delete(ctx context.Context, key string) error {
Girish Kumar935f7af2020-08-18 11:59:42 +0000224 span, ctx := log.CreateChildSpan(ctx, "etcd-delete")
225 defer span.Finish()
226
Neha Sharma96b7bf22020-06-15 10:37:32 +0000227 formattedPath := b.makePath(ctx, key)
228 logger.Debugw(ctx, "deleting-key", log.Fields{"key": key, "path": formattedPath})
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400229
npujarec5762e2020-01-01 14:08:48 +0530230 err := b.Client.Delete(ctx, formattedPath)
Girish Gowdra54934262019-11-13 14:19:55 +0530231
Neha Sharma96b7bf22020-06-15 10:37:32 +0000232 b.updateLiveness(ctx, b.isErrorIndicatingAliveKvstore(ctx, err))
Girish Gowdra54934262019-11-13 14:19:55 +0530233
234 return err
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400235}
236
serkant.uluderya7b8211e2021-02-24 16:39:18 +0300237// DeleteWithPrefix removes items having prefix key
238func (b *Backend) DeleteWithPrefix(ctx context.Context, prefixKey string) error {
239 span, ctx := log.CreateChildSpan(ctx, "etcd-delete-with-prefix")
240 defer span.Finish()
241
242 formattedPath := b.makePath(ctx, prefixKey)
243 logger.Debugw(ctx, "deleting-prefix-key", log.Fields{"key": prefixKey, "path": formattedPath})
244
245 err := b.Client.DeleteWithPrefix(ctx, formattedPath)
246
247 b.updateLiveness(ctx, b.isErrorIndicatingAliveKvstore(ctx, err))
248
249 return err
250}
251
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400252// CreateWatch starts watching events for the specified key
Scott Bakere701b862020-02-20 16:19:16 -0800253func (b *Backend) CreateWatch(ctx context.Context, key string, withPrefix bool) chan *kvstore.Event {
Girish Kumar935f7af2020-08-18 11:59:42 +0000254 span, ctx := log.CreateChildSpan(ctx, "etcd-create-watch")
255 defer span.Finish()
256
Neha Sharma96b7bf22020-06-15 10:37:32 +0000257 formattedPath := b.makePath(ctx, key)
258 logger.Debugw(ctx, "creating-key-watch", log.Fields{"key": key, "path": formattedPath})
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400259
Scott Bakere701b862020-02-20 16:19:16 -0800260 return b.Client.Watch(ctx, formattedPath, withPrefix)
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400261}
262
263// DeleteWatch stops watching events for the specified key
Neha Sharma96b7bf22020-06-15 10:37:32 +0000264func (b *Backend) DeleteWatch(ctx context.Context, key string, ch chan *kvstore.Event) {
Girish Kumar935f7af2020-08-18 11:59:42 +0000265 span, ctx := log.CreateChildSpan(ctx, "etcd-delete-watch")
266 defer span.Finish()
267
Neha Sharma96b7bf22020-06-15 10:37:32 +0000268 formattedPath := b.makePath(ctx, key)
269 logger.Debugw(ctx, "deleting-key-watch", log.Fields{"key": key, "path": formattedPath})
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400270
Neha Sharma96b7bf22020-06-15 10:37:32 +0000271 b.Client.CloseWatch(ctx, formattedPath, ch)
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400272}