blob: 9bb49acfcbd3f420173815b363536c20f39c942c [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"
Stephane Barbariedc5022d2018-11-19 15:21:44 -050024 "sync"
Scott Baker973f8ba2019-11-19 15:00:22 -080025 "time"
serkant.uluderya2ae470f2020-01-21 11:13:09 -080026
27 "github.com/opencord/voltha-lib-go/v3/pkg/db/kvstore"
28 "github.com/opencord/voltha-lib-go/v3/pkg/log"
29 "google.golang.org/grpc/codes"
30 "google.golang.org/grpc/status"
Scott Baker973f8ba2019-11-19 15:00:22 -080031)
32
33const (
34 // Default Minimal Interval for posting alive state of backend kvstore on Liveness Channel
35 DefaultLivenessChannelInterval = time.Second * 30
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040036)
37
Stephane Barbariedc5022d2018-11-19 15:21:44 -050038// Backend structure holds details for accessing the kv store
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040039type Backend struct {
Stephane Barbariedc5022d2018-11-19 15:21:44 -050040 sync.RWMutex
Scott Baker973f8ba2019-11-19 15:00:22 -080041 Client kvstore.Client
42 StoreType string
43 Host string
44 Port int
45 Timeout int
46 PathPrefix string
47 alive bool // Is this backend connection alive?
48 liveness chan bool // channel to post alive state
49 LivenessChannelInterval time.Duration // regularly push alive state beyond this interval
50 lastLivenessTime time.Time // Instant of last alive state push
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040051}
52
Stephane Barbariedc5022d2018-11-19 15:21:44 -050053// NewBackend creates a new instance of a Backend structure
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040054func NewBackend(storeType string, host string, port int, timeout int, pathPrefix string) *Backend {
55 var err error
56
57 b := &Backend{
Scott Baker973f8ba2019-11-19 15:00:22 -080058 StoreType: storeType,
59 Host: host,
60 Port: port,
61 Timeout: timeout,
62 LivenessChannelInterval: DefaultLivenessChannelInterval,
63 PathPrefix: pathPrefix,
64 alive: false, // connection considered down at start
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040065 }
66
67 address := host + ":" + strconv.Itoa(port)
68 if b.Client, err = b.newClient(address, timeout); err != nil {
serkant.uluderya2ae470f2020-01-21 11:13:09 -080069 logger.Errorw("failed-to-create-kv-client",
Stephane Barbariee0a4c792019-01-16 11:26:29 -050070 log.Fields{
71 "type": storeType, "host": host, "port": port,
72 "timeout": timeout, "prefix": pathPrefix,
73 "error": err.Error(),
74 })
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040075 }
76
77 return b
78}
79
80func (b *Backend) newClient(address string, timeout int) (kvstore.Client, error) {
81 switch b.StoreType {
82 case "consul":
83 return kvstore.NewConsulClient(address, timeout)
84 case "etcd":
85 return kvstore.NewEtcdClient(address, timeout)
86 }
Stephane Barbariee0a4c792019-01-16 11:26:29 -050087 return nil, errors.New("unsupported-kv-store")
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040088}
89
90func (b *Backend) makePath(key string) string {
Stephane Barbarieec0919b2018-09-05 14:14:29 -040091 path := fmt.Sprintf("%s/%s", b.PathPrefix, key)
Stephane Barbarieec0919b2018-09-05 14:14:29 -040092 return path
93}
Stephane Barbariedc5022d2018-11-19 15:21:44 -050094
Scott Baker973f8ba2019-11-19 15:00:22 -080095func (b *Backend) updateLiveness(alive bool) {
96 // Periodically push stream of liveness data to the channel,
97 // so that in a live state, the core does not timeout and
98 // send a forced liveness message. Push alive state if the
99 // last push to channel was beyond livenessChannelInterval
100 if b.liveness != nil {
101
102 if b.alive != alive {
serkant.uluderya2ae470f2020-01-21 11:13:09 -0800103 logger.Debug("update-liveness-channel-reason-change")
Scott Baker973f8ba2019-11-19 15:00:22 -0800104 b.liveness <- alive
105 b.lastLivenessTime = time.Now()
106 } else if time.Now().Sub(b.lastLivenessTime) > b.LivenessChannelInterval {
serkant.uluderya2ae470f2020-01-21 11:13:09 -0800107 logger.Debug("update-liveness-channel-reason-interval")
Scott Baker973f8ba2019-11-19 15:00:22 -0800108 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 {
serkant.uluderya2ae470f2020-01-21 11:13:09 -0800115 logger.Debugw("change-kvstore-alive-status", log.Fields{"alive": alive})
Scott Baker973f8ba2019-11-19 15:00:22 -0800116 b.alive = alive
117 }
118}
119
120// Perform a dummy Key Lookup on kvstore to test Connection Liveness and
121// post on Liveness channel
npujar467fe752020-01-16 20:17:45 +0530122func (b *Backend) PerformLivenessCheck(ctx context.Context) bool {
123 alive := b.Client.IsConnectionUp(ctx)
serkant.uluderya2ae470f2020-01-21 11:13:09 -0800124 logger.Debugw("kvstore-liveness-check-result", log.Fields{"alive": alive})
Scott Baker973f8ba2019-11-19 15:00:22 -0800125
126 b.updateLiveness(alive)
127 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.
135func (b *Backend) EnableLivenessChannel() chan bool {
serkant.uluderya2ae470f2020-01-21 11:13:09 -0800136 logger.Debug("enable-kvstore-liveness-channel")
Scott Baker973f8ba2019-11-19 15:00:22 -0800137
138 if b.liveness == nil {
serkant.uluderya2ae470f2020-01-21 11:13:09 -0800139 logger.Debug("create-kvstore-liveness-channel")
Scott Baker973f8ba2019-11-19 15:00:22 -0800140
141 // Channel size of 10 to avoid any possibility of blocking in Load conditions
142 b.liveness = make(chan bool, 10)
143
144 // Post initial alive state
145 b.liveness <- b.alive
146 b.lastLivenessTime = time.Now()
147 }
148
149 return b.liveness
150}
151
152// Extract Alive status of Kvstore based on type of error
153func (b *Backend) isErrorIndicatingAliveKvstore(err error) bool {
154 // Alive unless observed an error indicating so
155 alive := true
156
157 if err != nil {
158
159 // timeout indicates kvstore not reachable/alive
160 if err == context.DeadlineExceeded {
161 alive = false
162 }
163
164 // Need to analyze client-specific errors based on backend type
165 if b.StoreType == "etcd" {
166
167 // For etcd backend, consider not-alive only for errors indicating
168 // timedout request or unavailable/corrupted cluster. For all remaining
169 // error codes listed in https://godoc.org/google.golang.org/grpc/codes#Code,
170 // we would not infer a not-alive backend because such a error may also
171 // occur due to bad client requests or sequence of operations
172 switch status.Code(err) {
173 case codes.DeadlineExceeded:
174 fallthrough
175 case codes.Unavailable:
176 fallthrough
177 case codes.DataLoss:
178 alive = false
179 }
180
181 //} else {
182 // TODO: Implement for consul backend; would it be needed ever?
183 }
184 }
185
186 return alive
187}
188
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500189// List retrieves one or more items that match the specified key
npujar467fe752020-01-16 20:17:45 +0530190func (b *Backend) List(ctx context.Context, key string) (map[string]*kvstore.KVPair, error) {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500191 b.Lock()
192 defer b.Unlock()
193
194 formattedPath := b.makePath(key)
serkant.uluderya2ae470f2020-01-21 11:13:09 -0800195 logger.Debugw("listing-key", log.Fields{"key": key, "path": formattedPath})
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500196
npujar467fe752020-01-16 20:17:45 +0530197 pair, err := b.Client.List(ctx, formattedPath)
Scott Baker973f8ba2019-11-19 15:00:22 -0800198
199 b.updateLiveness(b.isErrorIndicatingAliveKvstore(err))
200
201 return pair, err
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400202}
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500203
204// Get retrieves an item that matches the specified key
npujar467fe752020-01-16 20:17:45 +0530205func (b *Backend) Get(ctx context.Context, key string) (*kvstore.KVPair, error) {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500206 b.Lock()
207 defer b.Unlock()
208
Stephane Barbarie88fbe7f2018-09-25 12:25:23 -0400209 formattedPath := b.makePath(key)
serkant.uluderya2ae470f2020-01-21 11:13:09 -0800210 logger.Debugw("getting-key", log.Fields{"key": key, "path": formattedPath})
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500211
npujar467fe752020-01-16 20:17:45 +0530212 pair, err := b.Client.Get(ctx, formattedPath)
Scott Baker973f8ba2019-11-19 15:00:22 -0800213
214 b.updateLiveness(b.isErrorIndicatingAliveKvstore(err))
215
216 return pair, err
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400217}
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500218
219// Put stores an item value under the specifed key
npujar467fe752020-01-16 20:17:45 +0530220func (b *Backend) Put(ctx context.Context, key string, value interface{}) error {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500221 b.Lock()
222 defer b.Unlock()
223
Stephane Barbarie88fbe7f2018-09-25 12:25:23 -0400224 formattedPath := b.makePath(key)
serkant.uluderya2ae470f2020-01-21 11:13:09 -0800225 logger.Debugw("putting-key", log.Fields{"key": key, "value": string(value.([]byte)), "path": formattedPath})
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500226
npujar467fe752020-01-16 20:17:45 +0530227 err := b.Client.Put(ctx, formattedPath, value)
Scott Baker973f8ba2019-11-19 15:00:22 -0800228
229 b.updateLiveness(b.isErrorIndicatingAliveKvstore(err))
230
231 return err
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400232}
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500233
234// Delete removes an item under the specified key
npujar467fe752020-01-16 20:17:45 +0530235func (b *Backend) Delete(ctx context.Context, key string) error {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500236 b.Lock()
237 defer b.Unlock()
238
239 formattedPath := b.makePath(key)
serkant.uluderya2ae470f2020-01-21 11:13:09 -0800240 logger.Debugw("deleting-key", log.Fields{"key": key, "path": formattedPath})
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500241
npujar467fe752020-01-16 20:17:45 +0530242 err := b.Client.Delete(ctx, formattedPath)
Scott Baker973f8ba2019-11-19 15:00:22 -0800243
244 b.updateLiveness(b.isErrorIndicatingAliveKvstore(err))
245
246 return err
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400247}
Stephane Barbariee0a4c792019-01-16 11:26:29 -0500248
249// CreateWatch starts watching events for the specified key
npujar467fe752020-01-16 20:17:45 +0530250func (b *Backend) CreateWatch(ctx context.Context, key string) chan *kvstore.Event {
Stephane Barbariee0a4c792019-01-16 11:26:29 -0500251 b.Lock()
252 defer b.Unlock()
253
254 formattedPath := b.makePath(key)
serkant.uluderya2ae470f2020-01-21 11:13:09 -0800255 logger.Debugw("creating-key-watch", log.Fields{"key": key, "path": formattedPath})
Stephane Barbariee0a4c792019-01-16 11:26:29 -0500256
npujar467fe752020-01-16 20:17:45 +0530257 return b.Client.Watch(ctx, formattedPath)
Stephane Barbariee0a4c792019-01-16 11:26:29 -0500258}
259
260// DeleteWatch stops watching events for the specified key
261func (b *Backend) DeleteWatch(key string, ch chan *kvstore.Event) {
262 b.Lock()
263 defer b.Unlock()
264
265 formattedPath := b.makePath(key)
serkant.uluderya2ae470f2020-01-21 11:13:09 -0800266 logger.Debugw("deleting-key-watch", log.Fields{"key": key, "path": formattedPath})
Stephane Barbariee0a4c792019-01-16 11:26:29 -0500267
268 b.Client.CloseWatch(formattedPath, ch)
269}