blob: 31f100bd64b4f32c3c12daa19f22c3d7443bf48a [file] [log] [blame]
khenaidood948f772021-08-11 17:49:24 -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
17package db
18
19import (
20 "context"
21 "errors"
22 "fmt"
23 "sync"
24 "time"
25
26 "github.com/opencord/voltha-lib-go/v7/pkg/db/kvstore"
27 "github.com/opencord/voltha-lib-go/v7/pkg/log"
28 "google.golang.org/grpc/codes"
29 "google.golang.org/grpc/status"
30)
31
32const (
33 // Default Minimal Interval for posting alive state of backend kvstore on Liveness Channel
34 DefaultLivenessChannelInterval = time.Second * 30
35)
36
37// Backend structure holds details for accessing the kv store
38type Backend struct {
39 Client kvstore.Client
40 StoreType string
41 Timeout time.Duration
42 Address string
43 PathPrefix string
44 alive bool // Is this backend connection alive?
45 livenessMutex sync.Mutex
46 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
49}
50
51// NewBackend creates a new instance of a Backend structure
52func NewBackend(ctx context.Context, storeType string, address string, timeout time.Duration, pathPrefix string) *Backend {
53 var err error
54
55 b := &Backend{
56 StoreType: storeType,
57 Address: address,
58 Timeout: timeout,
59 LivenessChannelInterval: DefaultLivenessChannelInterval,
60 PathPrefix: pathPrefix,
61 alive: false, // connection considered down at start
62 }
63
64 if b.Client, err = b.newClient(ctx, address, timeout); err != nil {
65 logger.Errorw(ctx, "failed-to-create-kv-client",
66 log.Fields{
67 "type": storeType, "address": address,
68 "timeout": timeout, "prefix": pathPrefix,
69 "error": err.Error(),
70 })
71 }
72
73 return b
74}
75
76func (b *Backend) newClient(ctx context.Context, address string, timeout time.Duration) (kvstore.Client, error) {
77 switch b.StoreType {
Joey Armstrong5f51f2e2023-01-17 17:06:26 -050078 case "redis":
79 return kvstore.NewRedisClient(address, timeout, false)
80 case "redis-sentinel":
81 return kvstore.NewRedisClient(address, timeout, true)
khenaidood948f772021-08-11 17:49:24 -040082 case "etcd":
83 return kvstore.NewEtcdClient(ctx, address, timeout, log.WarnLevel)
84 }
85 return nil, errors.New("unsupported-kv-store")
86}
87
88func (b *Backend) makePath(ctx context.Context, key string) string {
89 path := fmt.Sprintf("%s/%s", b.PathPrefix, key)
90 return path
91}
92
93func (b *Backend) updateLiveness(ctx context.Context, alive bool) {
94 // Periodically push stream of liveness data to the channel,
95 // so that in a live state, the core does not timeout and
96 // send a forced liveness message. Push alive state if the
97 // last push to channel was beyond livenessChannelInterval
98 b.livenessMutex.Lock()
99 defer b.livenessMutex.Unlock()
100 if b.liveness != nil {
101 if b.alive != alive {
102 logger.Debug(ctx, "update-liveness-channel-reason-change")
103 b.liveness <- alive
104 b.lastLivenessTime = time.Now()
105 } else if time.Since(b.lastLivenessTime) > b.LivenessChannelInterval {
106 logger.Debug(ctx, "update-liveness-channel-reason-interval")
107 b.liveness <- alive
108 b.lastLivenessTime = time.Now()
109 }
110 }
111
112 // Emit log message only for alive state change
113 if b.alive != alive {
114 logger.Debugw(ctx, "change-kvstore-alive-status", log.Fields{"alive": alive})
115 b.alive = alive
116 }
117}
118
119// Perform a dummy Key Lookup on kvstore to test Connection Liveness and
120// post on Liveness channel
121func (b *Backend) PerformLivenessCheck(ctx context.Context) bool {
122 alive := b.Client.IsConnectionUp(ctx)
123 logger.Debugw(ctx, "kvstore-liveness-check-result", log.Fields{"alive": alive})
124
125 b.updateLiveness(ctx, alive)
126 return alive
127}
128
129// Enable the liveness monitor channel. This channel will report
130// a "true" or "false" on every kvstore operation which indicates whether
131// or not the connection is still Live. This channel is then picked up
132// by the service (i.e. rw_core / ro_core) to update readiness status
133// and/or take other actions.
134func (b *Backend) EnableLivenessChannel(ctx context.Context) chan bool {
135 logger.Debug(ctx, "enable-kvstore-liveness-channel")
136 b.livenessMutex.Lock()
137 defer b.livenessMutex.Unlock()
138 if b.liveness == nil {
139 b.liveness = make(chan bool, 10)
140 b.liveness <- b.alive
141 b.lastLivenessTime = time.Now()
142 }
143
144 return b.liveness
145}
146
147// Extract Alive status of Kvstore based on type of error
148func (b *Backend) isErrorIndicatingAliveKvstore(ctx context.Context, err error) bool {
149 // Alive unless observed an error indicating so
150 alive := true
151
152 if err != nil {
153
154 // timeout indicates kvstore not reachable/alive
155 if err == context.DeadlineExceeded {
156 alive = false
157 }
158
159 // Need to analyze client-specific errors based on backend type
160 if b.StoreType == "etcd" {
161
162 // For etcd backend, consider not-alive only for errors indicating
163 // timedout request or unavailable/corrupted cluster. For all remaining
164 // error codes listed in https://godoc.org/google.golang.org/grpc/codes#Code,
165 // we would not infer a not-alive backend because such a error may also
166 // occur due to bad client requests or sequence of operations
167 switch status.Code(err) {
168 case codes.DeadlineExceeded:
169 fallthrough
170 case codes.Unavailable:
171 fallthrough
172 case codes.DataLoss:
173 alive = false
174 }
175 }
176 }
177
178 return alive
179}
180
181// List retrieves one or more items that match the specified key
182func (b *Backend) List(ctx context.Context, key string) (map[string]*kvstore.KVPair, error) {
Joey Armstrong5f51f2e2023-01-17 17:06:26 -0500183 span, ctx := log.CreateChildSpan(ctx, "kvs-list")
khenaidood948f772021-08-11 17:49:24 -0400184 defer span.Finish()
185
186 formattedPath := b.makePath(ctx, key)
187 logger.Debugw(ctx, "listing-key", log.Fields{"key": key, "path": formattedPath})
188
189 pair, err := b.Client.List(ctx, formattedPath)
190
191 b.updateLiveness(ctx, b.isErrorIndicatingAliveKvstore(ctx, err))
192
193 return pair, err
194}
195
196// Get retrieves an item that matches the specified key
197func (b *Backend) Get(ctx context.Context, key string) (*kvstore.KVPair, error) {
Joey Armstrong5f51f2e2023-01-17 17:06:26 -0500198 span, ctx := log.CreateChildSpan(ctx, "kvs-get")
khenaidood948f772021-08-11 17:49:24 -0400199 defer span.Finish()
200
201 formattedPath := b.makePath(ctx, key)
202 logger.Debugw(ctx, "getting-key", log.Fields{"key": key, "path": formattedPath})
203
204 pair, err := b.Client.Get(ctx, formattedPath)
205
206 b.updateLiveness(ctx, b.isErrorIndicatingAliveKvstore(ctx, err))
207
208 return pair, err
209}
210
211// Put stores an item value under the specifed key
212func (b *Backend) Put(ctx context.Context, key string, value interface{}) error {
Joey Armstrong5f51f2e2023-01-17 17:06:26 -0500213 span, ctx := log.CreateChildSpan(ctx, "kvs-put")
khenaidood948f772021-08-11 17:49:24 -0400214 defer span.Finish()
215
216 formattedPath := b.makePath(ctx, key)
217 logger.Debugw(ctx, "putting-key", log.Fields{"key": key, "path": formattedPath})
218
219 err := b.Client.Put(ctx, formattedPath, value)
220
221 b.updateLiveness(ctx, b.isErrorIndicatingAliveKvstore(ctx, err))
222
223 return err
224}
225
226// Delete removes an item under the specified key
227func (b *Backend) Delete(ctx context.Context, key string) error {
Joey Armstrong5f51f2e2023-01-17 17:06:26 -0500228 span, ctx := log.CreateChildSpan(ctx, "kvs-delete")
khenaidood948f772021-08-11 17:49:24 -0400229 defer span.Finish()
230
231 formattedPath := b.makePath(ctx, key)
232 logger.Debugw(ctx, "deleting-key", log.Fields{"key": key, "path": formattedPath})
233
234 err := b.Client.Delete(ctx, formattedPath)
235
236 b.updateLiveness(ctx, b.isErrorIndicatingAliveKvstore(ctx, err))
237
238 return err
239}
240
khenaidood948f772021-08-11 17:49:24 -0400241func (b *Backend) DeleteWithPrefix(ctx context.Context, prefixKey string) error {
Joey Armstrong5f51f2e2023-01-17 17:06:26 -0500242 span, ctx := log.CreateChildSpan(ctx, "kvs-delete-with-prefix")
khenaidood948f772021-08-11 17:49:24 -0400243 defer span.Finish()
244
245 formattedPath := b.makePath(ctx, prefixKey)
246 logger.Debugw(ctx, "deleting-prefix-key", log.Fields{"key": prefixKey, "path": formattedPath})
247
248 err := b.Client.DeleteWithPrefix(ctx, formattedPath)
249
250 b.updateLiveness(ctx, b.isErrorIndicatingAliveKvstore(ctx, err))
251
252 return err
253}
254
255// CreateWatch starts watching events for the specified key
256func (b *Backend) CreateWatch(ctx context.Context, key string, withPrefix bool) chan *kvstore.Event {
Joey Armstrong5f51f2e2023-01-17 17:06:26 -0500257 span, ctx := log.CreateChildSpan(ctx, "kvs-create-watch")
khenaidood948f772021-08-11 17:49:24 -0400258 defer span.Finish()
259
260 formattedPath := b.makePath(ctx, key)
261 logger.Debugw(ctx, "creating-key-watch", log.Fields{"key": key, "path": formattedPath})
262
263 return b.Client.Watch(ctx, formattedPath, withPrefix)
264}
265
266// DeleteWatch stops watching events for the specified key
267func (b *Backend) DeleteWatch(ctx context.Context, key string, ch chan *kvstore.Event) {
Joey Armstrong5f51f2e2023-01-17 17:06:26 -0500268 span, ctx := log.CreateChildSpan(ctx, "kvs-delete-watch")
khenaidood948f772021-08-11 17:49:24 -0400269 defer span.Finish()
270
271 formattedPath := b.makePath(ctx, key)
272 logger.Debugw(ctx, "deleting-key-watch", log.Fields{"key": key, "path": formattedPath})
273
274 b.Client.CloseWatch(ctx, formattedPath, ch)
275}