blob: 20bacad0af82f94608d14fda100e8b335b5052c9 [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"
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040023 "strconv"
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
41 Host string
42 Port int
Neha Sharma7d6f3a92020-04-14 15:26:22 +000043 Timeout time.Duration
Scott Baker973f8ba2019-11-19 15:00:22 -080044 PathPrefix string
45 alive bool // Is this backend connection alive?
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
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
Neha Sharma7d6f3a92020-04-14 15:26:22 +000052func NewBackend(storeType string, host string, port int, 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,
57 Host: host,
58 Port: port,
59 Timeout: timeout,
60 LivenessChannelInterval: DefaultLivenessChannelInterval,
61 PathPrefix: pathPrefix,
62 alive: false, // connection considered down at start
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040063 }
64
65 address := host + ":" + strconv.Itoa(port)
66 if b.Client, err = b.newClient(address, timeout); err != nil {
serkant.uluderya2ae470f2020-01-21 11:13:09 -080067 logger.Errorw("failed-to-create-kv-client",
Stephane Barbariee0a4c792019-01-16 11:26:29 -050068 log.Fields{
69 "type": storeType, "host": host, "port": port,
70 "timeout": timeout, "prefix": pathPrefix,
71 "error": err.Error(),
72 })
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040073 }
74
75 return b
76}
77
Neha Sharma7d6f3a92020-04-14 15:26:22 +000078func (b *Backend) newClient(address string, timeout time.Duration) (kvstore.Client, error) {
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040079 switch b.StoreType {
80 case "consul":
81 return kvstore.NewConsulClient(address, timeout)
82 case "etcd":
Scott Baker504b4802020-04-17 10:12:20 -070083 return kvstore.NewEtcdClient(address, timeout, log.WarnLevel)
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040084 }
Stephane Barbariee0a4c792019-01-16 11:26:29 -050085 return nil, errors.New("unsupported-kv-store")
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040086}
87
88func (b *Backend) makePath(key string) string {
Stephane Barbarieec0919b2018-09-05 14:14:29 -040089 path := fmt.Sprintf("%s/%s", b.PathPrefix, key)
Stephane Barbarieec0919b2018-09-05 14:14:29 -040090 return path
91}
Stephane Barbariedc5022d2018-11-19 15:21:44 -050092
Scott Baker973f8ba2019-11-19 15:00:22 -080093func (b *Backend) updateLiveness(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 if b.liveness != nil {
99
100 if b.alive != alive {
serkant.uluderya2ae470f2020-01-21 11:13:09 -0800101 logger.Debug("update-liveness-channel-reason-change")
Scott Baker973f8ba2019-11-19 15:00:22 -0800102 b.liveness <- alive
103 b.lastLivenessTime = time.Now()
Rohan Agrawal7f72f0c2020-01-14 12:05:51 +0000104 } else if time.Since(b.lastLivenessTime) > b.LivenessChannelInterval {
serkant.uluderya2ae470f2020-01-21 11:13:09 -0800105 logger.Debug("update-liveness-channel-reason-interval")
Scott Baker973f8ba2019-11-19 15:00:22 -0800106 b.liveness <- alive
107 b.lastLivenessTime = time.Now()
108 }
109 }
110
111 // Emit log message only for alive state change
112 if b.alive != alive {
serkant.uluderya2ae470f2020-01-21 11:13:09 -0800113 logger.Debugw("change-kvstore-alive-status", log.Fields{"alive": alive})
Scott Baker973f8ba2019-11-19 15:00:22 -0800114 b.alive = alive
115 }
116}
117
118// Perform a dummy Key Lookup on kvstore to test Connection Liveness and
119// post on Liveness channel
npujar467fe752020-01-16 20:17:45 +0530120func (b *Backend) PerformLivenessCheck(ctx context.Context) bool {
121 alive := b.Client.IsConnectionUp(ctx)
serkant.uluderya2ae470f2020-01-21 11:13:09 -0800122 logger.Debugw("kvstore-liveness-check-result", log.Fields{"alive": alive})
Scott Baker973f8ba2019-11-19 15:00:22 -0800123
124 b.updateLiveness(alive)
125 return alive
126}
127
128// Enable the liveness monitor channel. This channel will report
129// a "true" or "false" on every kvstore operation which indicates whether
130// or not the connection is still Live. This channel is then picked up
131// by the service (i.e. rw_core / ro_core) to update readiness status
132// and/or take other actions.
133func (b *Backend) EnableLivenessChannel() chan bool {
serkant.uluderya2ae470f2020-01-21 11:13:09 -0800134 logger.Debug("enable-kvstore-liveness-channel")
Scott Baker973f8ba2019-11-19 15:00:22 -0800135
136 if b.liveness == nil {
serkant.uluderya2ae470f2020-01-21 11:13:09 -0800137 logger.Debug("create-kvstore-liveness-channel")
Scott Baker973f8ba2019-11-19 15:00:22 -0800138
139 // Channel size of 10 to avoid any possibility of blocking in Load conditions
140 b.liveness = make(chan bool, 10)
141
142 // Post initial alive state
143 b.liveness <- b.alive
144 b.lastLivenessTime = time.Now()
145 }
146
147 return b.liveness
148}
149
150// Extract Alive status of Kvstore based on type of error
151func (b *Backend) isErrorIndicatingAliveKvstore(err error) bool {
152 // Alive unless observed an error indicating so
153 alive := true
154
155 if err != nil {
156
157 // timeout indicates kvstore not reachable/alive
158 if err == context.DeadlineExceeded {
159 alive = false
160 }
161
162 // Need to analyze client-specific errors based on backend type
163 if b.StoreType == "etcd" {
164
165 // For etcd backend, consider not-alive only for errors indicating
166 // timedout request or unavailable/corrupted cluster. For all remaining
167 // error codes listed in https://godoc.org/google.golang.org/grpc/codes#Code,
168 // we would not infer a not-alive backend because such a error may also
169 // occur due to bad client requests or sequence of operations
170 switch status.Code(err) {
171 case codes.DeadlineExceeded:
172 fallthrough
173 case codes.Unavailable:
174 fallthrough
175 case codes.DataLoss:
176 alive = false
177 }
178
179 //} else {
180 // TODO: Implement for consul backend; would it be needed ever?
181 }
182 }
183
184 return alive
185}
186
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500187// List retrieves one or more items that match the specified key
npujar467fe752020-01-16 20:17:45 +0530188func (b *Backend) List(ctx context.Context, key string) (map[string]*kvstore.KVPair, error) {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500189 formattedPath := b.makePath(key)
serkant.uluderya2ae470f2020-01-21 11:13:09 -0800190 logger.Debugw("listing-key", log.Fields{"key": key, "path": formattedPath})
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500191
npujar467fe752020-01-16 20:17:45 +0530192 pair, err := b.Client.List(ctx, formattedPath)
Scott Baker973f8ba2019-11-19 15:00:22 -0800193
194 b.updateLiveness(b.isErrorIndicatingAliveKvstore(err))
195
196 return pair, err
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400197}
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500198
199// Get retrieves an item that matches the specified key
npujar467fe752020-01-16 20:17:45 +0530200func (b *Backend) Get(ctx context.Context, key string) (*kvstore.KVPair, error) {
Stephane Barbarie88fbe7f2018-09-25 12:25:23 -0400201 formattedPath := b.makePath(key)
serkant.uluderya2ae470f2020-01-21 11:13:09 -0800202 logger.Debugw("getting-key", log.Fields{"key": key, "path": formattedPath})
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500203
npujar467fe752020-01-16 20:17:45 +0530204 pair, err := b.Client.Get(ctx, formattedPath)
Scott Baker973f8ba2019-11-19 15:00:22 -0800205
206 b.updateLiveness(b.isErrorIndicatingAliveKvstore(err))
207
208 return pair, err
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400209}
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500210
211// Put stores an item value under the specifed key
npujar467fe752020-01-16 20:17:45 +0530212func (b *Backend) Put(ctx context.Context, key string, value interface{}) error {
Stephane Barbarie88fbe7f2018-09-25 12:25:23 -0400213 formattedPath := b.makePath(key)
Scott Baker504b4802020-04-17 10:12:20 -0700214 logger.Debugw("putting-key", log.Fields{"key": key, "path": formattedPath})
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500215
npujar467fe752020-01-16 20:17:45 +0530216 err := b.Client.Put(ctx, formattedPath, value)
Scott Baker973f8ba2019-11-19 15:00:22 -0800217
218 b.updateLiveness(b.isErrorIndicatingAliveKvstore(err))
219
220 return err
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400221}
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500222
223// Delete removes an item under the specified key
npujar467fe752020-01-16 20:17:45 +0530224func (b *Backend) Delete(ctx context.Context, key string) error {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500225 formattedPath := b.makePath(key)
serkant.uluderya2ae470f2020-01-21 11:13:09 -0800226 logger.Debugw("deleting-key", log.Fields{"key": key, "path": formattedPath})
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500227
npujar467fe752020-01-16 20:17:45 +0530228 err := b.Client.Delete(ctx, formattedPath)
Scott Baker973f8ba2019-11-19 15:00:22 -0800229
230 b.updateLiveness(b.isErrorIndicatingAliveKvstore(err))
231
232 return err
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400233}
Stephane Barbariee0a4c792019-01-16 11:26:29 -0500234
235// CreateWatch starts watching events for the specified key
Scott Baker0e78ba22020-02-24 17:58:47 -0800236func (b *Backend) CreateWatch(ctx context.Context, key string, withPrefix bool) chan *kvstore.Event {
Stephane Barbariee0a4c792019-01-16 11:26:29 -0500237 formattedPath := b.makePath(key)
serkant.uluderya2ae470f2020-01-21 11:13:09 -0800238 logger.Debugw("creating-key-watch", log.Fields{"key": key, "path": formattedPath})
Stephane Barbariee0a4c792019-01-16 11:26:29 -0500239
Scott Baker0e78ba22020-02-24 17:58:47 -0800240 return b.Client.Watch(ctx, formattedPath, withPrefix)
Stephane Barbariee0a4c792019-01-16 11:26:29 -0500241}
242
243// DeleteWatch stops watching events for the specified key
244func (b *Backend) DeleteWatch(key string, ch chan *kvstore.Event) {
Stephane Barbariee0a4c792019-01-16 11:26:29 -0500245 formattedPath := b.makePath(key)
serkant.uluderya2ae470f2020-01-21 11:13:09 -0800246 logger.Debugw("deleting-key-watch", log.Fields{"key": key, "path": formattedPath})
Stephane Barbariee0a4c792019-01-16 11:26:29 -0500247
248 b.Client.CloseWatch(formattedPath, ch)
249}