blob: b881351dcee6f54597580b3d592e5dfdf3299a39 [file] [log] [blame]
khenaidoofffcc8a2019-03-13 11:54:41 -04001/*
2 * Copyright 2019-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 */
16package core
17
18import (
19 "context"
20 "fmt"
21 "github.com/opencord/voltha-go/common/log"
22 "github.com/opencord/voltha-go/db/kvstore"
23 "google.golang.org/grpc/codes"
24 "google.golang.org/grpc/status"
25 "sync"
26 "time"
27)
28
29type ownership struct {
30 id string
31 owned bool
32 chnl chan int
33}
34
35type DeviceOwnership struct {
36 instanceId string
37 exitChannel chan int
38 kvClient kvstore.Client
39 reservationTimeout int64 // Duration in seconds
40 ownershipPrefix string
41 deviceMap map[string]*ownership
42 deviceMapLock *sync.RWMutex
43}
44
45func NewDeviceOwnership(id string, kvClient kvstore.Client, ownershipPrefix string, reservationTimeout int64) *DeviceOwnership {
46 var deviceOwnership DeviceOwnership
47 deviceOwnership.instanceId = id
48 deviceOwnership.exitChannel = make(chan int, 1)
49 deviceOwnership.kvClient = kvClient
50 deviceOwnership.ownershipPrefix = ownershipPrefix
51 deviceOwnership.reservationTimeout = reservationTimeout
52 deviceOwnership.deviceMap = make(map[string]*ownership)
53 deviceOwnership.deviceMapLock = &sync.RWMutex{}
54 return &deviceOwnership
55}
56
57func (da *DeviceOwnership) Start(ctx context.Context) {
58 log.Info("starting-deviceOwnership", log.Fields{"instanceId": da.instanceId})
59 log.Info("deviceOwnership-started")
60}
61
62func (da *DeviceOwnership) Stop(ctx context.Context) {
63 log.Info("stopping-deviceOwnership")
64 da.exitChannel <- 1
65 // Need to flush all device reservations
66 log.Info("deviceOwnership-stopped")
67}
68
69func (da *DeviceOwnership) tryToReserveKey(id string) bool {
70 var currOwner string
71 // Try to reserve the key
72 kvKey := fmt.Sprintf("%s_%s", da.ownershipPrefix, id)
73 value, err := da.kvClient.Reserve(kvKey, da.instanceId, da.reservationTimeout)
74 if value != nil {
75 if currOwner, err = kvstore.ToString(value); err != nil {
76 log.Error("unexpected-owner-type")
77 }
78 return currOwner == da.instanceId
79 }
80 return false
81}
82
83func (da *DeviceOwnership) startOwnershipMonitoring(id string, chnl chan int) {
84startloop:
85 for {
86 if err := da.setOwnership(id, da.tryToReserveKey(id)); err != nil {
87 log.Errorw("unexpected-error", log.Fields{"error": err})
88 }
89 select {
90 case <-da.exitChannel:
91 log.Infow("closing-monitoring", log.Fields{"Id": id})
92 break startloop
93 case <-time.After(time.Duration(da.reservationTimeout) / 3 * time.Second):
94 log.Infow("renew-reservation", log.Fields{"Id": id})
95 case <-chnl:
96 log.Infow("closing-device-monitoring", log.Fields{"Id": id})
97 break startloop
98 }
99 }
100}
101
102func (da *DeviceOwnership) getOwnership(id string) bool {
103 da.deviceMapLock.RLock()
104 defer da.deviceMapLock.RUnlock()
105 if val, exist := da.deviceMap[id]; exist {
106 return val.owned
107 }
108 log.Debugw("setting-up-new-ownership", log.Fields{"Id": id})
109 // Not owned by me or maybe anybody else. Try to reserve it
110 reservedByMe := da.tryToReserveKey(id)
111 myChnl := make(chan int)
112 da.deviceMap[id] = &ownership{id: id, owned: reservedByMe, chnl: myChnl}
113 go da.startOwnershipMonitoring(id, myChnl)
114 return reservedByMe
115}
116
117func (da *DeviceOwnership) setOwnership(id string, owner bool) error {
118 da.deviceMapLock.Lock()
119 defer da.deviceMapLock.Unlock()
120 if _, exist := da.deviceMap[id]; exist {
121 if da.deviceMap[id].owned != owner {
122 log.Debugw("ownership-changed", log.Fields{"Id": id, "owner": owner})
123 }
124 da.deviceMap[id].owned = owner
125 return nil
126 }
127 return status.Error(codes.NotFound, fmt.Sprintf("id-inexistent-%s", id))
128}
129
130// OwnedByMe returns where this Core instance active owns this device. This function will automatically
131// trigger the process to monitor the device and update the device ownership regularly.
132func (da *DeviceOwnership) OwnedByMe(id string) bool {
133 return da.getOwnership(id)
134}
135
136//AbandonDevice must be invoked whenever a device is deleted from the Core
137func (da *DeviceOwnership) AbandonDevice(id string) error {
138 da.deviceMapLock.Lock()
139 defer da.deviceMapLock.Unlock()
140 if o, exist := da.deviceMap[id]; exist {
141 // Stop the Go routine monitoring the device
142 close(o.chnl)
143 delete(da.deviceMap, id)
144 log.Debugw("abandoning-device", log.Fields{"Id": id})
145 return nil
146 }
147 return status.Error(codes.NotFound, fmt.Sprintf("id-inexistent-%s", id))
148}