blob: efc0953dd16eabfdd01228523b4d3b931e884387 [file] [log] [blame]
khenaidoobf6e7bb2018-08-14 22:27:29 -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 */
Stephane Barbariedc5022d2018-11-19 15:21:44 -050016
sbarbari17d7e222019-11-05 10:02:29 -050017package db
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040018
19import (
Scott Baker973f8ba2019-11-19 15:00:22 -080020 "context"
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040021 "errors"
22 "fmt"
Girish Kumarf8d4f8d2020-08-18 11:45:30 +000023 "sync"
Scott Baker973f8ba2019-11-19 15:00:22 -080024 "time"
serkant.uluderya2ae470f2020-01-21 11:13:09 -080025
26 "github.com/opencord/voltha-lib-go/v3/pkg/db/kvstore"
27 "github.com/opencord/voltha-lib-go/v3/pkg/log"
28 "google.golang.org/grpc/codes"
29 "google.golang.org/grpc/status"
Scott Baker973f8ba2019-11-19 15:00:22 -080030)
31
32const (
33 // Default Minimal Interval for posting alive state of backend kvstore on Liveness Channel
34 DefaultLivenessChannelInterval = time.Second * 30
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040035)
36
Stephane Barbariedc5022d2018-11-19 15:21:44 -050037// Backend structure holds details for accessing the kv store
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040038type Backend struct {
Scott Baker973f8ba2019-11-19 15:00:22 -080039 Client kvstore.Client
40 StoreType string
Neha Sharma7d6f3a92020-04-14 15:26:22 +000041 Timeout time.Duration
Neha Sharmad1387da2020-05-07 20:07:28 +000042 Address string
Scott Baker973f8ba2019-11-19 15:00:22 -080043 PathPrefix string
Girish Kumarf8d4f8d2020-08-18 11:45:30 +000044 alive bool // Is this backend connection alive?
45 livenessMutex sync.Mutex
Scott Baker973f8ba2019-11-19 15:00:22 -080046 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
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040049}
50
Stephane Barbariedc5022d2018-11-19 15:21:44 -050051// NewBackend creates a new instance of a Backend structure
Rohan Agrawal31f21802020-06-12 05:38:46 +000052func NewBackend(ctx context.Context, storeType string, address string, timeout time.Duration, pathPrefix string) *Backend {
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040053 var err error
54
55 b := &Backend{
Scott Baker973f8ba2019-11-19 15:00:22 -080056 StoreType: storeType,
Neha Sharmad1387da2020-05-07 20:07:28 +000057 Address: address,
Scott Baker973f8ba2019-11-19 15:00:22 -080058 Timeout: timeout,
59 LivenessChannelInterval: DefaultLivenessChannelInterval,
60 PathPrefix: pathPrefix,
61 alive: false, // connection considered down at start
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040062 }
63
Rohan Agrawal31f21802020-06-12 05:38:46 +000064 if b.Client, err = b.newClient(ctx, address, timeout); err != nil {
65 logger.Errorw(ctx, "failed-to-create-kv-client",
Stephane Barbariee0a4c792019-01-16 11:26:29 -050066 log.Fields{
Neha Sharmad1387da2020-05-07 20:07:28 +000067 "type": storeType, "address": address,
Stephane Barbariee0a4c792019-01-16 11:26:29 -050068 "timeout": timeout, "prefix": pathPrefix,
69 "error": err.Error(),
70 })
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040071 }
72
73 return b
74}
75
Rohan Agrawal31f21802020-06-12 05:38:46 +000076func (b *Backend) newClient(ctx context.Context, address string, timeout time.Duration) (kvstore.Client, error) {
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040077 switch b.StoreType {
78 case "consul":
Rohan Agrawal31f21802020-06-12 05:38:46 +000079 return kvstore.NewConsulClient(ctx, address, timeout)
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040080 case "etcd":
Rohan Agrawal31f21802020-06-12 05:38:46 +000081 return kvstore.NewEtcdClient(ctx, address, timeout, log.WarnLevel)
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040082 }
Stephane Barbariee0a4c792019-01-16 11:26:29 -050083 return nil, errors.New("unsupported-kv-store")
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040084}
85
Rohan Agrawal31f21802020-06-12 05:38:46 +000086func (b *Backend) makePath(ctx context.Context, key string) string {
Stephane Barbarieec0919b2018-09-05 14:14:29 -040087 path := fmt.Sprintf("%s/%s", b.PathPrefix, key)
Stephane Barbarieec0919b2018-09-05 14:14:29 -040088 return path
89}
Stephane Barbariedc5022d2018-11-19 15:21:44 -050090
Rohan Agrawal31f21802020-06-12 05:38:46 +000091func (b *Backend) updateLiveness(ctx context.Context, alive bool) {
Scott Baker973f8ba2019-11-19 15:00:22 -080092 // Periodically push stream of liveness data to the channel,
93 // so that in a live state, the core does not timeout and
94 // send a forced liveness message. Push alive state if the
95 // last push to channel was beyond livenessChannelInterval
Girish Kumarf8d4f8d2020-08-18 11:45:30 +000096 b.livenessMutex.Lock()
97 defer b.livenessMutex.Unlock()
Scott Baker973f8ba2019-11-19 15:00:22 -080098 if b.liveness != nil {
Scott Baker973f8ba2019-11-19 15:00:22 -080099 if b.alive != alive {
Rohan Agrawal31f21802020-06-12 05:38:46 +0000100 logger.Debug(ctx, "update-liveness-channel-reason-change")
Scott Baker973f8ba2019-11-19 15:00:22 -0800101 b.liveness <- alive
102 b.lastLivenessTime = time.Now()
Rohan Agrawal7f72f0c2020-01-14 12:05:51 +0000103 } else if time.Since(b.lastLivenessTime) > b.LivenessChannelInterval {
Rohan Agrawal31f21802020-06-12 05:38:46 +0000104 logger.Debug(ctx, "update-liveness-channel-reason-interval")
Scott Baker973f8ba2019-11-19 15:00:22 -0800105 b.liveness <- alive
106 b.lastLivenessTime = time.Now()
107 }
108 }
109
110 // Emit log message only for alive state change
111 if b.alive != alive {
Rohan Agrawal31f21802020-06-12 05:38:46 +0000112 logger.Debugw(ctx, "change-kvstore-alive-status", log.Fields{"alive": alive})
Scott Baker973f8ba2019-11-19 15:00:22 -0800113 b.alive = alive
114 }
115}
116
117// Perform a dummy Key Lookup on kvstore to test Connection Liveness and
118// post on Liveness channel
npujar467fe752020-01-16 20:17:45 +0530119func (b *Backend) PerformLivenessCheck(ctx context.Context) bool {
120 alive := b.Client.IsConnectionUp(ctx)
Rohan Agrawal31f21802020-06-12 05:38:46 +0000121 logger.Debugw(ctx, "kvstore-liveness-check-result", log.Fields{"alive": alive})
Scott Baker973f8ba2019-11-19 15:00:22 -0800122
Rohan Agrawal31f21802020-06-12 05:38:46 +0000123 b.updateLiveness(ctx, alive)
Scott Baker973f8ba2019-11-19 15:00:22 -0800124 return alive
125}
126
127// Enable the liveness monitor channel. This channel will report
128// a "true" or "false" on every kvstore operation which indicates whether
129// or not the connection is still Live. This channel is then picked up
130// by the service (i.e. rw_core / ro_core) to update readiness status
131// and/or take other actions.
Rohan Agrawal31f21802020-06-12 05:38:46 +0000132func (b *Backend) EnableLivenessChannel(ctx context.Context) chan bool {
133 logger.Debug(ctx, "enable-kvstore-liveness-channel")
Girish Kumarf8d4f8d2020-08-18 11:45:30 +0000134 b.livenessMutex.Lock()
135 defer b.livenessMutex.Unlock()
Scott Baker973f8ba2019-11-19 15:00:22 -0800136 if b.liveness == nil {
Scott Baker973f8ba2019-11-19 15:00:22 -0800137 b.liveness = make(chan bool, 10)
Scott Baker973f8ba2019-11-19 15:00:22 -0800138 b.liveness <- b.alive
139 b.lastLivenessTime = time.Now()
140 }
141
142 return b.liveness
143}
144
145// Extract Alive status of Kvstore based on type of error
Rohan Agrawal31f21802020-06-12 05:38:46 +0000146func (b *Backend) isErrorIndicatingAliveKvstore(ctx context.Context, err error) bool {
Scott Baker973f8ba2019-11-19 15:00:22 -0800147 // Alive unless observed an error indicating so
148 alive := true
149
150 if err != nil {
151
152 // timeout indicates kvstore not reachable/alive
153 if err == context.DeadlineExceeded {
154 alive = false
155 }
156
157 // Need to analyze client-specific errors based on backend type
158 if b.StoreType == "etcd" {
159
160 // For etcd backend, consider not-alive only for errors indicating
161 // timedout request or unavailable/corrupted cluster. For all remaining
162 // error codes listed in https://godoc.org/google.golang.org/grpc/codes#Code,
163 // we would not infer a not-alive backend because such a error may also
164 // occur due to bad client requests or sequence of operations
165 switch status.Code(err) {
166 case codes.DeadlineExceeded:
167 fallthrough
168 case codes.Unavailable:
169 fallthrough
170 case codes.DataLoss:
171 alive = false
172 }
173
174 //} else {
175 // TODO: Implement for consul backend; would it be needed ever?
176 }
177 }
178
179 return alive
180}
181
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500182// List retrieves one or more items that match the specified key
npujar467fe752020-01-16 20:17:45 +0530183func (b *Backend) List(ctx context.Context, key string) (map[string]*kvstore.KVPair, error) {
Girish Kumarf8d4f8d2020-08-18 11:45:30 +0000184 span, ctx := log.CreateChildSpan(ctx, "etcd-list")
185 defer span.Finish()
186
Rohan Agrawal31f21802020-06-12 05:38:46 +0000187 formattedPath := b.makePath(ctx, key)
188 logger.Debugw(ctx, "listing-key", log.Fields{"key": key, "path": formattedPath})
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500189
npujar467fe752020-01-16 20:17:45 +0530190 pair, err := b.Client.List(ctx, formattedPath)
Scott Baker973f8ba2019-11-19 15:00:22 -0800191
Rohan Agrawal31f21802020-06-12 05:38:46 +0000192 b.updateLiveness(ctx, b.isErrorIndicatingAliveKvstore(ctx, err))
Scott Baker973f8ba2019-11-19 15:00:22 -0800193
194 return pair, err
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400195}
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500196
197// Get retrieves an item that matches the specified key
npujar467fe752020-01-16 20:17:45 +0530198func (b *Backend) Get(ctx context.Context, key string) (*kvstore.KVPair, error) {
Girish Kumarf8d4f8d2020-08-18 11:45:30 +0000199 span, ctx := log.CreateChildSpan(ctx, "etcd-get")
200 defer span.Finish()
201
Rohan Agrawal31f21802020-06-12 05:38:46 +0000202 formattedPath := b.makePath(ctx, key)
203 logger.Debugw(ctx, "getting-key", log.Fields{"key": key, "path": formattedPath})
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500204
npujar467fe752020-01-16 20:17:45 +0530205 pair, err := b.Client.Get(ctx, formattedPath)
Scott Baker973f8ba2019-11-19 15:00:22 -0800206
Rohan Agrawal31f21802020-06-12 05:38:46 +0000207 b.updateLiveness(ctx, b.isErrorIndicatingAliveKvstore(ctx, err))
Scott Baker973f8ba2019-11-19 15:00:22 -0800208
209 return pair, err
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400210}
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500211
212// Put stores an item value under the specifed key
npujar467fe752020-01-16 20:17:45 +0530213func (b *Backend) Put(ctx context.Context, key string, value interface{}) error {
Girish Kumarf8d4f8d2020-08-18 11:45:30 +0000214 span, ctx := log.CreateChildSpan(ctx, "etcd-put")
215 defer span.Finish()
216
Rohan Agrawal31f21802020-06-12 05:38:46 +0000217 formattedPath := b.makePath(ctx, key)
218 logger.Debugw(ctx, "putting-key", log.Fields{"key": key, "path": formattedPath})
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500219
npujar467fe752020-01-16 20:17:45 +0530220 err := b.Client.Put(ctx, formattedPath, value)
Scott Baker973f8ba2019-11-19 15:00:22 -0800221
Rohan Agrawal31f21802020-06-12 05:38:46 +0000222 b.updateLiveness(ctx, b.isErrorIndicatingAliveKvstore(ctx, err))
Scott Baker973f8ba2019-11-19 15:00:22 -0800223
224 return err
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400225}
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500226
227// Delete removes an item under the specified key
npujar467fe752020-01-16 20:17:45 +0530228func (b *Backend) Delete(ctx context.Context, key string) error {
Girish Kumarf8d4f8d2020-08-18 11:45:30 +0000229 span, ctx := log.CreateChildSpan(ctx, "etcd-delete")
230 defer span.Finish()
231
Rohan Agrawal31f21802020-06-12 05:38:46 +0000232 formattedPath := b.makePath(ctx, key)
233 logger.Debugw(ctx, "deleting-key", log.Fields{"key": key, "path": formattedPath})
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500234
npujar467fe752020-01-16 20:17:45 +0530235 err := b.Client.Delete(ctx, formattedPath)
Scott Baker973f8ba2019-11-19 15:00:22 -0800236
Rohan Agrawal31f21802020-06-12 05:38:46 +0000237 b.updateLiveness(ctx, b.isErrorIndicatingAliveKvstore(ctx, err))
Scott Baker973f8ba2019-11-19 15:00:22 -0800238
239 return err
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400240}
Stephane Barbariee0a4c792019-01-16 11:26:29 -0500241
242// CreateWatch starts watching events for the specified key
Scott Baker0e78ba22020-02-24 17:58:47 -0800243func (b *Backend) CreateWatch(ctx context.Context, key string, withPrefix bool) chan *kvstore.Event {
Girish Kumarf8d4f8d2020-08-18 11:45:30 +0000244 span, ctx := log.CreateChildSpan(ctx, "etcd-create-watch")
245 defer span.Finish()
246
Rohan Agrawal31f21802020-06-12 05:38:46 +0000247 formattedPath := b.makePath(ctx, key)
248 logger.Debugw(ctx, "creating-key-watch", log.Fields{"key": key, "path": formattedPath})
Stephane Barbariee0a4c792019-01-16 11:26:29 -0500249
Scott Baker0e78ba22020-02-24 17:58:47 -0800250 return b.Client.Watch(ctx, formattedPath, withPrefix)
Stephane Barbariee0a4c792019-01-16 11:26:29 -0500251}
252
253// DeleteWatch stops watching events for the specified key
Rohan Agrawal31f21802020-06-12 05:38:46 +0000254func (b *Backend) DeleteWatch(ctx context.Context, key string, ch chan *kvstore.Event) {
Girish Kumarf8d4f8d2020-08-18 11:45:30 +0000255 span, ctx := log.CreateChildSpan(ctx, "etcd-delete-watch")
256 defer span.Finish()
257
Rohan Agrawal31f21802020-06-12 05:38:46 +0000258 formattedPath := b.makePath(ctx, key)
259 logger.Debugw(ctx, "deleting-key-watch", log.Fields{"key": key, "path": formattedPath})
Stephane Barbariee0a4c792019-01-16 11:26:29 -0500260
Rohan Agrawal31f21802020-06-12 05:38:46 +0000261 b.Client.CloseWatch(ctx, formattedPath, ch)
Stephane Barbariee0a4c792019-01-16 11:26:29 -0500262}