VOL-2017 library code was moved to voltha-lib-go
Change-Id: Ia4b27695cf7ec3377a64572972998d52b70a0a5f
diff --git a/vendor/github.com/boljen/go-bitmap/LICENSE b/vendor/github.com/boljen/go-bitmap/LICENSE
deleted file mode 100644
index 13cc28c..0000000
--- a/vendor/github.com/boljen/go-bitmap/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-The MIT License (MIT)
-
-Copyright (c) 2015 Bol Christophe
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/vendor/github.com/boljen/go-bitmap/README.md b/vendor/github.com/boljen/go-bitmap/README.md
deleted file mode 100644
index 5ff5eba..0000000
--- a/vendor/github.com/boljen/go-bitmap/README.md
+++ /dev/null
@@ -1,30 +0,0 @@
-# Bitmap (Go)
-
-Package bitmap implements (thread-safe) bitmap functions and abstractions
-
-## Install
-
- go get github.com/boljen/go-bitmap
-
-## Documentation
-
-See [godoc](https://godoc.org/github.com/boljen/go-bitmap)
-
-## Example
-
- package main
-
- import (
- "fmt"
- "github.com/boljen/go-bitmap"
- )
-
- func main() {
- bm := bitmap.New(100)
- bm.Set(0, true)
- fmt.Println(bm.Get(0))
- }
-
-## License
-
-MIT
diff --git a/vendor/github.com/boljen/go-bitmap/atomic.go b/vendor/github.com/boljen/go-bitmap/atomic.go
deleted file mode 100644
index f04d76e..0000000
--- a/vendor/github.com/boljen/go-bitmap/atomic.go
+++ /dev/null
@@ -1,64 +0,0 @@
-package bitmap
-
-import (
- "sync/atomic"
- "unsafe"
-)
-
-var oobPanic = "SetAtomic not allowed on a bitmapSlice of cap() < 4"
-
-// SetAtomic is similar to Set except that it performs the operation atomically.
-func SetAtomic(bitmap []byte, targetBit int, targetValue bool) {
- ov := (*[1]uint32)(unsafe.Pointer(&bitmap[targetBit/32]))[:]
- SetAtomicUint32(ov, targetBit%32, targetValue)
-}
-
-// SetAtomic is similar to Set except that it performs the operation atomically.
-// It needs a bitmapSlice where the capacity is at least 4 bytes.
-func _SetAtomic(bitmapSlice []byte, targetBit int, targetValue bool) {
- targetByteIndex := targetBit / 8
- targetBitIndex := targetBit % 8
- targetOffset := 0
-
- // SetAtomic needs to modify 4 bytes of data so we panic when the slice
- // doesn't have a capacity of at least 4 bytes.
- if cap(bitmapSlice) < 4 {
- panic(oobPanic)
- }
-
- // Calculate the Offset of the targetByte inside the 4-byte atomic batch.
- // This is needed to ensure that atomic operations can happen as long as
- // the bitmapSlice equals 4 bytes or more.
- if cap(bitmapSlice) < targetByteIndex+3 {
- targetOffset = cap(bitmapSlice) - targetByteIndex
- }
-
- // This gets a pointer to the memory of 4 bytes inside the bitmapSlice.
- // It stores this pointer as an *uint32 so that it can be used to
- // execute sync.atomic operations.
- targetBytePointer := (*uint32)(unsafe.Pointer(&bitmapSlice[targetByteIndex-targetOffset]))
-
- for {
- // localValue is a copy of the uint32 value at *targetBytePointer.
- // It's used to check whether the targetBit must be updated,
- // and if so, to construct the new value for targetBytePointer.
- localValue := atomic.LoadUint32(targetBytePointer)
-
- // This "neutralizes" the uint32 conversion by getting a pointer to the
- // 4-byte array stored undereneath the uint32.
- targetByteCopyPointer := (*[4]byte)(unsafe.Pointer(&localValue))
-
- // Work is done when targetBit is already set to targetValue.
- if GetBit(targetByteCopyPointer[targetOffset], targetBitIndex) == targetValue {
- return
- }
-
- // Modify the targetBit and update memory so that the targetBit is the only bit
- // that has been modified in the batch.
- referenceValue := localValue
- SetBitRef(&targetByteCopyPointer[targetOffset], targetBitIndex, targetValue)
- if atomic.CompareAndSwapUint32(targetBytePointer, referenceValue, localValue) {
- break
- }
- }
-}
diff --git a/vendor/github.com/boljen/go-bitmap/bitmap.go b/vendor/github.com/boljen/go-bitmap/bitmap.go
deleted file mode 100644
index dfe5cc2..0000000
--- a/vendor/github.com/boljen/go-bitmap/bitmap.go
+++ /dev/null
@@ -1,200 +0,0 @@
-// Package bitmap implements (thread-safe) bitmap functions and abstractions.
-//
-// Installation
-//
-// go get github.com/boljen/go-bitmap
-package bitmap
-
-import "sync"
-
-var (
- tA = [8]byte{1, 2, 4, 8, 16, 32, 64, 128}
- tB = [8]byte{254, 253, 251, 247, 239, 223, 191, 127}
-)
-
-func dataOrCopy(d []byte, c bool) []byte {
- if !c {
- return d
- }
- ndata := make([]byte, len(d))
- copy(ndata, d)
- return ndata
-}
-
-// NewSlice creates a new byteslice with length l (in bits).
-// The actual size in bits might be up to 7 bits larger because
-// they are stored in a byteslice.
-func NewSlice(l int) []byte {
- remainder := l % 8
- if remainder != 0 {
- remainder = 1
- }
- return make([]byte, l/8+remainder)
-}
-
-// Get returns the value of bit i from map m.
-// It doesn't check the bounds of the slice.
-func Get(m []byte, i int) bool {
- return m[i/8]&tA[i%8] != 0
-}
-
-// Set sets bit i of map m to value v.
-// It doesn't check the bounds of the slice.
-func Set(m []byte, i int, v bool) {
- index := i / 8
- bit := i % 8
- if v {
- m[index] = m[index] | tA[bit]
- } else {
- m[index] = m[index] & tB[bit]
- }
-}
-
-// GetBit returns the value of bit i of byte b.
-// The bit index must be between 0 and 7.
-func GetBit(b byte, i int) bool {
- return b&tA[i] != 0
-}
-
-// SetBit sets bit i of byte b to value v.
-// The bit index must be between 0 and 7.
-func SetBit(b byte, i int, v bool) byte {
- if v {
- return b | tA[i]
- }
- return b & tB[i]
-}
-
-// SetBitRef sets bit i of byte *b to value v.
-func SetBitRef(b *byte, i int, v bool) {
- if v {
- *b = *b | tA[i]
- } else {
- *b = *b & tB[i]
- }
-}
-
-// Len returns the length (in bits) of the provided byteslice.
-// It will always be a multipile of 8 bits.
-func Len(m []byte) int {
- return len(m) * 8
-}
-
-// Bitmap is a byteslice with bitmap functions.
-// Creating one form existing data is as simple as bitmap := Bitmap(data).
-type Bitmap []byte
-
-// New creates a new Bitmap instance with length l (in bits).
-func New(l int) Bitmap {
- return NewSlice(l)
-}
-
-// Len wraps around the Len function.
-func (b Bitmap) Len() int {
- return Len(b)
-}
-
-// Get wraps around the Get function.
-func (b Bitmap) Get(i int) bool {
- return Get(b, i)
-}
-
-// Set wraps around the Set function.
-func (b Bitmap) Set(i int, v bool) {
- Set(b, i, v)
-}
-
-// Data returns the data of the bitmap.
-// If copy is false the actual underlying slice will be returned.
-func (b Bitmap) Data(copy bool) []byte {
- return dataOrCopy(b, copy)
-}
-
-// Threadsafe implements thread-safe read- and write locking for the bitmap.
-type Threadsafe struct {
- bm Bitmap
- mu sync.RWMutex
-}
-
-// TSFromData creates a new Threadsafe using the provided data.
-// If copy is true the actual slice will be used.
-func TSFromData(data []byte, copy bool) *Threadsafe {
- return &Threadsafe{
- bm: Bitmap(dataOrCopy(data, copy)),
- }
-}
-
-// NewTS creates a new Threadsafe instance.
-func NewTS(length int) *Threadsafe {
- return &Threadsafe{
- bm: New(length),
- }
-}
-
-// Data returns the data of the bitmap.
-// If copy is false the actual underlying slice will be returned.
-func (b *Threadsafe) Data(copy bool) []byte {
- b.mu.RLock()
- data := dataOrCopy(b.bm, copy)
- b.mu.RUnlock()
- return data
-}
-
-// Len wraps around the Len function.
-func (b Threadsafe) Len() int {
- b.mu.RLock()
- l := b.bm.Len()
- b.mu.RUnlock()
- return l
-}
-
-// Get wraps around the Get function.
-func (b Threadsafe) Get(i int) bool {
- b.mu.RLock()
- v := b.bm.Get(i)
- b.mu.RUnlock()
- return v
-}
-
-// Set wraps around the Set function.
-func (b Threadsafe) Set(i int, v bool) {
- b.mu.Lock()
- b.bm.Set(i, v)
- b.mu.Unlock()
-}
-
-// Concurrent is a bitmap implementation that achieves thread-safety
-// using atomic operations along with some unsafe.
-// It performs atomic operations on 32bits of data.
-type Concurrent []byte
-
-// NewConcurrent returns a concurrent bitmap.
-// It will create a bitmap
-func NewConcurrent(l int) Concurrent {
- remainder := l % 8
- if remainder != 0 {
- remainder = 1
- }
- return make([]byte, l/8+remainder, l/8+remainder+3)
-}
-
-// Get wraps around the Get function.
-func (c Concurrent) Get(b int) bool {
- return Get(c, b)
-}
-
-// Set wraps around the SetAtomic function.
-func (c Concurrent) Set(b int, v bool) {
- SetAtomic(c, b, v)
-}
-
-// Len wraps around the Len function.
-func (c Concurrent) Len() int {
- return Len(c)
-}
-
-// Data returns the data of the bitmap.
-// If copy is false the actual underlying slice will be returned.
-func (c Concurrent) Data(copy bool) []byte {
- return dataOrCopy(c, copy)
-}
diff --git a/vendor/github.com/boljen/go-bitmap/uintmap.go b/vendor/github.com/boljen/go-bitmap/uintmap.go
deleted file mode 100644
index 72cbf4a..0000000
--- a/vendor/github.com/boljen/go-bitmap/uintmap.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package bitmap
-
-import (
- "sync/atomic"
- "unsafe"
-)
-
-// SetAtomicUint32 sets the target bit to the target value inside the uint32
-// encded bitmap.
-func SetAtomicUint32(bitmap []uint32, targetBit int, targetValue bool) {
- targetIndex := targetBit / 32
- BitOffset := targetBit % 32
-
- for {
- localValue := atomic.LoadUint32(&bitmap[targetIndex])
- targetBytes := (*[4]byte)(unsafe.Pointer(&localValue))[:]
- if Get(targetBytes, BitOffset) == targetValue {
- return
- }
- referenceValue := localValue
- Set(targetBytes, BitOffset, targetValue)
- if atomic.CompareAndSwapUint32(&bitmap[targetIndex], referenceValue, localValue) {
- break
- }
- }
-}
-
-// GetAtomicUint32 gets the target bit from an uint32 encoded bitmap.
-func GetAtomicUint32(bitmap []uint32, targetBit int) bool {
- data := (*[4]byte)(unsafe.Pointer(&bitmap[targetBit/32]))[:]
- return Get(data, targetBit%32)
-}
diff --git a/vendor/github.com/opencord/voltha-lib-go/pkg/adapters/README.md b/vendor/github.com/opencord/voltha-lib-go/pkg/adapters/README.md
new file mode 100644
index 0000000..13479f8
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/pkg/adapters/README.md
@@ -0,0 +1,10 @@
+## How to Build and Run a Voltha Go language Adapter
+
+This directory is a repo for all voltha adapters written in Go language. At this time, the simulated_olt and
+simulated_onu adapters are the only adapters using the Go language. These adapters provide basic capabilities
+which will be used for high availability and capacity testing.
+
+### Building and running the Simulated OLT and ONU Adapters
+
+Please refer to the ```BUILD.md``` file under the voltha-go repo
+
diff --git a/vendor/github.com/opencord/voltha-lib-go/pkg/adapters/adapterif/adapter_proxy_if.go b/vendor/github.com/opencord/voltha-lib-go/pkg/adapters/adapterif/adapter_proxy_if.go
new file mode 100644
index 0000000..26b1448
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/pkg/adapters/adapterif/adapter_proxy_if.go
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package adapterif
+
+import (
+ "context"
+
+ "github.com/golang/protobuf/proto"
+ ic "github.com/opencord/voltha-protos/go/inter_container"
+)
+
+// AdapterProxy interface for AdapterProxy implementation.
+type AdapterProxy interface {
+ SendInterAdapterMessage(ctx context.Context,
+ msg proto.Message,
+ msgType ic.InterAdapterMessageType_Types,
+ fromAdapter string,
+ toAdapter string,
+ toDeviceID string,
+ proxyDeviceID string,
+ messageID string) error
+}
diff --git a/vendor/github.com/opencord/voltha-lib-go/pkg/adapters/adapterif/core_proxy_if.go b/vendor/github.com/opencord/voltha-lib-go/pkg/adapters/adapterif/core_proxy_if.go
new file mode 100644
index 0000000..26d021f
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/pkg/adapters/adapterif/core_proxy_if.go
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package adapterif
+
+import (
+ "context"
+
+ "github.com/opencord/voltha-protos/go/voltha"
+)
+
+// CoreProxy interface for voltha-go coreproxy.
+type CoreProxy interface {
+ UpdateCoreReference(deviceID string, coreReference string)
+ DeleteCoreReference(deviceID string)
+ // getCoreTopic(deviceID string) kafka.Topic
+ //GetAdapterTopic(args ...string) kafka.Topic
+ // getAdapterTopic(args ...string) kafka.Topic
+ RegisterAdapter(ctx context.Context, adapter *voltha.Adapter, deviceTypes *voltha.DeviceTypes) error
+ DeviceUpdate(ctx context.Context, device *voltha.Device) error
+ PortCreated(ctx context.Context, deviceID string, port *voltha.Port) error
+ PortsStateUpdate(ctx context.Context, deviceID string, operStatus voltha.OperStatus_OperStatus) error
+ DeleteAllPorts(ctx context.Context, deviceID string) error
+ DeviceStateUpdate(ctx context.Context, deviceID string,
+ connStatus voltha.ConnectStatus_ConnectStatus, operStatus voltha.OperStatus_OperStatus) error
+
+ ChildDeviceDetected(ctx context.Context, parentDeviceID string, parentPortNo int,
+ childDeviceType string, channelID int, vendorID string, serialNumber string, onuID int64) (*voltha.Device, error)
+
+ ChildDevicesLost(ctx context.Context, parentDeviceID string) error
+ ChildDevicesDetected(ctx context.Context, parentDeviceID string) error
+ GetDevice(ctx context.Context, parentDeviceID string, deviceID string) (*voltha.Device, error)
+ GetChildDevice(ctx context.Context, parentDeviceID string, kwargs map[string]interface{}) (*voltha.Device, error)
+ GetChildDevices(ctx context.Context, parentDeviceID string) (*voltha.Devices, error)
+ SendPacketIn(ctx context.Context, deviceID string, port uint32, pktPayload []byte) error
+}
diff --git a/vendor/github.com/opencord/voltha-lib-go/pkg/adapters/adapterif/events_proxy_if.go b/vendor/github.com/opencord/voltha-lib-go/pkg/adapters/adapterif/events_proxy_if.go
new file mode 100644
index 0000000..00a86a5
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/pkg/adapters/adapterif/events_proxy_if.go
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package adapterif
+
+import (
+ "github.com/opencord/voltha-protos/go/voltha"
+)
+
+// EventProxy interface for eventproxy
+type EventProxy interface {
+ SendDeviceEvent(deviceEvent *voltha.DeviceEvent, category EventCategory,
+ subCategory EventSubCategory, raisedTs int64) error
+}
+
+const (
+ EventTypeVersion = "0.1"
+)
+
+type (
+ EventType = voltha.EventType_EventType
+ EventCategory = voltha.EventCategory_EventCategory
+ EventSubCategory = voltha.EventSubCategory_EventSubCategory
+)
diff --git a/vendor/github.com/opencord/voltha-lib-go/pkg/adapters/common/adapter_proxy.go b/vendor/github.com/opencord/voltha-lib-go/pkg/adapters/common/adapter_proxy.go
new file mode 100644
index 0000000..fee70c8
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/pkg/adapters/common/adapter_proxy.go
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package common
+
+import (
+ "context"
+ "github.com/golang/protobuf/proto"
+ "github.com/golang/protobuf/ptypes"
+ "github.com/golang/protobuf/ptypes/any"
+ "github.com/google/uuid"
+ "github.com/opencord/voltha-lib-go/pkg/kafka"
+ "github.com/opencord/voltha-lib-go/pkg/log"
+ ic "github.com/opencord/voltha-protos/go/inter_container"
+ "time"
+)
+
+type AdapterProxy struct {
+ kafkaICProxy *kafka.InterContainerProxy
+ adapterTopic string
+ coreTopic string
+}
+
+func NewAdapterProxy(kafkaProxy *kafka.InterContainerProxy, adapterTopic string, coreTopic string) *AdapterProxy {
+ var proxy AdapterProxy
+ proxy.kafkaICProxy = kafkaProxy
+ proxy.adapterTopic = adapterTopic
+ proxy.coreTopic = coreTopic
+ log.Debugw("TOPICS", log.Fields{"core": proxy.coreTopic, "adapter": proxy.adapterTopic})
+ return &proxy
+}
+
+func (ap *AdapterProxy) SendInterAdapterMessage(ctx context.Context,
+ msg proto.Message,
+ msgType ic.InterAdapterMessageType_Types,
+ fromAdapter string,
+ toAdapter string,
+ toDeviceId string,
+ proxyDeviceId string,
+ messageId string) error {
+ log.Debugw("sending-inter-adapter-message", log.Fields{"type": msgType, "from": fromAdapter,
+ "to": toAdapter, "toDevice": toDeviceId, "proxyDevice": proxyDeviceId})
+
+ //Marshal the message
+ var marshalledMsg *any.Any
+ var err error
+ if marshalledMsg, err = ptypes.MarshalAny(msg); err != nil {
+ log.Warnw("cannot-marshal-msg", log.Fields{"error": err})
+ return err
+ }
+
+ //Build the inter adapter message
+ header := &ic.InterAdapterHeader{
+ Type: msgType,
+ FromTopic: fromAdapter,
+ ToTopic: toAdapter,
+ ToDeviceId: toDeviceId,
+ ProxyDeviceId: proxyDeviceId,
+ }
+ if messageId != "" {
+ header.Id = messageId
+ } else {
+ header.Id = uuid.New().String()
+ }
+ header.Timestamp = time.Now().Unix()
+ iaMsg := &ic.InterAdapterMessage{
+ Header: header,
+ Body: marshalledMsg,
+ }
+ args := make([]*kafka.KVArg, 1)
+ args[0] = &kafka.KVArg{
+ Key: "msg",
+ Value: iaMsg,
+ }
+
+ // Set up the required rpc arguments
+ topic := kafka.Topic{Name: toAdapter}
+ replyToTopic := kafka.Topic{Name: fromAdapter}
+ rpc := "process_inter_adapter_message"
+
+ success, result := ap.kafkaICProxy.InvokeRPC(ctx, rpc, &topic, &replyToTopic, true, proxyDeviceId, args...)
+ log.Debugw("inter-adapter-msg-response", log.Fields{"replyTopic": replyToTopic, "success": success})
+ return unPackResponse(rpc, "", success, result)
+}
diff --git a/vendor/github.com/opencord/voltha-lib-go/pkg/adapters/common/core_proxy.go b/vendor/github.com/opencord/voltha-lib-go/pkg/adapters/common/core_proxy.go
new file mode 100644
index 0000000..30117ca
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/pkg/adapters/common/core_proxy.go
@@ -0,0 +1,536 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package common
+
+import (
+ "context"
+ "github.com/golang/protobuf/ptypes"
+ a "github.com/golang/protobuf/ptypes/any"
+ "github.com/opencord/voltha-lib-go/pkg/kafka"
+ "github.com/opencord/voltha-lib-go/pkg/log"
+ ic "github.com/opencord/voltha-protos/go/inter_container"
+ "github.com/opencord/voltha-protos/go/voltha"
+ "google.golang.org/grpc/codes"
+ "google.golang.org/grpc/status"
+ "sync"
+)
+
+type CoreProxy struct {
+ kafkaICProxy *kafka.InterContainerProxy
+ adapterTopic string
+ coreTopic string
+ deviceIdCoreMap map[string]string
+ lockDeviceIdCoreMap sync.RWMutex
+}
+
+func NewCoreProxy(kafkaProxy *kafka.InterContainerProxy, adapterTopic string, coreTopic string) *CoreProxy {
+ var proxy CoreProxy
+ proxy.kafkaICProxy = kafkaProxy
+ proxy.adapterTopic = adapterTopic
+ proxy.coreTopic = coreTopic
+ proxy.deviceIdCoreMap = make(map[string]string)
+ proxy.lockDeviceIdCoreMap = sync.RWMutex{}
+ log.Debugw("TOPICS", log.Fields{"core": proxy.coreTopic, "adapter": proxy.adapterTopic})
+
+ return &proxy
+}
+
+func unPackResponse(rpc string, deviceId string, success bool, response *a.Any) error {
+ if success {
+ return nil
+ } else {
+ unpackResult := &ic.Error{}
+ var err error
+ if err = ptypes.UnmarshalAny(response, unpackResult); err != nil {
+ log.Warnw("cannot-unmarshal-response", log.Fields{"error": err})
+ }
+ log.Debugw("response", log.Fields{"rpc": rpc, "deviceId": deviceId, "success": success, "error": err})
+ // TODO: Need to get the real error code
+ return status.Errorf(codes.Canceled, "%s", unpackResult.Reason)
+ }
+}
+
+// UpdateCoreReference adds or update a core reference (really the topic name) for a given device Id
+func (ap *CoreProxy) UpdateCoreReference(deviceId string, coreReference string) {
+ ap.lockDeviceIdCoreMap.Lock()
+ defer ap.lockDeviceIdCoreMap.Unlock()
+ ap.deviceIdCoreMap[deviceId] = coreReference
+}
+
+// DeleteCoreReference removes a core reference (really the topic name) for a given device Id
+func (ap *CoreProxy) DeleteCoreReference(deviceId string) {
+ ap.lockDeviceIdCoreMap.Lock()
+ defer ap.lockDeviceIdCoreMap.Unlock()
+ delete(ap.deviceIdCoreMap, deviceId)
+}
+
+func (ap *CoreProxy) getCoreTopic(deviceId string) kafka.Topic {
+ ap.lockDeviceIdCoreMap.Lock()
+ defer ap.lockDeviceIdCoreMap.Unlock()
+
+ if t, exist := ap.deviceIdCoreMap[deviceId]; exist {
+ return kafka.Topic{Name: t}
+ }
+
+ return kafka.Topic{Name: ap.coreTopic}
+}
+
+func (ap *CoreProxy) getAdapterTopic(args ...string) kafka.Topic {
+ return kafka.Topic{Name: ap.adapterTopic}
+}
+
+func (ap *CoreProxy) RegisterAdapter(ctx context.Context, adapter *voltha.Adapter, deviceTypes *voltha.DeviceTypes) error {
+ log.Debugw("registering-adapter", log.Fields{"coreTopic": ap.coreTopic, "adapterTopic": ap.adapterTopic})
+ rpc := "Register"
+ topic := kafka.Topic{Name: ap.coreTopic}
+ replyToTopic := ap.getAdapterTopic()
+ args := make([]*kafka.KVArg, 2)
+ args[0] = &kafka.KVArg{
+ Key: "adapter",
+ Value: adapter,
+ }
+ args[1] = &kafka.KVArg{
+ Key: "deviceTypes",
+ Value: deviceTypes,
+ }
+
+ success, result := ap.kafkaICProxy.InvokeRPC(ctx, rpc, &topic, &replyToTopic, true, "", args...)
+ log.Debugw("Register-Adapter-response", log.Fields{"replyTopic": replyToTopic, "success": success})
+ return unPackResponse(rpc, "", success, result)
+}
+
+func (ap *CoreProxy) DeviceUpdate(ctx context.Context, device *voltha.Device) error {
+ log.Debugw("DeviceUpdate", log.Fields{"deviceId": device.Id})
+ rpc := "DeviceUpdate"
+ toTopic := ap.getCoreTopic(device.Id)
+ args := make([]*kafka.KVArg, 1)
+ args[0] = &kafka.KVArg{
+ Key: "device",
+ Value: device,
+ }
+ // Use a device specific topic as we are the only adaptercore handling requests for this device
+ replyToTopic := ap.getAdapterTopic()
+ success, result := ap.kafkaICProxy.InvokeRPC(nil, rpc, &toTopic, &replyToTopic, true, device.Id, args...)
+ log.Debugw("DeviceUpdate-response", log.Fields{"deviceId": device.Id, "success": success})
+ return unPackResponse(rpc, device.Id, success, result)
+}
+
+func (ap *CoreProxy) PortCreated(ctx context.Context, deviceId string, port *voltha.Port) error {
+ log.Debugw("PortCreated", log.Fields{"portNo": port.PortNo})
+ rpc := "PortCreated"
+ // Use a device specific topic to send the request. The adapter handling the device creates a device
+ // specific topic
+ toTopic := ap.getCoreTopic(deviceId)
+ args := make([]*kafka.KVArg, 2)
+ id := &voltha.ID{Id: deviceId}
+ args[0] = &kafka.KVArg{
+ Key: "device_id",
+ Value: id,
+ }
+ args[1] = &kafka.KVArg{
+ Key: "port",
+ Value: port,
+ }
+
+ // Use a device specific topic as we are the only adaptercore handling requests for this device
+ replyToTopic := ap.getAdapterTopic()
+ success, result := ap.kafkaICProxy.InvokeRPC(nil, rpc, &toTopic, &replyToTopic, true, deviceId, args...)
+ log.Debugw("PortCreated-response", log.Fields{"deviceId": deviceId, "success": success})
+ return unPackResponse(rpc, deviceId, success, result)
+}
+
+func (ap *CoreProxy) PortsStateUpdate(ctx context.Context, deviceId string, operStatus voltha.OperStatus_OperStatus) error {
+ log.Debugw("PortsStateUpdate", log.Fields{"deviceId": deviceId})
+ rpc := "PortsStateUpdate"
+ // Use a device specific topic to send the request. The adapter handling the device creates a device
+ // specific topic
+ toTopic := ap.getCoreTopic(deviceId)
+ args := make([]*kafka.KVArg, 2)
+ id := &voltha.ID{Id: deviceId}
+ oStatus := &ic.IntType{Val: int64(operStatus)}
+
+ args[0] = &kafka.KVArg{
+ Key: "device_id",
+ Value: id,
+ }
+ args[1] = &kafka.KVArg{
+ Key: "oper_status",
+ Value: oStatus,
+ }
+
+ // Use a device specific topic as we are the only adaptercore handling requests for this device
+ replyToTopic := ap.getAdapterTopic()
+ success, result := ap.kafkaICProxy.InvokeRPC(nil, rpc, &toTopic, &replyToTopic, true, deviceId, args...)
+ log.Debugw("PortsStateUpdate-response", log.Fields{"deviceId": deviceId, "success": success})
+ return unPackResponse(rpc, deviceId, success, result)
+}
+
+func (ap *CoreProxy) DeleteAllPorts(ctx context.Context, deviceId string) error {
+ log.Debugw("DeleteAllPorts", log.Fields{"deviceId": deviceId})
+ rpc := "DeleteAllPorts"
+ // Use a device specific topic to send the request. The adapter handling the device creates a device
+ // specific topic
+ toTopic := ap.getCoreTopic(deviceId)
+ args := make([]*kafka.KVArg, 2)
+ id := &voltha.ID{Id: deviceId}
+
+ args[0] = &kafka.KVArg{
+ Key: "device_id",
+ Value: id,
+ }
+
+ // Use a device specific topic as we are the only adaptercore handling requests for this device
+ replyToTopic := ap.getAdapterTopic()
+ success, result := ap.kafkaICProxy.InvokeRPC(nil, rpc, &toTopic, &replyToTopic, true, deviceId, args...)
+ log.Debugw("DeleteAllPorts-response", log.Fields{"deviceId": deviceId, "success": success})
+ return unPackResponse(rpc, deviceId, success, result)
+}
+
+func (ap *CoreProxy) DeviceStateUpdate(ctx context.Context, deviceId string,
+ connStatus voltha.ConnectStatus_ConnectStatus, operStatus voltha.OperStatus_OperStatus) error {
+ log.Debugw("DeviceStateUpdate", log.Fields{"deviceId": deviceId})
+ rpc := "DeviceStateUpdate"
+ // Use a device specific topic to send the request. The adapter handling the device creates a device
+ // specific topic
+ toTopic := ap.getCoreTopic(deviceId)
+ args := make([]*kafka.KVArg, 3)
+ id := &voltha.ID{Id: deviceId}
+ oStatus := &ic.IntType{Val: int64(operStatus)}
+ cStatus := &ic.IntType{Val: int64(connStatus)}
+
+ args[0] = &kafka.KVArg{
+ Key: "device_id",
+ Value: id,
+ }
+ args[1] = &kafka.KVArg{
+ Key: "oper_status",
+ Value: oStatus,
+ }
+ args[2] = &kafka.KVArg{
+ Key: "connect_status",
+ Value: cStatus,
+ }
+ // Use a device specific topic as we are the only adaptercore handling requests for this device
+ replyToTopic := ap.getAdapterTopic()
+ success, result := ap.kafkaICProxy.InvokeRPC(nil, rpc, &toTopic, &replyToTopic, true, deviceId, args...)
+ log.Debugw("DeviceStateUpdate-response", log.Fields{"deviceId": deviceId, "success": success})
+ return unPackResponse(rpc, deviceId, success, result)
+}
+
+func (ap *CoreProxy) ChildDeviceDetected(ctx context.Context, parentDeviceId string, parentPortNo int,
+ childDeviceType string, channelId int, vendorId string, serialNumber string, onuId int64) (*voltha.Device, error) {
+ log.Debugw("ChildDeviceDetected", log.Fields{"pDeviceId": parentDeviceId, "channelId": channelId})
+ rpc := "ChildDeviceDetected"
+ // Use a device specific topic to send the request. The adapter handling the device creates a device
+ // specific topic
+ toTopic := ap.getCoreTopic(parentDeviceId)
+ replyToTopic := ap.getAdapterTopic()
+
+ args := make([]*kafka.KVArg, 7)
+ id := &voltha.ID{Id: parentDeviceId}
+ args[0] = &kafka.KVArg{
+ Key: "parent_device_id",
+ Value: id,
+ }
+ ppn := &ic.IntType{Val: int64(parentPortNo)}
+ args[1] = &kafka.KVArg{
+ Key: "parent_port_no",
+ Value: ppn,
+ }
+ cdt := &ic.StrType{Val: childDeviceType}
+ args[2] = &kafka.KVArg{
+ Key: "child_device_type",
+ Value: cdt,
+ }
+ channel := &ic.IntType{Val: int64(channelId)}
+ args[3] = &kafka.KVArg{
+ Key: "channel_id",
+ Value: channel,
+ }
+ vId := &ic.StrType{Val: vendorId}
+ args[4] = &kafka.KVArg{
+ Key: "vendor_id",
+ Value: vId,
+ }
+ sNo := &ic.StrType{Val: serialNumber}
+ args[5] = &kafka.KVArg{
+ Key: "serial_number",
+ Value: sNo,
+ }
+ oId := &ic.IntType{Val: int64(onuId)}
+ args[6] = &kafka.KVArg{
+ Key: "onu_id",
+ Value: oId,
+ }
+
+ success, result := ap.kafkaICProxy.InvokeRPC(nil, rpc, &toTopic, &replyToTopic, true, parentDeviceId, args...)
+ log.Debugw("ChildDeviceDetected-response", log.Fields{"pDeviceId": parentDeviceId, "success": success})
+
+ if success {
+ volthaDevice := &voltha.Device{}
+ if err := ptypes.UnmarshalAny(result, volthaDevice); err != nil {
+ log.Warnw("cannot-unmarshal-response", log.Fields{"error": err})
+ return nil, status.Errorf(codes.InvalidArgument, "%s", err.Error())
+ }
+ return volthaDevice, nil
+ } else {
+ unpackResult := &ic.Error{}
+ var err error
+ if err = ptypes.UnmarshalAny(result, unpackResult); err != nil {
+ log.Warnw("cannot-unmarshal-response", log.Fields{"error": err})
+ }
+ log.Debugw("ChildDeviceDetected-return", log.Fields{"deviceid": parentDeviceId, "success": success, "error": err})
+ // TODO: Need to get the real error code
+ return nil, status.Errorf(codes.Internal, "%s", unpackResult.Reason)
+ }
+
+}
+
+func (ap *CoreProxy) ChildDevicesLost(ctx context.Context, parentDeviceId string) error {
+ log.Debugw("ChildDevicesLost", log.Fields{"pDeviceId": parentDeviceId})
+ rpc := "ChildDevicesLost"
+ // Use a device specific topic to send the request. The adapter handling the device creates a device
+ // specific topic
+ toTopic := ap.getCoreTopic(parentDeviceId)
+ replyToTopic := ap.getAdapterTopic()
+
+ args := make([]*kafka.KVArg, 1)
+ id := &voltha.ID{Id: parentDeviceId}
+ args[0] = &kafka.KVArg{
+ Key: "parent_device_id",
+ Value: id,
+ }
+
+ success, result := ap.kafkaICProxy.InvokeRPC(nil, rpc, &toTopic, &replyToTopic, true, parentDeviceId, args...)
+ log.Debugw("ChildDevicesLost-response", log.Fields{"pDeviceId": parentDeviceId, "success": success})
+ return unPackResponse(rpc, parentDeviceId, success, result)
+}
+
+func (ap *CoreProxy) ChildDevicesDetected(ctx context.Context, parentDeviceId string) error {
+ log.Debugw("ChildDevicesDetected", log.Fields{"pDeviceId": parentDeviceId})
+ rpc := "ChildDevicesDetected"
+ // Use a device specific topic to send the request. The adapter handling the device creates a device
+ // specific topic
+ toTopic := ap.getCoreTopic(parentDeviceId)
+ replyToTopic := ap.getAdapterTopic()
+
+ args := make([]*kafka.KVArg, 1)
+ id := &voltha.ID{Id: parentDeviceId}
+ args[0] = &kafka.KVArg{
+ Key: "parent_device_id",
+ Value: id,
+ }
+
+ success, result := ap.kafkaICProxy.InvokeRPC(nil, rpc, &toTopic, &replyToTopic, true, parentDeviceId, args...)
+ log.Debugw("ChildDevicesDetected-response", log.Fields{"pDeviceId": parentDeviceId, "success": success})
+ return unPackResponse(rpc, parentDeviceId, success, result)
+}
+
+func (ap *CoreProxy) GetDevice(ctx context.Context, parentDeviceId string, deviceId string) (*voltha.Device, error) {
+ log.Debugw("GetDevice", log.Fields{"deviceId": deviceId})
+ rpc := "GetDevice"
+
+ toTopic := ap.getCoreTopic(parentDeviceId)
+ replyToTopic := ap.getAdapterTopic()
+
+ args := make([]*kafka.KVArg, 1)
+ id := &voltha.ID{Id: deviceId}
+ args[0] = &kafka.KVArg{
+ Key: "device_id",
+ Value: id,
+ }
+
+ success, result := ap.kafkaICProxy.InvokeRPC(nil, rpc, &toTopic, &replyToTopic, true, parentDeviceId, args...)
+ log.Debugw("GetDevice-response", log.Fields{"pDeviceId": parentDeviceId, "success": success})
+
+ if success {
+ volthaDevice := &voltha.Device{}
+ if err := ptypes.UnmarshalAny(result, volthaDevice); err != nil {
+ log.Warnw("cannot-unmarshal-response", log.Fields{"error": err})
+ return nil, status.Errorf(codes.InvalidArgument, "%s", err.Error())
+ }
+ return volthaDevice, nil
+ } else {
+ unpackResult := &ic.Error{}
+ var err error
+ if err = ptypes.UnmarshalAny(result, unpackResult); err != nil {
+ log.Warnw("cannot-unmarshal-response", log.Fields{"error": err})
+ }
+ log.Debugw("GetDevice-return", log.Fields{"deviceid": parentDeviceId, "success": success, "error": err})
+ // TODO: Need to get the real error code
+ return nil, status.Errorf(codes.Internal, "%s", unpackResult.Reason)
+ }
+}
+
+func (ap *CoreProxy) GetChildDevice(ctx context.Context, parentDeviceId string, kwargs map[string]interface{}) (*voltha.Device, error) {
+ log.Debugw("GetChildDevice", log.Fields{"parentDeviceId": parentDeviceId, "kwargs": kwargs})
+ rpc := "GetChildDevice"
+
+ toTopic := ap.getCoreTopic(parentDeviceId)
+ replyToTopic := ap.getAdapterTopic()
+
+ args := make([]*kafka.KVArg, 4)
+ id := &voltha.ID{Id: parentDeviceId}
+ args[0] = &kafka.KVArg{
+ Key: "device_id",
+ Value: id,
+ }
+
+ var cnt uint8 = 0
+ for k, v := range kwargs {
+ cnt += 1
+ if k == "serial_number" {
+ val := &ic.StrType{Val: v.(string)}
+ args[cnt] = &kafka.KVArg{
+ Key: k,
+ Value: val,
+ }
+ } else if k == "onu_id" {
+ val := &ic.IntType{Val: int64(v.(uint32))}
+ args[cnt] = &kafka.KVArg{
+ Key: k,
+ Value: val,
+ }
+ } else if k == "parent_port_no" {
+ val := &ic.IntType{Val: int64(v.(uint32))}
+ args[cnt] = &kafka.KVArg{
+ Key: k,
+ Value: val,
+ }
+ }
+ }
+
+ success, result := ap.kafkaICProxy.InvokeRPC(nil, rpc, &toTopic, &replyToTopic, true, parentDeviceId, args...)
+ log.Debugw("GetChildDevice-response", log.Fields{"pDeviceId": parentDeviceId, "success": success})
+
+ if success {
+ volthaDevice := &voltha.Device{}
+ if err := ptypes.UnmarshalAny(result, volthaDevice); err != nil {
+ log.Warnw("cannot-unmarshal-response", log.Fields{"error": err})
+ return nil, status.Errorf(codes.InvalidArgument, "%s", err.Error())
+ }
+ return volthaDevice, nil
+ } else {
+ unpackResult := &ic.Error{}
+ var err error
+ if err = ptypes.UnmarshalAny(result, unpackResult); err != nil {
+ log.Warnw("cannot-unmarshal-response", log.Fields{"error": err})
+ }
+ log.Debugw("GetChildDevice-return", log.Fields{"deviceid": parentDeviceId, "success": success, "error": err})
+ // TODO: Need to get the real error code
+ return nil, status.Errorf(codes.Internal, "%s", unpackResult.Reason)
+ }
+}
+
+func (ap *CoreProxy) GetChildDevices(ctx context.Context, parentDeviceId string) (*voltha.Devices, error) {
+ log.Debugw("GetChildDevices", log.Fields{"parentDeviceId": parentDeviceId})
+ rpc := "GetChildDevices"
+
+ toTopic := ap.getCoreTopic(parentDeviceId)
+ replyToTopic := ap.getAdapterTopic()
+
+ args := make([]*kafka.KVArg, 1)
+ id := &voltha.ID{Id: parentDeviceId}
+ args[0] = &kafka.KVArg{
+ Key: "device_id",
+ Value: id,
+ }
+
+ success, result := ap.kafkaICProxy.InvokeRPC(nil, rpc, &toTopic, &replyToTopic, true, parentDeviceId, args...)
+ log.Debugw("GetChildDevices-response", log.Fields{"pDeviceId": parentDeviceId, "success": success})
+
+ if success {
+ volthaDevices := &voltha.Devices{}
+ if err := ptypes.UnmarshalAny(result, volthaDevices); err != nil {
+ log.Warnw("cannot-unmarshal-response", log.Fields{"error": err})
+ return nil, status.Errorf(codes.InvalidArgument, "%s", err.Error())
+ }
+ return volthaDevices, nil
+ } else {
+ unpackResult := &ic.Error{}
+ var err error
+ if err = ptypes.UnmarshalAny(result, unpackResult); err != nil {
+ log.Warnw("cannot-unmarshal-response", log.Fields{"error": err})
+ }
+ log.Debugw("GetChildDevices-return", log.Fields{"deviceid": parentDeviceId, "success": success, "error": err})
+ // TODO: Need to get the real error code
+ return nil, status.Errorf(codes.Internal, "%s", unpackResult.Reason)
+ }
+}
+
+func (ap *CoreProxy) SendPacketIn(ctx context.Context, deviceId string, port uint32, pktPayload []byte) error {
+ log.Debugw("SendPacketIn", log.Fields{"deviceId": deviceId, "port": port, "pktPayload": pktPayload})
+ rpc := "PacketIn"
+ // Use a device specific topic to send the request. The adapter handling the device creates a device
+ // specific topic
+ toTopic := ap.getCoreTopic(deviceId)
+ replyToTopic := ap.getAdapterTopic()
+
+ args := make([]*kafka.KVArg, 3)
+ id := &voltha.ID{Id: deviceId}
+ args[0] = &kafka.KVArg{
+ Key: "device_id",
+ Value: id,
+ }
+ portNo := &ic.IntType{Val: int64(port)}
+ args[1] = &kafka.KVArg{
+ Key: "port",
+ Value: portNo,
+ }
+ pkt := &ic.Packet{Payload: pktPayload}
+ args[2] = &kafka.KVArg{
+ Key: "packet",
+ Value: pkt,
+ }
+ success, result := ap.kafkaICProxy.InvokeRPC(nil, rpc, &toTopic, &replyToTopic, true, deviceId, args...)
+ log.Debugw("SendPacketIn-response", log.Fields{"pDeviceId": deviceId, "success": success})
+ return unPackResponse(rpc, deviceId, success, result)
+}
+
+func (ap *CoreProxy) DevicePMConfigUpdate(ctx context.Context, pmConfigs *voltha.PmConfigs) error {
+ log.Debugw("DevicePMConfigUpdate", log.Fields{"pmConfigs": pmConfigs})
+ rpc := "DevicePMConfigUpdate"
+ // Use a device specific topic to send the request. The adapter handling the device creates a device
+ // specific topic
+ toTopic := ap.getCoreTopic(pmConfigs.Id)
+ replyToTopic := ap.getAdapterTopic()
+
+ args := make([]*kafka.KVArg, 1)
+ args[0] = &kafka.KVArg{
+ Key: "device_pm_config",
+ Value: pmConfigs,
+ }
+ success, result := ap.kafkaICProxy.InvokeRPC(nil, rpc, &toTopic, &replyToTopic, true, pmConfigs.Id, args...)
+ log.Debugw("DevicePMConfigUpdate-response", log.Fields{"pDeviceId": pmConfigs.Id, "success": success})
+ return unPackResponse(rpc, pmConfigs.Id, success, result)
+}
+
+func (ap *CoreProxy) ReconcileChildDevices(ctx context.Context, parentDeviceId string) error {
+ log.Debugw("ReconcileChildDevices", log.Fields{"parentDeviceId": parentDeviceId})
+ rpc := "ReconcileChildDevices"
+ // Use a device specific topic to send the request. The adapter handling the device creates a device
+ // specific topic
+ toTopic := ap.getCoreTopic(parentDeviceId)
+ replyToTopic := ap.getAdapterTopic()
+
+ args := []*kafka.KVArg{
+ {Key: "parent_device_id", Value: &voltha.ID{Id: parentDeviceId}},
+ }
+
+ success, result := ap.kafkaICProxy.InvokeRPC(nil, rpc, &toTopic, &replyToTopic, true, parentDeviceId, args...)
+ log.Debugw("ReconcileChildDevices-response", log.Fields{"pDeviceId": parentDeviceId, "success": success})
+ return unPackResponse(rpc, parentDeviceId, success, result)
+}
diff --git a/vendor/github.com/opencord/voltha-lib-go/pkg/adapters/common/events_proxy.go b/vendor/github.com/opencord/voltha-lib-go/pkg/adapters/common/events_proxy.go
new file mode 100644
index 0000000..25b1be4
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/pkg/adapters/common/events_proxy.go
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package common
+
+import (
+ "errors"
+ "fmt"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/opencord/voltha-lib-go/pkg/adapters/adapterif"
+ "github.com/opencord/voltha-lib-go/pkg/kafka"
+ "github.com/opencord/voltha-lib-go/pkg/log"
+ "github.com/opencord/voltha-protos/go/voltha"
+)
+
+type EventProxy struct {
+ kafkaClient kafka.Client
+ eventTopic kafka.Topic
+}
+
+func NewEventProxy(opts ...EventProxyOption) *EventProxy {
+ var proxy EventProxy
+ for _, option := range opts {
+ option(&proxy)
+ }
+ return &proxy
+}
+
+type EventProxyOption func(*EventProxy)
+
+func MsgClient(client kafka.Client) EventProxyOption {
+ return func(args *EventProxy) {
+ args.kafkaClient = client
+ }
+}
+
+func MsgTopic(topic kafka.Topic) EventProxyOption {
+ return func(args *EventProxy) {
+ args.eventTopic = topic
+ }
+}
+
+func (ep *EventProxy) formatId(eventName string) string {
+ return fmt.Sprintf("Voltha.openolt.%s.%s", eventName, strconv.FormatInt(time.Now().UnixNano(), 10))
+}
+
+func (ep *EventProxy) getEventHeader(eventName string, category adapterif.EventCategory, subCategory adapterif.EventSubCategory, eventType adapterif.EventType, raisedTs int64) *voltha.EventHeader {
+ var header voltha.EventHeader
+ if strings.Contains(eventName, "_") {
+ eventName = strings.Join(strings.Split(eventName, "_")[:len(strings.Split(eventName, "_"))-2], "_")
+ } else {
+ eventName = "UNKNOWN_EVENT"
+ }
+ /* Populating event header */
+ header.Id = ep.formatId(eventName)
+ header.Category = category
+ header.SubCategory = subCategory
+ header.Type = eventType
+ header.TypeVersion = adapterif.EventTypeVersion
+ header.RaisedTs = float32(raisedTs)
+ header.ReportedTs = float32(time.Now().UnixNano())
+ return &header
+}
+
+/* Send out device events*/
+func (ep *EventProxy) SendDeviceEvent(deviceEvent *voltha.DeviceEvent, category adapterif.EventCategory, subCategory adapterif.EventSubCategory, raisedTs int64) error {
+ if deviceEvent == nil {
+ log.Error("Recieved empty device event")
+ return errors.New("Device event nil")
+ }
+ var event voltha.Event
+ var de voltha.Event_DeviceEvent
+ de.DeviceEvent = deviceEvent
+ event.Header = ep.getEventHeader(deviceEvent.DeviceEventName, category, subCategory, voltha.EventType_DEVICE_EVENT, raisedTs)
+ event.EventType = &de
+ if err := ep.sendEvent(&event); err != nil {
+ log.Errorw("Failed to send device event to KAFKA bus", log.Fields{"device-event": deviceEvent})
+ return err
+ }
+ log.Infow("Successfully sent device event KAFKA", log.Fields{"Id": event.Header.Id, "Category": event.Header.Category,
+ "SubCategory": event.Header.SubCategory, "Type": event.Header.Type, "TypeVersion": event.Header.TypeVersion,
+ "ReportedTs": event.Header.ReportedTs, "ResourceId": deviceEvent.ResourceId, "Context": deviceEvent.Context,
+ "DeviceEventName": deviceEvent.DeviceEventName})
+
+ return nil
+
+}
+
+/* TODO: Send out KPI events*/
+
+func (ep *EventProxy) sendEvent(event *voltha.Event) error {
+ if err := ep.kafkaClient.Send(event, &ep.eventTopic); err != nil {
+ return err
+ }
+ log.Debugw("Sent event to kafka", log.Fields{"event": event})
+
+ return nil
+}
diff --git a/vendor/github.com/opencord/voltha-lib-go/pkg/adapters/common/performance_metrics.go b/vendor/github.com/opencord/voltha-lib-go/pkg/adapters/common/performance_metrics.go
new file mode 100644
index 0000000..8f74439
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/pkg/adapters/common/performance_metrics.go
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package common
+
+import (
+ "github.com/opencord/voltha-protos/go/voltha"
+)
+
+type PmMetrics struct {
+ deviceId string
+ frequency uint32
+ grouped bool
+ frequencyOverride bool
+ metrics map[string]*voltha.PmConfig
+}
+
+type PmMetricsOption func(*PmMetrics)
+
+func Frequency(frequency uint32) PmMetricsOption {
+ return func(args *PmMetrics) {
+ args.frequency = frequency
+ }
+}
+
+func Grouped(grouped bool) PmMetricsOption {
+ return func(args *PmMetrics) {
+ args.grouped = grouped
+ }
+}
+
+func FrequencyOverride(frequencyOverride bool) PmMetricsOption {
+ return func(args *PmMetrics) {
+ args.frequencyOverride = frequencyOverride
+ }
+}
+
+func Metrics(pmNames []string) PmMetricsOption {
+ return func(args *PmMetrics) {
+ args.metrics = make(map[string]*voltha.PmConfig)
+ for _, name := range pmNames {
+ args.metrics[name] = &voltha.PmConfig{
+ Name: name,
+ Type: voltha.PmConfig_COUNTER,
+ Enabled: true,
+ }
+ }
+ }
+}
+
+func NewPmMetrics(deviceId string, opts ...PmMetricsOption) *PmMetrics {
+ pm := &PmMetrics{deviceId: deviceId}
+ for _, option := range opts {
+ option(pm)
+ }
+ return pm
+}
+
+func (pm *PmMetrics) ToPmConfigs() *voltha.PmConfigs {
+ pmConfigs := &voltha.PmConfigs{
+ Id: pm.deviceId,
+ DefaultFreq: pm.frequency,
+ Grouped: pm.grouped,
+ FreqOverride: pm.frequencyOverride,
+ }
+ for _, v := range pm.metrics {
+ pmConfigs.Metrics = append(pmConfigs.Metrics, &voltha.PmConfig{Name: v.Name, Type: v.Type, Enabled: v.Enabled})
+ }
+ return pmConfigs
+}
diff --git a/vendor/github.com/opencord/voltha-lib-go/pkg/adapters/common/request_handler.go b/vendor/github.com/opencord/voltha-lib-go/pkg/adapters/common/request_handler.go
new file mode 100644
index 0000000..27f9846
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/pkg/adapters/common/request_handler.go
@@ -0,0 +1,614 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package common
+
+import (
+ "errors"
+ "github.com/golang/protobuf/ptypes"
+ "github.com/golang/protobuf/ptypes/empty"
+ "github.com/opencord/voltha-lib-go/pkg/adapters"
+ "github.com/opencord/voltha-lib-go/pkg/adapters/adapterif"
+ "github.com/opencord/voltha-lib-go/pkg/kafka"
+ "github.com/opencord/voltha-lib-go/pkg/log"
+ ic "github.com/opencord/voltha-protos/go/inter_container"
+ "github.com/opencord/voltha-protos/go/openflow_13"
+ "github.com/opencord/voltha-protos/go/voltha"
+ "google.golang.org/grpc/codes"
+ "google.golang.org/grpc/status"
+)
+
+type RequestHandlerProxy struct {
+ TestMode bool
+ coreInstanceId string
+ adapter adapters.IAdapter
+ coreProxy adapterif.CoreProxy
+}
+
+func NewRequestHandlerProxy(coreInstanceId string, iadapter adapters.IAdapter, cProxy adapterif.CoreProxy) *RequestHandlerProxy {
+ var proxy RequestHandlerProxy
+ proxy.coreInstanceId = coreInstanceId
+ proxy.adapter = iadapter
+ proxy.coreProxy = cProxy
+ return &proxy
+}
+
+func (rhp *RequestHandlerProxy) Adapter_descriptor() (*empty.Empty, error) {
+ return new(empty.Empty), nil
+}
+
+func (rhp *RequestHandlerProxy) Device_types() (*voltha.DeviceTypes, error) {
+ return nil, nil
+}
+
+func (rhp *RequestHandlerProxy) Health() (*voltha.HealthStatus, error) {
+ return nil, nil
+}
+
+func (rhp *RequestHandlerProxy) Adopt_device(args []*ic.Argument) (*empty.Empty, error) {
+ if len(args) < 3 {
+ log.Warn("invalid-number-of-args", log.Fields{"args": args})
+ err := errors.New("invalid-number-of-args")
+ return nil, err
+ }
+ device := &voltha.Device{}
+ transactionID := &ic.StrType{}
+ fromTopic := &ic.StrType{}
+ for _, arg := range args {
+ switch arg.Key {
+ case "device":
+ if err := ptypes.UnmarshalAny(arg.Value, device); err != nil {
+ log.Warnw("cannot-unmarshal-device", log.Fields{"error": err})
+ return nil, err
+ }
+ case kafka.TransactionKey:
+ if err := ptypes.UnmarshalAny(arg.Value, transactionID); err != nil {
+ log.Warnw("cannot-unmarshal-transaction-ID", log.Fields{"error": err})
+ return nil, err
+ }
+ case kafka.FromTopic:
+ if err := ptypes.UnmarshalAny(arg.Value, fromTopic); err != nil {
+ log.Warnw("cannot-unmarshal-from-topic", log.Fields{"error": err})
+ return nil, err
+ }
+ }
+ }
+
+ log.Debugw("Adopt_device", log.Fields{"deviceId": device.Id})
+
+ //Update the core reference for that device
+ rhp.coreProxy.UpdateCoreReference(device.Id, fromTopic.Val)
+
+ //Invoke the adopt device on the adapter
+ if err := rhp.adapter.Adopt_device(device); err != nil {
+ return nil, status.Errorf(codes.NotFound, "%s", err.Error())
+ }
+
+ return new(empty.Empty), nil
+}
+
+func (rhp *RequestHandlerProxy) Reconcile_device(args []*ic.Argument) (*empty.Empty, error) {
+ if len(args) < 3 {
+ log.Warn("invalid-number-of-args", log.Fields{"args": args})
+ err := errors.New("invalid-number-of-args")
+ return nil, err
+ }
+
+ device := &voltha.Device{}
+ transactionID := &ic.StrType{}
+ fromTopic := &ic.StrType{}
+ for _, arg := range args {
+ switch arg.Key {
+ case "device":
+ if err := ptypes.UnmarshalAny(arg.Value, device); err != nil {
+ log.Warnw("cannot-unmarshal-device", log.Fields{"error": err})
+ return nil, err
+ }
+ case kafka.TransactionKey:
+ if err := ptypes.UnmarshalAny(arg.Value, transactionID); err != nil {
+ log.Warnw("cannot-unmarshal-transaction-ID", log.Fields{"error": err})
+ return nil, err
+ }
+ case kafka.FromTopic:
+ if err := ptypes.UnmarshalAny(arg.Value, fromTopic); err != nil {
+ log.Warnw("cannot-unmarshal-from-topic", log.Fields{"error": err})
+ return nil, err
+ }
+ }
+ }
+ //Update the core reference for that device
+ rhp.coreProxy.UpdateCoreReference(device.Id, fromTopic.Val)
+
+ //Invoke the reconcile device API on the adapter
+ if err := rhp.adapter.Reconcile_device(device); err != nil {
+ return nil, status.Errorf(codes.NotFound, "%s", err.Error())
+ }
+ return new(empty.Empty), nil
+}
+
+func (rhp *RequestHandlerProxy) Abandon_device(args []*ic.Argument) (*empty.Empty, error) {
+ return new(empty.Empty), nil
+}
+
+func (rhp *RequestHandlerProxy) Disable_device(args []*ic.Argument) (*empty.Empty, error) {
+ if len(args) < 3 {
+ log.Warn("invalid-number-of-args", log.Fields{"args": args})
+ err := errors.New("invalid-number-of-args")
+ return nil, err
+ }
+
+ device := &voltha.Device{}
+ transactionID := &ic.StrType{}
+ fromTopic := &ic.StrType{}
+ for _, arg := range args {
+ switch arg.Key {
+ case "device":
+ if err := ptypes.UnmarshalAny(arg.Value, device); err != nil {
+ log.Warnw("cannot-unmarshal-device", log.Fields{"error": err})
+ return nil, err
+ }
+ case kafka.TransactionKey:
+ if err := ptypes.UnmarshalAny(arg.Value, transactionID); err != nil {
+ log.Warnw("cannot-unmarshal-transaction-ID", log.Fields{"error": err})
+ return nil, err
+ }
+ case kafka.FromTopic:
+ if err := ptypes.UnmarshalAny(arg.Value, fromTopic); err != nil {
+ log.Warnw("cannot-unmarshal-from-topic", log.Fields{"error": err})
+ return nil, err
+ }
+ }
+ }
+ //Update the core reference for that device
+ rhp.coreProxy.UpdateCoreReference(device.Id, fromTopic.Val)
+ //Invoke the Disable_device API on the adapter
+ if err := rhp.adapter.Disable_device(device); err != nil {
+ return nil, status.Errorf(codes.NotFound, "%s", err.Error())
+ }
+ return new(empty.Empty), nil
+}
+
+func (rhp *RequestHandlerProxy) Reenable_device(args []*ic.Argument) (*empty.Empty, error) {
+ if len(args) < 3 {
+ log.Warn("invalid-number-of-args", log.Fields{"args": args})
+ err := errors.New("invalid-number-of-args")
+ return nil, err
+ }
+
+ device := &voltha.Device{}
+ transactionID := &ic.StrType{}
+ fromTopic := &ic.StrType{}
+ for _, arg := range args {
+ switch arg.Key {
+ case "device":
+ if err := ptypes.UnmarshalAny(arg.Value, device); err != nil {
+ log.Warnw("cannot-unmarshal-device", log.Fields{"error": err})
+ return nil, err
+ }
+ case kafka.TransactionKey:
+ if err := ptypes.UnmarshalAny(arg.Value, transactionID); err != nil {
+ log.Warnw("cannot-unmarshal-transaction-ID", log.Fields{"error": err})
+ return nil, err
+ }
+ case kafka.FromTopic:
+ if err := ptypes.UnmarshalAny(arg.Value, fromTopic); err != nil {
+ log.Warnw("cannot-unmarshal-from-topic", log.Fields{"error": err})
+ return nil, err
+ }
+ }
+ }
+ //Update the core reference for that device
+ rhp.coreProxy.UpdateCoreReference(device.Id, fromTopic.Val)
+ //Invoke the Reenable_device API on the adapter
+ if err := rhp.adapter.Reenable_device(device); err != nil {
+ return nil, status.Errorf(codes.NotFound, "%s", err.Error())
+ }
+ return new(empty.Empty), nil
+}
+
+func (rhp *RequestHandlerProxy) Reboot_device(args []*ic.Argument) (*empty.Empty, error) {
+ if len(args) < 3 {
+ log.Warn("invalid-number-of-args", log.Fields{"args": args})
+ err := errors.New("invalid-number-of-args")
+ return nil, err
+ }
+
+ device := &voltha.Device{}
+ transactionID := &ic.StrType{}
+ fromTopic := &ic.StrType{}
+ for _, arg := range args {
+ switch arg.Key {
+ case "device":
+ if err := ptypes.UnmarshalAny(arg.Value, device); err != nil {
+ log.Warnw("cannot-unmarshal-device", log.Fields{"error": err})
+ return nil, err
+ }
+ case kafka.TransactionKey:
+ if err := ptypes.UnmarshalAny(arg.Value, transactionID); err != nil {
+ log.Warnw("cannot-unmarshal-transaction-ID", log.Fields{"error": err})
+ return nil, err
+ }
+ case kafka.FromTopic:
+ if err := ptypes.UnmarshalAny(arg.Value, fromTopic); err != nil {
+ log.Warnw("cannot-unmarshal-from-topic", log.Fields{"error": err})
+ return nil, err
+ }
+ }
+ }
+ //Update the core reference for that device
+ rhp.coreProxy.UpdateCoreReference(device.Id, fromTopic.Val)
+ //Invoke the Reboot_device API on the adapter
+ if err := rhp.adapter.Reboot_device(device); err != nil {
+ return nil, status.Errorf(codes.NotFound, "%s", err.Error())
+ }
+ return new(empty.Empty), nil
+
+}
+
+func (rhp *RequestHandlerProxy) Self_test_device(args []*ic.Argument) (*empty.Empty, error) {
+ return new(empty.Empty), nil
+}
+
+func (rhp *RequestHandlerProxy) Delete_device(args []*ic.Argument) (*empty.Empty, error) {
+ if len(args) < 3 {
+ log.Warn("invalid-number-of-args", log.Fields{"args": args})
+ err := errors.New("invalid-number-of-args")
+ return nil, err
+ }
+
+ device := &voltha.Device{}
+ transactionID := &ic.StrType{}
+ fromTopic := &ic.StrType{}
+ for _, arg := range args {
+ switch arg.Key {
+ case "device":
+ if err := ptypes.UnmarshalAny(arg.Value, device); err != nil {
+ log.Warnw("cannot-unmarshal-device", log.Fields{"error": err})
+ return nil, err
+ }
+ case kafka.TransactionKey:
+ if err := ptypes.UnmarshalAny(arg.Value, transactionID); err != nil {
+ log.Warnw("cannot-unmarshal-transaction-ID", log.Fields{"error": err})
+ return nil, err
+ }
+ case kafka.FromTopic:
+ if err := ptypes.UnmarshalAny(arg.Value, fromTopic); err != nil {
+ log.Warnw("cannot-unmarshal-from-topic", log.Fields{"error": err})
+ return nil, err
+ }
+ }
+ }
+ //Update the core reference for that device
+ rhp.coreProxy.UpdateCoreReference(device.Id, fromTopic.Val)
+ //Invoke the delete_device API on the adapter
+ if err := rhp.adapter.Delete_device(device); err != nil {
+ return nil, status.Errorf(codes.NotFound, "%s", err.Error())
+ }
+ return new(empty.Empty), nil
+}
+
+func (rhp *RequestHandlerProxy) Get_device_details(args []*ic.Argument) (*empty.Empty, error) {
+ return new(empty.Empty), nil
+}
+
+func (rhp *RequestHandlerProxy) Update_flows_bulk(args []*ic.Argument) (*empty.Empty, error) {
+ log.Debug("Update_flows_bulk")
+ if len(args) < 5 {
+ log.Warn("Update_flows_bulk-invalid-number-of-args", log.Fields{"args": args})
+ err := errors.New("invalid-number-of-args")
+ return nil, err
+ }
+ device := &voltha.Device{}
+ transactionID := &ic.StrType{}
+ flows := &voltha.Flows{}
+ flowMetadata := &voltha.FlowMetadata{}
+ groups := &voltha.FlowGroups{}
+ for _, arg := range args {
+ switch arg.Key {
+ case "device":
+ if err := ptypes.UnmarshalAny(arg.Value, device); err != nil {
+ log.Warnw("cannot-unmarshal-device", log.Fields{"error": err})
+ return nil, err
+ }
+ case "flows":
+ if err := ptypes.UnmarshalAny(arg.Value, flows); err != nil {
+ log.Warnw("cannot-unmarshal-flows", log.Fields{"error": err})
+ return nil, err
+ }
+ case "groups":
+ if err := ptypes.UnmarshalAny(arg.Value, groups); err != nil {
+ log.Warnw("cannot-unmarshal-groups", log.Fields{"error": err})
+ return nil, err
+ }
+ case "flow_metadata":
+ if err := ptypes.UnmarshalAny(arg.Value, flowMetadata); err != nil {
+ log.Warnw("cannot-unmarshal-metadata", log.Fields{"error": err})
+ return nil, err
+ }
+ case kafka.TransactionKey:
+ if err := ptypes.UnmarshalAny(arg.Value, transactionID); err != nil {
+ log.Warnw("cannot-unmarshal-transaction-ID", log.Fields{"error": err})
+ return nil, err
+ }
+ }
+ }
+ log.Debugw("Update_flows_bulk", log.Fields{"flows": flows, "groups": groups})
+ //Invoke the bulk flow update API of the adapter
+ if err := rhp.adapter.Update_flows_bulk(device, flows, groups, flowMetadata); err != nil {
+ return nil, status.Errorf(codes.NotFound, "%s", err.Error())
+ }
+ return new(empty.Empty), nil
+}
+
+func (rhp *RequestHandlerProxy) Update_flows_incrementally(args []*ic.Argument) (*empty.Empty, error) {
+ log.Debug("Update_flows_incrementally")
+ if len(args) < 5 {
+ log.Warn("Update_flows_incrementally-invalid-number-of-args", log.Fields{"args": args})
+ err := errors.New("invalid-number-of-args")
+ return nil, err
+ }
+ device := &voltha.Device{}
+ transactionID := &ic.StrType{}
+ flows := &openflow_13.FlowChanges{}
+ flowMetadata := &voltha.FlowMetadata{}
+ groups := &openflow_13.FlowGroupChanges{}
+ for _, arg := range args {
+ switch arg.Key {
+ case "device":
+ if err := ptypes.UnmarshalAny(arg.Value, device); err != nil {
+ log.Warnw("cannot-unmarshal-device", log.Fields{"error": err})
+ return nil, err
+ }
+ case "flow_changes":
+ if err := ptypes.UnmarshalAny(arg.Value, flows); err != nil {
+ log.Warnw("cannot-unmarshal-flows", log.Fields{"error": err})
+ return nil, err
+ }
+ case "group_changes":
+ if err := ptypes.UnmarshalAny(arg.Value, groups); err != nil {
+ log.Warnw("cannot-unmarshal-groups", log.Fields{"error": err})
+ return nil, err
+ }
+ case "flow_metadata":
+ if err := ptypes.UnmarshalAny(arg.Value, flowMetadata); err != nil {
+ log.Warnw("cannot-unmarshal-metadata", log.Fields{"error": err})
+ return nil, err
+ }
+ case kafka.TransactionKey:
+ if err := ptypes.UnmarshalAny(arg.Value, transactionID); err != nil {
+ log.Warnw("cannot-unmarshal-transaction-ID", log.Fields{"error": err})
+ return nil, err
+ }
+ }
+ }
+ log.Debugw("Update_flows_incrementally", log.Fields{"flows": flows, "groups": groups})
+ //Invoke the incremental flow update API of the adapter
+ if err := rhp.adapter.Update_flows_incrementally(device, flows, groups, flowMetadata); err != nil {
+ return nil, status.Errorf(codes.NotFound, "%s", err.Error())
+ }
+ return new(empty.Empty), nil
+}
+
+func (rhp *RequestHandlerProxy) Update_pm_config(args []*ic.Argument) (*empty.Empty, error) {
+ log.Debug("Update_pm_config")
+ if len(args) < 2 {
+ log.Warn("Update_pm_config-invalid-number-of-args", log.Fields{"args": args})
+ err := errors.New("invalid-number-of-args")
+ return nil, err
+ }
+ device := &voltha.Device{}
+ transactionID := &ic.StrType{}
+ pmConfigs := &voltha.PmConfigs{}
+ for _, arg := range args {
+ switch arg.Key {
+ case "device":
+ if err := ptypes.UnmarshalAny(arg.Value, device); err != nil {
+ log.Warnw("cannot-unmarshal-device", log.Fields{"error": err})
+ return nil, err
+ }
+ case "pm_configs":
+ if err := ptypes.UnmarshalAny(arg.Value, pmConfigs); err != nil {
+ log.Warnw("cannot-unmarshal-pm-configs", log.Fields{"error": err})
+ return nil, err
+ }
+ case kafka.TransactionKey:
+ if err := ptypes.UnmarshalAny(arg.Value, transactionID); err != nil {
+ log.Warnw("cannot-unmarshal-transaction-ID", log.Fields{"error": err})
+ return nil, err
+ }
+ }
+ }
+ log.Debugw("Update_pm_config", log.Fields{"deviceId": device.Id, "pmConfigs": pmConfigs})
+ //Invoke the pm config update API of the adapter
+ if err := rhp.adapter.Update_pm_config(device, pmConfigs); err != nil {
+ return nil, status.Errorf(codes.NotFound, "%s", err.Error())
+ }
+ return new(empty.Empty), nil
+}
+
+func (rhp *RequestHandlerProxy) Receive_packet_out(args []*ic.Argument) (*empty.Empty, error) {
+ log.Debugw("Receive_packet_out", log.Fields{"args": args})
+ if len(args) < 3 {
+ log.Warn("Receive_packet_out-invalid-number-of-args", log.Fields{"args": args})
+ err := errors.New("invalid-number-of-args")
+ return nil, err
+ }
+ deviceId := &ic.StrType{}
+ egressPort := &ic.IntType{}
+ packet := &openflow_13.OfpPacketOut{}
+ transactionID := &ic.StrType{}
+ for _, arg := range args {
+ switch arg.Key {
+ case "deviceId":
+ if err := ptypes.UnmarshalAny(arg.Value, deviceId); err != nil {
+ log.Warnw("cannot-unmarshal-deviceId", log.Fields{"error": err})
+ return nil, err
+ }
+ case "outPort":
+ if err := ptypes.UnmarshalAny(arg.Value, egressPort); err != nil {
+ log.Warnw("cannot-unmarshal-egressPort", log.Fields{"error": err})
+ return nil, err
+ }
+ case "packet":
+ if err := ptypes.UnmarshalAny(arg.Value, packet); err != nil {
+ log.Warnw("cannot-unmarshal-packet", log.Fields{"error": err})
+ return nil, err
+ }
+ case kafka.TransactionKey:
+ if err := ptypes.UnmarshalAny(arg.Value, transactionID); err != nil {
+ log.Warnw("cannot-unmarshal-transaction-ID", log.Fields{"error": err})
+ return nil, err
+ }
+ }
+ }
+ log.Debugw("Receive_packet_out", log.Fields{"deviceId": deviceId.Val, "outPort": egressPort, "packet": packet})
+ //Invoke the adopt device on the adapter
+ if err := rhp.adapter.Receive_packet_out(deviceId.Val, int(egressPort.Val), packet); err != nil {
+ return nil, status.Errorf(codes.NotFound, "%s", err.Error())
+ }
+ return new(empty.Empty), nil
+}
+
+func (rhp *RequestHandlerProxy) Suppress_alarm(args []*ic.Argument) (*empty.Empty, error) {
+ return new(empty.Empty), nil
+}
+
+func (rhp *RequestHandlerProxy) Unsuppress_alarm(args []*ic.Argument) (*empty.Empty, error) {
+ return new(empty.Empty), nil
+}
+
+func (rhp *RequestHandlerProxy) Get_ofp_device_info(args []*ic.Argument) (*ic.SwitchCapability, error) {
+ if len(args) < 2 {
+ log.Warn("invalid-number-of-args", log.Fields{"args": args})
+ err := errors.New("invalid-number-of-args")
+ return nil, err
+ }
+ device := &voltha.Device{}
+ transactionID := &ic.StrType{}
+ for _, arg := range args {
+ switch arg.Key {
+ case "device":
+ if err := ptypes.UnmarshalAny(arg.Value, device); err != nil {
+ log.Warnw("cannot-unmarshal-device", log.Fields{"error": err})
+ return nil, err
+ }
+ case kafka.TransactionKey:
+ if err := ptypes.UnmarshalAny(arg.Value, transactionID); err != nil {
+ log.Warnw("cannot-unmarshal-transaction-ID", log.Fields{"error": err})
+ return nil, err
+ }
+ }
+ }
+
+ log.Debugw("Get_ofp_device_info", log.Fields{"deviceId": device.Id})
+
+ var cap *ic.SwitchCapability
+ var err error
+ if cap, err = rhp.adapter.Get_ofp_device_info(device); err != nil {
+ return nil, status.Errorf(codes.NotFound, "%s", err.Error())
+ }
+ log.Debugw("Get_ofp_device_info", log.Fields{"cap": cap})
+ return cap, nil
+}
+
+func (rhp *RequestHandlerProxy) Get_ofp_port_info(args []*ic.Argument) (*ic.PortCapability, error) {
+ if len(args) < 3 {
+ log.Warn("invalid-number-of-args", log.Fields{"args": args})
+ err := errors.New("invalid-number-of-args")
+ return nil, err
+ }
+ device := &voltha.Device{}
+ pNo := &ic.IntType{}
+ transactionID := &ic.StrType{}
+ for _, arg := range args {
+ switch arg.Key {
+ case "device":
+ if err := ptypes.UnmarshalAny(arg.Value, device); err != nil {
+ log.Warnw("cannot-unmarshal-device", log.Fields{"error": err})
+ return nil, err
+ }
+ case "port_no":
+ if err := ptypes.UnmarshalAny(arg.Value, pNo); err != nil {
+ log.Warnw("cannot-unmarshal-port-no", log.Fields{"error": err})
+ return nil, err
+ }
+ case kafka.TransactionKey:
+ if err := ptypes.UnmarshalAny(arg.Value, transactionID); err != nil {
+ log.Warnw("cannot-unmarshal-transaction-ID", log.Fields{"error": err})
+ return nil, err
+ }
+ }
+ }
+ log.Debugw("Get_ofp_port_info", log.Fields{"deviceId": device.Id, "portNo": pNo.Val})
+ var cap *ic.PortCapability
+ var err error
+ if cap, err = rhp.adapter.Get_ofp_port_info(device, pNo.Val); err != nil {
+ return nil, status.Errorf(codes.NotFound, "%s", err.Error())
+ }
+ return cap, nil
+}
+
+func (rhp *RequestHandlerProxy) Process_inter_adapter_message(args []*ic.Argument) (*empty.Empty, error) {
+ if len(args) < 2 {
+ log.Warn("invalid-number-of-args", log.Fields{"args": args})
+ err := errors.New("invalid-number-of-args")
+ return nil, err
+ }
+ iaMsg := &ic.InterAdapterMessage{}
+ transactionID := &ic.StrType{}
+ for _, arg := range args {
+ switch arg.Key {
+ case "msg":
+ if err := ptypes.UnmarshalAny(arg.Value, iaMsg); err != nil {
+ log.Warnw("cannot-unmarshal-device", log.Fields{"error": err})
+ return nil, err
+ }
+ case kafka.TransactionKey:
+ if err := ptypes.UnmarshalAny(arg.Value, transactionID); err != nil {
+ log.Warnw("cannot-unmarshal-transaction-ID", log.Fields{"error": err})
+ return nil, err
+ }
+ }
+ }
+
+ log.Debugw("Process_inter_adapter_message", log.Fields{"msgId": iaMsg.Header.Id})
+
+ //Invoke the inter adapter API on the handler
+ if err := rhp.adapter.Process_inter_adapter_message(iaMsg); err != nil {
+ return nil, status.Errorf(codes.NotFound, "%s", err.Error())
+ }
+
+ return new(empty.Empty), nil
+}
+
+func (rhp *RequestHandlerProxy) Download_image(args []*ic.Argument) (*voltha.ImageDownload, error) {
+ return &voltha.ImageDownload{}, nil
+}
+
+func (rhp *RequestHandlerProxy) Get_image_download_status(args []*ic.Argument) (*voltha.ImageDownload, error) {
+ return &voltha.ImageDownload{}, nil
+}
+
+func (rhp *RequestHandlerProxy) Cancel_image_download(args []*ic.Argument) (*voltha.ImageDownload, error) {
+ return &voltha.ImageDownload{}, nil
+}
+
+func (rhp *RequestHandlerProxy) Activate_image_update(args []*ic.Argument) (*voltha.ImageDownload, error) {
+ return &voltha.ImageDownload{}, nil
+}
+
+func (rhp *RequestHandlerProxy) Revert_image_update(args []*ic.Argument) (*voltha.ImageDownload, error) {
+ return &voltha.ImageDownload{}, nil
+}
diff --git a/vendor/github.com/opencord/voltha-lib-go/pkg/adapters/common/utils.go b/vendor/github.com/opencord/voltha-lib-go/pkg/adapters/common/utils.go
new file mode 100644
index 0000000..d3c562a
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/pkg/adapters/common/utils.go
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package common
+
+import (
+ "fmt"
+ "math/rand"
+ "time"
+)
+
+//GetRandomSerialNumber returns a serial number formatted as "HOST:PORT"
+func GetRandomSerialNumber() string {
+ rand.Seed(time.Now().UnixNano())
+ return fmt.Sprintf("%d.%d.%d.%d:%d",
+ rand.Intn(255),
+ rand.Intn(255),
+ rand.Intn(255),
+ rand.Intn(255),
+ rand.Intn(9000)+1000,
+ )
+}
+
+//GetRandomMacAddress returns a random mac address
+func GetRandomMacAddress() string {
+ rand.Seed(time.Now().UnixNano())
+ return fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x",
+ rand.Intn(128),
+ rand.Intn(128),
+ rand.Intn(128),
+ rand.Intn(128),
+ rand.Intn(128),
+ rand.Intn(128),
+ )
+}
+
+const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+const (
+ letterIdxBits = 6 // 6 bits to represent a letter index
+ letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
+ letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
+)
+
+var src = rand.NewSource(time.Now().UnixNano())
+
+func GetRandomString(n int) string {
+ b := make([]byte, n)
+ // A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
+ for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
+ if remain == 0 {
+ cache, remain = src.Int63(), letterIdxMax
+ }
+ if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
+ b[i] = letterBytes[idx]
+ i--
+ }
+ cache >>= letterIdxBits
+ remain--
+ }
+ return string(b)
+}
diff --git a/vendor/github.com/opencord/voltha-lib-go/pkg/adapters/iAdapter.go b/vendor/github.com/opencord/voltha-lib-go/pkg/adapters/iAdapter.go
new file mode 100644
index 0000000..82fa644
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/pkg/adapters/iAdapter.go
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package adapters
+
+import (
+ ic "github.com/opencord/voltha-protos/go/inter_container"
+ "github.com/opencord/voltha-protos/go/openflow_13"
+ "github.com/opencord/voltha-protos/go/voltha"
+)
+
+//IAdapter represents the set of APIs a voltha adapter has to support.
+type IAdapter interface {
+ Adapter_descriptor() error
+ Device_types() (*voltha.DeviceTypes, error)
+ Health() (*voltha.HealthStatus, error)
+ Adopt_device(device *voltha.Device) error
+ Reconcile_device(device *voltha.Device) error
+ Abandon_device(device *voltha.Device) error
+ Disable_device(device *voltha.Device) error
+ Reenable_device(device *voltha.Device) error
+ Reboot_device(device *voltha.Device) error
+ Self_test_device(device *voltha.Device) error
+ Delete_device(device *voltha.Device) error
+ Get_device_details(device *voltha.Device) error
+ Update_flows_bulk(device *voltha.Device, flows *voltha.Flows, groups *voltha.FlowGroups, flowMetadata *voltha.FlowMetadata) error
+ Update_flows_incrementally(device *voltha.Device, flows *openflow_13.FlowChanges, groups *openflow_13.FlowGroupChanges, flowMetadata *voltha.FlowMetadata) error
+ Update_pm_config(device *voltha.Device, pm_configs *voltha.PmConfigs) error
+ Receive_packet_out(deviceId string, egress_port_no int, msg *openflow_13.OfpPacketOut) error
+ Suppress_alarm(filter *voltha.AlarmFilter) error
+ Unsuppress_alarm(filter *voltha.AlarmFilter) error
+ Get_ofp_device_info(device *voltha.Device) (*ic.SwitchCapability, error)
+ Get_ofp_port_info(device *voltha.Device, port_no int64) (*ic.PortCapability, error)
+ Process_inter_adapter_message(msg *ic.InterAdapterMessage) error
+ Download_image(device *voltha.Device, request *voltha.ImageDownload) (*voltha.ImageDownload, error)
+ Get_image_download_status(device *voltha.Device, request *voltha.ImageDownload) (*voltha.ImageDownload, error)
+ Cancel_image_download(device *voltha.Device, request *voltha.ImageDownload) (*voltha.ImageDownload, error)
+ Activate_image_update(device *voltha.Device, request *voltha.ImageDownload) (*voltha.ImageDownload, error)
+ Revert_image_update(device *voltha.Device, request *voltha.ImageDownload) (*voltha.ImageDownload, error)
+}
diff --git a/vendor/github.com/opencord/voltha-lib-go/pkg/db/kvstore/client.go b/vendor/github.com/opencord/voltha-lib-go/pkg/db/kvstore/client.go
new file mode 100644
index 0000000..f40d10e
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/pkg/db/kvstore/client.go
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package kvstore
+
+import (
+ "github.com/opencord/voltha-lib-go/pkg/log"
+)
+
+const (
+ // Default timeout in seconds when making a kvstore request
+ defaultKVGetTimeout = 5
+ // Maximum channel buffer between publisher/subscriber goroutines
+ maxClientChannelBufferSize = 10
+)
+
+// These constants represent the event types returned by the KV client
+const (
+ PUT = iota
+ DELETE
+ CONNECTIONDOWN
+ UNKNOWN
+)
+
+// KVPair is a common wrapper for key-value pairs returned from the KV store
+type KVPair struct {
+ Key string
+ Value interface{}
+ Version int64
+ Session string
+ Lease int64
+}
+
+func init() {
+ log.AddPackage(log.JSON, log.WarnLevel, nil)
+}
+
+// NewKVPair creates a new KVPair object
+func NewKVPair(key string, value interface{}, session string, lease int64, version int64) *KVPair {
+ kv := new(KVPair)
+ kv.Key = key
+ kv.Value = value
+ kv.Session = session
+ kv.Lease = lease
+ kv.Version = version
+ return kv
+}
+
+// Event is generated by the KV client when a key change is detected
+type Event struct {
+ EventType int
+ Key interface{}
+ Value interface{}
+ Version int64
+}
+
+// NewEvent creates a new Event object
+func NewEvent(eventType int, key interface{}, value interface{}, version int64) *Event {
+ evnt := new(Event)
+ evnt.EventType = eventType
+ evnt.Key = key
+ evnt.Value = value
+ evnt.Version = version
+
+ return evnt
+}
+
+// Client represents the set of APIs a KV Client must implement
+type Client interface {
+ List(key string, timeout int, lock ...bool) (map[string]*KVPair, error)
+ Get(key string, timeout int, lock ...bool) (*KVPair, error)
+ Put(key string, value interface{}, timeout int, lock ...bool) error
+ Delete(key string, timeout int, lock ...bool) error
+ Reserve(key string, value interface{}, ttl int64) (interface{}, error)
+ ReleaseReservation(key string) error
+ ReleaseAllReservations() error
+ RenewReservation(key string) error
+ Watch(key string) chan *Event
+ AcquireLock(lockName string, timeout int) error
+ ReleaseLock(lockName string) error
+ IsConnectionUp(timeout int) bool // timeout in second
+ CloseWatch(key string, ch chan *Event)
+ Close()
+}
diff --git a/vendor/github.com/opencord/voltha-lib-go/pkg/db/kvstore/consulclient.go b/vendor/github.com/opencord/voltha-lib-go/pkg/db/kvstore/consulclient.go
new file mode 100644
index 0000000..c3e3999
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/pkg/db/kvstore/consulclient.go
@@ -0,0 +1,513 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package kvstore
+
+import (
+ "bytes"
+ "context"
+ "errors"
+ log "github.com/opencord/voltha-lib-go/pkg/log"
+ "sync"
+ "time"
+ //log "ciena.com/coordinator/common"
+ consulapi "github.com/hashicorp/consul/api"
+)
+
+type channelContextMap struct {
+ ctx context.Context
+ channel chan *Event
+ cancel context.CancelFunc
+}
+
+// ConsulClient represents the consul KV store client
+type ConsulClient struct {
+ session *consulapi.Session
+ sessionID string
+ consul *consulapi.Client
+ doneCh *chan int
+ keyReservations map[string]interface{}
+ watchedChannelsContext map[string][]*channelContextMap
+ writeLock sync.Mutex
+}
+
+// NewConsulClient returns a new client for the Consul KV store
+func NewConsulClient(addr string, timeout int) (*ConsulClient, error) {
+
+ duration := GetDuration(timeout)
+
+ config := consulapi.DefaultConfig()
+ config.Address = addr
+ config.WaitTime = duration
+ consul, err := consulapi.NewClient(config)
+ if err != nil {
+ log.Error(err)
+ return nil, err
+ }
+
+ doneCh := make(chan int, 1)
+ wChannelsContext := make(map[string][]*channelContextMap)
+ reservations := make(map[string]interface{})
+ return &ConsulClient{consul: consul, doneCh: &doneCh, watchedChannelsContext: wChannelsContext, keyReservations: reservations}, nil
+}
+
+// IsConnectionUp returns whether the connection to the Consul KV store is up
+func (c *ConsulClient) IsConnectionUp(timeout int) bool {
+ log.Error("Unimplemented function")
+ return false
+}
+
+// List returns an array of key-value pairs with key as a prefix. Timeout defines how long the function will
+// wait for a response
+func (c *ConsulClient) List(key string, timeout int, lock ...bool) (map[string]*KVPair, error) {
+ duration := GetDuration(timeout)
+
+ kv := c.consul.KV()
+ var queryOptions consulapi.QueryOptions
+ queryOptions.WaitTime = duration
+ // For now we ignore meta data
+ kvps, _, err := kv.List(key, &queryOptions)
+ if err != nil {
+ log.Error(err)
+ return nil, err
+ }
+ m := make(map[string]*KVPair)
+ for _, kvp := range kvps {
+ m[string(kvp.Key)] = NewKVPair(string(kvp.Key), kvp.Value, string(kvp.Session), 0, -1)
+ }
+ return m, nil
+}
+
+// Get returns a key-value pair for a given key. Timeout defines how long the function will
+// wait for a response
+func (c *ConsulClient) Get(key string, timeout int, lock ...bool) (*KVPair, error) {
+
+ duration := GetDuration(timeout)
+
+ kv := c.consul.KV()
+ var queryOptions consulapi.QueryOptions
+ queryOptions.WaitTime = duration
+ // For now we ignore meta data
+ kvp, _, err := kv.Get(key, &queryOptions)
+ if err != nil {
+ log.Error(err)
+ return nil, err
+ }
+ if kvp != nil {
+ return NewKVPair(string(kvp.Key), kvp.Value, string(kvp.Session), 0, -1), nil
+ }
+
+ return nil, nil
+}
+
+// Put writes a key-value pair to the KV store. Value can only be a string or []byte since the consul API
+// accepts only a []byte as a value for a put operation. Timeout defines how long the function will
+// wait for a response
+func (c *ConsulClient) Put(key string, value interface{}, timeout int, lock ...bool) error {
+
+ // Validate that we can create a byte array from the value as consul API expects a byte array
+ var val []byte
+ var er error
+ if val, er = ToByte(value); er != nil {
+ log.Error(er)
+ return er
+ }
+
+ // Create a key value pair
+ kvp := consulapi.KVPair{Key: key, Value: val}
+ kv := c.consul.KV()
+ var writeOptions consulapi.WriteOptions
+ c.writeLock.Lock()
+ defer c.writeLock.Unlock()
+ _, err := kv.Put(&kvp, &writeOptions)
+ if err != nil {
+ log.Error(err)
+ return err
+ }
+ return nil
+}
+
+// Delete removes a key from the KV store. Timeout defines how long the function will
+// wait for a response
+func (c *ConsulClient) Delete(key string, timeout int, lock ...bool) error {
+ kv := c.consul.KV()
+ var writeOptions consulapi.WriteOptions
+ c.writeLock.Lock()
+ defer c.writeLock.Unlock()
+ _, err := kv.Delete(key, &writeOptions)
+ if err != nil {
+ log.Error(err)
+ return err
+ }
+ return nil
+}
+
+func (c *ConsulClient) deleteSession() {
+ if c.sessionID != "" {
+ log.Debug("cleaning-up-session")
+ session := c.consul.Session()
+ _, err := session.Destroy(c.sessionID, nil)
+ if err != nil {
+ log.Errorw("error-cleaning-session", log.Fields{"session": c.sessionID, "error": err})
+ }
+ }
+ c.sessionID = ""
+ c.session = nil
+}
+
+func (c *ConsulClient) createSession(ttl int64, retries int) (*consulapi.Session, string, error) {
+ session := c.consul.Session()
+ entry := &consulapi.SessionEntry{
+ Behavior: consulapi.SessionBehaviorDelete,
+ TTL: "10s", // strconv.FormatInt(ttl, 10) + "s", // disable ttl
+ }
+
+ for {
+ id, meta, err := session.Create(entry, nil)
+ if err != nil {
+ log.Errorw("create-session-error", log.Fields{"error": err})
+ if retries == 0 {
+ return nil, "", err
+ }
+ } else if meta.RequestTime == 0 {
+ log.Errorw("create-session-bad-meta-data", log.Fields{"meta-data": meta})
+ if retries == 0 {
+ return nil, "", errors.New("bad-meta-data")
+ }
+ } else if id == "" {
+ log.Error("create-session-nil-id")
+ if retries == 0 {
+ return nil, "", errors.New("ID-nil")
+ }
+ } else {
+ return session, id, nil
+ }
+ // If retry param is -1 we will retry indefinitely
+ if retries > 0 {
+ retries--
+ }
+ log.Debug("retrying-session-create-after-a-second-delay")
+ time.Sleep(time.Duration(1) * time.Second)
+ }
+}
+
+// Helper function to verify mostly whether the content of two interface types are the same. Focus is []byte and
+// string types
+func isEqual(val1 interface{}, val2 interface{}) bool {
+ b1, err := ToByte(val1)
+ b2, er := ToByte(val2)
+ if err == nil && er == nil {
+ return bytes.Equal(b1, b2)
+ }
+ return val1 == val2
+}
+
+// Reserve is invoked to acquire a key and set it to a given value. Value can only be a string or []byte since
+// the consul API accepts only a []byte. Timeout defines how long the function will wait for a response. TTL
+// defines how long that reservation is valid. When TTL expires the key is unreserved by the KV store itself.
+// If the key is acquired then the value returned will be the value passed in. If the key is already acquired
+// then the value assigned to that key will be returned.
+func (c *ConsulClient) Reserve(key string, value interface{}, ttl int64) (interface{}, error) {
+
+ // Validate that we can create a byte array from the value as consul API expects a byte array
+ var val []byte
+ var er error
+ if val, er = ToByte(value); er != nil {
+ log.Error(er)
+ return nil, er
+ }
+
+ // Cleanup any existing session and recreate new ones. A key is reserved against a session
+ if c.sessionID != "" {
+ c.deleteSession()
+ }
+
+ // Clear session if reservation is not successful
+ reservationSuccessful := false
+ defer func() {
+ if !reservationSuccessful {
+ log.Debug("deleting-session")
+ c.deleteSession()
+ }
+ }()
+
+ session, sessionID, err := c.createSession(ttl, -1)
+ if err != nil {
+ log.Errorw("no-session-created", log.Fields{"error": err})
+ return "", errors.New("no-session-created")
+ }
+ log.Debugw("session-created", log.Fields{"session-id": sessionID})
+ c.sessionID = sessionID
+ c.session = session
+
+ // Try to grap the Key using the session
+ kv := c.consul.KV()
+ kvp := consulapi.KVPair{Key: key, Value: val, Session: c.sessionID}
+ result, _, err := kv.Acquire(&kvp, nil)
+ if err != nil {
+ log.Errorw("error-acquiring-keys", log.Fields{"error": err})
+ return nil, err
+ }
+
+ log.Debugw("key-acquired", log.Fields{"key": key, "status": result})
+
+ // Irrespective whether we were successful in acquiring the key, let's read it back and see if it's us.
+ m, err := c.Get(key, defaultKVGetTimeout)
+ if err != nil {
+ return nil, err
+ }
+ if m != nil {
+ log.Debugw("response-received", log.Fields{"key": m.Key, "m.value": string(m.Value.([]byte)), "value": value})
+ if m.Key == key && isEqual(m.Value, value) {
+ // My reservation is successful - register it. For now, support is only for 1 reservation per key
+ // per session.
+ reservationSuccessful = true
+ c.writeLock.Lock()
+ c.keyReservations[key] = m.Value
+ c.writeLock.Unlock()
+ return m.Value, nil
+ }
+ // My reservation has failed. Return the owner of that key
+ return m.Value, nil
+ }
+ return nil, nil
+}
+
+// ReleaseAllReservations releases all key reservations previously made (using Reserve API)
+func (c *ConsulClient) ReleaseAllReservations() error {
+ kv := c.consul.KV()
+ var kvp consulapi.KVPair
+ var result bool
+ var err error
+
+ c.writeLock.Lock()
+ defer c.writeLock.Unlock()
+
+ for key, value := range c.keyReservations {
+ kvp = consulapi.KVPair{Key: key, Value: value.([]byte), Session: c.sessionID}
+ result, _, err = kv.Release(&kvp, nil)
+ if err != nil {
+ log.Errorw("cannot-release-reservation", log.Fields{"key": key, "error": err})
+ return err
+ }
+ if !result {
+ log.Errorw("cannot-release-reservation", log.Fields{"key": key})
+ }
+ delete(c.keyReservations, key)
+ }
+ return nil
+}
+
+// ReleaseReservation releases reservation for a specific key.
+func (c *ConsulClient) ReleaseReservation(key string) error {
+ var ok bool
+ var reservedValue interface{}
+ c.writeLock.Lock()
+ defer c.writeLock.Unlock()
+ if reservedValue, ok = c.keyReservations[key]; !ok {
+ return errors.New("key-not-reserved:" + key)
+ }
+ // Release the reservation
+ kv := c.consul.KV()
+ kvp := consulapi.KVPair{Key: key, Value: reservedValue.([]byte), Session: c.sessionID}
+
+ result, _, er := kv.Release(&kvp, nil)
+ if er != nil {
+ return er
+ }
+ // Remove that key entry on success
+ if result {
+ delete(c.keyReservations, key)
+ return nil
+ }
+ return errors.New("key-cannot-be-unreserved")
+}
+
+// RenewReservation renews a reservation. A reservation will go stale after the specified TTL (Time To Live)
+// period specified when reserving the key
+func (c *ConsulClient) RenewReservation(key string) error {
+ // In the case of Consul, renew reservation of a reserve key only require renewing the client session.
+
+ c.writeLock.Lock()
+ defer c.writeLock.Unlock()
+
+ // Verify the key was reserved
+ if _, ok := c.keyReservations[key]; !ok {
+ return errors.New("key-not-reserved")
+ }
+
+ if c.session == nil {
+ return errors.New("no-session-exist")
+ }
+
+ var writeOptions consulapi.WriteOptions
+ if _, _, err := c.session.Renew(c.sessionID, &writeOptions); err != nil {
+ return err
+ }
+ return nil
+}
+
+// Watch provides the watch capability on a given key. It returns a channel onto which the callee needs to
+// listen to receive Events.
+func (c *ConsulClient) Watch(key string) chan *Event {
+
+ // Create a new channel
+ ch := make(chan *Event, maxClientChannelBufferSize)
+
+ // Create a context to track this request
+ watchContext, cFunc := context.WithCancel(context.Background())
+
+ // Save the channel and context reference for later
+ c.writeLock.Lock()
+ defer c.writeLock.Unlock()
+ ccm := channelContextMap{channel: ch, ctx: watchContext, cancel: cFunc}
+ c.watchedChannelsContext[key] = append(c.watchedChannelsContext[key], &ccm)
+
+ // Launch a go routine to listen for updates
+ go c.listenForKeyChange(watchContext, key, ch)
+
+ return ch
+}
+
+// CloseWatch closes a specific watch. Both the key and the channel are required when closing a watch as there
+// may be multiple listeners on the same key. The previously created channel serves as a key
+func (c *ConsulClient) CloseWatch(key string, ch chan *Event) {
+ // First close the context
+ var ok bool
+ var watchedChannelsContexts []*channelContextMap
+ c.writeLock.Lock()
+ defer c.writeLock.Unlock()
+ if watchedChannelsContexts, ok = c.watchedChannelsContext[key]; !ok {
+ log.Errorw("key-has-no-watched-context-or-channel", log.Fields{"key": key})
+ return
+ }
+ // Look for the channels
+ var pos = -1
+ for i, chCtxMap := range watchedChannelsContexts {
+ if chCtxMap.channel == ch {
+ log.Debug("channel-found")
+ chCtxMap.cancel()
+ //close the channel
+ close(ch)
+ pos = i
+ break
+ }
+ }
+ // Remove that entry if present
+ if pos >= 0 {
+ c.watchedChannelsContext[key] = append(c.watchedChannelsContext[key][:pos], c.watchedChannelsContext[key][pos+1:]...)
+ }
+ log.Debugw("watched-channel-exiting", log.Fields{"key": key, "channel": c.watchedChannelsContext[key]})
+}
+
+func (c *ConsulClient) isKVEqual(kv1 *consulapi.KVPair, kv2 *consulapi.KVPair) bool {
+ if (kv1 == nil) && (kv2 == nil) {
+ return true
+ } else if (kv1 == nil) || (kv2 == nil) {
+ return false
+ }
+ // Both the KV should be non-null here
+ if kv1.Key != kv2.Key ||
+ !bytes.Equal(kv1.Value, kv2.Value) ||
+ kv1.Session != kv2.Session ||
+ kv1.LockIndex != kv2.LockIndex ||
+ kv1.ModifyIndex != kv2.ModifyIndex {
+ return false
+ }
+ return true
+}
+
+func (c *ConsulClient) listenForKeyChange(watchContext context.Context, key string, ch chan *Event) {
+ log.Debugw("start-watching-channel", log.Fields{"key": key, "channel": ch})
+
+ defer c.CloseWatch(key, ch)
+ duration := GetDuration(defaultKVGetTimeout)
+ kv := c.consul.KV()
+ var queryOptions consulapi.QueryOptions
+ queryOptions.WaitTime = duration
+
+ // Get the existing value, if any
+ previousKVPair, meta, err := kv.Get(key, &queryOptions)
+ if err != nil {
+ log.Debug(err)
+ }
+ lastIndex := meta.LastIndex
+
+ // Wait for change. Push any change onto the channel and keep waiting for new update
+ //var waitOptions consulapi.QueryOptions
+ var pair *consulapi.KVPair
+ //watchContext, _ := context.WithCancel(context.Background())
+ waitOptions := queryOptions.WithContext(watchContext)
+ for {
+ //waitOptions = consulapi.QueryOptions{WaitIndex: lastIndex}
+ waitOptions.WaitIndex = lastIndex
+ pair, meta, err = kv.Get(key, waitOptions)
+ select {
+ case <-watchContext.Done():
+ log.Debug("done-event-received-exiting")
+ return
+ default:
+ if err != nil {
+ log.Warnw("error-from-watch", log.Fields{"error": err})
+ ch <- NewEvent(CONNECTIONDOWN, key, []byte(""), -1)
+ } else {
+ log.Debugw("index-state", log.Fields{"lastindex": lastIndex, "newindex": meta.LastIndex, "key": key})
+ }
+ }
+ if err != nil {
+ log.Debug(err)
+ // On error, block for 10 milliseconds to prevent endless loop
+ time.Sleep(10 * time.Millisecond)
+ } else if meta.LastIndex <= lastIndex {
+ log.Info("no-index-change-or-negative")
+ } else {
+ log.Debugw("update-received", log.Fields{"pair": pair})
+ if pair == nil {
+ ch <- NewEvent(DELETE, key, []byte(""), -1)
+ } else if !c.isKVEqual(pair, previousKVPair) {
+ // Push the change onto the channel if the data has changed
+ // For now just assume it's a PUT change
+ log.Debugw("pair-details", log.Fields{"session": pair.Session, "key": pair.Key, "value": pair.Value})
+ ch <- NewEvent(PUT, pair.Key, pair.Value, -1)
+ }
+ previousKVPair = pair
+ lastIndex = meta.LastIndex
+ }
+ }
+}
+
+// Close closes the KV store client
+func (c *ConsulClient) Close() {
+ var writeOptions consulapi.WriteOptions
+ // Inform any goroutine it's time to say goodbye.
+ c.writeLock.Lock()
+ defer c.writeLock.Unlock()
+ if c.doneCh != nil {
+ close(*c.doneCh)
+ }
+
+ // Clear the sessionID
+ if _, err := c.consul.Session().Destroy(c.sessionID, &writeOptions); err != nil {
+ log.Errorw("error-closing-client", log.Fields{"error": err})
+ }
+}
+
+func (c *ConsulClient) AcquireLock(lockName string, timeout int) error {
+ return nil
+}
+
+func (c *ConsulClient) ReleaseLock(lockName string) error {
+ return nil
+}
diff --git a/vendor/github.com/opencord/voltha-lib-go/pkg/db/kvstore/etcdclient.go b/vendor/github.com/opencord/voltha-lib-go/pkg/db/kvstore/etcdclient.go
new file mode 100644
index 0000000..beac4e0
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/pkg/db/kvstore/etcdclient.go
@@ -0,0 +1,504 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package kvstore
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "github.com/opencord/voltha-lib-go/pkg/log"
+ v3Client "go.etcd.io/etcd/clientv3"
+ v3Concurrency "go.etcd.io/etcd/clientv3/concurrency"
+ v3rpcTypes "go.etcd.io/etcd/etcdserver/api/v3rpc/rpctypes"
+ "sync"
+)
+
+// EtcdClient represents the Etcd KV store client
+type EtcdClient struct {
+ ectdAPI *v3Client.Client
+ leaderRev v3Client.Client
+ keyReservations map[string]*v3Client.LeaseID
+ watchedChannels sync.Map
+ writeLock sync.Mutex
+ lockToMutexMap map[string]*v3Concurrency.Mutex
+ lockToSessionMap map[string]*v3Concurrency.Session
+ lockToMutexLock sync.Mutex
+}
+
+// NewEtcdClient returns a new client for the Etcd KV store
+func NewEtcdClient(addr string, timeout int) (*EtcdClient, error) {
+ duration := GetDuration(timeout)
+
+ c, err := v3Client.New(v3Client.Config{
+ Endpoints: []string{addr},
+ DialTimeout: duration,
+ })
+ if err != nil {
+ log.Error(err)
+ return nil, err
+ }
+
+ reservations := make(map[string]*v3Client.LeaseID)
+ lockMutexMap := make(map[string]*v3Concurrency.Mutex)
+ lockSessionMap := make(map[string]*v3Concurrency.Session)
+
+ return &EtcdClient{ectdAPI: c, keyReservations: reservations, lockToMutexMap: lockMutexMap,
+ lockToSessionMap: lockSessionMap}, nil
+}
+
+// IsConnectionUp returns whether the connection to the Etcd KV store is up. If a timeout occurs then
+// it is assumed the connection is down or unreachable.
+func (c *EtcdClient) IsConnectionUp(timeout int) bool {
+ // Let's try to get a non existent key. If the connection is up then there will be no error returned.
+ if _, err := c.Get("non-existent-key", timeout); err != nil {
+ return false
+ }
+ return true
+}
+
+// List returns an array of key-value pairs with key as a prefix. Timeout defines how long the function will
+// wait for a response
+func (c *EtcdClient) List(key string, timeout int, lock ...bool) (map[string]*KVPair, error) {
+ duration := GetDuration(timeout)
+
+ ctx, cancel := context.WithTimeout(context.Background(), duration)
+
+ resp, err := c.ectdAPI.Get(ctx, key, v3Client.WithPrefix())
+ cancel()
+ if err != nil {
+ log.Error(err)
+ return nil, err
+ }
+ m := make(map[string]*KVPair)
+ for _, ev := range resp.Kvs {
+ m[string(ev.Key)] = NewKVPair(string(ev.Key), ev.Value, "", ev.Lease, ev.Version)
+ }
+ return m, nil
+}
+
+// Get returns a key-value pair for a given key. Timeout defines how long the function will
+// wait for a response
+func (c *EtcdClient) Get(key string, timeout int, lock ...bool) (*KVPair, error) {
+ duration := GetDuration(timeout)
+
+ ctx, cancel := context.WithTimeout(context.Background(), duration)
+
+ resp, err := c.ectdAPI.Get(ctx, key)
+ cancel()
+ if err != nil {
+ log.Error(err)
+ return nil, err
+ }
+ for _, ev := range resp.Kvs {
+ // Only one value is returned
+ return NewKVPair(string(ev.Key), ev.Value, "", ev.Lease, ev.Version), nil
+ }
+ return nil, nil
+}
+
+// Put writes a key-value pair to the KV store. Value can only be a string or []byte since the etcd API
+// accepts only a string as a value for a put operation. Timeout defines how long the function will
+// wait for a response
+func (c *EtcdClient) Put(key string, value interface{}, timeout int, lock ...bool) error {
+
+ // Validate that we can convert value to a string as etcd API expects a string
+ var val string
+ var er error
+ if val, er = ToString(value); er != nil {
+ return fmt.Errorf("unexpected-type-%T", value)
+ }
+
+ duration := GetDuration(timeout)
+
+ ctx, cancel := context.WithTimeout(context.Background(), duration)
+
+ c.writeLock.Lock()
+ defer c.writeLock.Unlock()
+
+ var err error
+ // Check if there is already a lease for this key - if there is then use it, otherwise a PUT will make
+ // that KV key permanent instead of automatically removing it after a lease expiration
+ if leaseID, ok := c.keyReservations[key]; ok {
+ _, err = c.ectdAPI.Put(ctx, key, val, v3Client.WithLease(*leaseID))
+ } else {
+ _, err = c.ectdAPI.Put(ctx, key, val)
+ }
+ cancel()
+ if err != nil {
+ switch err {
+ case context.Canceled:
+ log.Warnw("context-cancelled", log.Fields{"error": err})
+ case context.DeadlineExceeded:
+ log.Warnw("context-deadline-exceeded", log.Fields{"error": err})
+ case v3rpcTypes.ErrEmptyKey:
+ log.Warnw("etcd-client-error", log.Fields{"error": err})
+ default:
+ log.Warnw("bad-endpoints", log.Fields{"error": err})
+ }
+ return err
+ }
+ return nil
+}
+
+// Delete removes a key from the KV store. Timeout defines how long the function will
+// wait for a response
+func (c *EtcdClient) Delete(key string, timeout int, lock ...bool) error {
+
+ duration := GetDuration(timeout)
+
+ ctx, cancel := context.WithTimeout(context.Background(), duration)
+
+ defer cancel()
+
+ c.writeLock.Lock()
+ defer c.writeLock.Unlock()
+
+ // delete the key
+ if _, err := c.ectdAPI.Delete(ctx, key); err != nil {
+ log.Errorw("failed-to-delete-key", log.Fields{"key": key, "error": err})
+ return err
+ }
+ log.Debugw("key(s)-deleted", log.Fields{"key": key})
+ return nil
+}
+
+// Reserve is invoked to acquire a key and set it to a given value. Value can only be a string or []byte since
+// the etcd API accepts only a string. Timeout defines how long the function will wait for a response. TTL
+// defines how long that reservation is valid. When TTL expires the key is unreserved by the KV store itself.
+// If the key is acquired then the value returned will be the value passed in. If the key is already acquired
+// then the value assigned to that key will be returned.
+func (c *EtcdClient) Reserve(key string, value interface{}, ttl int64) (interface{}, error) {
+ // Validate that we can convert value to a string as etcd API expects a string
+ var val string
+ var er error
+ if val, er = ToString(value); er != nil {
+ return nil, fmt.Errorf("unexpected-type%T", value)
+ }
+
+ // Create a lease
+ resp, err := c.ectdAPI.Grant(context.Background(), ttl)
+ if err != nil {
+ log.Error(err)
+ return nil, err
+ }
+ // Register the lease id
+ c.writeLock.Lock()
+ c.keyReservations[key] = &resp.ID
+ c.writeLock.Unlock()
+
+ // Revoke lease if reservation is not successful
+ reservationSuccessful := false
+ defer func() {
+ if !reservationSuccessful {
+ if err = c.ReleaseReservation(key); err != nil {
+ log.Error("cannot-release-lease")
+ }
+ }
+ }()
+
+ // Try to grap the Key with the above lease
+ c.ectdAPI.Txn(context.Background())
+ txn := c.ectdAPI.Txn(context.Background())
+ txn = txn.If(v3Client.Compare(v3Client.Version(key), "=", 0))
+ txn = txn.Then(v3Client.OpPut(key, val, v3Client.WithLease(resp.ID)))
+ txn = txn.Else(v3Client.OpGet(key))
+ result, er := txn.Commit()
+ if er != nil {
+ return nil, er
+ }
+
+ if !result.Succeeded {
+ // Verify whether we are already the owner of that Key
+ if len(result.Responses) > 0 &&
+ len(result.Responses[0].GetResponseRange().Kvs) > 0 {
+ kv := result.Responses[0].GetResponseRange().Kvs[0]
+ if string(kv.Value) == val {
+ reservationSuccessful = true
+ return value, nil
+ }
+ return kv.Value, nil
+ }
+ } else {
+ // Read the Key to ensure this is our Key
+ m, err := c.Get(key, defaultKVGetTimeout, false)
+ if err != nil {
+ return nil, err
+ }
+ if m != nil {
+ if m.Key == key && isEqual(m.Value, value) {
+ // My reservation is successful - register it. For now, support is only for 1 reservation per key
+ // per session.
+ reservationSuccessful = true
+ return value, nil
+ }
+ // My reservation has failed. Return the owner of that key
+ return m.Value, nil
+ }
+ }
+ return nil, nil
+}
+
+// ReleaseAllReservations releases all key reservations previously made (using Reserve API)
+func (c *EtcdClient) ReleaseAllReservations() error {
+ c.writeLock.Lock()
+ defer c.writeLock.Unlock()
+ for key, leaseID := range c.keyReservations {
+ _, err := c.ectdAPI.Revoke(context.Background(), *leaseID)
+ if err != nil {
+ log.Errorw("cannot-release-reservation", log.Fields{"key": key, "error": err})
+ return err
+ }
+ delete(c.keyReservations, key)
+ }
+ return nil
+}
+
+// ReleaseReservation releases reservation for a specific key.
+func (c *EtcdClient) ReleaseReservation(key string) error {
+ // Get the leaseid using the key
+ log.Debugw("Release-reservation", log.Fields{"key": key})
+ var ok bool
+ var leaseID *v3Client.LeaseID
+ c.writeLock.Lock()
+ defer c.writeLock.Unlock()
+ if leaseID, ok = c.keyReservations[key]; !ok {
+ return nil
+ }
+ if leaseID != nil {
+ _, err := c.ectdAPI.Revoke(context.Background(), *leaseID)
+ if err != nil {
+ log.Error(err)
+ return err
+ }
+ delete(c.keyReservations, key)
+ }
+ return nil
+}
+
+// RenewReservation renews a reservation. A reservation will go stale after the specified TTL (Time To Live)
+// period specified when reserving the key
+func (c *EtcdClient) RenewReservation(key string) error {
+ // Get the leaseid using the key
+ var ok bool
+ var leaseID *v3Client.LeaseID
+ c.writeLock.Lock()
+ defer c.writeLock.Unlock()
+ if leaseID, ok = c.keyReservations[key]; !ok {
+ return errors.New("key-not-reserved")
+ }
+
+ if leaseID != nil {
+ _, err := c.ectdAPI.KeepAliveOnce(context.Background(), *leaseID)
+ if err != nil {
+ log.Errorw("lease-may-have-expired", log.Fields{"error": err})
+ return err
+ }
+ } else {
+ return errors.New("lease-expired")
+ }
+ return nil
+}
+
+// Watch provides the watch capability on a given key. It returns a channel onto which the callee needs to
+// listen to receive Events.
+func (c *EtcdClient) Watch(key string) chan *Event {
+ w := v3Client.NewWatcher(c.ectdAPI)
+ ctx, cancel := context.WithCancel(context.Background())
+ channel := w.Watch(ctx, key)
+
+ // Create a new channel
+ ch := make(chan *Event, maxClientChannelBufferSize)
+
+ // Keep track of the created channels so they can be closed when required
+ channelMap := make(map[chan *Event]v3Client.Watcher)
+ channelMap[ch] = w
+
+ channelMaps := c.addChannelMap(key, channelMap)
+
+ // Changing the log field (from channelMaps) as the underlying logger cannot format the map of channels into a
+ // json format.
+ log.Debugw("watched-channels", log.Fields{"len": len(channelMaps)})
+ // Launch a go routine to listen for updates
+ go c.listenForKeyChange(channel, ch, cancel)
+
+ return ch
+
+}
+
+func (c *EtcdClient) addChannelMap(key string, channelMap map[chan *Event]v3Client.Watcher) []map[chan *Event]v3Client.Watcher {
+ var channels interface{}
+ var exists bool
+
+ if channels, exists = c.watchedChannels.Load(key); exists {
+ channels = append(channels.([]map[chan *Event]v3Client.Watcher), channelMap)
+ } else {
+ channels = []map[chan *Event]v3Client.Watcher{channelMap}
+ }
+ c.watchedChannels.Store(key, channels)
+
+ return channels.([]map[chan *Event]v3Client.Watcher)
+}
+
+func (c *EtcdClient) removeChannelMap(key string, pos int) []map[chan *Event]v3Client.Watcher {
+ var channels interface{}
+ var exists bool
+
+ if channels, exists = c.watchedChannels.Load(key); exists {
+ channels = append(channels.([]map[chan *Event]v3Client.Watcher)[:pos], channels.([]map[chan *Event]v3Client.Watcher)[pos+1:]...)
+ c.watchedChannels.Store(key, channels)
+ }
+
+ return channels.([]map[chan *Event]v3Client.Watcher)
+}
+
+func (c *EtcdClient) getChannelMaps(key string) ([]map[chan *Event]v3Client.Watcher, bool) {
+ var channels interface{}
+ var exists bool
+
+ channels, exists = c.watchedChannels.Load(key)
+
+ if channels == nil {
+ return nil, exists
+ }
+
+ return channels.([]map[chan *Event]v3Client.Watcher), exists
+}
+
+// CloseWatch closes a specific watch. Both the key and the channel are required when closing a watch as there
+// may be multiple listeners on the same key. The previously created channel serves as a key
+func (c *EtcdClient) CloseWatch(key string, ch chan *Event) {
+ // Get the array of channels mapping
+ var watchedChannels []map[chan *Event]v3Client.Watcher
+ var ok bool
+ c.writeLock.Lock()
+ defer c.writeLock.Unlock()
+
+ if watchedChannels, ok = c.getChannelMaps(key); !ok {
+ log.Warnw("key-has-no-watched-channels", log.Fields{"key": key})
+ return
+ }
+ // Look for the channels
+ var pos = -1
+ for i, chMap := range watchedChannels {
+ if t, ok := chMap[ch]; ok {
+ log.Debug("channel-found")
+ // Close the etcd watcher before the client channel. This should close the etcd channel as well
+ if err := t.Close(); err != nil {
+ log.Errorw("watcher-cannot-be-closed", log.Fields{"key": key, "error": err})
+ }
+ pos = i
+ break
+ }
+ }
+
+ channelMaps, _ := c.getChannelMaps(key)
+ // Remove that entry if present
+ if pos >= 0 {
+ channelMaps = c.removeChannelMap(key, pos)
+ }
+ log.Infow("watcher-channel-exiting", log.Fields{"key": key, "channel": channelMaps})
+}
+
+func (c *EtcdClient) listenForKeyChange(channel v3Client.WatchChan, ch chan<- *Event, cancel context.CancelFunc) {
+ log.Debug("start-listening-on-channel ...")
+ defer cancel()
+ defer close(ch)
+ for resp := range channel {
+ for _, ev := range resp.Events {
+ ch <- NewEvent(getEventType(ev), ev.Kv.Key, ev.Kv.Value, ev.Kv.Version)
+ }
+ }
+ log.Debug("stop-listening-on-channel ...")
+}
+
+func getEventType(event *v3Client.Event) int {
+ switch event.Type {
+ case v3Client.EventTypePut:
+ return PUT
+ case v3Client.EventTypeDelete:
+ return DELETE
+ }
+ return UNKNOWN
+}
+
+// Close closes the KV store client
+func (c *EtcdClient) Close() {
+ c.writeLock.Lock()
+ defer c.writeLock.Unlock()
+ if err := c.ectdAPI.Close(); err != nil {
+ log.Errorw("error-closing-client", log.Fields{"error": err})
+ }
+}
+
+func (c *EtcdClient) addLockName(lockName string, lock *v3Concurrency.Mutex, session *v3Concurrency.Session) {
+ c.lockToMutexLock.Lock()
+ defer c.lockToMutexLock.Unlock()
+ c.lockToMutexMap[lockName] = lock
+ c.lockToSessionMap[lockName] = session
+}
+
+func (c *EtcdClient) deleteLockName(lockName string) {
+ c.lockToMutexLock.Lock()
+ defer c.lockToMutexLock.Unlock()
+ delete(c.lockToMutexMap, lockName)
+ delete(c.lockToSessionMap, lockName)
+}
+
+func (c *EtcdClient) getLock(lockName string) (*v3Concurrency.Mutex, *v3Concurrency.Session) {
+ c.lockToMutexLock.Lock()
+ defer c.lockToMutexLock.Unlock()
+ var lock *v3Concurrency.Mutex
+ var session *v3Concurrency.Session
+ if l, exist := c.lockToMutexMap[lockName]; exist {
+ lock = l
+ }
+ if s, exist := c.lockToSessionMap[lockName]; exist {
+ session = s
+ }
+ return lock, session
+}
+
+func (c *EtcdClient) AcquireLock(lockName string, timeout int) error {
+ duration := GetDuration(timeout)
+ ctx, cancel := context.WithTimeout(context.Background(), duration)
+ defer cancel()
+ session, _ := v3Concurrency.NewSession(c.ectdAPI, v3Concurrency.WithContext(ctx))
+ mu := v3Concurrency.NewMutex(session, "/devicelock_"+lockName)
+ if err := mu.Lock(context.Background()); err != nil {
+ cancel()
+ return err
+ }
+ c.addLockName(lockName, mu, session)
+ return nil
+}
+
+func (c *EtcdClient) ReleaseLock(lockName string) error {
+ lock, session := c.getLock(lockName)
+ var err error
+ if lock != nil {
+ if e := lock.Unlock(context.Background()); e != nil {
+ err = e
+ }
+ }
+ if session != nil {
+ if e := session.Close(); e != nil {
+ err = e
+ }
+ }
+ c.deleteLockName(lockName)
+
+ return err
+}
diff --git a/vendor/github.com/opencord/voltha-lib-go/pkg/db/kvstore/kvutils.go b/vendor/github.com/opencord/voltha-lib-go/pkg/db/kvstore/kvutils.go
new file mode 100644
index 0000000..cf9a95c
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/pkg/db/kvstore/kvutils.go
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package kvstore
+
+import (
+ "fmt"
+ "time"
+)
+
+// GetDuration converts a timeout value from int to duration. If the timeout value is
+// either not set of -ve then we default KV timeout (configurable) is used.
+func GetDuration(timeout int) time.Duration {
+ if timeout <= 0 {
+ return defaultKVGetTimeout * time.Second
+ }
+ return time.Duration(timeout) * time.Second
+}
+
+// ToString converts an interface value to a string. The interface should either be of
+// a string type or []byte. Otherwise, an error is returned.
+func ToString(value interface{}) (string, error) {
+ switch t := value.(type) {
+ case []byte:
+ return string(value.([]byte)), nil
+ case string:
+ return value.(string), nil
+ default:
+ return "", fmt.Errorf("unexpected-type-%T", t)
+ }
+}
+
+// ToByte converts an interface value to a []byte. The interface should either be of
+// a string type or []byte. Otherwise, an error is returned.
+func ToByte(value interface{}) ([]byte, error) {
+ switch t := value.(type) {
+ case []byte:
+ return value.([]byte), nil
+ case string:
+ return []byte(value.(string)), nil
+ default:
+ return nil, fmt.Errorf("unexpected-type-%T", t)
+ }
+}
diff --git a/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/backend.go b/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/backend.go
new file mode 100644
index 0000000..fb2c813
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/backend.go
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package model
+
+import (
+ "errors"
+ "fmt"
+ "github.com/opencord/voltha-lib-go/pkg/db/kvstore"
+ "github.com/opencord/voltha-lib-go/pkg/log"
+ "strconv"
+ "sync"
+ "time"
+)
+
+//TODO: missing cache stuff
+//TODO: missing retry stuff
+//TODO: missing proper logging
+
+// Backend structure holds details for accessing the kv store
+type Backend struct {
+ sync.RWMutex
+ Client kvstore.Client
+ StoreType string
+ Host string
+ Port int
+ Timeout int
+ PathPrefix string
+}
+
+// NewBackend creates a new instance of a Backend structure
+func NewBackend(storeType string, host string, port int, timeout int, pathPrefix string) *Backend {
+ var err error
+
+ b := &Backend{
+ StoreType: storeType,
+ Host: host,
+ Port: port,
+ Timeout: timeout,
+ PathPrefix: pathPrefix,
+ }
+
+ address := host + ":" + strconv.Itoa(port)
+ if b.Client, err = b.newClient(address, timeout); err != nil {
+ log.Errorw("failed-to-create-kv-client",
+ log.Fields{
+ "type": storeType, "host": host, "port": port,
+ "timeout": timeout, "prefix": pathPrefix,
+ "error": err.Error(),
+ })
+ }
+
+ return b
+}
+
+func (b *Backend) newClient(address string, timeout int) (kvstore.Client, error) {
+ switch b.StoreType {
+ case "consul":
+ return kvstore.NewConsulClient(address, timeout)
+ case "etcd":
+ return kvstore.NewEtcdClient(address, timeout)
+ }
+ return nil, errors.New("unsupported-kv-store")
+}
+
+func (b *Backend) makePath(key string) string {
+ path := fmt.Sprintf("%s/%s", b.PathPrefix, key)
+ return path
+}
+
+// List retrieves one or more items that match the specified key
+func (b *Backend) List(key string, lock ...bool) (map[string]*kvstore.KVPair, error) {
+ b.Lock()
+ defer b.Unlock()
+
+ formattedPath := b.makePath(key)
+ log.Debugw("listing-key", log.Fields{"key": key, "path": formattedPath, "lock": lock})
+
+ return b.Client.List(formattedPath, b.Timeout, lock...)
+}
+
+// Get retrieves an item that matches the specified key
+func (b *Backend) Get(key string, lock ...bool) (*kvstore.KVPair, error) {
+ b.Lock()
+ defer b.Unlock()
+
+ formattedPath := b.makePath(key)
+ log.Debugw("getting-key", log.Fields{"key": key, "path": formattedPath, "lock": lock})
+
+ start := time.Now()
+ err, pair := b.Client.Get(formattedPath, b.Timeout, lock...)
+ stop := time.Now()
+
+ GetProfiling().AddToDatabaseRetrieveTime(stop.Sub(start).Seconds())
+
+ return err, pair
+}
+
+// Put stores an item value under the specifed key
+func (b *Backend) Put(key string, value interface{}, lock ...bool) error {
+ b.Lock()
+ defer b.Unlock()
+
+ formattedPath := b.makePath(key)
+ log.Debugw("putting-key", log.Fields{"key": key, "value": string(value.([]byte)), "path": formattedPath, "lock": lock})
+
+ return b.Client.Put(formattedPath, value, b.Timeout, lock...)
+}
+
+// Delete removes an item under the specified key
+func (b *Backend) Delete(key string, lock ...bool) error {
+ b.Lock()
+ defer b.Unlock()
+
+ formattedPath := b.makePath(key)
+ log.Debugw("deleting-key", log.Fields{"key": key, "path": formattedPath, "lock": lock})
+
+ return b.Client.Delete(formattedPath, b.Timeout, lock...)
+}
+
+// CreateWatch starts watching events for the specified key
+func (b *Backend) CreateWatch(key string) chan *kvstore.Event {
+ b.Lock()
+ defer b.Unlock()
+
+ formattedPath := b.makePath(key)
+ log.Debugw("creating-key-watch", log.Fields{"key": key, "path": formattedPath})
+
+ return b.Client.Watch(formattedPath)
+}
+
+// DeleteWatch stops watching events for the specified key
+func (b *Backend) DeleteWatch(key string, ch chan *kvstore.Event) {
+ b.Lock()
+ defer b.Unlock()
+
+ formattedPath := b.makePath(key)
+ log.Debugw("deleting-key-watch", log.Fields{"key": key, "path": formattedPath})
+
+ b.Client.CloseWatch(formattedPath, ch)
+}
diff --git a/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/branch.go b/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/branch.go
new file mode 100644
index 0000000..2075b7e
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/branch.go
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package model
+
+import (
+ "github.com/opencord/voltha-lib-go/pkg/log"
+ "sync"
+)
+
+// TODO: implement weak references or something equivalent
+// TODO: missing proper logging
+
+// Branch structure is used to classify a collection of transaction based revisions
+type Branch struct {
+ mutex sync.RWMutex
+ Node *node
+ Txid string
+ Origin Revision
+ Revisions map[string]Revision
+ LatestLock sync.RWMutex
+ Latest Revision
+}
+
+// NewBranch creates a new instance of the Branch structure
+func NewBranch(node *node, txid string, origin Revision, autoPrune bool) *Branch {
+ b := &Branch{}
+ b.Node = node
+ b.Txid = txid
+ b.Origin = origin
+ b.Revisions = make(map[string]Revision)
+ b.Latest = origin
+
+ return b
+}
+
+// Utility function to extract all children names for a given revision (mostly for debugging purposes)
+func (b *Branch) retrieveChildrenNames(revision Revision) []string {
+ var childrenNames []string
+
+ for _, child := range revision.GetChildren("devices") {
+ childrenNames = append(childrenNames, child.GetName())
+ }
+
+ return childrenNames
+}
+
+// Utility function to compare children names and report the missing ones (mostly for debugging purposes)
+func (b *Branch) findMissingChildrenNames(previousNames, latestNames []string) []string {
+ var missingNames []string
+
+ for _, previousName := range previousNames {
+ found := false
+
+ if len(latestNames) == 0 {
+ break
+ }
+
+ for _, latestName := range latestNames {
+ if previousName == latestName {
+ found = true
+ break
+ }
+ }
+ if !found {
+ missingNames = append(missingNames, previousName)
+ }
+ }
+
+ return missingNames
+}
+
+// SetLatest assigns the latest revision for this branch
+func (b *Branch) SetLatest(latest Revision) {
+ b.mutex.Lock()
+ defer b.mutex.Unlock()
+
+ if b.Latest != nil {
+ log.Debugw("updating-latest-revision", log.Fields{"current": b.Latest.GetHash(), "new": latest.GetHash()})
+
+ // Go through list of children names in current revision and new revision
+ // and then compare the resulting outputs to ensure that we have not lost any entries.
+
+ if level, _ := log.GetPackageLogLevel(); level == log.DebugLevel {
+ var previousNames, latestNames, missingNames []string
+
+ if previousNames = b.retrieveChildrenNames(b.Latest); len(previousNames) > 0 {
+ log.Debugw("children-of-previous-revision", log.Fields{"hash": b.Latest.GetHash(), "names": previousNames})
+ }
+
+ if latestNames = b.retrieveChildrenNames(b.Latest); len(latestNames) > 0 {
+ log.Debugw("children-of-latest-revision", log.Fields{"hash": latest.GetHash(), "names": latestNames})
+ }
+
+ if missingNames = b.findMissingChildrenNames(previousNames, latestNames); len(missingNames) > 0 {
+ log.Debugw("children-missing-in-latest-revision", log.Fields{"hash": latest.GetHash(), "names": missingNames})
+ }
+ }
+
+ } else {
+ log.Debugw("setting-latest-revision", log.Fields{"new": latest.GetHash()})
+ }
+
+ b.Latest = latest
+}
+
+// GetLatest retrieves the latest revision of the branch
+func (b *Branch) GetLatest() Revision {
+ b.mutex.RLock()
+ defer b.mutex.RUnlock()
+
+ return b.Latest
+}
+
+// GetOrigin retrieves the original revision of the branch
+func (b *Branch) GetOrigin() Revision {
+ b.mutex.RLock()
+ defer b.mutex.RUnlock()
+
+ return b.Origin
+}
+
+// AddRevision inserts a new revision to the branch
+func (b *Branch) AddRevision(revision Revision) {
+ if revision != nil && b.GetRevision(revision.GetHash()) == nil {
+ b.SetRevision(revision.GetHash(), revision)
+ }
+}
+
+// GetRevision pulls a revision entry at the specified hash
+func (b *Branch) GetRevision(hash string) Revision {
+ b.mutex.RLock()
+ defer b.mutex.RUnlock()
+
+ if revision, ok := b.Revisions[hash]; ok {
+ return revision
+ }
+
+ return nil
+}
+
+// SetRevision updates a revision entry at the specified hash
+func (b *Branch) SetRevision(hash string, revision Revision) {
+ b.mutex.Lock()
+ defer b.mutex.Unlock()
+
+ b.Revisions[hash] = revision
+}
+
+// DeleteRevision removes a revision with the specified hash
+func (b *Branch) DeleteRevision(hash string) {
+ b.mutex.Lock()
+ defer b.mutex.Unlock()
+
+ if _, ok := b.Revisions[hash]; ok {
+ delete(b.Revisions, hash)
+ }
+}
diff --git a/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/callback_type.go b/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/callback_type.go
new file mode 100644
index 0000000..b530dee
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/callback_type.go
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package model
+
+// CallbackType is an enumerated value to express when a callback should be executed
+type CallbackType uint8
+
+// Enumerated list of callback types
+const (
+ GET CallbackType = iota
+ PRE_UPDATE
+ POST_UPDATE
+ PRE_ADD
+ POST_ADD
+ PRE_REMOVE
+ POST_REMOVE
+ POST_LISTCHANGE
+)
+
+var enumCallbackTypes = []string{
+ "GET",
+ "PRE_UPDATE",
+ "POST_UPDATE",
+ "PRE_ADD",
+ "POST_ADD",
+ "PRE_REMOVE",
+ "POST_REMOVE",
+ "POST_LISTCHANGE",
+}
+
+func (t CallbackType) String() string {
+ return enumCallbackTypes[t]
+}
diff --git a/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/child_type.go b/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/child_type.go
new file mode 100644
index 0000000..fae9844
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/child_type.go
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package model
+
+import (
+ desc "github.com/golang/protobuf/descriptor"
+ "github.com/golang/protobuf/proto"
+ "github.com/golang/protobuf/protoc-gen-go/descriptor"
+ "github.com/opencord/voltha-lib-go/pkg/log"
+ "github.com/opencord/voltha-protos/go/common"
+ "reflect"
+ "strconv"
+ "sync"
+)
+
+type childTypesSingleton struct {
+ mutex sync.RWMutex
+ Cache map[interface{}]map[string]*ChildType
+}
+
+var instanceChildTypes *childTypesSingleton
+var onceChildTypes sync.Once
+
+func getChildTypes() *childTypesSingleton {
+ onceChildTypes.Do(func() {
+ instanceChildTypes = &childTypesSingleton{}
+ })
+ return instanceChildTypes
+}
+
+func (s *childTypesSingleton) GetCache() map[interface{}]map[string]*ChildType {
+ s.mutex.RLock()
+ defer s.mutex.RUnlock()
+ return s.Cache
+}
+
+func (s *childTypesSingleton) SetCache(cache map[interface{}]map[string]*ChildType) {
+ s.mutex.Lock()
+ defer s.mutex.Unlock()
+ s.Cache = cache
+}
+
+func (s *childTypesSingleton) GetCacheEntry(key interface{}) (map[string]*ChildType, bool) {
+ s.mutex.RLock()
+ defer s.mutex.RUnlock()
+ childTypeMap, exists := s.Cache[key]
+ return childTypeMap, exists
+}
+
+func (s *childTypesSingleton) SetCacheEntry(key interface{}, value map[string]*ChildType) {
+ s.mutex.Lock()
+ defer s.mutex.Unlock()
+ s.Cache[key] = value
+}
+
+func (s *childTypesSingleton) ResetCache() {
+ s.mutex.Lock()
+ defer s.mutex.Unlock()
+ s.Cache = make(map[interface{}]map[string]*ChildType)
+}
+
+// ChildType structure contains construct details of an object
+type ChildType struct {
+ ClassModule string
+ ClassType reflect.Type
+ IsContainer bool
+ Key string
+ KeyFromStr func(s string) interface{}
+}
+
+// ChildrenFields retrieves list of child objects associated to a given interface
+func ChildrenFields(cls interface{}) map[string]*ChildType {
+ if cls == nil {
+ return nil
+ }
+ var names map[string]*ChildType
+ var namesExist bool
+
+ if getChildTypes().Cache == nil {
+ getChildTypes().Cache = make(map[interface{}]map[string]*ChildType)
+ }
+
+ msgType := reflect.TypeOf(cls)
+ inst := getChildTypes()
+
+ if names, namesExist = inst.Cache[msgType.String()]; !namesExist {
+ names = make(map[string]*ChildType)
+
+ _, md := desc.ForMessage(cls.(desc.Message))
+
+ // TODO: Do we need to validate MD for nil, panic or exception?
+ for _, field := range md.Field {
+ if options := field.GetOptions(); options != nil {
+ if proto.HasExtension(options, common.E_ChildNode) {
+ isContainer := *field.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED
+ meta, _ := proto.GetExtension(options, common.E_ChildNode)
+
+ var keyFromStr func(string) interface{}
+ var ct ChildType
+
+ parentType := FindOwnerType(reflect.ValueOf(cls), field.GetName(), 0, false)
+ if meta.(*common.ChildNode).GetKey() != "" {
+ keyType := FindKeyOwner(reflect.New(parentType).Elem().Interface(), meta.(*common.ChildNode).GetKey(), 0)
+
+ switch keyType.(reflect.Type).Name() {
+ case "string":
+ keyFromStr = func(s string) interface{} {
+ return s
+ }
+ case "int32":
+ keyFromStr = func(s string) interface{} {
+ i, _ := strconv.Atoi(s)
+ return int32(i)
+ }
+ case "int64":
+ keyFromStr = func(s string) interface{} {
+ i, _ := strconv.Atoi(s)
+ return int64(i)
+ }
+ case "uint32":
+ keyFromStr = func(s string) interface{} {
+ i, _ := strconv.Atoi(s)
+ return uint32(i)
+ }
+ case "uint64":
+ keyFromStr = func(s string) interface{} {
+ i, _ := strconv.Atoi(s)
+ return uint64(i)
+ }
+ default:
+ log.Errorf("Key type not implemented - type: %s\n", keyType.(reflect.Type))
+ }
+ }
+
+ ct = ChildType{
+ ClassModule: parentType.String(),
+ ClassType: parentType,
+ IsContainer: isContainer,
+ Key: meta.(*common.ChildNode).GetKey(),
+ KeyFromStr: keyFromStr,
+ }
+
+ names[field.GetName()] = &ct
+ }
+ }
+ }
+
+ getChildTypes().Cache[msgType.String()] = names
+ } else {
+ entry, _ := inst.GetCacheEntry(msgType.String())
+ log.Debugf("Cache entry for %s: %+v", msgType.String(), entry)
+ }
+
+ return names
+}
diff --git a/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/data_revision.go b/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/data_revision.go
new file mode 100644
index 0000000..b930ae1
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/data_revision.go
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package model
+
+import (
+ "bytes"
+ "crypto/md5"
+ "encoding/json"
+ "fmt"
+ "github.com/golang/protobuf/proto"
+ "github.com/opencord/voltha-lib-go/pkg/log"
+ "reflect"
+)
+
+// DataRevision stores the data associated to a revision along with its calculated checksum hash value
+type DataRevision struct {
+ Data interface{}
+ Hash string
+}
+
+// NewDataRevision creates a new instance of a DataRevision structure
+func NewDataRevision(root *root, data interface{}) *DataRevision {
+ dr := DataRevision{}
+ dr.Data = data
+ dr.Hash = dr.hashData(root, data)
+
+ return &dr
+}
+
+func (dr *DataRevision) hashData(root *root, data interface{}) string {
+ var buffer bytes.Buffer
+
+ if IsProtoMessage(data) {
+ if pbdata, err := proto.Marshal(data.(proto.Message)); err != nil {
+ log.Debugf("problem to marshal protobuf data --> err: %s", err.Error())
+ } else {
+ buffer.Write(pbdata)
+ // To ensure uniqueness in case data is nil, also include data type
+ buffer.Write([]byte(reflect.TypeOf(data).String()))
+ }
+
+ } else if reflect.ValueOf(data).IsValid() {
+ dataObj := reflect.New(reflect.TypeOf(data).Elem())
+ if json, err := json.Marshal(dataObj.Interface()); err != nil {
+ log.Debugf("problem to marshal data --> err: %s", err.Error())
+ } else {
+ buffer.Write(json)
+ }
+ } else {
+ dataObj := reflect.New(reflect.TypeOf(data).Elem())
+ buffer.Write(dataObj.Bytes())
+ }
+
+ // Add the root pointer that owns the current data for extra uniqueness
+ rootPtr := fmt.Sprintf("%p", root)
+ buffer.Write([]byte(rootPtr))
+
+ return fmt.Sprintf("%x", md5.Sum(buffer.Bytes()))[:12]
+}
diff --git a/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/event_bus.go b/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/event_bus.go
new file mode 100644
index 0000000..3af2556
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/event_bus.go
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package model
+
+import (
+ "encoding/json"
+ "github.com/golang/protobuf/proto"
+ "github.com/opencord/voltha-lib-go/pkg/log"
+ "github.com/opencord/voltha-protos/go/voltha"
+)
+
+// EventBus contains the details required to communicate with the event bus mechanism
+type EventBus struct {
+ client *EventBusClient
+ topic string
+}
+
+// ignoredCallbacks keeps a list of callbacks that should not be advertised on the event bus
+var (
+ ignoredCallbacks = map[CallbackType]struct{}{
+ PRE_ADD: {},
+ GET: {},
+ POST_LISTCHANGE: {},
+ PRE_REMOVE: {},
+ PRE_UPDATE: {},
+ }
+)
+
+// NewEventBus creates a new instance of the EventBus structure
+func NewEventBus() *EventBus {
+ bus := &EventBus{
+ client: NewEventBusClient(),
+ topic: "model-change-events",
+ }
+ return bus
+}
+
+// Advertise will publish the provided information to the event bus
+func (bus *EventBus) Advertise(args ...interface{}) interface{} {
+ eventType := args[0].(CallbackType)
+ hash := args[1].(string)
+ data := args[2:]
+
+ if _, ok := ignoredCallbacks[eventType]; ok {
+ log.Debugf("ignoring event - type:%s, data:%+v", eventType, data)
+ }
+ var kind voltha.ConfigEventType_ConfigEventType
+ switch eventType {
+ case POST_ADD:
+ kind = voltha.ConfigEventType_add
+ case POST_REMOVE:
+ kind = voltha.ConfigEventType_remove
+ default:
+ kind = voltha.ConfigEventType_update
+ }
+
+ var msg []byte
+ var err error
+ if IsProtoMessage(data) {
+ if msg, err = proto.Marshal(data[0].(proto.Message)); err != nil {
+ log.Debugf("problem marshalling proto data: %+v, err:%s", data[0], err.Error())
+ }
+ } else if data[0] != nil {
+ if msg, err = json.Marshal(data[0]); err != nil {
+ log.Debugf("problem marshalling json data: %+v, err:%s", data[0], err.Error())
+ }
+ } else {
+ log.Debugf("no data to advertise : %+v", data[0])
+ }
+
+ event := voltha.ConfigEvent{
+ Type: kind,
+ Hash: hash,
+ Data: string(msg),
+ }
+
+ bus.client.Publish(bus.topic, event)
+
+ return nil
+}
diff --git a/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/event_bus_client.go b/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/event_bus_client.go
new file mode 100644
index 0000000..fdc02f9
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/event_bus_client.go
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package model
+
+import (
+ "github.com/opencord/voltha-lib-go/pkg/log"
+ "github.com/opencord/voltha-protos/go/voltha"
+)
+
+// EventBusClient is an abstraction layer structure to communicate with an event bus mechanism
+type EventBusClient struct {
+}
+
+// NewEventBusClient creates a new EventBusClient instance
+func NewEventBusClient() *EventBusClient {
+ return &EventBusClient{}
+}
+
+// Publish sends a event to the bus
+func (ebc *EventBusClient) Publish(topic string, event voltha.ConfigEvent) {
+ log.Debugf("publishing event:%+v, topic:%s\n", event, topic)
+}
diff --git a/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/merge.go b/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/merge.go
new file mode 100644
index 0000000..752d025
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/merge.go
@@ -0,0 +1,273 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package model
+
+import (
+ "github.com/opencord/voltha-lib-go/pkg/log"
+)
+
+func revisionsAreEqual(a, b []Revision) bool {
+ // If one is nil, the other must also be nil.
+ if (a == nil) != (b == nil) {
+ return false
+ }
+
+ if len(a) != len(b) {
+ return false
+ }
+
+ for i := range a {
+ if a[i] != b[i] {
+ return false
+ }
+ }
+
+ return true
+}
+
+type changeAnalysis struct {
+ KeyMap1 map[string]int
+ KeyMap2 map[string]int
+ AddedKeys map[string]struct{}
+ RemovedKeys map[string]struct{}
+ ChangedKeys map[string]struct{}
+}
+
+func newChangeAnalysis(lst1, lst2 []Revision, keyName string) *changeAnalysis {
+ changes := &changeAnalysis{}
+
+ changes.KeyMap1 = make(map[string]int)
+ changes.KeyMap2 = make(map[string]int)
+
+ changes.AddedKeys = make(map[string]struct{})
+ changes.RemovedKeys = make(map[string]struct{})
+ changes.ChangedKeys = make(map[string]struct{})
+
+ for i, rev := range lst1 {
+ _, v := GetAttributeValue(rev.GetData(), keyName, 0)
+ changes.KeyMap1[v.String()] = i
+ }
+ for i, rev := range lst2 {
+ _, v := GetAttributeValue(rev.GetData(), keyName, 0)
+ changes.KeyMap2[v.String()] = i
+ }
+ for v := range changes.KeyMap2 {
+ if _, ok := changes.KeyMap1[v]; !ok {
+ changes.AddedKeys[v] = struct{}{}
+ }
+ }
+ for v := range changes.KeyMap1 {
+ if _, ok := changes.KeyMap2[v]; !ok {
+ changes.RemovedKeys[v] = struct{}{}
+ }
+ }
+ for v := range changes.KeyMap1 {
+ if _, ok := changes.KeyMap2[v]; ok && lst1[changes.KeyMap1[v]].GetHash() != lst2[changes.KeyMap2[v]].GetHash() {
+ changes.ChangedKeys[v] = struct{}{}
+ }
+ }
+
+ return changes
+}
+
+// Merge3Way takes care of combining the revision contents of the same data set
+func Merge3Way(
+ forkRev, srcRev, dstRev Revision,
+ mergeChildFunc func(Revision) Revision,
+ dryRun bool) (rev Revision, changes []ChangeTuple) {
+
+ log.Debugw("3-way-merge-request", log.Fields{"dryRun": dryRun})
+
+ var configChanged bool
+ var revsToDiscard []Revision
+
+ if dstRev.GetConfig() == forkRev.GetConfig() {
+ configChanged = dstRev.GetConfig() != srcRev.GetConfig()
+ } else {
+ if dstRev.GetConfig().Hash != srcRev.GetConfig().Hash {
+ log.Error("config-collision")
+ }
+ configChanged = true
+ }
+
+ //newChildren := reflect.ValueOf(dstRev.GetAllChildren()).Interface().(map[string][]Revision)
+ newChildren := make(map[string][]Revision)
+ for entryName, childrenEntry := range dstRev.GetAllChildren() {
+ //newRev.Children[entryName] = append(newRev.Children[entryName], childrenEntry...)
+ newChildren[entryName] = make([]Revision, len(childrenEntry))
+ copy(newChildren[entryName], childrenEntry)
+ }
+
+ childrenFields := ChildrenFields(forkRev.GetData())
+
+ for fieldName, field := range childrenFields {
+ forkList := forkRev.GetChildren(fieldName)
+ srcList := srcRev.GetChildren(fieldName)
+ dstList := dstRev.GetChildren(fieldName)
+
+ if revisionsAreEqual(dstList, srcList) {
+ for _, rev := range srcList {
+ mergeChildFunc(rev)
+ }
+ continue
+ }
+
+ if field.Key == "" {
+ if revisionsAreEqual(dstList, forkList) {
+ if !revisionsAreEqual(srcList, forkList) {
+ log.Error("we should not be here")
+ } else {
+ for _, rev := range srcList {
+ newChildren[fieldName] = append(newChildren[fieldName], mergeChildFunc(rev))
+ }
+ if field.IsContainer {
+ changes = append(
+ changes, ChangeTuple{POST_LISTCHANGE,
+ NewOperationContext("", nil, fieldName, ""), nil},
+ )
+ }
+ }
+ } else {
+ if !revisionsAreEqual(srcList, forkList) {
+ log.Error("cannot merge - single child node or un-keyed children list has changed")
+ }
+ }
+ } else {
+ if revisionsAreEqual(dstList, forkList) {
+ src := newChangeAnalysis(forkList, srcList, field.Key)
+
+ newList := make([]Revision, len(srcList))
+ copy(newList, srcList)
+
+ for key := range src.AddedKeys {
+ idx := src.KeyMap2[key]
+ newRev := mergeChildFunc(newList[idx])
+
+ // FIXME: newRev may come back as nil... exclude those entries for now
+ if newRev != nil {
+ newList[idx] = newRev
+ changes = append(changes, ChangeTuple{POST_ADD, newList[idx].GetData(), newRev.GetData()})
+ }
+ }
+ for key := range src.RemovedKeys {
+ oldRev := forkList[src.KeyMap1[key]]
+ revsToDiscard = append(revsToDiscard, oldRev)
+ changes = append(changes, ChangeTuple{POST_REMOVE, oldRev.GetData(), nil})
+ }
+ for key := range src.ChangedKeys {
+ idx := src.KeyMap2[key]
+ newRev := mergeChildFunc(newList[idx])
+
+ // FIXME: newRev may come back as nil... exclude those entries for now
+ if newRev != nil {
+ newList[idx] = newRev
+ }
+ }
+
+ if !dryRun {
+ newChildren[fieldName] = newList
+ }
+ } else {
+ src := newChangeAnalysis(forkList, srcList, field.Key)
+ dst := newChangeAnalysis(forkList, dstList, field.Key)
+
+ newList := make([]Revision, len(dstList))
+ copy(newList, dstList)
+
+ for key := range src.AddedKeys {
+ if _, exists := dst.AddedKeys[key]; exists {
+ childDstRev := dstList[dst.KeyMap2[key]]
+ childSrcRev := srcList[src.KeyMap2[key]]
+ if childDstRev.GetHash() == childSrcRev.GetHash() {
+ mergeChildFunc(childDstRev)
+ } else {
+ log.Error("conflict error - revision has been added is different")
+ }
+ } else {
+ newRev := mergeChildFunc(srcList[src.KeyMap2[key]])
+ newList = append(newList, newRev)
+ changes = append(changes, ChangeTuple{POST_ADD, srcList[src.KeyMap2[key]], newRev.GetData()})
+ }
+ }
+ for key := range src.ChangedKeys {
+ if _, removed := dst.RemovedKeys[key]; removed {
+ log.Error("conflict error - revision has been removed")
+ } else if _, changed := dst.ChangedKeys[key]; changed {
+ childDstRev := dstList[dst.KeyMap2[key]]
+ childSrcRev := srcList[src.KeyMap2[key]]
+ if childDstRev.GetHash() == childSrcRev.GetHash() {
+ mergeChildFunc(childSrcRev)
+ } else if childDstRev.GetConfig().Hash != childSrcRev.GetConfig().Hash {
+ log.Error("conflict error - revision has been changed and is different")
+ } else {
+ newRev := mergeChildFunc(srcList[src.KeyMap2[key]])
+ newList[dst.KeyMap2[key]] = newRev
+ }
+ } else {
+ newRev := mergeChildFunc(srcList[src.KeyMap2[key]])
+ newList[dst.KeyMap2[key]] = newRev
+ }
+ }
+
+ // TODO: how do i sort this map in reverse order?
+ for key := range src.RemovedKeys {
+ if _, changed := dst.ChangedKeys[key]; changed {
+ log.Error("conflict error - revision has changed")
+ }
+ if _, removed := dst.RemovedKeys[key]; !removed {
+ dstIdx := dst.KeyMap2[key]
+ oldRev := newList[dstIdx]
+ revsToDiscard = append(revsToDiscard, oldRev)
+
+ copy(newList[dstIdx:], newList[dstIdx+1:])
+ newList[len(newList)-1] = nil
+ newList = newList[:len(newList)-1]
+
+ changes = append(changes, ChangeTuple{POST_REMOVE, oldRev.GetData(), nil})
+ }
+ }
+
+ if !dryRun {
+ newChildren[fieldName] = newList
+ }
+ }
+ }
+ }
+
+ if !dryRun && len(newChildren) > 0 {
+ if configChanged {
+ rev = srcRev
+ } else {
+ rev = dstRev
+ }
+
+ for _, discarded := range revsToDiscard {
+ discarded.Drop("", true)
+ }
+
+ // FIXME: Do not discard the latest value for now
+ //dstRev.GetBranch().GetLatest().Drop("", configChanged)
+ rev = rev.UpdateAllChildren(newChildren, dstRev.GetBranch())
+
+ if configChanged {
+ changes = append(changes, ChangeTuple{POST_UPDATE, dstRev.GetBranch().GetLatest().GetData(), rev.GetData()})
+ }
+ return rev, changes
+ }
+
+ return nil, nil
+}
diff --git a/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/model.go b/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/model.go
new file mode 100644
index 0000000..8087919
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/model.go
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package model
+
+import (
+ "github.com/opencord/voltha-lib-go/pkg/log"
+)
+
+func init() {
+ log.AddPackage(log.JSON, log.InfoLevel, log.Fields{"instanceId": "DB_MODEL"})
+ defer log.CleanUp()
+}
+
+const (
+ // period to determine when data requires a refresh (in milliseconds)
+ // TODO: make this configurable?
+ DataRefreshPeriod int64 = 5000
+
+ // Attribute used to store a timestamp in the context object
+ RequestTimestamp = "request-timestamp"
+
+ // Time limit for a KV path reservation (in seconds)
+ ReservationTTL int64 = 180
+)
diff --git a/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/node.go b/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/node.go
new file mode 100644
index 0000000..7fd0250
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/node.go
@@ -0,0 +1,1161 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package model
+
+// TODO: proper error handling
+// TODO: proper logging
+
+import (
+ "context"
+ "fmt"
+ "github.com/golang/protobuf/proto"
+ "github.com/opencord/voltha-lib-go/pkg/log"
+ "reflect"
+ "strings"
+ "sync"
+ "time"
+)
+
+// When a branch has no transaction id, everything gets stored in NONE
+const (
+ NONE string = "none"
+)
+
+// Node interface is an abstraction of the node data structure
+type Node interface {
+ MakeLatest(branch *Branch, revision Revision, changeAnnouncement []ChangeTuple)
+
+ // CRUD functions
+ Add(ctx context.Context, path string, data interface{}, txid string, makeBranch MakeBranchFunction) Revision
+ Get(ctx context.Context, path string, hash string, depth int, deep bool, txid string) interface{}
+ List(ctx context.Context, path string, hash string, depth int, deep bool, txid string) interface{}
+ Update(ctx context.Context, path string, data interface{}, strict bool, txid string, makeBranch MakeBranchFunction) Revision
+ Remove(ctx context.Context, path string, txid string, makeBranch MakeBranchFunction) Revision
+ CreateProxy(ctx context.Context, path string, exclusive bool) *Proxy
+
+ GetProxy() *Proxy
+
+ MakeBranch(txid string) *Branch
+ DeleteBranch(txid string)
+ MergeBranch(txid string, dryRun bool) (Revision, error)
+
+ MakeTxBranch() string
+ DeleteTxBranch(txid string)
+ FoldTxBranch(txid string)
+}
+
+type node struct {
+ mutex sync.RWMutex
+ Root *root
+ Type interface{}
+ Branches map[string]*Branch
+ Tags map[string]Revision
+ Proxy *Proxy
+ EventBus *EventBus
+ AutoPrune bool
+}
+
+// ChangeTuple holds details of modifications made to a revision
+type ChangeTuple struct {
+ Type CallbackType
+ PreviousData interface{}
+ LatestData interface{}
+}
+
+// NewNode creates a new instance of the node data structure
+func NewNode(root *root, initialData interface{}, autoPrune bool, txid string) *node {
+ n := &node{}
+
+ n.Root = root
+ n.Branches = make(map[string]*Branch)
+ n.Tags = make(map[string]Revision)
+ n.Proxy = nil
+ n.EventBus = nil
+ n.AutoPrune = autoPrune
+
+ if IsProtoMessage(initialData) {
+ n.Type = reflect.ValueOf(initialData).Interface()
+ dataCopy := proto.Clone(initialData.(proto.Message))
+ n.initialize(dataCopy, txid)
+ } else if reflect.ValueOf(initialData).IsValid() {
+ // FIXME: this block does not reflect the original implementation
+ // it should be checking if the provided initial_data is already a type!??!
+ // it should be checked before IsProtoMessage
+ n.Type = reflect.ValueOf(initialData).Interface()
+ } else {
+ // not implemented error
+ log.Errorf("cannot process initial data - %+v", initialData)
+ }
+
+ return n
+}
+
+// MakeNode creates a new node in the tree
+func (n *node) MakeNode(data interface{}, txid string) *node {
+ return NewNode(n.Root, data, true, txid)
+}
+
+// MakeRevision create a new revision of the node in the tree
+func (n *node) MakeRevision(branch *Branch, data interface{}, children map[string][]Revision) Revision {
+ return n.GetRoot().MakeRevision(branch, data, children)
+}
+
+// makeLatest will mark the revision of a node as being the latest
+func (n *node) makeLatest(branch *Branch, revision Revision, changeAnnouncement []ChangeTuple) {
+ // Keep a reference to the current revision
+ var previous string
+ if branch.GetLatest() != nil {
+ previous = branch.GetLatest().GetHash()
+ }
+
+ branch.AddRevision(revision)
+
+ // If anything is new, then set the revision as the latest
+ if branch.GetLatest() == nil || revision.GetHash() != branch.GetLatest().GetHash() {
+ if revision.GetName() != "" {
+ log.Debugw("saving-latest-data", log.Fields{"hash": revision.GetHash(), "data": revision.GetData()})
+ // Tag a timestamp to that revision
+ revision.SetLastUpdate()
+ GetRevCache().Set(revision.GetName(), revision)
+ }
+ branch.SetLatest(revision)
+ }
+
+ // Delete the previous revision if anything has changed
+ if previous != "" && previous != branch.GetLatest().GetHash() {
+ branch.DeleteRevision(previous)
+ }
+
+ if changeAnnouncement != nil && branch.Txid == "" {
+ if n.Proxy != nil {
+ for _, change := range changeAnnouncement {
+ log.Debugw("adding-callback",
+ log.Fields{
+ "callbacks": n.GetProxy().getCallbacks(change.Type),
+ "type": change.Type,
+ "previousData": change.PreviousData,
+ "latestData": change.LatestData,
+ })
+ n.Root.AddCallback(
+ n.GetProxy().InvokeCallbacks,
+ change.Type,
+ true,
+ change.PreviousData,
+ change.LatestData)
+ }
+ }
+ }
+}
+
+// Latest returns the latest revision of node with or without the transaction id
+func (n *node) Latest(txid ...string) Revision {
+ var branch *Branch
+
+ if len(txid) > 0 && txid[0] != "" {
+ if branch = n.GetBranch(txid[0]); branch != nil {
+ return branch.GetLatest()
+ }
+ } else if branch = n.GetBranch(NONE); branch != nil {
+ return branch.GetLatest()
+ }
+ return nil
+}
+
+// initialize prepares the content of a node along with its possible ramifications
+func (n *node) initialize(data interface{}, txid string) {
+ children := make(map[string][]Revision)
+ for fieldName, field := range ChildrenFields(n.Type) {
+ _, fieldValue := GetAttributeValue(data, fieldName, 0)
+
+ if fieldValue.IsValid() {
+ if field.IsContainer {
+ if field.Key != "" {
+ for i := 0; i < fieldValue.Len(); i++ {
+ v := fieldValue.Index(i)
+
+ if rev := n.MakeNode(v.Interface(), txid).Latest(txid); rev != nil {
+ children[fieldName] = append(children[fieldName], rev)
+ }
+
+ // TODO: The following logic was ported from v1.0. Need to verify if it is required
+ //var keysSeen []string
+ //_, key := GetAttributeValue(v.Interface(), field.Key, 0)
+ //for _, k := range keysSeen {
+ // if k == key.String() {
+ // //log.Errorf("duplicate key - %s", k)
+ // }
+ //}
+ //keysSeen = append(keysSeen, key.String())
+ }
+
+ } else {
+ for i := 0; i < fieldValue.Len(); i++ {
+ v := fieldValue.Index(i)
+ if newNodeRev := n.MakeNode(v.Interface(), txid).Latest(); newNodeRev != nil {
+ children[fieldName] = append(children[fieldName], newNodeRev)
+ }
+ }
+ }
+ } else {
+ if newNodeRev := n.MakeNode(fieldValue.Interface(), txid).Latest(); newNodeRev != nil {
+ children[fieldName] = append(children[fieldName], newNodeRev)
+ }
+ }
+ } else {
+ log.Errorf("field is invalid - %+v", fieldValue)
+ }
+ }
+
+ branch := NewBranch(n, "", nil, n.AutoPrune)
+ rev := n.MakeRevision(branch, data, children)
+ n.makeLatest(branch, rev, nil)
+
+ if txid == "" {
+ n.SetBranch(NONE, branch)
+ } else {
+ n.SetBranch(txid, branch)
+ }
+}
+
+// findRevByKey retrieves a specific revision from a node tree
+func (n *node) findRevByKey(revs []Revision, keyName string, value interface{}) (int, Revision) {
+ for i, rev := range revs {
+ dataValue := reflect.ValueOf(rev.GetData())
+ dataStruct := GetAttributeStructure(rev.GetData(), keyName, 0)
+
+ fieldValue := dataValue.Elem().FieldByName(dataStruct.Name)
+
+ a := fmt.Sprintf("%s", fieldValue.Interface())
+ b := fmt.Sprintf("%s", value)
+ if a == b {
+ return i, revs[i]
+ }
+ }
+
+ return -1, nil
+}
+
+// Get retrieves the data from a node tree that resides at the specified path
+func (n *node) List(ctx context.Context, path string, hash string, depth int, deep bool, txid string) interface{} {
+ n.mutex.Lock()
+ defer n.mutex.Unlock()
+
+ log.Debugw("node-list-request", log.Fields{"path": path, "hash": hash, "depth": depth, "deep": deep, "txid": txid})
+ if deep {
+ depth = -1
+ }
+
+ for strings.HasPrefix(path, "/") {
+ path = path[1:]
+ }
+
+ var branch *Branch
+ var rev Revision
+
+ if branch = n.GetBranch(txid); txid == "" || branch == nil {
+ branch = n.GetBranch(NONE)
+ }
+
+ if hash != "" {
+ rev = branch.GetRevision(hash)
+ } else {
+ rev = branch.GetLatest()
+ }
+
+ var result interface{}
+ var prList []interface{}
+ if pr := rev.LoadFromPersistence(ctx, path, txid, nil); pr != nil {
+ for _, revEntry := range pr {
+ prList = append(prList, revEntry.GetData())
+ }
+ result = prList
+ }
+
+ return result
+}
+
+// Get retrieves the data from a node tree that resides at the specified path
+func (n *node) Get(ctx context.Context, path string, hash string, depth int, reconcile bool, txid string) interface{} {
+ n.mutex.Lock()
+ defer n.mutex.Unlock()
+
+ log.Debugw("node-get-request", log.Fields{"path": path, "hash": hash, "depth": depth, "reconcile": reconcile, "txid": txid})
+
+ for strings.HasPrefix(path, "/") {
+ path = path[1:]
+ }
+
+ var branch *Branch
+ var rev Revision
+
+ if branch = n.GetBranch(txid); txid == "" || branch == nil {
+ branch = n.GetBranch(NONE)
+ }
+
+ if hash != "" {
+ rev = branch.GetRevision(hash)
+ } else {
+ rev = branch.GetLatest()
+ }
+
+ var result interface{}
+
+ // If there is no request to reconcile, try to get it from memory
+ if !reconcile {
+ // Try to find an entry matching the path value from one of these sources
+ // 1. Start with the cache which stores revisions by watch names
+ // 2. Then look in the revision tree, especially if it's a sub-path such as /devices/1234/flows
+ // 3. Move on to the KV store if that path cannot be found or if the entry has expired
+ if entry, exists := GetRevCache().Get(path); exists && entry.(Revision) != nil {
+ entryAge := time.Now().Sub(entry.(Revision).GetLastUpdate()).Nanoseconds() / int64(time.Millisecond)
+ if entryAge < DataRefreshPeriod {
+ log.Debugw("using-cache-entry", log.Fields{
+ "path": path,
+ "hash": hash,
+ "age": entryAge,
+ })
+ return proto.Clone(entry.(Revision).GetData().(proto.Message))
+ } else {
+ log.Debugw("cache-entry-expired", log.Fields{"path": path, "hash": hash, "age": entryAge})
+ }
+ } else if result = n.getPath(ctx, rev.GetBranch().GetLatest(), path, depth); result != nil && reflect.ValueOf(result).IsValid() && !reflect.ValueOf(result).IsNil() {
+ log.Debugw("using-rev-tree-entry", log.Fields{"path": path, "hash": hash, "depth": depth, "reconcile": reconcile, "txid": txid})
+ return result
+ } else {
+ log.Debugw("not-using-cache-entry", log.Fields{
+ "path": path,
+ "hash": hash, "depth": depth,
+ "reconcile": reconcile,
+ "txid": txid,
+ })
+ }
+ } else {
+ log.Debugw("reconcile-requested", log.Fields{
+ "path": path,
+ "hash": hash,
+ "reconcile": reconcile,
+ })
+ }
+
+ // If we got to this point, we are either trying to reconcile with the db
+ // or we simply failed at getting information from memory
+ if n.Root.KvStore != nil {
+ if pr := rev.LoadFromPersistence(ctx, path, txid, nil); pr != nil && len(pr) > 0 {
+ // Did we receive a single or multiple revisions?
+ if len(pr) > 1 {
+ var revs []interface{}
+ for _, revEntry := range pr {
+ revs = append(revs, revEntry.GetData())
+ }
+ result = revs
+ } else {
+ result = pr[0].GetData()
+ }
+ }
+ }
+
+ return result
+}
+
+//getPath traverses the specified path and retrieves the data associated to it
+func (n *node) getPath(ctx context.Context, rev Revision, path string, depth int) interface{} {
+ if path == "" {
+ return n.getData(rev, depth)
+ }
+
+ partition := strings.SplitN(path, "/", 2)
+ name := partition[0]
+
+ if len(partition) < 2 {
+ path = ""
+ } else {
+ path = partition[1]
+ }
+
+ names := ChildrenFields(n.Type)
+ field := names[name]
+
+ if field != nil && field.IsContainer {
+ children := make([]Revision, len(rev.GetChildren(name)))
+ copy(children, rev.GetChildren(name))
+
+ if field.Key != "" {
+ if path != "" {
+ partition = strings.SplitN(path, "/", 2)
+ key := partition[0]
+ path = ""
+ keyValue := field.KeyFromStr(key)
+ if _, childRev := n.findRevByKey(children, field.Key, keyValue); childRev == nil {
+ return nil
+ } else {
+ childNode := childRev.GetNode()
+ return childNode.getPath(ctx, childRev, path, depth)
+ }
+ } else {
+ var response []interface{}
+ for _, childRev := range children {
+ childNode := childRev.GetNode()
+ value := childNode.getData(childRev, depth)
+ response = append(response, value)
+ }
+ return response
+ }
+ } else {
+ var response []interface{}
+ if path != "" {
+ // TODO: raise error
+ return response
+ }
+ for _, childRev := range children {
+ childNode := childRev.GetNode()
+ value := childNode.getData(childRev, depth)
+ response = append(response, value)
+ }
+ return response
+ }
+ } else if children := rev.GetChildren(name); children != nil && len(children) > 0 {
+ childRev := children[0]
+ childNode := childRev.GetNode()
+ return childNode.getPath(ctx, childRev, path, depth)
+ }
+
+ return nil
+}
+
+// getData retrieves the data from a node revision
+func (n *node) getData(rev Revision, depth int) interface{} {
+ msg := rev.GetBranch().GetLatest().Get(depth)
+ var modifiedMsg interface{}
+
+ if n.GetProxy() != nil {
+ log.Debugw("invoking-get-callbacks", log.Fields{"data": msg})
+ if modifiedMsg = n.GetProxy().InvokeCallbacks(GET, false, msg); modifiedMsg != nil {
+ msg = modifiedMsg
+ }
+
+ }
+
+ return msg
+}
+
+// Update changes the content of a node at the specified path with the provided data
+func (n *node) Update(ctx context.Context, path string, data interface{}, strict bool, txid string, makeBranch MakeBranchFunction) Revision {
+ n.mutex.Lock()
+ defer n.mutex.Unlock()
+
+ log.Debugw("node-update-request", log.Fields{"path": path, "strict": strict, "txid": txid})
+
+ for strings.HasPrefix(path, "/") {
+ path = path[1:]
+ }
+
+ var branch *Branch
+ if txid == "" {
+ branch = n.GetBranch(NONE)
+ } else if branch = n.GetBranch(txid); branch == nil {
+ branch = makeBranch(n)
+ }
+
+ if branch.GetLatest() != nil {
+ log.Debugf("Branch data : %+v, Passed data: %+v", branch.GetLatest().GetData(), data)
+ }
+ if path == "" {
+ return n.doUpdate(ctx, branch, data, strict)
+ }
+
+ rev := branch.GetLatest()
+
+ partition := strings.SplitN(path, "/", 2)
+ name := partition[0]
+
+ if len(partition) < 2 {
+ path = ""
+ } else {
+ path = partition[1]
+ }
+
+ field := ChildrenFields(n.Type)[name]
+ var children []Revision
+
+ if field == nil {
+ return n.doUpdate(ctx, branch, data, strict)
+ }
+
+ if field.IsContainer {
+ if path == "" {
+ log.Errorf("cannot update a list")
+ } else if field.Key != "" {
+ partition := strings.SplitN(path, "/", 2)
+ key := partition[0]
+ if len(partition) < 2 {
+ path = ""
+ } else {
+ path = partition[1]
+ }
+ keyValue := field.KeyFromStr(key)
+
+ children = make([]Revision, len(rev.GetChildren(name)))
+ copy(children, rev.GetChildren(name))
+
+ idx, childRev := n.findRevByKey(children, field.Key, keyValue)
+
+ if childRev == nil {
+ log.Debugw("child-revision-is-nil", log.Fields{"key": keyValue})
+ return branch.GetLatest()
+ }
+
+ childNode := childRev.GetNode()
+
+ // Save proxy in child node to ensure callbacks are called later on
+ // only assign in cases of non sub-folder proxies, i.e. "/"
+ if childNode.Proxy == nil && n.Proxy != nil && n.GetProxy().getFullPath() == "" {
+ childNode.Proxy = n.Proxy
+ }
+
+ newChildRev := childNode.Update(ctx, path, data, strict, txid, makeBranch)
+
+ if newChildRev.GetHash() == childRev.GetHash() {
+ if newChildRev != childRev {
+ log.Debug("clear-hash - %s %+v", newChildRev.GetHash(), newChildRev)
+ newChildRev.ClearHash()
+ }
+ log.Debugw("child-revisions-have-matching-hash", log.Fields{"hash": childRev.GetHash(), "key": keyValue})
+ return branch.GetLatest()
+ }
+
+ _, newKey := GetAttributeValue(newChildRev.GetData(), field.Key, 0)
+
+ _newKeyType := fmt.Sprintf("%s", newKey)
+ _keyValueType := fmt.Sprintf("%s", keyValue)
+
+ if _newKeyType != _keyValueType {
+ log.Errorf("cannot change key field")
+ }
+
+ // Prefix the hash value with the data type (e.g. devices, logical_devices, adapters)
+ newChildRev.SetName(name + "/" + _keyValueType)
+
+ branch.LatestLock.Lock()
+ defer branch.LatestLock.Unlock()
+
+ if idx >= 0 {
+ children[idx] = newChildRev
+ } else {
+ children = append(children, newChildRev)
+ }
+
+ updatedRev := rev.UpdateChildren(ctx, name, children, branch)
+
+ n.makeLatest(branch, updatedRev, nil)
+ updatedRev.ChildDrop(name, childRev.GetHash())
+
+ return newChildRev
+
+ } else {
+ log.Errorf("cannot index into container with no keys")
+ }
+ } else {
+ childRev := rev.GetChildren(name)[0]
+ childNode := childRev.GetNode()
+ newChildRev := childNode.Update(ctx, path, data, strict, txid, makeBranch)
+
+ branch.LatestLock.Lock()
+ defer branch.LatestLock.Unlock()
+
+ updatedRev := rev.UpdateChildren(ctx, name, []Revision{newChildRev}, branch)
+ n.makeLatest(branch, updatedRev, nil)
+
+ updatedRev.ChildDrop(name, childRev.GetHash())
+
+ return newChildRev
+ }
+
+ return nil
+}
+
+func (n *node) doUpdate(ctx context.Context, branch *Branch, data interface{}, strict bool) Revision {
+ log.Debugw("comparing-types", log.Fields{"expected": reflect.ValueOf(n.Type).Type(), "actual": reflect.TypeOf(data)})
+
+ if reflect.TypeOf(data) != reflect.ValueOf(n.Type).Type() {
+ // TODO raise error
+ log.Errorw("types-do-not-match: %+v", log.Fields{"actual": reflect.TypeOf(data), "expected": n.Type})
+ return nil
+ }
+
+ // TODO: validate that this actually works
+ //if n.hasChildren(data) {
+ // return nil
+ //}
+
+ if n.GetProxy() != nil {
+ log.Debug("invoking proxy PRE_UPDATE Callbacks")
+ n.GetProxy().InvokeCallbacks(PRE_UPDATE, false, branch.GetLatest(), data)
+ }
+
+ if branch.GetLatest().GetData().(proto.Message).String() != data.(proto.Message).String() {
+ if strict {
+ // TODO: checkAccessViolations(data, Branch.GetLatest.data)
+ log.Debugf("checking access violations")
+ }
+
+ rev := branch.GetLatest().UpdateData(ctx, data, branch)
+ changes := []ChangeTuple{{POST_UPDATE, branch.GetLatest().GetData(), rev.GetData()}}
+ n.makeLatest(branch, rev, changes)
+
+ return rev
+ }
+ return branch.GetLatest()
+}
+
+// Add inserts a new node at the specified path with the provided data
+func (n *node) Add(ctx context.Context, path string, data interface{}, txid string, makeBranch MakeBranchFunction) Revision {
+ n.mutex.Lock()
+ defer n.mutex.Unlock()
+
+ log.Debugw("node-add-request", log.Fields{"path": path, "txid": txid})
+
+ for strings.HasPrefix(path, "/") {
+ path = path[1:]
+ }
+ if path == "" {
+ // TODO raise error
+ log.Errorf("cannot add for non-container mode")
+ return nil
+ }
+
+ var branch *Branch
+ if txid == "" {
+ branch = n.GetBranch(NONE)
+ } else if branch = n.GetBranch(txid); branch == nil {
+ branch = makeBranch(n)
+ }
+
+ rev := branch.GetLatest()
+
+ partition := strings.SplitN(path, "/", 2)
+ name := partition[0]
+
+ if len(partition) < 2 {
+ path = ""
+ } else {
+ path = partition[1]
+ }
+
+ field := ChildrenFields(n.Type)[name]
+
+ var children []Revision
+
+ if field.IsContainer {
+ if path == "" {
+ if field.Key != "" {
+ if n.GetProxy() != nil {
+ log.Debug("invoking proxy PRE_ADD Callbacks")
+ n.GetProxy().InvokeCallbacks(PRE_ADD, false, data)
+ }
+
+ children = make([]Revision, len(rev.GetChildren(name)))
+ copy(children, rev.GetChildren(name))
+
+ _, key := GetAttributeValue(data, field.Key, 0)
+
+ if _, exists := n.findRevByKey(children, field.Key, key.String()); exists != nil {
+ // TODO raise error
+ log.Warnw("duplicate-key-found", log.Fields{"key": key.String()})
+ return exists
+ }
+ childRev := n.MakeNode(data, "").Latest()
+
+ // Prefix the hash with the data type (e.g. devices, logical_devices, adapters)
+ childRev.SetName(name + "/" + key.String())
+
+ branch.LatestLock.Lock()
+ defer branch.LatestLock.Unlock()
+
+ children = append(children, childRev)
+
+ updatedRev := rev.UpdateChildren(ctx, name, children, branch)
+ changes := []ChangeTuple{{POST_ADD, nil, childRev.GetData()}}
+ childRev.SetupWatch(childRev.GetName())
+
+ n.makeLatest(branch, updatedRev, changes)
+
+ return childRev
+ }
+ log.Errorf("cannot add to non-keyed container")
+
+ } else if field.Key != "" {
+ partition := strings.SplitN(path, "/", 2)
+ key := partition[0]
+ if len(partition) < 2 {
+ path = ""
+ } else {
+ path = partition[1]
+ }
+ keyValue := field.KeyFromStr(key)
+
+ children = make([]Revision, len(rev.GetChildren(name)))
+ copy(children, rev.GetChildren(name))
+
+ idx, childRev := n.findRevByKey(children, field.Key, keyValue)
+
+ if childRev == nil {
+ return branch.GetLatest()
+ }
+
+ childNode := childRev.GetNode()
+ newChildRev := childNode.Add(ctx, path, data, txid, makeBranch)
+
+ // Prefix the hash with the data type (e.g. devices, logical_devices, adapters)
+ newChildRev.SetName(name + "/" + keyValue.(string))
+
+ branch.LatestLock.Lock()
+ defer branch.LatestLock.Unlock()
+
+ if idx >= 0 {
+ children[idx] = newChildRev
+ } else {
+ children = append(children, newChildRev)
+ }
+
+ updatedRev := rev.UpdateChildren(ctx, name, children, branch)
+ n.makeLatest(branch, updatedRev, nil)
+
+ updatedRev.ChildDrop(name, childRev.GetHash())
+
+ return newChildRev
+ } else {
+ log.Errorf("cannot add to non-keyed container")
+ }
+ } else {
+ log.Errorf("cannot add to non-container field")
+ }
+
+ return nil
+}
+
+// Remove eliminates a node at the specified path
+func (n *node) Remove(ctx context.Context, path string, txid string, makeBranch MakeBranchFunction) Revision {
+ n.mutex.Lock()
+ defer n.mutex.Unlock()
+
+ log.Debugw("node-remove-request", log.Fields{"path": path, "txid": txid, "makeBranch": makeBranch})
+
+ for strings.HasPrefix(path, "/") {
+ path = path[1:]
+ }
+ if path == "" {
+ // TODO raise error
+ log.Errorf("cannot remove for non-container mode")
+ }
+ var branch *Branch
+ if txid == "" {
+ branch = n.GetBranch(NONE)
+ } else if branch = n.GetBranch(txid); branch == nil {
+ branch = makeBranch(n)
+ }
+
+ rev := branch.GetLatest()
+
+ partition := strings.SplitN(path, "/", 2)
+ name := partition[0]
+ if len(partition) < 2 {
+ path = ""
+ } else {
+ path = partition[1]
+ }
+
+ field := ChildrenFields(n.Type)[name]
+ var children []Revision
+ postAnnouncement := []ChangeTuple{}
+
+ if field.IsContainer {
+ if path == "" {
+ log.Errorw("cannot-remove-without-key", log.Fields{"name": name, "key": path})
+ } else if field.Key != "" {
+ partition := strings.SplitN(path, "/", 2)
+ key := partition[0]
+ if len(partition) < 2 {
+ path = ""
+ } else {
+ path = partition[1]
+ }
+
+ keyValue := field.KeyFromStr(key)
+ children = make([]Revision, len(rev.GetChildren(name)))
+ copy(children, rev.GetChildren(name))
+
+ if path != "" {
+ if idx, childRev := n.findRevByKey(children, field.Key, keyValue); childRev != nil {
+ childNode := childRev.GetNode()
+ if childNode.Proxy == nil {
+ childNode.Proxy = n.Proxy
+ }
+ newChildRev := childNode.Remove(ctx, path, txid, makeBranch)
+
+ branch.LatestLock.Lock()
+ defer branch.LatestLock.Unlock()
+
+ if idx >= 0 {
+ children[idx] = newChildRev
+ } else {
+ children = append(children, newChildRev)
+ }
+
+ rev.SetChildren(name, children)
+ branch.GetLatest().Drop(txid, false)
+ n.makeLatest(branch, rev, nil)
+ }
+ return branch.GetLatest()
+ }
+
+ if idx, childRev := n.findRevByKey(children, field.Key, keyValue); childRev != nil && idx >= 0 {
+ if n.GetProxy() != nil {
+ data := childRev.GetData()
+ n.GetProxy().InvokeCallbacks(PRE_REMOVE, false, data)
+ postAnnouncement = append(postAnnouncement, ChangeTuple{POST_REMOVE, data, nil})
+ } else {
+ postAnnouncement = append(postAnnouncement, ChangeTuple{POST_REMOVE, childRev.GetData(), nil})
+ }
+
+ childRev.StorageDrop(txid, true)
+ GetRevCache().Delete(childRev.GetName())
+
+ branch.LatestLock.Lock()
+ defer branch.LatestLock.Unlock()
+
+ children = append(children[:idx], children[idx+1:]...)
+ rev.SetChildren(name, children)
+
+ branch.GetLatest().Drop(txid, false)
+ n.makeLatest(branch, rev, postAnnouncement)
+
+ return rev
+ } else {
+ log.Errorw("failed-to-find-revision", log.Fields{"name": name, "key": keyValue.(string)})
+ }
+ }
+ log.Errorw("cannot-add-to-non-keyed-container", log.Fields{"name": name, "path": path, "fieldKey": field.Key})
+
+ } else {
+ log.Errorw("cannot-add-to-non-container-field", log.Fields{"name": name, "path": path})
+ }
+
+ return nil
+}
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Branching ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+// MakeBranchFunction is a type for function references intented to create a branch
+type MakeBranchFunction func(*node) *Branch
+
+// MakeBranch creates a new branch for the provided transaction id
+func (n *node) MakeBranch(txid string) *Branch {
+ branchPoint := n.GetBranch(NONE).GetLatest()
+ branch := NewBranch(n, txid, branchPoint, true)
+ n.SetBranch(txid, branch)
+ return branch
+}
+
+// DeleteBranch removes a branch with the specified id
+func (n *node) DeleteBranch(txid string) {
+ delete(n.Branches, txid)
+}
+
+func (n *node) mergeChild(txid string, dryRun bool) func(Revision) Revision {
+ f := func(rev Revision) Revision {
+ childBranch := rev.GetBranch()
+
+ if childBranch.Txid == txid {
+ rev, _ = childBranch.Node.MergeBranch(txid, dryRun)
+ }
+
+ return rev
+ }
+ return f
+}
+
+// MergeBranch will integrate the contents of a transaction branch within the latest branch of a given node
+func (n *node) MergeBranch(txid string, dryRun bool) (Revision, error) {
+ srcBranch := n.GetBranch(txid)
+ dstBranch := n.GetBranch(NONE)
+
+ forkRev := srcBranch.Origin
+ srcRev := srcBranch.GetLatest()
+ dstRev := dstBranch.GetLatest()
+
+ rev, changes := Merge3Way(forkRev, srcRev, dstRev, n.mergeChild(txid, dryRun), dryRun)
+
+ if !dryRun {
+ if rev != nil {
+ rev.SetName(dstRev.GetName())
+ n.makeLatest(dstBranch, rev, changes)
+ }
+ n.DeleteBranch(txid)
+ }
+
+ // TODO: return proper error when one occurs
+ return rev, nil
+}
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Diff utility ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+//func (n *node) diff(hash1, hash2, txid string) {
+// branch := n.Branches[txid]
+// rev1 := branch.GetHash(hash1)
+// rev2 := branch.GetHash(hash2)
+//
+// if rev1.GetHash() == rev2.GetHash() {
+// // empty patch
+// } else {
+// // translate data to json and generate patch
+// patch, err := jsonpatch.MakePatch(rev1.GetData(), rev2.GetData())
+// patch.
+// }
+//}
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Tag utility ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+// TODO: is tag mgmt used in the python implementation? Need to validate
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Internals ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+func (n *node) hasChildren(data interface{}) bool {
+ for fieldName, field := range ChildrenFields(n.Type) {
+ _, fieldValue := GetAttributeValue(data, fieldName, 0)
+
+ if (field.IsContainer && fieldValue.Len() > 0) || !fieldValue.IsNil() {
+ log.Error("cannot update external children")
+ return true
+ }
+ }
+
+ return false
+}
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ node Proxy ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+// CreateProxy returns a reference to a sub-tree of the data model
+func (n *node) CreateProxy(ctx context.Context, path string, exclusive bool) *Proxy {
+ return n.createProxy(ctx, path, path, n, exclusive)
+}
+
+func (n *node) createProxy(ctx context.Context, path string, fullPath string, parentNode *node, exclusive bool) *Proxy {
+ log.Debugw("node-create-proxy", log.Fields{
+ "node-type": reflect.ValueOf(n.Type).Type(),
+ "parent-node-type": reflect.ValueOf(parentNode.Type).Type(),
+ "path": path,
+ "fullPath": fullPath,
+ })
+
+ for strings.HasPrefix(path, "/") {
+ path = path[1:]
+ }
+ if path == "" {
+ return n.makeProxy(path, fullPath, parentNode, exclusive)
+ }
+
+ rev := n.GetBranch(NONE).GetLatest()
+ partition := strings.SplitN(path, "/", 2)
+ name := partition[0]
+ var nodeType interface{}
+ if len(partition) < 2 {
+ path = ""
+ nodeType = n.Type
+ } else {
+ path = partition[1]
+ nodeType = parentNode.Type
+ }
+
+ field := ChildrenFields(nodeType)[name]
+
+ if field != nil {
+ if field.IsContainer {
+ log.Debugw("container-field", log.Fields{
+ "node-type": reflect.ValueOf(n.Type).Type(),
+ "parent-node-type": reflect.ValueOf(parentNode.Type).Type(),
+ "path": path,
+ "name": name,
+ })
+ if path == "" {
+ log.Debugw("folder-proxy", log.Fields{
+ "node-type": reflect.ValueOf(n.Type).Type(),
+ "parent-node-type": reflect.ValueOf(parentNode.Type).Type(),
+ "fullPath": fullPath,
+ "name": name,
+ })
+ newNode := n.MakeNode(reflect.New(field.ClassType.Elem()).Interface(), "")
+ return newNode.makeProxy(path, fullPath, parentNode, exclusive)
+ } else if field.Key != "" {
+ log.Debugw("key-proxy", log.Fields{
+ "node-type": reflect.ValueOf(n.Type).Type(),
+ "parent-node-type": reflect.ValueOf(parentNode.Type).Type(),
+ "fullPath": fullPath,
+ "name": name,
+ })
+ partition := strings.SplitN(path, "/", 2)
+ key := partition[0]
+ if len(partition) < 2 {
+ path = ""
+ } else {
+ path = partition[1]
+ }
+ keyValue := field.KeyFromStr(key)
+ var children []Revision
+ children = make([]Revision, len(rev.GetChildren(name)))
+ copy(children, rev.GetChildren(name))
+
+ var childRev Revision
+ if _, childRev = n.findRevByKey(children, field.Key, keyValue); childRev != nil {
+ log.Debugw("found-revision-matching-key-in-memory", log.Fields{
+ "node-type": reflect.ValueOf(n.Type).Type(),
+ "parent-node-type": reflect.ValueOf(parentNode.Type).Type(),
+ "fullPath": fullPath,
+ "name": name,
+ })
+ } else if revs := n.GetBranch(NONE).GetLatest().LoadFromPersistence(ctx, fullPath, "", nil); revs != nil && len(revs) > 0 {
+ log.Debugw("found-revision-matching-key-in-db", log.Fields{
+ "node-type": reflect.ValueOf(n.Type).Type(),
+ "parent-node-type": reflect.ValueOf(parentNode.Type).Type(),
+ "fullPath": fullPath,
+ "name": name,
+ })
+ childRev = revs[0]
+ } else {
+ log.Debugw("no-revision-matching-key", log.Fields{
+ "node-type": reflect.ValueOf(n.Type).Type(),
+ "parent-node-type": reflect.ValueOf(parentNode.Type).Type(),
+ "fullPath": fullPath,
+ "name": name,
+ })
+ }
+ if childRev != nil {
+ childNode := childRev.GetNode()
+ return childNode.createProxy(ctx, path, fullPath, n, exclusive)
+ }
+ } else {
+ log.Errorw("cannot-access-index-of-empty-container", log.Fields{
+ "node-type": reflect.ValueOf(n.Type).Type(),
+ "parent-node-type": reflect.ValueOf(parentNode.Type).Type(),
+ "path": path,
+ "name": name,
+ })
+ }
+ } else {
+ log.Debugw("non-container-field", log.Fields{
+ "node-type": reflect.ValueOf(n.Type).Type(),
+ "parent-node-type": reflect.ValueOf(parentNode.Type).Type(),
+ "path": path,
+ "name": name,
+ })
+ childRev := rev.GetChildren(name)[0]
+ childNode := childRev.GetNode()
+ return childNode.createProxy(ctx, path, fullPath, n, exclusive)
+ }
+ } else {
+ log.Debugw("field-object-is-nil", log.Fields{
+ "node-type": reflect.ValueOf(n.Type).Type(),
+ "parent-node-type": reflect.ValueOf(parentNode.Type).Type(),
+ "fullPath": fullPath,
+ "name": name,
+ })
+ }
+
+ log.Warnw("cannot-create-proxy", log.Fields{
+ "node-type": reflect.ValueOf(n.Type).Type(),
+ "parent-node-type": reflect.ValueOf(parentNode.Type).Type(),
+ "path": path,
+ "fullPath": fullPath,
+ "latest-rev": rev.GetHash(),
+ })
+ return nil
+}
+
+func (n *node) makeProxy(path string, fullPath string, parentNode *node, exclusive bool) *Proxy {
+ log.Debugw("node-make-proxy", log.Fields{
+ "node-type": reflect.ValueOf(n.Type).Type(),
+ "parent-node-type": reflect.ValueOf(parentNode.Type).Type(),
+ "path": path,
+ "fullPath": fullPath,
+ })
+
+ r := &root{
+ node: n,
+ Callbacks: n.Root.GetCallbacks(),
+ NotificationCallbacks: n.Root.GetNotificationCallbacks(),
+ DirtyNodes: n.Root.DirtyNodes,
+ KvStore: n.Root.KvStore,
+ Loading: n.Root.Loading,
+ RevisionClass: n.Root.RevisionClass,
+ }
+
+ if n.Proxy == nil {
+ log.Debugw("constructing-new-proxy", log.Fields{
+ "node-type": reflect.ValueOf(n.Type).Type(),
+ "parent-node-type": reflect.ValueOf(parentNode.Type).Type(),
+ "path": path,
+ "fullPath": fullPath,
+ })
+ n.Proxy = NewProxy(r, n, parentNode, path, fullPath, exclusive)
+ } else {
+ log.Debugw("node-has-existing-proxy", log.Fields{
+ "node-type": reflect.ValueOf(n.GetProxy().Node.Type).Type(),
+ "parent-node-type": reflect.ValueOf(n.GetProxy().ParentNode.Type).Type(),
+ "path": n.GetProxy().Path,
+ "fullPath": n.GetProxy().FullPath,
+ })
+ if n.GetProxy().Exclusive {
+ log.Error("node is already owned exclusively")
+ }
+ }
+
+ return n.Proxy
+}
+
+func (n *node) makeEventBus() *EventBus {
+ if n.EventBus == nil {
+ n.EventBus = NewEventBus()
+ }
+ return n.EventBus
+}
+
+func (n *node) SetProxy(proxy *Proxy) {
+ n.Proxy = proxy
+}
+
+func (n *node) GetProxy() *Proxy {
+ return n.Proxy
+}
+
+func (n *node) GetBranch(key string) *Branch {
+ if n.Branches != nil {
+ if branch, exists := n.Branches[key]; exists {
+ return branch
+ }
+ }
+ return nil
+}
+
+func (n *node) SetBranch(key string, branch *Branch) {
+ n.Branches[key] = branch
+}
+
+func (n *node) GetRoot() *root {
+ return n.Root
+}
+func (n *node) SetRoot(root *root) {
+ n.Root = root
+}
diff --git a/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/non_persisted_revision.go b/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/non_persisted_revision.go
new file mode 100644
index 0000000..88320cb
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/non_persisted_revision.go
@@ -0,0 +1,497 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package model
+
+import (
+ "bytes"
+ "context"
+ "crypto/md5"
+ "fmt"
+ "github.com/golang/protobuf/proto"
+ "github.com/opencord/voltha-lib-go/pkg/db/kvstore"
+ "github.com/opencord/voltha-lib-go/pkg/log"
+ "reflect"
+ "sort"
+ "sync"
+ "time"
+)
+
+// TODO: Cache logic will have to be revisited to cleanup unused entries in memory (disabled for now)
+//
+type revCacheSingleton struct {
+ sync.RWMutex
+ Cache sync.Map
+}
+
+func (s *revCacheSingleton) Get(path string) (interface{}, bool) {
+ return s.Cache.Load(path)
+}
+func (s *revCacheSingleton) Set(path string, value interface{}) {
+ s.Cache.Store(path, value)
+}
+func (s *revCacheSingleton) Delete(path string) {
+ s.Cache.Delete(path)
+}
+
+var revCacheInstance *revCacheSingleton
+var revCacheOnce sync.Once
+
+func GetRevCache() *revCacheSingleton {
+ revCacheOnce.Do(func() {
+ revCacheInstance = &revCacheSingleton{Cache: sync.Map{}}
+ })
+ return revCacheInstance
+}
+
+type NonPersistedRevision struct {
+ mutex sync.RWMutex
+ Root *root
+ Config *DataRevision
+ childrenLock sync.RWMutex
+ Children map[string][]Revision
+ Hash string
+ Branch *Branch
+ WeakRef string
+ Name string
+ lastUpdate time.Time
+}
+
+func NewNonPersistedRevision(root *root, branch *Branch, data interface{}, children map[string][]Revision) Revision {
+ r := &NonPersistedRevision{}
+ r.Root = root
+ r.Branch = branch
+ r.Config = NewDataRevision(root, data)
+ r.Children = children
+ r.Hash = r.hashContent()
+ return r
+}
+
+func (npr *NonPersistedRevision) SetConfig(config *DataRevision) {
+ npr.mutex.Lock()
+ defer npr.mutex.Unlock()
+ npr.Config = config
+}
+
+func (npr *NonPersistedRevision) GetConfig() *DataRevision {
+ npr.mutex.Lock()
+ defer npr.mutex.Unlock()
+ return npr.Config
+}
+
+func (npr *NonPersistedRevision) SetAllChildren(children map[string][]Revision) {
+ npr.childrenLock.Lock()
+ defer npr.childrenLock.Unlock()
+ npr.Children = make(map[string][]Revision)
+
+ for key, value := range children {
+ npr.Children[key] = make([]Revision, len(value))
+ copy(npr.Children[key], value)
+ }
+}
+
+func (npr *NonPersistedRevision) SetChildren(name string, children []Revision) {
+ npr.childrenLock.Lock()
+ defer npr.childrenLock.Unlock()
+
+ npr.Children[name] = make([]Revision, len(children))
+ copy(npr.Children[name], children)
+}
+
+func (npr *NonPersistedRevision) GetAllChildren() map[string][]Revision {
+ npr.childrenLock.Lock()
+ defer npr.childrenLock.Unlock()
+
+ return npr.Children
+}
+
+func (npr *NonPersistedRevision) GetChildren(name string) []Revision {
+ npr.childrenLock.Lock()
+ defer npr.childrenLock.Unlock()
+
+ if _, exists := npr.Children[name]; exists {
+ return npr.Children[name]
+ }
+ return nil
+}
+
+func (npr *NonPersistedRevision) SetHash(hash string) {
+ npr.mutex.Lock()
+ defer npr.mutex.Unlock()
+ npr.Hash = hash
+}
+
+func (npr *NonPersistedRevision) GetHash() string {
+ //npr.mutex.Lock()
+ //defer npr.mutex.Unlock()
+ return npr.Hash
+}
+
+func (npr *NonPersistedRevision) ClearHash() {
+ npr.mutex.Lock()
+ defer npr.mutex.Unlock()
+ npr.Hash = ""
+}
+
+func (npr *NonPersistedRevision) GetName() string {
+ //npr.mutex.Lock()
+ //defer npr.mutex.Unlock()
+ return npr.Name
+}
+
+func (npr *NonPersistedRevision) SetName(name string) {
+ //npr.mutex.Lock()
+ //defer npr.mutex.Unlock()
+ npr.Name = name
+}
+func (npr *NonPersistedRevision) SetBranch(branch *Branch) {
+ npr.mutex.Lock()
+ defer npr.mutex.Unlock()
+ npr.Branch = branch
+}
+
+func (npr *NonPersistedRevision) GetBranch() *Branch {
+ npr.mutex.Lock()
+ defer npr.mutex.Unlock()
+ return npr.Branch
+}
+
+func (npr *NonPersistedRevision) GetData() interface{} {
+ npr.mutex.Lock()
+ defer npr.mutex.Unlock()
+ if npr.Config == nil {
+ return nil
+ }
+ return npr.Config.Data
+}
+
+func (npr *NonPersistedRevision) GetNode() *node {
+ npr.mutex.Lock()
+ defer npr.mutex.Unlock()
+ return npr.Branch.Node
+}
+
+func (npr *NonPersistedRevision) Finalize(skipOnExist bool) {
+ npr.Hash = npr.hashContent()
+}
+
+// hashContent generates a hash string based on the contents of the revision.
+// The string should be unique to avoid conflicts with other revisions
+func (npr *NonPersistedRevision) hashContent() string {
+ var buffer bytes.Buffer
+ var childrenKeys []string
+
+ if npr.Config != nil {
+ buffer.WriteString(npr.Config.Hash)
+ }
+
+ if npr.Name != "" {
+ buffer.WriteString(npr.Name)
+ }
+
+ for key := range npr.Children {
+ childrenKeys = append(childrenKeys, key)
+ }
+
+ sort.Strings(childrenKeys)
+
+ if len(npr.Children) > 0 {
+ // Loop through sorted Children keys
+ for _, key := range childrenKeys {
+ for _, child := range npr.Children[key] {
+ if child != nil && child.GetHash() != "" {
+ buffer.WriteString(child.GetHash())
+ }
+ }
+ }
+ }
+
+ return fmt.Sprintf("%x", md5.Sum(buffer.Bytes()))[:12]
+}
+
+// Get will retrieve the data for the current revision
+func (npr *NonPersistedRevision) Get(depth int) interface{} {
+ // 1. Clone the data to avoid any concurrent access issues
+ // 2. The current rev might still be pointing to an old config
+ // thus, force the revision to get its latest value
+ latestRev := npr.GetBranch().GetLatest()
+ originalData := proto.Clone(latestRev.GetData().(proto.Message))
+ data := originalData
+
+ if depth != 0 {
+ // FIXME: Traversing the struct through reflection sometimes corrupts the data.
+ // Unlike the original python implementation, golang structs are not lazy loaded.
+ // Keeping this non-critical logic for now, but Get operations should be forced to
+ // depth=0 to avoid going through the following loop.
+ for fieldName, field := range ChildrenFields(latestRev.GetData()) {
+ childDataName, childDataHolder := GetAttributeValue(data, fieldName, 0)
+ if field.IsContainer {
+ for _, rev := range latestRev.GetChildren(fieldName) {
+ childData := rev.Get(depth - 1)
+ foundEntry := false
+ for i := 0; i < childDataHolder.Len(); i++ {
+ cdh_if := childDataHolder.Index(i).Interface()
+ if cdh_if.(proto.Message).String() == childData.(proto.Message).String() {
+ foundEntry = true
+ break
+ }
+ }
+ if !foundEntry {
+ // avoid duplicates by adding it only if the child was not found in the holder
+ childDataHolder = reflect.Append(childDataHolder, reflect.ValueOf(childData))
+ }
+ }
+ } else {
+ if revs := npr.GetBranch().GetLatest().GetChildren(fieldName); revs != nil && len(revs) > 0 {
+ rev := revs[0]
+ if rev != nil {
+ childData := rev.Get(depth - 1)
+ if reflect.TypeOf(childData) == reflect.TypeOf(childDataHolder.Interface()) {
+ childDataHolder = reflect.ValueOf(childData)
+ }
+ }
+ }
+ }
+ // Merge child data with cloned object
+ reflect.ValueOf(data).Elem().FieldByName(childDataName).Set(childDataHolder)
+ }
+ }
+
+ result := data
+
+ if result != nil {
+ // We need to send back a copy of the retrieved object
+ result = proto.Clone(data.(proto.Message))
+ }
+
+ return result
+}
+
+// UpdateData will refresh the data content of the revision
+func (npr *NonPersistedRevision) UpdateData(ctx context.Context, data interface{}, branch *Branch) Revision {
+ npr.mutex.Lock()
+ defer npr.mutex.Unlock()
+
+ if ctx != nil {
+ if ctxTS, ok := ctx.Value(RequestTimestamp).(int64); ok && npr.lastUpdate.UnixNano() > ctxTS {
+ log.Warnw("data-is-older-than-current", log.Fields{"ctx-ts": ctxTS, "rev-ts": npr.lastUpdate.UnixNano()})
+ return npr
+ }
+ }
+
+ // Do not update the revision if data is the same
+ if npr.Config.Data != nil && npr.Config.hashData(npr.Root, data) == npr.Config.Hash {
+ log.Debugw("stored-data-matches-latest", log.Fields{"stored": npr.Config.Data, "provided": data})
+ return npr
+ }
+
+ // Construct a new revision based on the current one
+ newRev := NonPersistedRevision{}
+ newRev.Config = NewDataRevision(npr.Root, data)
+ newRev.Hash = npr.Hash
+ newRev.Root = npr.Root
+ newRev.Name = npr.Name
+ newRev.Branch = branch
+ newRev.lastUpdate = npr.lastUpdate
+
+ newRev.Children = make(map[string][]Revision)
+ for entryName, childrenEntry := range branch.GetLatest().GetAllChildren() {
+ newRev.Children[entryName] = append(newRev.Children[entryName], childrenEntry...)
+ }
+
+ newRev.Finalize(false)
+
+ return &newRev
+}
+
+// UpdateChildren will refresh the list of children with the provided ones
+// It will carefully go through the list and ensure that no child is lost
+func (npr *NonPersistedRevision) UpdateChildren(ctx context.Context, name string, children []Revision, branch *Branch) Revision {
+ npr.mutex.Lock()
+ defer npr.mutex.Unlock()
+
+ // Construct a new revision based on the current one
+ updatedRev := &NonPersistedRevision{}
+ updatedRev.Config = NewDataRevision(npr.Root, npr.Config.Data)
+ updatedRev.Hash = npr.Hash
+ updatedRev.Branch = branch
+ updatedRev.Name = npr.Name
+ updatedRev.lastUpdate = npr.lastUpdate
+
+ updatedRev.Children = make(map[string][]Revision)
+ for entryName, childrenEntry := range branch.GetLatest().GetAllChildren() {
+ updatedRev.Children[entryName] = append(updatedRev.Children[entryName], childrenEntry...)
+ }
+
+ var updatedChildren []Revision
+
+ // Verify if the map contains already contains an entry matching the name value
+ // If so, we need to retain the contents of that entry and merge them with the provided children revision list
+ if existingChildren := branch.GetLatest().GetChildren(name); existingChildren != nil {
+ // Construct a map of unique child names with the respective index value
+ // for the children in the existing revision as well as the new ones
+ existingNames := make(map[string]int)
+ newNames := make(map[string]int)
+
+ for i, newChild := range children {
+ newNames[newChild.GetName()] = i
+ }
+
+ for i, existingChild := range existingChildren {
+ existingNames[existingChild.GetName()] = i
+
+ // If an existing entry is not in the new list, add it to the updated list, so it is not forgotten
+ if _, exists := newNames[existingChild.GetName()]; !exists {
+ updatedChildren = append(updatedChildren, existingChild)
+ }
+ }
+
+ log.Debugw("existing-children-names", log.Fields{"hash": npr.GetHash(), "names": existingNames})
+
+ // Merge existing and new children
+ for _, newChild := range children {
+ nameIndex, nameExists := existingNames[newChild.GetName()]
+
+ // Does the existing list contain a child with that name?
+ if nameExists {
+ // Check if the data has changed or not
+ if existingChildren[nameIndex].GetData().(proto.Message).String() != newChild.GetData().(proto.Message).String() {
+ log.Debugw("replacing-existing-child", log.Fields{
+ "old-hash": existingChildren[nameIndex].GetHash(),
+ "old-data": existingChildren[nameIndex].GetData(),
+ "new-hash": newChild.GetHash(),
+ "new-data": newChild.GetData(),
+ })
+
+ // replace entry
+ newChild.GetNode().SetRoot(existingChildren[nameIndex].GetNode().GetRoot())
+ updatedChildren = append(updatedChildren, newChild)
+ } else {
+ log.Debugw("keeping-existing-child", log.Fields{
+ "old-hash": existingChildren[nameIndex].GetHash(),
+ "old-data": existingChildren[nameIndex].GetData(),
+ "new-hash": newChild.GetHash(),
+ "new-data": newChild.GetData(),
+ })
+
+ // keep existing entry
+ updatedChildren = append(updatedChildren, existingChildren[nameIndex])
+ }
+ } else {
+ log.Debugw("adding-unknown-child", log.Fields{
+ "hash": newChild.GetHash(),
+ "data": newChild.GetData(),
+ })
+
+ // new entry ... just add it
+ updatedChildren = append(updatedChildren, newChild)
+ }
+ }
+
+ // Save children in new revision
+ updatedRev.SetChildren(name, updatedChildren)
+
+ updatedNames := make(map[string]int)
+ for i, updatedChild := range updatedChildren {
+ updatedNames[updatedChild.GetName()] = i
+ }
+
+ log.Debugw("updated-children-names", log.Fields{"hash": npr.GetHash(), "names": updatedNames})
+
+ } else {
+ // There are no children available, just save the provided ones
+ updatedRev.SetChildren(name, children)
+ }
+
+ updatedRev.Finalize(false)
+
+ return updatedRev
+}
+
+// UpdateAllChildren will replace the current list of children with the provided ones
+func (npr *NonPersistedRevision) UpdateAllChildren(children map[string][]Revision, branch *Branch) Revision {
+ npr.mutex.Lock()
+ defer npr.mutex.Unlock()
+
+ newRev := npr
+ newRev.Config = npr.Config
+ newRev.Hash = npr.Hash
+ newRev.Branch = branch
+ newRev.Name = npr.Name
+ newRev.lastUpdate = npr.lastUpdate
+
+ newRev.Children = make(map[string][]Revision)
+ for entryName, childrenEntry := range children {
+ newRev.Children[entryName] = append(newRev.Children[entryName], childrenEntry...)
+ }
+ newRev.Finalize(false)
+
+ return newRev
+}
+
+// Drop is used to indicate when a revision is no longer required
+func (npr *NonPersistedRevision) Drop(txid string, includeConfig bool) {
+ log.Debugw("dropping-revision", log.Fields{"hash": npr.GetHash(), "name": npr.GetName()})
+}
+
+// ChildDrop will remove a child entry matching the provided parameters from the current revision
+func (npr *NonPersistedRevision) ChildDrop(childType string, childHash string) {
+ if childType != "" {
+ children := make([]Revision, len(npr.GetChildren(childType)))
+ copy(children, npr.GetChildren(childType))
+ for i, child := range children {
+ if child.GetHash() == childHash {
+ children = append(children[:i], children[i+1:]...)
+ npr.SetChildren(childType, children)
+ break
+ }
+ }
+ }
+}
+
+func (npr *NonPersistedRevision) SetLastUpdate(ts ...time.Time) {
+ npr.mutex.Lock()
+ defer npr.mutex.Unlock()
+
+ if ts != nil && len(ts) > 0 {
+ npr.lastUpdate = ts[0]
+ } else {
+ npr.lastUpdate = time.Now()
+ }
+}
+
+func (npr *NonPersistedRevision) GetLastUpdate() time.Time {
+ npr.mutex.RLock()
+ defer npr.mutex.RUnlock()
+
+ return npr.lastUpdate
+}
+
+func (npr *NonPersistedRevision) LoadFromPersistence(ctx context.Context, path string, txid string, blobs map[string]*kvstore.KVPair) []Revision {
+ // stub... required by interface
+ return nil
+}
+
+func (npr *NonPersistedRevision) SetupWatch(key string) {
+ // stub ... required by interface
+}
+
+func (npr *NonPersistedRevision) StorageDrop(txid string, includeConfig bool) {
+ // stub ... required by interface
+}
+
+func (npr *NonPersistedRevision) getVersion() int64 {
+ return -1
+}
diff --git a/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/persisted_revision.go b/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/persisted_revision.go
new file mode 100644
index 0000000..c644e14
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/persisted_revision.go
@@ -0,0 +1,614 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package model
+
+import (
+ "bytes"
+ "compress/gzip"
+ "context"
+ "github.com/golang/protobuf/proto"
+ "github.com/google/uuid"
+ "github.com/opencord/voltha-lib-go/pkg/db/kvstore"
+ "github.com/opencord/voltha-lib-go/pkg/log"
+ "reflect"
+ "strings"
+ "sync"
+)
+
+// PersistedRevision holds information of revision meant to be saved in a persistent storage
+type PersistedRevision struct {
+ Revision
+ Compress bool
+
+ events chan *kvstore.Event
+ kvStore *Backend
+ mutex sync.RWMutex
+ versionMutex sync.RWMutex
+ Version int64
+ isStored bool
+ isWatched bool
+}
+
+type watchCache struct {
+ Cache sync.Map
+}
+
+var watchCacheInstance *watchCache
+var watchCacheOne sync.Once
+
+func Watches() *watchCache {
+ watchCacheOne.Do(func() {
+ watchCacheInstance = &watchCache{Cache: sync.Map{}}
+ })
+ return watchCacheInstance
+}
+
+// NewPersistedRevision creates a new instance of a PersistentRevision structure
+func NewPersistedRevision(branch *Branch, data interface{}, children map[string][]Revision) Revision {
+ pr := &PersistedRevision{}
+ pr.kvStore = branch.Node.GetRoot().KvStore
+ pr.Version = 1
+ pr.Revision = NewNonPersistedRevision(nil, branch, data, children)
+ return pr
+}
+
+func (pr *PersistedRevision) getVersion() int64 {
+ pr.versionMutex.RLock()
+ defer pr.versionMutex.RUnlock()
+ return pr.Version
+}
+
+func (pr *PersistedRevision) setVersion(version int64) {
+ pr.versionMutex.Lock()
+ defer pr.versionMutex.Unlock()
+ pr.Version = version
+}
+
+// Finalize is responsible of saving the revision in the persistent storage
+func (pr *PersistedRevision) Finalize(skipOnExist bool) {
+ pr.store(skipOnExist)
+}
+
+func (pr *PersistedRevision) store(skipOnExist bool) {
+ if pr.GetBranch().Txid != "" {
+ return
+ }
+
+ log.Debugw("ready-to-store-revision", log.Fields{"hash": pr.GetHash(), "name": pr.GetName(), "data": pr.GetData()})
+
+ // clone the revision data to avoid any race conditions with processes
+ // accessing the same data
+ cloned := proto.Clone(pr.GetConfig().Data.(proto.Message))
+
+ if blob, err := proto.Marshal(cloned); err != nil {
+ log.Errorw("problem-to-marshal", log.Fields{"error": err, "hash": pr.GetHash(), "name": pr.GetName(), "data": pr.GetData()})
+ } else {
+ if pr.Compress {
+ var b bytes.Buffer
+ w := gzip.NewWriter(&b)
+ w.Write(blob)
+ w.Close()
+ blob = b.Bytes()
+ }
+
+ GetRevCache().Set(pr.GetName(), pr)
+ if err := pr.kvStore.Put(pr.GetName(), blob); err != nil {
+ log.Warnw("problem-storing-revision", log.Fields{"error": err, "hash": pr.GetHash(), "name": pr.GetName(), "data": pr.GetConfig().Data})
+ } else {
+ log.Debugw("storing-revision", log.Fields{"hash": pr.GetHash(), "name": pr.GetName(), "data": pr.GetConfig().Data, "version": pr.getVersion()})
+ pr.isStored = true
+ }
+ }
+}
+
+func (pr *PersistedRevision) SetupWatch(key string) {
+ if key == "" {
+ log.Debugw("ignoring-watch", log.Fields{"key": key, "revision-hash": pr.GetHash()})
+ return
+ }
+
+ if _, exists := Watches().Cache.LoadOrStore(key+"-"+pr.GetHash(), struct{}{}); exists {
+ return
+ }
+
+ if pr.events == nil {
+ pr.events = make(chan *kvstore.Event)
+
+ log.Debugw("setting-watch-channel", log.Fields{"key": key, "revision-hash": pr.GetHash()})
+
+ pr.SetName(key)
+ pr.events = pr.kvStore.CreateWatch(key)
+ }
+
+ if !pr.isWatched {
+ pr.isWatched = true
+
+ log.Debugw("setting-watch-routine", log.Fields{"key": key, "revision-hash": pr.GetHash()})
+
+ // Start watching
+ go pr.startWatching()
+ }
+}
+
+func (pr *PersistedRevision) startWatching() {
+ log.Debugw("starting-watch", log.Fields{"key": pr.GetHash(), "watch": pr.GetName()})
+
+StopWatchLoop:
+ for {
+ latestRev := pr.GetBranch().GetLatest()
+
+ select {
+ case event, ok := <-pr.events:
+ if !ok {
+ log.Errorw("event-channel-failure: stopping watch loop", log.Fields{"key": latestRev.GetHash(), "watch": latestRev.GetName()})
+ break StopWatchLoop
+ }
+ log.Debugw("received-event", log.Fields{"type": event.EventType, "watch": latestRev.GetName()})
+
+ switch event.EventType {
+ case kvstore.DELETE:
+ log.Debugw("delete-from-memory", log.Fields{"key": latestRev.GetHash(), "watch": latestRev.GetName()})
+ pr.Revision.Drop("", true)
+ break StopWatchLoop
+
+ case kvstore.PUT:
+ log.Debugw("update-in-memory", log.Fields{"key": latestRev.GetHash(), "watch": latestRev.GetName()})
+ if latestRev.getVersion() >= event.Version {
+ log.Debugw("skipping-matching-or-older-revision", log.Fields{
+ "watch": latestRev.GetName(),
+ "watch-version": event.Version,
+ "latest-version": latestRev.getVersion(),
+ })
+ continue
+ } else {
+ log.Debugw("watch-revision-is-newer", log.Fields{
+ "watch": latestRev.GetName(),
+ "watch-version": event.Version,
+ "latest-version": latestRev.getVersion(),
+ })
+ }
+
+ data := reflect.New(reflect.TypeOf(latestRev.GetData()).Elem())
+
+ if err := proto.Unmarshal(event.Value.([]byte), data.Interface().(proto.Message)); err != nil {
+ log.Errorw("failed-to-unmarshal-watch-data", log.Fields{"key": latestRev.GetHash(), "watch": latestRev.GetName(), "error": err})
+ } else {
+ log.Debugw("un-marshaled-watch-data", log.Fields{"key": latestRev.GetHash(), "watch": latestRev.GetName(), "data": data.Interface()})
+
+ var pathLock string
+ var blobs map[string]*kvstore.KVPair
+
+ // The watch reported new persistence data.
+ // Construct an object that will be used to update the memory
+ blobs = make(map[string]*kvstore.KVPair)
+ key, _ := kvstore.ToString(event.Key)
+ blobs[key] = &kvstore.KVPair{
+ Key: key,
+ Value: event.Value,
+ Session: "",
+ Lease: 0,
+ Version: event.Version,
+ }
+
+ if latestRev.GetNode().GetProxy() != nil {
+ //
+ // If a proxy exists for this revision, use it to lock access to the path
+ // and prevent simultaneous updates to the object in memory
+ //
+
+ //If the proxy already has a request in progress, then there is no need to process the watch
+ if latestRev.GetNode().GetProxy().GetOperation() != PROXY_NONE {
+ log.Debugw("operation-in-progress", log.Fields{
+ "key": latestRev.GetHash(),
+ "path": latestRev.GetNode().GetProxy().getFullPath(),
+ "operation": latestRev.GetNode().GetProxy().operation.String(),
+ })
+ continue
+ }
+
+ pathLock, _ = latestRev.GetNode().GetProxy().parseForControlledPath(latestRev.GetNode().GetProxy().getFullPath())
+
+ // Reserve the path to prevent others to modify while we reload from persistence
+ latestRev.GetNode().GetProxy().GetRoot().KvStore.Client.Reserve(pathLock+"_", uuid.New().String(), ReservationTTL)
+ latestRev.GetNode().GetProxy().SetOperation(PROXY_WATCH)
+
+ // Load changes and apply to memory
+ latestRev.LoadFromPersistence(context.Background(), latestRev.GetName(), "", blobs)
+
+ // Release path
+ latestRev.GetNode().GetProxy().GetRoot().KvStore.Client.ReleaseReservation(pathLock + "_")
+
+ } else {
+ // This block should be reached only if coming from a non-proxied request
+ log.Debugw("revision-with-no-proxy", log.Fields{"key": latestRev.GetHash(), "watch": latestRev.GetName()})
+
+ // Load changes and apply to memory
+ latestRev.LoadFromPersistence(context.Background(), latestRev.GetName(), "", blobs)
+ }
+ }
+
+ default:
+ log.Debugw("unhandled-event", log.Fields{"key": latestRev.GetHash(), "watch": latestRev.GetName(), "type": event.EventType})
+ }
+ }
+ }
+
+ Watches().Cache.Delete(pr.GetName() + "-" + pr.GetHash())
+
+ log.Debugw("exiting-watch", log.Fields{"key": pr.GetHash(), "watch": pr.GetName()})
+}
+
+// UpdateData modifies the information in the data model and saves it in the persistent storage
+func (pr *PersistedRevision) UpdateData(ctx context.Context, data interface{}, branch *Branch) Revision {
+ log.Debugw("updating-persisted-data", log.Fields{"hash": pr.GetHash()})
+
+ newNPR := pr.Revision.UpdateData(ctx, data, branch)
+
+ newPR := &PersistedRevision{
+ Revision: newNPR,
+ Compress: pr.Compress,
+ kvStore: pr.kvStore,
+ events: pr.events,
+ Version: pr.getVersion(),
+ isWatched: pr.isWatched,
+ }
+
+ if newPR.GetHash() != pr.GetHash() {
+ newPR.isStored = false
+ pr.Drop(branch.Txid, false)
+ pr.Drop(branch.Txid, false)
+ } else {
+ newPR.isStored = true
+ }
+
+ return newPR
+}
+
+// UpdateChildren modifies the children of a revision and of a specific component and saves it in the persistent storage
+func (pr *PersistedRevision) UpdateChildren(ctx context.Context, name string, children []Revision, branch *Branch) Revision {
+ log.Debugw("updating-persisted-children", log.Fields{"hash": pr.GetHash()})
+
+ newNPR := pr.Revision.UpdateChildren(ctx, name, children, branch)
+
+ newPR := &PersistedRevision{
+ Revision: newNPR,
+ Compress: pr.Compress,
+ kvStore: pr.kvStore,
+ events: pr.events,
+ Version: pr.getVersion(),
+ isWatched: pr.isWatched,
+ }
+
+ if newPR.GetHash() != pr.GetHash() {
+ newPR.isStored = false
+ pr.Drop(branch.Txid, false)
+ } else {
+ newPR.isStored = true
+ }
+
+ return newPR
+}
+
+// UpdateAllChildren modifies the children for all components of a revision and saves it in the peristent storage
+func (pr *PersistedRevision) UpdateAllChildren(children map[string][]Revision, branch *Branch) Revision {
+ log.Debugw("updating-all-persisted-children", log.Fields{"hash": pr.GetHash()})
+
+ newNPR := pr.Revision.UpdateAllChildren(children, branch)
+
+ newPR := &PersistedRevision{
+ Revision: newNPR,
+ Compress: pr.Compress,
+ kvStore: pr.kvStore,
+ events: pr.events,
+ Version: pr.getVersion(),
+ isWatched: pr.isWatched,
+ }
+
+ if newPR.GetHash() != pr.GetHash() {
+ newPR.isStored = false
+ pr.Drop(branch.Txid, false)
+ } else {
+ newPR.isStored = true
+ }
+
+ return newPR
+}
+
+// Drop takes care of eliminating a revision hash that is no longer needed
+// and its associated config when required
+func (pr *PersistedRevision) Drop(txid string, includeConfig bool) {
+ pr.Revision.Drop(txid, includeConfig)
+}
+
+// Drop takes care of eliminating a revision hash that is no longer needed
+// and its associated config when required
+func (pr *PersistedRevision) StorageDrop(txid string, includeConfig bool) {
+ log.Debugw("dropping-revision", log.Fields{"txid": txid, "hash": pr.GetHash(), "config-hash": pr.GetConfig().Hash})
+
+ pr.mutex.Lock()
+ defer pr.mutex.Unlock()
+ if pr.kvStore != nil && txid == "" {
+ if pr.isStored {
+ if pr.isWatched {
+ pr.kvStore.DeleteWatch(pr.GetName(), pr.events)
+ pr.isWatched = false
+ }
+
+ if err := pr.kvStore.Delete(pr.GetName()); err != nil {
+ log.Errorw("failed-to-remove-revision", log.Fields{"hash": pr.GetHash(), "error": err.Error()})
+ } else {
+ pr.isStored = false
+ }
+ }
+
+ } else {
+ if includeConfig {
+ log.Debugw("attempted-to-remove-transacted-revision-config", log.Fields{"hash": pr.GetConfig().Hash, "txid": txid})
+ }
+ log.Debugw("attempted-to-remove-transacted-revision", log.Fields{"hash": pr.GetHash(), "txid": txid})
+ }
+
+ pr.Revision.Drop(txid, includeConfig)
+}
+
+// verifyPersistedEntry validates if the provided data is available or not in memory and applies updates as required
+func (pr *PersistedRevision) verifyPersistedEntry(ctx context.Context, data interface{}, typeName string, keyName string,
+ keyValue string, txid string, version int64) (response Revision) {
+ // Parent which holds the current node entry
+ parent := pr.GetBranch().Node.GetRoot()
+
+ // Get a copy of the parent's children
+ children := make([]Revision, len(parent.GetBranch(NONE).Latest.GetChildren(typeName)))
+ copy(children, parent.GetBranch(NONE).Latest.GetChildren(typeName))
+
+ // Verify if a child with the provided key value can be found
+ if childIdx, childRev := pr.GetNode().findRevByKey(children, keyName, keyValue); childRev != nil {
+ // A child matching the provided key exists in memory
+ // Verify if the data differs from what was retrieved from persistence
+ // Also check if we are treating a newer revision of the data or not
+ if childRev.GetData().(proto.Message).String() != data.(proto.Message).String() && childRev.getVersion() < version {
+ log.Debugw("revision-data-is-different", log.Fields{
+ "key": childRev.GetHash(),
+ "name": childRev.GetName(),
+ "data": childRev.GetData(),
+ "version": childRev.getVersion(),
+ })
+
+ //
+ // Data has changed; replace the child entry and update the parent revision
+ //
+
+ // BEGIN Lock child -- prevent any incoming changes
+ childRev.GetBranch().LatestLock.Lock()
+
+ // Update child
+ updatedChildRev := childRev.UpdateData(ctx, data, childRev.GetBranch())
+
+ updatedChildRev.GetNode().SetProxy(childRev.GetNode().GetProxy())
+ updatedChildRev.SetupWatch(updatedChildRev.GetName())
+ updatedChildRev.SetLastUpdate()
+ updatedChildRev.(*PersistedRevision).setVersion(version)
+
+ // Update cache
+ GetRevCache().Set(updatedChildRev.GetName(), updatedChildRev)
+ childRev.Drop(txid, false)
+
+ childRev.GetBranch().LatestLock.Unlock()
+ // END lock child
+
+ // Update child entry
+ children[childIdx] = updatedChildRev
+
+ // BEGIN lock parent -- Update parent
+ parent.GetBranch(NONE).LatestLock.Lock()
+
+ updatedRev := parent.GetBranch(NONE).GetLatest().UpdateChildren(ctx, typeName, children, parent.GetBranch(NONE))
+ parent.GetBranch(NONE).Node.makeLatest(parent.GetBranch(NONE), updatedRev, nil)
+
+ parent.GetBranch(NONE).LatestLock.Unlock()
+ // END lock parent
+
+ // Drop the previous child revision
+ parent.GetBranch(NONE).Latest.ChildDrop(typeName, childRev.GetHash())
+
+ if updatedChildRev != nil {
+ log.Debugw("verify-persisted-entry--adding-child", log.Fields{
+ "key": updatedChildRev.GetHash(),
+ "name": updatedChildRev.GetName(),
+ "data": updatedChildRev.GetData(),
+ })
+ response = updatedChildRev
+ }
+ } else {
+ if childRev != nil {
+ log.Debugw("keeping-revision-data", log.Fields{
+ "key": childRev.GetHash(),
+ "name": childRev.GetName(),
+ "data": childRev.GetData(),
+ })
+
+ // Update timestamp to reflect when it was last read and to reset tracked timeout
+ childRev.SetLastUpdate()
+ if childRev.getVersion() < version {
+ childRev.(*PersistedRevision).setVersion(version)
+ }
+ GetRevCache().Set(childRev.GetName(), childRev)
+ response = childRev
+ }
+ }
+
+ } else {
+ // There is no available child with that key value.
+ // Create a new child and update the parent revision.
+ log.Debugw("no-such-revision-entry", log.Fields{
+ "key": keyValue,
+ "name": typeName,
+ "data": data,
+ })
+
+ // BEGIN child lock
+ pr.GetBranch().LatestLock.Lock()
+
+ // Construct a new child node with the retrieved persistence data
+ childRev = pr.GetBranch().Node.MakeNode(data, txid).Latest(txid)
+
+ // We need to start watching this entry for future changes
+ childRev.SetName(typeName + "/" + keyValue)
+ childRev.SetupWatch(childRev.GetName())
+ childRev.(*PersistedRevision).setVersion(version)
+
+ // Add entry to cache
+ GetRevCache().Set(childRev.GetName(), childRev)
+
+ pr.GetBranch().LatestLock.Unlock()
+ // END child lock
+
+ //
+ // Add the child to the parent revision
+ //
+
+ // BEGIN parent lock
+ parent.GetBranch(NONE).LatestLock.Lock()
+ children = append(children, childRev)
+ updatedRev := parent.GetBranch(NONE).GetLatest().UpdateChildren(ctx, typeName, children, parent.GetBranch(NONE))
+ updatedRev.GetNode().SetProxy(parent.GetBranch(NONE).Node.GetProxy())
+ parent.GetBranch(NONE).Node.makeLatest(parent.GetBranch(NONE), updatedRev, nil)
+ parent.GetBranch(NONE).LatestLock.Unlock()
+ // END parent lock
+
+ // Child entry is valid and can be included in the response object
+ if childRev != nil {
+ log.Debugw("adding-revision-to-response", log.Fields{
+ "key": childRev.GetHash(),
+ "name": childRev.GetName(),
+ "data": childRev.GetData(),
+ })
+ response = childRev
+ }
+ }
+
+ return response
+}
+
+// LoadFromPersistence retrieves data from kv store at the specified location and refreshes the memory
+// by adding missing entries, updating changed entries and ignoring unchanged ones
+func (pr *PersistedRevision) LoadFromPersistence(ctx context.Context, path string, txid string, blobs map[string]*kvstore.KVPair) []Revision {
+ pr.mutex.Lock()
+ defer pr.mutex.Unlock()
+
+ log.Debugw("loading-from-persistence", log.Fields{"path": path, "txid": txid})
+
+ var response []Revision
+
+ for strings.HasPrefix(path, "/") {
+ path = path[1:]
+ }
+
+ if pr.kvStore != nil && path != "" {
+ if blobs == nil || len(blobs) == 0 {
+ log.Debugw("retrieve-from-kv", log.Fields{"path": path, "txid": txid})
+ blobs, _ = pr.kvStore.List(path)
+ }
+
+ partition := strings.SplitN(path, "/", 2)
+ name := partition[0]
+
+ var nodeType interface{}
+ if len(partition) < 2 {
+ path = ""
+ nodeType = pr.GetBranch().Node.Type
+ } else {
+ path = partition[1]
+ nodeType = pr.GetBranch().Node.GetRoot().Type
+ }
+
+ field := ChildrenFields(nodeType)[name]
+
+ if field != nil && field.IsContainer {
+ log.Debugw("parsing-data-blobs", log.Fields{
+ "path": path,
+ "name": name,
+ "size": len(blobs),
+ })
+
+ for _, blob := range blobs {
+ output := blob.Value.([]byte)
+
+ data := reflect.New(field.ClassType.Elem())
+
+ if err := proto.Unmarshal(output, data.Interface().(proto.Message)); err != nil {
+ log.Errorw("failed-to-unmarshal", log.Fields{
+ "path": path,
+ "txid": txid,
+ "error": err,
+ })
+ } else if path == "" {
+ if field.Key != "" {
+ log.Debugw("no-path-with-container-key", log.Fields{
+ "path": path,
+ "txid": txid,
+ "data": data.Interface(),
+ })
+
+ // Retrieve the key identifier value from the data structure
+ // based on the field's key attribute
+ _, key := GetAttributeValue(data.Interface(), field.Key, 0)
+
+ if entry := pr.verifyPersistedEntry(ctx, data.Interface(), name, field.Key, key.String(), txid, blob.Version); entry != nil {
+ response = append(response, entry)
+ }
+ } else {
+ log.Debugw("path-with-no-container-key", log.Fields{
+ "path": path,
+ "txid": txid,
+ "data": data.Interface(),
+ })
+ }
+
+ } else if field.Key != "" {
+ log.Debugw("path-with-container-key", log.Fields{
+ "path": path,
+ "txid": txid,
+ "data": data.Interface(),
+ })
+ // The request is for a specific entry/id
+ partition := strings.SplitN(path, "/", 2)
+ key := partition[0]
+ if len(partition) < 2 {
+ path = ""
+ } else {
+ path = partition[1]
+ }
+ keyValue := field.KeyFromStr(key)
+
+ if entry := pr.verifyPersistedEntry(ctx, data.Interface(), name, field.Key, keyValue.(string), txid, blob.Version); entry != nil {
+ response = append(response, entry)
+ }
+ }
+ }
+
+ log.Debugw("no-more-data-blobs", log.Fields{"path": path, "name": name})
+ } else {
+ log.Debugw("cannot-process-field", log.Fields{
+ "type": pr.GetBranch().Node.Type,
+ "name": name,
+ })
+ }
+ }
+
+ return response
+}
diff --git a/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/profiling.go b/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/profiling.go
new file mode 100644
index 0000000..c50c9f6
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/profiling.go
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package model
+
+import (
+ "github.com/opencord/voltha-lib-go/pkg/log"
+ "sync"
+)
+
+// Profiling is used to store performance details collected at runtime
+type profiling struct {
+ sync.RWMutex
+ DatabaseRetrieveTime float64
+ DatabaseRetrieveCount int
+ InMemoryModelTime float64
+ InMemoryModelCount int
+ InMemoryProcessTime float64
+ DatabaseStoreTime float64
+ InMemoryLockTime float64
+ InMemoryLockCount int
+}
+
+var profilingInstance *profiling
+var profilingOnce sync.Once
+
+// GetProfiling returns a singleton instance of the Profiling structure
+func GetProfiling() *profiling {
+ profilingOnce.Do(func() {
+ profilingInstance = &profiling{}
+ })
+ return profilingInstance
+}
+
+// AddToDatabaseRetrieveTime appends a time period to retrieve data from the database
+func (p *profiling) AddToDatabaseRetrieveTime(period float64) {
+ p.Lock()
+ defer p.Unlock()
+
+ p.DatabaseRetrieveTime += period
+ p.DatabaseRetrieveCount++
+}
+
+// AddToInMemoryModelTime appends a time period to construct/deconstruct data in memory
+func (p *profiling) AddToInMemoryModelTime(period float64) {
+ p.Lock()
+ defer p.Unlock()
+
+ p.InMemoryModelTime += period
+ p.InMemoryModelCount++
+}
+
+// AddToInMemoryProcessTime appends a time period to process data
+func (p *profiling) AddToInMemoryProcessTime(period float64) {
+ p.Lock()
+ defer p.Unlock()
+
+ p.InMemoryProcessTime += period
+}
+
+// AddToDatabaseStoreTime appends a time period to store data in the database
+func (p *profiling) AddToDatabaseStoreTime(period float64) {
+ p.Lock()
+ defer p.Unlock()
+
+ p.DatabaseStoreTime += period
+}
+
+// AddToInMemoryLockTime appends a time period when a code block was locked
+func (p *profiling) AddToInMemoryLockTime(period float64) {
+ p.Lock()
+ defer p.Unlock()
+
+ p.InMemoryLockTime += period
+ p.InMemoryLockCount++
+}
+
+// Reset initializes the profile counters
+func (p *profiling) Reset() {
+ p.Lock()
+ defer p.Unlock()
+
+ p.DatabaseRetrieveTime = 0
+ p.DatabaseRetrieveCount = 0
+ p.InMemoryModelTime = 0
+ p.InMemoryModelCount = 0
+ p.InMemoryProcessTime = 0
+ p.DatabaseStoreTime = 0
+ p.InMemoryLockTime = 0
+ p.InMemoryLockCount = 0
+}
+
+// Report will provide the current profile counter status
+func (p *profiling) Report() {
+ p.Lock()
+ defer p.Unlock()
+
+ log.Infof("[ Profiling Report ]")
+ log.Infof("Database Retrieval : %f", p.DatabaseRetrieveTime)
+ log.Infof("Database Retrieval Count : %d", p.DatabaseRetrieveCount)
+ log.Infof("Avg Database Retrieval : %f", p.DatabaseRetrieveTime/float64(p.DatabaseRetrieveCount))
+ log.Infof("In-Memory Modeling : %f", p.InMemoryModelTime)
+ log.Infof("In-Memory Modeling Count: %d", p.InMemoryModelCount)
+ log.Infof("Avg In-Memory Modeling : %f", p.InMemoryModelTime/float64(p.InMemoryModelCount))
+ log.Infof("In-Memory Locking : %f", p.InMemoryLockTime)
+ log.Infof("In-Memory Locking Count: %d", p.InMemoryLockCount)
+ log.Infof("Avg In-Memory Locking : %f", p.InMemoryLockTime/float64(p.InMemoryLockCount))
+
+}
diff --git a/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/proxy.go b/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/proxy.go
new file mode 100644
index 0000000..2d2c24e
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/proxy.go
@@ -0,0 +1,598 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package model
+
+import (
+ "context"
+ "crypto/md5"
+ "errors"
+ "fmt"
+ "github.com/google/uuid"
+ "github.com/opencord/voltha-lib-go/pkg/log"
+ "reflect"
+ "runtime"
+ "strings"
+ "sync"
+)
+
+// OperationContext holds details on the information used during an operation
+type OperationContext struct {
+ Path string
+ Data interface{}
+ FieldName string
+ ChildKey string
+}
+
+// NewOperationContext instantiates a new OperationContext structure
+func NewOperationContext(path string, data interface{}, fieldName string, childKey string) *OperationContext {
+ oc := &OperationContext{
+ Path: path,
+ Data: data,
+ FieldName: fieldName,
+ ChildKey: childKey,
+ }
+ return oc
+}
+
+// Update applies new data to the context structure
+func (oc *OperationContext) Update(data interface{}) *OperationContext {
+ oc.Data = data
+ return oc
+}
+
+// Proxy holds the information for a specific location with the data model
+type Proxy struct {
+ mutex sync.RWMutex
+ Root *root
+ Node *node
+ ParentNode *node
+ Path string
+ FullPath string
+ Exclusive bool
+ Callbacks map[CallbackType]map[string]*CallbackTuple
+ operation ProxyOperation
+}
+
+// NewProxy instantiates a new proxy to a specific location
+func NewProxy(root *root, node *node, parentNode *node, path string, fullPath string, exclusive bool) *Proxy {
+ callbacks := make(map[CallbackType]map[string]*CallbackTuple)
+ if fullPath == "/" {
+ fullPath = ""
+ }
+ p := &Proxy{
+ Root: root,
+ Node: node,
+ ParentNode: parentNode,
+ Exclusive: exclusive,
+ Path: path,
+ FullPath: fullPath,
+ Callbacks: callbacks,
+ }
+ return p
+}
+
+// GetRoot returns the root attribute of the proxy
+func (p *Proxy) GetRoot() *root {
+ return p.Root
+}
+
+// getPath returns the path attribute of the proxy
+func (p *Proxy) getPath() string {
+ return p.Path
+}
+
+// getFullPath returns the full path attribute of the proxy
+func (p *Proxy) getFullPath() string {
+ return p.FullPath
+}
+
+// getCallbacks returns the full list of callbacks associated to the proxy
+func (p *Proxy) getCallbacks(callbackType CallbackType) map[string]*CallbackTuple {
+ p.mutex.RLock()
+ defer p.mutex.RUnlock()
+
+ if p != nil {
+ if cb, exists := p.Callbacks[callbackType]; exists {
+ return cb
+ }
+ } else {
+ log.Debugw("proxy-is-nil", log.Fields{"callback-type": callbackType.String()})
+ }
+ return nil
+}
+
+// getCallback returns a specific callback matching the type and function hash
+func (p *Proxy) getCallback(callbackType CallbackType, funcHash string) *CallbackTuple {
+ p.mutex.Lock()
+ defer p.mutex.Unlock()
+ if tuple, exists := p.Callbacks[callbackType][funcHash]; exists {
+ return tuple
+ }
+ return nil
+}
+
+// setCallbacks applies a callbacks list to a type
+func (p *Proxy) setCallbacks(callbackType CallbackType, callbacks map[string]*CallbackTuple) {
+ p.mutex.Lock()
+ defer p.mutex.Unlock()
+ p.Callbacks[callbackType] = callbacks
+}
+
+// setCallback applies a callback to a type and hash value
+func (p *Proxy) setCallback(callbackType CallbackType, funcHash string, tuple *CallbackTuple) {
+ p.mutex.Lock()
+ defer p.mutex.Unlock()
+ p.Callbacks[callbackType][funcHash] = tuple
+}
+
+// DeleteCallback removes a callback matching the type and hash
+func (p *Proxy) DeleteCallback(callbackType CallbackType, funcHash string) {
+ p.mutex.Lock()
+ defer p.mutex.Unlock()
+ delete(p.Callbacks[callbackType], funcHash)
+}
+
+// CallbackType is an enumerated value to express when a callback should be executed
+type ProxyOperation uint8
+
+// Enumerated list of callback types
+const (
+ PROXY_NONE ProxyOperation = iota
+ PROXY_GET
+ PROXY_LIST
+ PROXY_ADD
+ PROXY_UPDATE
+ PROXY_REMOVE
+ PROXY_CREATE
+ PROXY_WATCH
+)
+
+var proxyOperationTypes = []string{
+ "PROXY_NONE",
+ "PROXY_GET",
+ "PROXY_LIST",
+ "PROXY_ADD",
+ "PROXY_UPDATE",
+ "PROXY_REMOVE",
+ "PROXY_CREATE",
+ "PROXY_WATCH",
+}
+
+func (t ProxyOperation) String() string {
+ return proxyOperationTypes[t]
+}
+
+func (p *Proxy) GetOperation() ProxyOperation {
+ p.mutex.RLock()
+ defer p.mutex.RUnlock()
+ return p.operation
+}
+
+func (p *Proxy) SetOperation(operation ProxyOperation) {
+ p.mutex.Lock()
+ defer p.mutex.Unlock()
+ p.operation = operation
+}
+
+// parseForControlledPath verifies if a proxy path matches a pattern
+// for locations that need to be access controlled.
+func (p *Proxy) parseForControlledPath(path string) (pathLock string, controlled bool) {
+ // TODO: Add other path prefixes that may need control
+ if strings.HasPrefix(path, "/devices") ||
+ strings.HasPrefix(path, "/logical_devices") ||
+ strings.HasPrefix(path, "/adapters") {
+
+ split := strings.SplitN(path, "/", -1)
+ switch len(split) {
+ case 2:
+ controlled = false
+ pathLock = ""
+ break
+ case 3:
+ fallthrough
+ default:
+ pathLock = fmt.Sprintf("%s/%s", split[1], split[2])
+ controlled = true
+ }
+ }
+ return pathLock, controlled
+}
+
+// List will retrieve information from the data model at the specified path location
+// A list operation will force access to persistence storage
+func (p *Proxy) List(ctx context.Context, path string, depth int, deep bool, txid string) interface{} {
+ var effectivePath string
+ if path == "/" {
+ effectivePath = p.getFullPath()
+ } else {
+ effectivePath = p.getFullPath() + path
+ }
+
+ pathLock, controlled := p.parseForControlledPath(effectivePath)
+
+ p.SetOperation(PROXY_LIST)
+ defer p.SetOperation(PROXY_NONE)
+
+ log.Debugw("proxy-list", log.Fields{
+ "path": path,
+ "effective": effectivePath,
+ "pathLock": pathLock,
+ "controlled": controlled,
+ "operation": p.GetOperation(),
+ })
+
+ rv := p.GetRoot().List(ctx, path, "", depth, deep, txid)
+
+ return rv
+}
+
+// Get will retrieve information from the data model at the specified path location
+func (p *Proxy) Get(ctx context.Context, path string, depth int, deep bool, txid string) interface{} {
+ var effectivePath string
+ if path == "/" {
+ effectivePath = p.getFullPath()
+ } else {
+ effectivePath = p.getFullPath() + path
+ }
+
+ pathLock, controlled := p.parseForControlledPath(effectivePath)
+
+ p.SetOperation(PROXY_GET)
+ defer p.SetOperation(PROXY_NONE)
+
+ log.Debugw("proxy-get", log.Fields{
+ "path": path,
+ "effective": effectivePath,
+ "pathLock": pathLock,
+ "controlled": controlled,
+ "operation": p.GetOperation(),
+ })
+
+ rv := p.GetRoot().Get(ctx, path, "", depth, deep, txid)
+
+ return rv
+}
+
+// Update will modify information in the data model at the specified location with the provided data
+func (p *Proxy) Update(ctx context.Context, path string, data interface{}, strict bool, txid string) interface{} {
+ if !strings.HasPrefix(path, "/") {
+ log.Errorf("invalid path: %s", path)
+ return nil
+ }
+ var fullPath string
+ var effectivePath string
+ if path == "/" {
+ fullPath = p.getPath()
+ effectivePath = p.getFullPath()
+ } else {
+ fullPath = p.getPath() + path
+ effectivePath = p.getFullPath() + path
+ }
+
+ pathLock, controlled := p.parseForControlledPath(effectivePath)
+
+ p.SetOperation(PROXY_UPDATE)
+ defer p.SetOperation(PROXY_NONE)
+
+ log.Debugw("proxy-update", log.Fields{
+ "path": path,
+ "effective": effectivePath,
+ "full": fullPath,
+ "pathLock": pathLock,
+ "controlled": controlled,
+ "operation": p.GetOperation(),
+ })
+
+ if p.GetRoot().KvStore != nil {
+ p.GetRoot().KvStore.Client.Reserve(pathLock+"_", uuid.New().String(), ReservationTTL)
+ defer p.GetRoot().KvStore.Client.ReleaseReservation(pathLock + "_")
+ }
+
+ result := p.GetRoot().Update(ctx, fullPath, data, strict, txid, nil)
+
+ if result != nil {
+ return result.GetData()
+ }
+
+ return nil
+}
+
+// AddWithID will insert new data at specified location.
+// This method also allows the user to specify the ID of the data entry to ensure
+// that access control is active while inserting the information.
+func (p *Proxy) AddWithID(ctx context.Context, path string, id string, data interface{}, txid string) interface{} {
+ if !strings.HasPrefix(path, "/") {
+ log.Errorf("invalid path: %s", path)
+ return nil
+ }
+ var fullPath string
+ var effectivePath string
+ if path == "/" {
+ fullPath = p.getPath()
+ effectivePath = p.getFullPath()
+ } else {
+ fullPath = p.getPath() + path
+ effectivePath = p.getFullPath() + path + "/" + id
+ }
+
+ pathLock, controlled := p.parseForControlledPath(effectivePath)
+
+ p.SetOperation(PROXY_ADD)
+ defer p.SetOperation(PROXY_NONE)
+
+ log.Debugw("proxy-add-with-id", log.Fields{
+ "path": path,
+ "effective": effectivePath,
+ "full": fullPath,
+ "pathLock": pathLock,
+ "controlled": controlled,
+ "operation": p.GetOperation(),
+ })
+
+ if p.GetRoot().KvStore != nil {
+ p.GetRoot().KvStore.Client.Reserve(pathLock+"_", uuid.New().String(), ReservationTTL)
+ defer p.GetRoot().KvStore.Client.ReleaseReservation(pathLock + "_")
+ }
+
+ result := p.GetRoot().Add(ctx, fullPath, data, txid, nil)
+
+ if result != nil {
+ return result.GetData()
+ }
+
+ return nil
+}
+
+// Add will insert new data at specified location.
+func (p *Proxy) Add(ctx context.Context, path string, data interface{}, txid string) interface{} {
+ if !strings.HasPrefix(path, "/") {
+ log.Errorf("invalid path: %s", path)
+ return nil
+ }
+ var fullPath string
+ var effectivePath string
+ if path == "/" {
+ fullPath = p.getPath()
+ effectivePath = p.getFullPath()
+ } else {
+ fullPath = p.getPath() + path
+ effectivePath = p.getFullPath() + path
+ }
+
+ pathLock, controlled := p.parseForControlledPath(effectivePath)
+
+ p.SetOperation(PROXY_ADD)
+ defer p.SetOperation(PROXY_NONE)
+
+ log.Debugw("proxy-add", log.Fields{
+ "path": path,
+ "effective": effectivePath,
+ "full": fullPath,
+ "pathLock": pathLock,
+ "controlled": controlled,
+ "operation": p.GetOperation(),
+ })
+
+ if p.GetRoot().KvStore != nil {
+ p.GetRoot().KvStore.Client.Reserve(pathLock+"_", uuid.New().String(), ReservationTTL)
+ defer p.GetRoot().KvStore.Client.ReleaseReservation(pathLock + "_")
+ }
+
+ result := p.GetRoot().Add(ctx, fullPath, data, txid, nil)
+
+ if result != nil {
+ return result.GetData()
+ }
+
+ return nil
+}
+
+// Remove will delete an entry at the specified location
+func (p *Proxy) Remove(ctx context.Context, path string, txid string) interface{} {
+ if !strings.HasPrefix(path, "/") {
+ log.Errorf("invalid path: %s", path)
+ return nil
+ }
+ var fullPath string
+ var effectivePath string
+ if path == "/" {
+ fullPath = p.getPath()
+ effectivePath = p.getFullPath()
+ } else {
+ fullPath = p.getPath() + path
+ effectivePath = p.getFullPath() + path
+ }
+
+ pathLock, controlled := p.parseForControlledPath(effectivePath)
+
+ p.SetOperation(PROXY_REMOVE)
+ defer p.SetOperation(PROXY_NONE)
+
+ log.Debugw("proxy-remove", log.Fields{
+ "path": path,
+ "effective": effectivePath,
+ "full": fullPath,
+ "pathLock": pathLock,
+ "controlled": controlled,
+ "operation": p.GetOperation(),
+ })
+
+ if p.GetRoot().KvStore != nil {
+ p.GetRoot().KvStore.Client.Reserve(pathLock+"_", uuid.New().String(), ReservationTTL)
+ defer p.GetRoot().KvStore.Client.ReleaseReservation(pathLock + "_")
+ }
+
+ result := p.GetRoot().Remove(ctx, fullPath, txid, nil)
+
+ if result != nil {
+ return result.GetData()
+ }
+
+ return nil
+}
+
+// CreateProxy to interact with specific path directly
+func (p *Proxy) CreateProxy(ctx context.Context, path string, exclusive bool) *Proxy {
+ if !strings.HasPrefix(path, "/") {
+ log.Errorf("invalid path: %s", path)
+ return nil
+ }
+
+ var fullPath string
+ var effectivePath string
+ if path == "/" {
+ fullPath = p.getPath()
+ effectivePath = p.getFullPath()
+ } else {
+ fullPath = p.getPath() + path
+ effectivePath = p.getFullPath() + path
+ }
+
+ pathLock, controlled := p.parseForControlledPath(effectivePath)
+
+ p.SetOperation(PROXY_CREATE)
+ defer p.SetOperation(PROXY_NONE)
+
+ log.Debugw("proxy-create", log.Fields{
+ "path": path,
+ "effective": effectivePath,
+ "full": fullPath,
+ "pathLock": pathLock,
+ "controlled": controlled,
+ "operation": p.GetOperation(),
+ })
+
+ if p.GetRoot().KvStore != nil {
+ p.GetRoot().KvStore.Client.Reserve(pathLock+"_", uuid.New().String(), ReservationTTL)
+ defer p.GetRoot().KvStore.Client.ReleaseReservation(pathLock + "_")
+ }
+
+ return p.GetRoot().CreateProxy(ctx, fullPath, exclusive)
+}
+
+// OpenTransaction creates a new transaction branch to isolate operations made to the data model
+func (p *Proxy) OpenTransaction() *Transaction {
+ txid := p.GetRoot().MakeTxBranch()
+ return NewTransaction(p, txid)
+}
+
+// commitTransaction will apply and merge modifications made in the transaction branch to the data model
+func (p *Proxy) commitTransaction(txid string) {
+ p.GetRoot().FoldTxBranch(txid)
+}
+
+// cancelTransaction will terminate a transaction branch along will all changes within it
+func (p *Proxy) cancelTransaction(txid string) {
+ p.GetRoot().DeleteTxBranch(txid)
+}
+
+// CallbackFunction is a type used to define callback functions
+type CallbackFunction func(args ...interface{}) interface{}
+
+// CallbackTuple holds the function and arguments details of a callback
+type CallbackTuple struct {
+ callback CallbackFunction
+ args []interface{}
+}
+
+// Execute will process the a callback with its provided arguments
+func (tuple *CallbackTuple) Execute(contextArgs []interface{}) interface{} {
+ args := []interface{}{}
+
+ for _, ta := range tuple.args {
+ args = append(args, ta)
+ }
+
+ if contextArgs != nil {
+ for _, ca := range contextArgs {
+ args = append(args, ca)
+ }
+ }
+
+ return tuple.callback(args...)
+}
+
+// RegisterCallback associates a callback to the proxy
+func (p *Proxy) RegisterCallback(callbackType CallbackType, callback CallbackFunction, args ...interface{}) {
+ if p.getCallbacks(callbackType) == nil {
+ p.setCallbacks(callbackType, make(map[string]*CallbackTuple))
+ }
+ funcName := runtime.FuncForPC(reflect.ValueOf(callback).Pointer()).Name()
+ log.Debugf("value of function: %s", funcName)
+ funcHash := fmt.Sprintf("%x", md5.Sum([]byte(funcName)))[:12]
+
+ p.setCallback(callbackType, funcHash, &CallbackTuple{callback, args})
+}
+
+// UnregisterCallback removes references to a callback within a proxy
+func (p *Proxy) UnregisterCallback(callbackType CallbackType, callback CallbackFunction, args ...interface{}) {
+ if p.getCallbacks(callbackType) == nil {
+ log.Errorf("no such callback type - %s", callbackType.String())
+ return
+ }
+
+ funcName := runtime.FuncForPC(reflect.ValueOf(callback).Pointer()).Name()
+ funcHash := fmt.Sprintf("%x", md5.Sum([]byte(funcName)))[:12]
+
+ log.Debugf("value of function: %s", funcName)
+
+ if p.getCallback(callbackType, funcHash) == nil {
+ log.Errorf("function with hash value: '%s' not registered with callback type: '%s'", funcHash, callbackType)
+ return
+ }
+
+ p.DeleteCallback(callbackType, funcHash)
+}
+
+func (p *Proxy) invoke(callback *CallbackTuple, context []interface{}) (result interface{}, err error) {
+ defer func() {
+ if r := recover(); r != nil {
+ errStr := fmt.Sprintf("callback error occurred: %+v", r)
+ err = errors.New(errStr)
+ log.Error(errStr)
+ }
+ }()
+
+ result = callback.Execute(context)
+
+ return result, err
+}
+
+// InvokeCallbacks executes all callbacks associated to a specific type
+func (p *Proxy) InvokeCallbacks(args ...interface{}) (result interface{}) {
+ callbackType := args[0].(CallbackType)
+ proceedOnError := args[1].(bool)
+ context := args[2:]
+
+ var err error
+
+ if callbacks := p.getCallbacks(callbackType); callbacks != nil {
+ p.mutex.Lock()
+ for _, callback := range callbacks {
+ if result, err = p.invoke(callback, context); err != nil {
+ if !proceedOnError {
+ log.Info("An error occurred. Stopping callback invocation")
+ break
+ }
+ log.Info("An error occurred. Invoking next callback")
+ }
+ }
+ p.mutex.Unlock()
+ }
+
+ return result
+}
diff --git a/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/revision.go b/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/revision.go
new file mode 100644
index 0000000..bb61355
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/revision.go
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package model
+
+import (
+ "context"
+ "github.com/opencord/voltha-lib-go/pkg/db/kvstore"
+ "time"
+)
+
+type Revision interface {
+ Finalize(bool)
+ SetConfig(revision *DataRevision)
+ GetConfig() *DataRevision
+ Drop(txid string, includeConfig bool)
+ StorageDrop(txid string, includeConfig bool)
+ ChildDrop(childType string, childHash string)
+ SetChildren(name string, children []Revision)
+ GetChildren(name string) []Revision
+ SetAllChildren(children map[string][]Revision)
+ GetAllChildren() map[string][]Revision
+ SetHash(hash string)
+ GetHash() string
+ ClearHash()
+ getVersion() int64
+ SetupWatch(key string)
+ SetName(name string)
+ GetName() string
+ SetBranch(branch *Branch)
+ GetBranch() *Branch
+ Get(int) interface{}
+ GetData() interface{}
+ GetNode() *node
+ SetLastUpdate(ts ...time.Time)
+ GetLastUpdate() time.Time
+ LoadFromPersistence(ctx context.Context, path string, txid string, blobs map[string]*kvstore.KVPair) []Revision
+ UpdateData(ctx context.Context, data interface{}, branch *Branch) Revision
+ UpdateChildren(ctx context.Context, name string, children []Revision, branch *Branch) Revision
+ UpdateAllChildren(children map[string][]Revision, branch *Branch) Revision
+}
diff --git a/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/root.go b/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/root.go
new file mode 100644
index 0000000..20c3721
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/root.go
@@ -0,0 +1,310 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package model
+
+import (
+ "context"
+ "encoding/hex"
+ "encoding/json"
+ "github.com/golang/protobuf/proto"
+ "github.com/google/uuid"
+ "github.com/opencord/voltha-lib-go/pkg/log"
+ "reflect"
+ "sync"
+)
+
+// Root is used to provide an abstraction to the base root structure
+type Root interface {
+ Node
+
+ ExecuteCallbacks()
+ AddCallback(callback CallbackFunction, args ...interface{})
+ AddNotificationCallback(callback CallbackFunction, args ...interface{})
+}
+
+// root points to the top of the data model tree or sub-tree identified by a proxy
+type root struct {
+ *node
+
+ Callbacks []CallbackTuple
+ NotificationCallbacks []CallbackTuple
+
+ DirtyNodes map[string][]*node
+ KvStore *Backend
+ Loading bool
+ RevisionClass interface{}
+
+ mutex sync.RWMutex
+}
+
+// NewRoot creates an new instance of a root object
+func NewRoot(initialData interface{}, kvStore *Backend) *root {
+ root := &root{}
+
+ root.KvStore = kvStore
+ root.DirtyNodes = make(map[string][]*node)
+ root.Loading = false
+
+ // If there is no storage in place just revert to
+ // a non persistent mechanism
+ if kvStore != nil {
+ root.RevisionClass = reflect.TypeOf(PersistedRevision{})
+ } else {
+ root.RevisionClass = reflect.TypeOf(NonPersistedRevision{})
+ }
+
+ root.Callbacks = []CallbackTuple{}
+ root.NotificationCallbacks = []CallbackTuple{}
+
+ root.node = NewNode(root, initialData, false, "")
+
+ return root
+}
+
+// MakeTxBranch creates a new transaction branch
+func (r *root) MakeTxBranch() string {
+ txidBin, _ := uuid.New().MarshalBinary()
+ txid := hex.EncodeToString(txidBin)[:12]
+
+ r.DirtyNodes[txid] = []*node{r.node}
+ r.node.MakeBranch(txid)
+
+ return txid
+}
+
+// DeleteTxBranch removes a transaction branch
+func (r *root) DeleteTxBranch(txid string) {
+ for _, dirtyNode := range r.DirtyNodes[txid] {
+ dirtyNode.DeleteBranch(txid)
+ }
+ delete(r.DirtyNodes, txid)
+ r.node.DeleteBranch(txid)
+}
+
+// FoldTxBranch will merge the contents of a transaction branch with the root object
+func (r *root) FoldTxBranch(txid string) {
+ // Start by doing a dry run of the merge
+ // If that fails, it bails out and the branch is deleted
+ if _, err := r.node.MergeBranch(txid, true); err != nil {
+ // Merge operation fails
+ r.DeleteTxBranch(txid)
+ } else {
+ r.node.MergeBranch(txid, false)
+ r.node.GetRoot().ExecuteCallbacks()
+ r.DeleteTxBranch(txid)
+ }
+}
+
+// ExecuteCallbacks will invoke all the callbacks linked to root object
+func (r *root) ExecuteCallbacks() {
+ r.mutex.Lock()
+ defer r.mutex.Unlock()
+
+ for len(r.Callbacks) > 0 {
+ callback := r.Callbacks[0]
+ r.Callbacks = r.Callbacks[1:]
+ go callback.Execute(nil)
+ }
+ //for len(r.NotificationCallbacks) > 0 {
+ // callback := r.NotificationCallbacks[0]
+ // r.NotificationCallbacks = r.NotificationCallbacks[1:]
+ // go callback.Execute(nil)
+ //}
+}
+
+func (r *root) hasCallbacks() bool {
+ return len(r.Callbacks) == 0
+}
+
+// getCallbacks returns the available callbacks
+func (r *root) GetCallbacks() []CallbackTuple {
+ r.mutex.Lock()
+ defer r.mutex.Unlock()
+
+ return r.Callbacks
+}
+
+// getCallbacks returns the available notification callbacks
+func (r *root) GetNotificationCallbacks() []CallbackTuple {
+ r.mutex.Lock()
+ defer r.mutex.Unlock()
+
+ return r.NotificationCallbacks
+}
+
+// AddCallback inserts a new callback with its arguments
+func (r *root) AddCallback(callback CallbackFunction, args ...interface{}) {
+ r.mutex.Lock()
+ defer r.mutex.Unlock()
+
+ r.Callbacks = append(r.Callbacks, CallbackTuple{callback, args})
+}
+
+// AddNotificationCallback inserts a new notification callback with its arguments
+func (r *root) AddNotificationCallback(callback CallbackFunction, args ...interface{}) {
+ r.mutex.Lock()
+ defer r.mutex.Unlock()
+
+ r.NotificationCallbacks = append(r.NotificationCallbacks, CallbackTuple{callback, args})
+}
+
+func (r *root) syncParent(childRev Revision, txid string) {
+ data := proto.Clone(r.GetProxy().ParentNode.Latest().GetData().(proto.Message))
+
+ for fieldName, _ := range ChildrenFields(data) {
+ childDataName, childDataHolder := GetAttributeValue(data, fieldName, 0)
+ if reflect.TypeOf(childRev.GetData()) == reflect.TypeOf(childDataHolder.Interface()) {
+ childDataHolder = reflect.ValueOf(childRev.GetData())
+ reflect.ValueOf(data).Elem().FieldByName(childDataName).Set(childDataHolder)
+ }
+ }
+
+ r.GetProxy().ParentNode.Latest().SetConfig(NewDataRevision(r.GetProxy().ParentNode.GetRoot(), data))
+ r.GetProxy().ParentNode.Latest(txid).Finalize(false)
+}
+
+// Update modifies the content of an object at a given path with the provided data
+func (r *root) Update(ctx context.Context, path string, data interface{}, strict bool, txid string, makeBranch MakeBranchFunction) Revision {
+ var result Revision
+
+ if makeBranch != nil {
+ // TODO: raise error
+ }
+
+ if r.hasCallbacks() {
+ // TODO: raise error
+ }
+
+ if txid != "" {
+ trackDirty := func(node *node) *Branch {
+ r.DirtyNodes[txid] = append(r.DirtyNodes[txid], node)
+ return node.MakeBranch(txid)
+ }
+ result = r.node.Update(ctx, path, data, strict, txid, trackDirty)
+ } else {
+ result = r.node.Update(ctx, path, data, strict, "", nil)
+ }
+
+ if result != nil {
+ if r.GetProxy().FullPath != r.GetProxy().Path {
+ r.syncParent(result, txid)
+ } else {
+ result.Finalize(false)
+ }
+ }
+
+ r.node.GetRoot().ExecuteCallbacks()
+
+ return result
+}
+
+// Add creates a new object at the given path with the provided data
+func (r *root) Add(ctx context.Context, path string, data interface{}, txid string, makeBranch MakeBranchFunction) Revision {
+ var result Revision
+
+ if makeBranch != nil {
+ // TODO: raise error
+ }
+
+ if r.hasCallbacks() {
+ // TODO: raise error
+ }
+
+ if txid != "" {
+ trackDirty := func(node *node) *Branch {
+ r.DirtyNodes[txid] = append(r.DirtyNodes[txid], node)
+ return node.MakeBranch(txid)
+ }
+ result = r.node.Add(ctx, path, data, txid, trackDirty)
+ } else {
+ result = r.node.Add(ctx, path, data, "", nil)
+ }
+
+ if result != nil {
+ result.Finalize(true)
+ r.node.GetRoot().ExecuteCallbacks()
+ }
+ return result
+}
+
+// Remove discards an object at a given path
+func (r *root) Remove(ctx context.Context, path string, txid string, makeBranch MakeBranchFunction) Revision {
+ var result Revision
+
+ if makeBranch != nil {
+ // TODO: raise error
+ }
+
+ if r.hasCallbacks() {
+ // TODO: raise error
+ }
+
+ if txid != "" {
+ trackDirty := func(node *node) *Branch {
+ r.DirtyNodes[txid] = append(r.DirtyNodes[txid], node)
+ return node.MakeBranch(txid)
+ }
+ result = r.node.Remove(ctx, path, txid, trackDirty)
+ } else {
+ result = r.node.Remove(ctx, path, "", nil)
+ }
+
+ r.node.GetRoot().ExecuteCallbacks()
+
+ return result
+}
+
+// MakeLatest updates a branch with the latest node revision
+func (r *root) MakeLatest(branch *Branch, revision Revision, changeAnnouncement []ChangeTuple) {
+ r.makeLatest(branch, revision, changeAnnouncement)
+}
+
+func (r *root) MakeRevision(branch *Branch, data interface{}, children map[string][]Revision) Revision {
+ if r.RevisionClass.(reflect.Type) == reflect.TypeOf(PersistedRevision{}) {
+ return NewPersistedRevision(branch, data, children)
+ }
+
+ return NewNonPersistedRevision(r, branch, data, children)
+}
+
+func (r *root) makeLatest(branch *Branch, revision Revision, changeAnnouncement []ChangeTuple) {
+ r.node.makeLatest(branch, revision, changeAnnouncement)
+
+ if r.KvStore != nil && branch.Txid == "" {
+ tags := make(map[string]string)
+ for k, v := range r.node.Tags {
+ tags[k] = v.GetHash()
+ }
+ data := &rootData{
+ Latest: branch.GetLatest().GetHash(),
+ Tags: tags,
+ }
+ if blob, err := json.Marshal(data); err != nil {
+ // TODO report error
+ } else {
+ log.Debugf("Changing root to : %s", string(blob))
+ if err := r.KvStore.Put("root", blob); err != nil {
+ log.Errorf("failed to properly put value in kvstore - err: %s", err.Error())
+ }
+ }
+ }
+}
+
+type rootData struct {
+ Latest string `json:"latest"`
+ Tags map[string]string `json:"tags"`
+}
diff --git a/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/transaction.go b/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/transaction.go
new file mode 100644
index 0000000..5bef77e
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/transaction.go
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package model
+
+import (
+ "context"
+ "github.com/opencord/voltha-lib-go/pkg/log"
+)
+
+type Transaction struct {
+ proxy *Proxy
+ txid string
+}
+
+func NewTransaction(proxy *Proxy, txid string) *Transaction {
+ tx := &Transaction{
+ proxy: proxy,
+ txid: txid,
+ }
+ return tx
+}
+func (t *Transaction) Get(ctx context.Context, path string, depth int, deep bool) interface{} {
+ if t.txid == "" {
+ log.Errorf("closed transaction")
+ return nil
+ }
+ // TODO: need to review the return values at the different layers!!!!!
+ return t.proxy.Get(ctx, path, depth, deep, t.txid)
+}
+func (t *Transaction) Update(ctx context.Context, path string, data interface{}, strict bool) interface{} {
+ if t.txid == "" {
+ log.Errorf("closed transaction")
+ return nil
+ }
+ return t.proxy.Update(ctx, path, data, strict, t.txid)
+}
+func (t *Transaction) Add(ctx context.Context, path string, data interface{}) interface{} {
+ if t.txid == "" {
+ log.Errorf("closed transaction")
+ return nil
+ }
+ return t.proxy.Add(ctx, path, data, t.txid)
+}
+func (t *Transaction) Remove(ctx context.Context, path string) interface{} {
+ if t.txid == "" {
+ log.Errorf("closed transaction")
+ return nil
+ }
+ return t.proxy.Remove(ctx, path, t.txid)
+}
+func (t *Transaction) Cancel() {
+ t.proxy.cancelTransaction(t.txid)
+ t.txid = ""
+}
+func (t *Transaction) Commit() {
+ t.proxy.commitTransaction(t.txid)
+ t.txid = ""
+}
diff --git a/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/utils.go b/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/utils.go
new file mode 100644
index 0000000..b28e92f
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/pkg/db/model/utils.go
@@ -0,0 +1,275 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package model
+
+import (
+ "bytes"
+ "encoding/gob"
+ "reflect"
+ "strings"
+)
+
+// IsProtoMessage determines if the specified implements proto.Message type
+func IsProtoMessage(object interface{}) bool {
+ var ok = false
+
+ if object != nil {
+ st := reflect.TypeOf(object)
+ _, ok = st.MethodByName("ProtoMessage")
+ }
+ return ok
+}
+
+// FindOwnerType will traverse a data structure and find the parent type of the specified object
+func FindOwnerType(obj reflect.Value, name string, depth int, found bool) reflect.Type {
+ prefix := ""
+ for d := 0; d < depth; d++ {
+ prefix += ">>"
+ }
+ k := obj.Kind()
+ switch k {
+ case reflect.Ptr:
+ if found {
+ return obj.Type()
+ }
+
+ t := obj.Type().Elem()
+ n := reflect.New(t)
+
+ if rc := FindOwnerType(n.Elem(), name, depth+1, found); rc != nil {
+ return rc
+ }
+
+ case reflect.Struct:
+ if found {
+ return obj.Type()
+ }
+
+ for i := 0; i < obj.NumField(); i++ {
+ v := reflect.Indirect(obj)
+
+ json := strings.Split(v.Type().Field(i).Tag.Get("json"), ",")
+
+ if json[0] == name {
+ return FindOwnerType(obj.Field(i), name, depth+1, true)
+ }
+
+ if rc := FindOwnerType(obj.Field(i), name, depth+1, found); rc != nil {
+ return rc
+ }
+ }
+ case reflect.Slice:
+ s := reflect.MakeSlice(obj.Type(), 1, 1)
+ n := reflect.New(obj.Type())
+ n.Elem().Set(s)
+
+ for i := 0; i < n.Elem().Len(); i++ {
+ if found {
+ return reflect.ValueOf(n.Elem().Index(i).Interface()).Type()
+ }
+ }
+
+ for i := 0; i < obj.Len(); i++ {
+ if found {
+ return obj.Index(i).Type()
+ }
+
+ if rc := FindOwnerType(obj.Index(i), name, depth+1, found); rc != nil {
+ return rc
+ }
+ }
+ default:
+ //log.Debugf("%s Unhandled <%+v> ... It's a %+v\n", prefix, obj, k)
+ }
+
+ return nil
+}
+
+// FindKeyOwner will traverse a structure to find the owner type of the specified name
+func FindKeyOwner(iface interface{}, name string, depth int) interface{} {
+ obj := reflect.ValueOf(iface)
+ k := obj.Kind()
+ switch k {
+ case reflect.Ptr:
+ t := obj.Type().Elem()
+ n := reflect.New(t)
+
+ if rc := FindKeyOwner(n.Elem().Interface(), name, depth+1); rc != nil {
+ return rc
+ }
+
+ case reflect.Struct:
+ for i := 0; i < obj.NumField(); i++ {
+ json := strings.Split(obj.Type().Field(i).Tag.Get("json"), ",")
+
+ if json[0] == name {
+ return obj.Type().Field(i).Type
+ }
+
+ if rc := FindKeyOwner(obj.Field(i).Interface(), name, depth+1); rc != nil {
+ return rc
+ }
+ }
+
+ case reflect.Slice:
+ s := reflect.MakeSlice(obj.Type(), 1, 1)
+ n := reflect.New(obj.Type())
+ n.Elem().Set(s)
+
+ for i := 0; i < n.Elem().Len(); i++ {
+ if rc := FindKeyOwner(n.Elem().Index(i).Interface(), name, depth+1); rc != nil {
+ return rc
+ }
+ }
+ default:
+ //log.Debugf("%s Unhandled <%+v> ... It's a %+v\n", prefix, obj, k)
+ }
+
+ return nil
+}
+
+// GetAttributeValue traverse a structure to find the value of an attribute
+// FIXME: Need to figure out if GetAttributeValue and GetAttributeStructure can become one
+// Code is repeated in both, but outputs have a different purpose
+// Left as-is for now to get things working
+func GetAttributeValue(data interface{}, name string, depth int) (string, reflect.Value) {
+ var attribName string
+ var attribValue reflect.Value
+ obj := reflect.ValueOf(data)
+
+ if !obj.IsValid() {
+ return attribName, attribValue
+ }
+
+ k := obj.Kind()
+ switch k {
+ case reflect.Ptr:
+ if obj.IsNil() {
+ return attribName, attribValue
+ }
+
+ if attribName, attribValue = GetAttributeValue(obj.Elem().Interface(), name, depth+1); attribValue.IsValid() {
+ return attribName, attribValue
+ }
+
+ case reflect.Struct:
+ for i := 0; i < obj.NumField(); i++ {
+ json := strings.Split(obj.Type().Field(i).Tag.Get("json"), ",")
+
+ if json[0] == name {
+ return obj.Type().Field(i).Name, obj.Field(i)
+ }
+
+ if obj.Field(i).IsValid() {
+ if attribName, attribValue = GetAttributeValue(obj.Field(i).Interface(), name, depth+1); attribValue.IsValid() {
+ return attribName, attribValue
+ }
+ }
+ }
+
+ case reflect.Slice:
+ s := reflect.MakeSlice(obj.Type(), 1, 1)
+ n := reflect.New(obj.Type())
+ n.Elem().Set(s)
+
+ for i := 0; i < obj.Len(); i++ {
+ if attribName, attribValue = GetAttributeValue(obj.Index(i).Interface(), name, depth+1); attribValue.IsValid() {
+ return attribName, attribValue
+ }
+ }
+ default:
+ //log.Debugf("%s Unhandled <%+v> ... It's a %+v\n", prefix, obj, k)
+ }
+
+ return attribName, attribValue
+
+}
+
+// GetAttributeStructure will traverse a structure to find the data structure for the named attribute
+// FIXME: See GetAttributeValue(...) comment
+func GetAttributeStructure(data interface{}, name string, depth int) reflect.StructField {
+ var result reflect.StructField
+ obj := reflect.ValueOf(data)
+
+ if !obj.IsValid() {
+ return result
+ }
+
+ k := obj.Kind()
+ switch k {
+ case reflect.Ptr:
+ t := obj.Type().Elem()
+ n := reflect.New(t)
+
+ if rc := GetAttributeStructure(n.Elem().Interface(), name, depth+1); rc.Name != "" {
+ return rc
+ }
+
+ case reflect.Struct:
+ for i := 0; i < obj.NumField(); i++ {
+ v := reflect.Indirect(obj)
+ json := strings.Split(obj.Type().Field(i).Tag.Get("json"), ",")
+
+ if json[0] == name {
+ return v.Type().Field(i)
+ }
+
+ if obj.Field(i).IsValid() {
+ if rc := GetAttributeStructure(obj.Field(i).Interface(), name, depth+1); rc.Name != "" {
+ return rc
+ }
+ }
+ }
+
+ case reflect.Slice:
+ s := reflect.MakeSlice(obj.Type(), 1, 1)
+ n := reflect.New(obj.Type())
+ n.Elem().Set(s)
+
+ for i := 0; i < obj.Len(); i++ {
+ if rc := GetAttributeStructure(obj.Index(i).Interface(), name, depth+1); rc.Name != "" {
+ return rc
+ }
+
+ }
+ default:
+ //log.Debugf("%s Unhandled <%+v> ... It's a %+v\n", prefix, obj, k)
+ }
+
+ return result
+
+}
+
+func clone2(a interface{}) interface{} {
+ b := reflect.ValueOf(a)
+ buff := new(bytes.Buffer)
+ enc := gob.NewEncoder(buff)
+ dec := gob.NewDecoder(buff)
+ enc.Encode(a)
+ dec.Decode(b.Elem().Interface())
+
+ return b.Interface()
+}
+
+func clone(a, b interface{}) interface{} {
+ buff := new(bytes.Buffer)
+ enc := gob.NewEncoder(buff)
+ dec := gob.NewDecoder(buff)
+ enc.Encode(a)
+ dec.Decode(b)
+ return b
+}
diff --git a/vendor/github.com/opencord/voltha-lib-go/pkg/grpc/security.go b/vendor/github.com/opencord/voltha-lib-go/pkg/grpc/security.go
new file mode 100644
index 0000000..930d2c8
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/pkg/grpc/security.go
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package grpc
+
+type GrpcSecurity struct {
+ KeyFile string
+ CertFile string
+ CaFile string
+}
diff --git a/vendor/github.com/opencord/voltha-lib-go/pkg/grpc/server.go b/vendor/github.com/opencord/voltha-lib-go/pkg/grpc/server.go
new file mode 100644
index 0000000..d2438e0
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/pkg/grpc/server.go
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package grpc
+
+import (
+ "context"
+ "fmt"
+ "github.com/opencord/voltha-lib-go/pkg/log"
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/credentials"
+ "net"
+)
+
+/*
+To add a GRPC server to your existing component simply follow these steps:
+
+1. Create a server instance by passing the host and port where it should run and optionally add certificate information
+
+ e.g.
+ s.server = server.NewGrpcServer(s.config.GrpcHost, s.config.GrpcPort, nil, false)
+
+2. Create a function that will register your service with the GRPC server
+
+ e.g.
+ f := func(gs *grpc.Server) {
+ voltha.RegisterVolthaReadOnlyServiceServer(
+ gs,
+ core.NewReadOnlyServiceHandler(s.root),
+ )
+ }
+
+3. Add the service to the server
+
+ e.g.
+ s.server.AddService(f)
+
+4. Start the server
+
+ s.server.Start(ctx)
+*/
+
+type GrpcServer struct {
+ gs *grpc.Server
+ address string
+ port int
+ secure bool
+ services []func(*grpc.Server)
+
+ *GrpcSecurity
+}
+
+/*
+Instantiate a GRPC server data structure
+*/
+func NewGrpcServer(
+ address string,
+ port int,
+ certs *GrpcSecurity,
+ secure bool,
+) *GrpcServer {
+ server := &GrpcServer{
+ address: address,
+ port: port,
+ secure: secure,
+ GrpcSecurity: certs,
+ }
+ return server
+}
+
+/*
+Start prepares the GRPC server and starts servicing requests
+*/
+func (s *GrpcServer) Start(ctx context.Context) {
+
+ host := fmt.Sprintf("%s:%d", s.address, s.port)
+
+ lis, err := net.Listen("tcp", host)
+ if err != nil {
+ log.Fatalf("failed to listen: %v", err)
+ }
+
+ if s.secure && s.GrpcSecurity != nil {
+ creds, err := credentials.NewServerTLSFromFile(s.CertFile, s.KeyFile)
+ if err != nil {
+ log.Fatalf("could not load TLS keys: %s", err)
+ }
+ s.gs = grpc.NewServer(grpc.Creds(creds))
+
+ } else {
+ log.Info("starting-insecure-grpc-server")
+ s.gs = grpc.NewServer()
+ }
+
+ // Register all required services
+ for _, service := range s.services {
+ service(s.gs)
+ }
+
+ if err := s.gs.Serve(lis); err != nil {
+ log.Fatalf("failed to serve: %v\n", err)
+ }
+}
+
+/*
+Stop servicing GRPC requests
+*/
+func (s *GrpcServer) Stop() {
+ if s.gs != nil {
+ s.gs.Stop()
+ }
+}
+
+/*
+AddService appends a generic service request function
+*/
+func (s *GrpcServer) AddService(
+ registerFunction func(*grpc.Server),
+) {
+ s.services = append(s.services, registerFunction)
+}
diff --git a/vendor/github.com/opencord/voltha-lib-go/pkg/kafka/client.go b/vendor/github.com/opencord/voltha-lib-go/pkg/kafka/client.go
new file mode 100644
index 0000000..36c1ede
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/pkg/kafka/client.go
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package kafka
+
+import (
+ "time"
+
+ ca "github.com/opencord/voltha-protos/go/inter_container"
+)
+
+const (
+ PartitionConsumer = iota
+ GroupCustomer = iota
+)
+
+const (
+ OffsetNewest = -1
+ OffsetOldest = -2
+)
+
+const (
+ GroupIdKey = "groupId"
+ Offset = "offset"
+)
+
+const (
+ DefaultKafkaHost = "127.0.0.1"
+ DefaultKafkaPort = 9092
+ DefaultGroupName = "voltha"
+ DefaultSleepOnError = 1
+ DefaultProducerFlushFrequency = 10
+ DefaultProducerFlushMessages = 10
+ DefaultProducerFlushMaxmessages = 100
+ DefaultProducerReturnSuccess = true
+ DefaultProducerReturnErrors = true
+ DefaultProducerRetryMax = 3
+ DefaultProducerRetryBackoff = time.Millisecond * 100
+ DefaultConsumerMaxwait = 100
+ DefaultMaxProcessingTime = 100
+ DefaultConsumerType = PartitionConsumer
+ DefaultNumberPartitions = 3
+ DefaultNumberReplicas = 1
+ DefaultAutoCreateTopic = false
+ DefaultMetadataMaxRetry = 3
+)
+
+// MsgClient represents the set of APIs a Kafka MsgClient must implement
+type Client interface {
+ Start() error
+ Stop()
+ CreateTopic(topic *Topic, numPartition int, repFactor int) error
+ DeleteTopic(topic *Topic) error
+ Subscribe(topic *Topic, kvArgs ...*KVArg) (<-chan *ca.InterContainerMessage, error)
+ UnSubscribe(topic *Topic, ch <-chan *ca.InterContainerMessage) error
+ Send(msg interface{}, topic *Topic, keys ...string) error
+}
diff --git a/vendor/github.com/opencord/voltha-lib-go/pkg/kafka/kafka_inter_container_library.go b/vendor/github.com/opencord/voltha-lib-go/pkg/kafka/kafka_inter_container_library.go
new file mode 100644
index 0000000..d2e0702
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/pkg/kafka/kafka_inter_container_library.go
@@ -0,0 +1,833 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package kafka
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "github.com/golang/protobuf/proto"
+ "github.com/golang/protobuf/ptypes"
+ "github.com/golang/protobuf/ptypes/any"
+ "github.com/google/uuid"
+ "github.com/opencord/voltha-lib-go/pkg/log"
+ ic "github.com/opencord/voltha-protos/go/inter_container"
+ "reflect"
+ "strings"
+ "sync"
+ "time"
+)
+
+// Initialize the logger - gets the default until the main function setup the logger
+func init() {
+ log.AddPackage(log.JSON, log.DebugLevel, nil)
+}
+
+const (
+ DefaultMaxRetries = 3
+ DefaultRequestTimeout = 10000 // 10000 milliseconds - to handle a wider latency range
+)
+
+const (
+ TransactionKey = "transactionID"
+ FromTopic = "fromTopic"
+)
+
+var ErrorTransactionNotAcquired = errors.New("transaction-not-acquired")
+var ErrorTransactionInvalidId = errors.New("transaction-invalid-id")
+
+// requestHandlerChannel represents an interface associated with a channel. Whenever, an event is
+// obtained from that channel, this interface is invoked. This is used to handle
+// async requests into the Core via the kafka messaging bus
+type requestHandlerChannel struct {
+ requesthandlerInterface interface{}
+ ch <-chan *ic.InterContainerMessage
+}
+
+// transactionChannel represents a combination of a topic and a channel onto which a response received
+// on the kafka bus will be sent to
+type transactionChannel struct {
+ topic *Topic
+ ch chan *ic.InterContainerMessage
+}
+
+// InterContainerProxy represents the messaging proxy
+type InterContainerProxy struct {
+ kafkaHost string
+ kafkaPort int
+ DefaultTopic *Topic
+ defaultRequestHandlerInterface interface{}
+ deviceDiscoveryTopic *Topic
+ kafkaClient Client
+ doneCh chan int
+
+ // This map is used to map a topic to an interface and channel. When a request is received
+ // on that channel (registered to the topic) then that interface is invoked.
+ topicToRequestHandlerChannelMap map[string]*requestHandlerChannel
+ lockTopicRequestHandlerChannelMap sync.RWMutex
+
+ // This map is used to map a channel to a response topic. This channel handles all responses on that
+ // channel for that topic and forward them to the appropriate consumers channel, using the
+ // transactionIdToChannelMap.
+ topicToResponseChannelMap map[string]<-chan *ic.InterContainerMessage
+ lockTopicResponseChannelMap sync.RWMutex
+
+ // This map is used to map a transaction to a consumers channel. This is used whenever a request has been
+ // sent out and we are waiting for a response.
+ transactionIdToChannelMap map[string]*transactionChannel
+ lockTransactionIdToChannelMap sync.RWMutex
+}
+
+type InterContainerProxyOption func(*InterContainerProxy)
+
+func InterContainerHost(host string) InterContainerProxyOption {
+ return func(args *InterContainerProxy) {
+ args.kafkaHost = host
+ }
+}
+
+func InterContainerPort(port int) InterContainerProxyOption {
+ return func(args *InterContainerProxy) {
+ args.kafkaPort = port
+ }
+}
+
+func DefaultTopic(topic *Topic) InterContainerProxyOption {
+ return func(args *InterContainerProxy) {
+ args.DefaultTopic = topic
+ }
+}
+
+func DeviceDiscoveryTopic(topic *Topic) InterContainerProxyOption {
+ return func(args *InterContainerProxy) {
+ args.deviceDiscoveryTopic = topic
+ }
+}
+
+func RequestHandlerInterface(handler interface{}) InterContainerProxyOption {
+ return func(args *InterContainerProxy) {
+ args.defaultRequestHandlerInterface = handler
+ }
+}
+
+func MsgClient(client Client) InterContainerProxyOption {
+ return func(args *InterContainerProxy) {
+ args.kafkaClient = client
+ }
+}
+
+func NewInterContainerProxy(opts ...InterContainerProxyOption) (*InterContainerProxy, error) {
+ proxy := &InterContainerProxy{
+ kafkaHost: DefaultKafkaHost,
+ kafkaPort: DefaultKafkaPort,
+ }
+
+ for _, option := range opts {
+ option(proxy)
+ }
+
+ // Create the locks for all the maps
+ proxy.lockTopicRequestHandlerChannelMap = sync.RWMutex{}
+ proxy.lockTransactionIdToChannelMap = sync.RWMutex{}
+ proxy.lockTopicResponseChannelMap = sync.RWMutex{}
+
+ return proxy, nil
+}
+
+func (kp *InterContainerProxy) Start() error {
+ log.Info("Starting-Proxy")
+
+ // Kafka MsgClient should already have been created. If not, output fatal error
+ if kp.kafkaClient == nil {
+ log.Fatal("kafka-client-not-set")
+ }
+
+ // Create the Done channel
+ kp.doneCh = make(chan int, 1)
+
+ // Start the kafka client
+ if err := kp.kafkaClient.Start(); err != nil {
+ log.Errorw("Cannot-create-kafka-proxy", log.Fields{"error": err})
+ return err
+ }
+
+ // Create the topic to response channel map
+ kp.topicToResponseChannelMap = make(map[string]<-chan *ic.InterContainerMessage)
+ //
+ // Create the transactionId to Channel Map
+ kp.transactionIdToChannelMap = make(map[string]*transactionChannel)
+
+ // Create the topic to request channel map
+ kp.topicToRequestHandlerChannelMap = make(map[string]*requestHandlerChannel)
+
+ return nil
+}
+
+func (kp *InterContainerProxy) Stop() {
+ log.Info("stopping-intercontainer-proxy")
+ kp.doneCh <- 1
+ // TODO : Perform cleanup
+ kp.kafkaClient.Stop()
+ //kp.deleteAllTopicRequestHandlerChannelMap()
+ //kp.deleteAllTopicResponseChannelMap()
+ //kp.deleteAllTransactionIdToChannelMap()
+}
+
+// DeviceDiscovered publish the discovered device onto the kafka messaging bus
+func (kp *InterContainerProxy) DeviceDiscovered(deviceId string, deviceType string, parentId string, publisher string) error {
+ log.Debugw("sending-device-discovery-msg", log.Fields{"deviceId": deviceId})
+ // Simple validation
+ if deviceId == "" || deviceType == "" {
+ log.Errorw("invalid-parameters", log.Fields{"id": deviceId, "type": deviceType})
+ return errors.New("invalid-parameters")
+ }
+ // Create the device discovery message
+ header := &ic.Header{
+ Id: uuid.New().String(),
+ Type: ic.MessageType_DEVICE_DISCOVERED,
+ FromTopic: kp.DefaultTopic.Name,
+ ToTopic: kp.deviceDiscoveryTopic.Name,
+ Timestamp: time.Now().UnixNano(),
+ }
+ body := &ic.DeviceDiscovered{
+ Id: deviceId,
+ DeviceType: deviceType,
+ ParentId: parentId,
+ Publisher: publisher,
+ }
+
+ var marshalledData *any.Any
+ var err error
+ if marshalledData, err = ptypes.MarshalAny(body); err != nil {
+ log.Errorw("cannot-marshal-request", log.Fields{"error": err})
+ return err
+ }
+ msg := &ic.InterContainerMessage{
+ Header: header,
+ Body: marshalledData,
+ }
+
+ // Send the message
+ if err := kp.kafkaClient.Send(msg, kp.deviceDiscoveryTopic); err != nil {
+ log.Errorw("cannot-send-device-discovery-message", log.Fields{"error": err})
+ return err
+ }
+ return nil
+}
+
+// InvokeRPC is used to send a request to a given topic
+func (kp *InterContainerProxy) InvokeRPC(ctx context.Context, rpc string, toTopic *Topic, replyToTopic *Topic,
+ waitForResponse bool, key string, kvArgs ...*KVArg) (bool, *any.Any) {
+
+ // If a replyToTopic is provided then we use it, otherwise just use the default toTopic. The replyToTopic is
+ // typically the device ID.
+ responseTopic := replyToTopic
+ if responseTopic == nil {
+ responseTopic = kp.DefaultTopic
+ }
+
+ // Encode the request
+ protoRequest, err := encodeRequest(rpc, toTopic, responseTopic, key, kvArgs...)
+ if err != nil {
+ log.Warnw("cannot-format-request", log.Fields{"rpc": rpc, "error": err})
+ return false, nil
+ }
+
+ // Subscribe for response, if needed, before sending request
+ var ch <-chan *ic.InterContainerMessage
+ if waitForResponse {
+ var err error
+ if ch, err = kp.subscribeForResponse(*responseTopic, protoRequest.Header.Id); err != nil {
+ log.Errorw("failed-to-subscribe-for-response", log.Fields{"error": err, "toTopic": toTopic.Name})
+ }
+ }
+
+ // Send request - if the topic is formatted with a device Id then we will send the request using a
+ // specific key, hence ensuring a single partition is used to publish the request. This ensures that the
+ // subscriber on that topic will receive the request in the order it was sent. The key used is the deviceId.
+ //key := GetDeviceIdFromTopic(*toTopic)
+ log.Debugw("sending-msg", log.Fields{"rpc": rpc, "toTopic": toTopic, "replyTopic": responseTopic, "key": key, "xId": protoRequest.Header.Id})
+ go kp.kafkaClient.Send(protoRequest, toTopic, key)
+
+ if waitForResponse {
+ // Create a child context based on the parent context, if any
+ var cancel context.CancelFunc
+ childCtx := context.Background()
+ if ctx == nil {
+ ctx, cancel = context.WithTimeout(context.Background(), DefaultRequestTimeout*time.Millisecond)
+ } else {
+ childCtx, cancel = context.WithTimeout(ctx, DefaultRequestTimeout*time.Millisecond)
+ }
+ defer cancel()
+
+ // Wait for response as well as timeout or cancellation
+ // Remove the subscription for a response on return
+ defer kp.unSubscribeForResponse(protoRequest.Header.Id)
+ select {
+ case msg, ok := <-ch:
+ if !ok {
+ log.Warnw("channel-closed", log.Fields{"rpc": rpc, "replyTopic": replyToTopic.Name})
+ protoError := &ic.Error{Reason: "channel-closed"}
+ var marshalledArg *any.Any
+ if marshalledArg, err = ptypes.MarshalAny(protoError); err != nil {
+ return false, nil // Should never happen
+ }
+ return false, marshalledArg
+ }
+ log.Debugw("received-response", log.Fields{"rpc": rpc, "msgHeader": msg.Header})
+ var responseBody *ic.InterContainerResponseBody
+ var err error
+ if responseBody, err = decodeResponse(msg); err != nil {
+ log.Errorw("decode-response-error", log.Fields{"error": err})
+ }
+ return responseBody.Success, responseBody.Result
+ case <-ctx.Done():
+ log.Debugw("context-cancelled", log.Fields{"rpc": rpc, "ctx": ctx.Err()})
+ // pack the error as proto any type
+ protoError := &ic.Error{Reason: ctx.Err().Error()}
+ var marshalledArg *any.Any
+ if marshalledArg, err = ptypes.MarshalAny(protoError); err != nil {
+ return false, nil // Should never happen
+ }
+ return false, marshalledArg
+ case <-childCtx.Done():
+ log.Debugw("context-cancelled", log.Fields{"rpc": rpc, "ctx": childCtx.Err()})
+ // pack the error as proto any type
+ protoError := &ic.Error{Reason: childCtx.Err().Error()}
+ var marshalledArg *any.Any
+ if marshalledArg, err = ptypes.MarshalAny(protoError); err != nil {
+ return false, nil // Should never happen
+ }
+ return false, marshalledArg
+ case <-kp.doneCh:
+ log.Infow("received-exit-signal", log.Fields{"toTopic": toTopic.Name, "rpc": rpc})
+ return true, nil
+ }
+ }
+ return true, nil
+}
+
+// SubscribeWithRequestHandlerInterface allows a caller to assign a target object to be invoked automatically
+// when a message is received on a given topic
+func (kp *InterContainerProxy) SubscribeWithRequestHandlerInterface(topic Topic, handler interface{}) error {
+
+ // Subscribe to receive messages for that topic
+ var ch <-chan *ic.InterContainerMessage
+ var err error
+ if ch, err = kp.kafkaClient.Subscribe(&topic); err != nil {
+ //if ch, err = kp.Subscribe(topic); err != nil {
+ log.Errorw("failed-to-subscribe", log.Fields{"error": err, "topic": topic.Name})
+ return err
+ }
+
+ kp.defaultRequestHandlerInterface = handler
+ kp.addToTopicRequestHandlerChannelMap(topic.Name, &requestHandlerChannel{requesthandlerInterface: handler, ch: ch})
+ // Launch a go routine to receive and process kafka messages
+ go kp.waitForMessages(ch, topic, handler)
+
+ return nil
+}
+
+// SubscribeWithDefaultRequestHandler allows a caller to add a topic to an existing target object to be invoked automatically
+// when a message is received on a given topic. So far there is only 1 target registered per microservice
+func (kp *InterContainerProxy) SubscribeWithDefaultRequestHandler(topic Topic, initialOffset int64) error {
+ // Subscribe to receive messages for that topic
+ var ch <-chan *ic.InterContainerMessage
+ var err error
+ if ch, err = kp.kafkaClient.Subscribe(&topic, &KVArg{Key: Offset, Value: initialOffset}); err != nil {
+ log.Errorw("failed-to-subscribe", log.Fields{"error": err, "topic": topic.Name})
+ return err
+ }
+ kp.addToTopicRequestHandlerChannelMap(topic.Name, &requestHandlerChannel{requesthandlerInterface: kp.defaultRequestHandlerInterface, ch: ch})
+
+ // Launch a go routine to receive and process kafka messages
+ go kp.waitForMessages(ch, topic, kp.defaultRequestHandlerInterface)
+
+ return nil
+}
+
+func (kp *InterContainerProxy) UnSubscribeFromRequestHandler(topic Topic) error {
+ return kp.deleteFromTopicRequestHandlerChannelMap(topic.Name)
+}
+
+// setupTopicResponseChannelMap sets up single consumers channel that will act as a broadcast channel for all
+// responses from that topic.
+func (kp *InterContainerProxy) setupTopicResponseChannelMap(topic string, arg <-chan *ic.InterContainerMessage) {
+ kp.lockTopicResponseChannelMap.Lock()
+ defer kp.lockTopicResponseChannelMap.Unlock()
+ if _, exist := kp.topicToResponseChannelMap[topic]; !exist {
+ kp.topicToResponseChannelMap[topic] = arg
+ }
+}
+
+func (kp *InterContainerProxy) isTopicSubscribedForResponse(topic string) bool {
+ kp.lockTopicResponseChannelMap.RLock()
+ defer kp.lockTopicResponseChannelMap.RUnlock()
+ _, exist := kp.topicToResponseChannelMap[topic]
+ return exist
+}
+
+func (kp *InterContainerProxy) deleteFromTopicResponseChannelMap(topic string) error {
+ kp.lockTopicResponseChannelMap.Lock()
+ defer kp.lockTopicResponseChannelMap.Unlock()
+ if _, exist := kp.topicToResponseChannelMap[topic]; exist {
+ // Unsubscribe to this topic first - this will close the subscribed channel
+ var err error
+ if err = kp.kafkaClient.UnSubscribe(&Topic{Name: topic}, kp.topicToResponseChannelMap[topic]); err != nil {
+ log.Errorw("unsubscribing-error", log.Fields{"topic": topic})
+ }
+ delete(kp.topicToResponseChannelMap, topic)
+ return err
+ } else {
+ return errors.New(fmt.Sprintf("%s-Topic-not-found", topic))
+ }
+}
+
+func (kp *InterContainerProxy) deleteAllTopicResponseChannelMap() error {
+ kp.lockTopicResponseChannelMap.Lock()
+ defer kp.lockTopicResponseChannelMap.Unlock()
+ var err error
+ for topic, _ := range kp.topicToResponseChannelMap {
+ // Unsubscribe to this topic first - this will close the subscribed channel
+ if err = kp.kafkaClient.UnSubscribe(&Topic{Name: topic}, kp.topicToResponseChannelMap[topic]); err != nil {
+ log.Errorw("unsubscribing-error", log.Fields{"topic": topic, "error": err})
+ }
+ delete(kp.topicToResponseChannelMap, topic)
+ }
+ return err
+}
+
+func (kp *InterContainerProxy) addToTopicRequestHandlerChannelMap(topic string, arg *requestHandlerChannel) {
+ kp.lockTopicRequestHandlerChannelMap.Lock()
+ defer kp.lockTopicRequestHandlerChannelMap.Unlock()
+ if _, exist := kp.topicToRequestHandlerChannelMap[topic]; !exist {
+ kp.topicToRequestHandlerChannelMap[topic] = arg
+ }
+}
+
+func (kp *InterContainerProxy) deleteFromTopicRequestHandlerChannelMap(topic string) error {
+ kp.lockTopicRequestHandlerChannelMap.Lock()
+ defer kp.lockTopicRequestHandlerChannelMap.Unlock()
+ if _, exist := kp.topicToRequestHandlerChannelMap[topic]; exist {
+ // Close the kafka client client first by unsubscribing to this topic
+ kp.kafkaClient.UnSubscribe(&Topic{Name: topic}, kp.topicToRequestHandlerChannelMap[topic].ch)
+ delete(kp.topicToRequestHandlerChannelMap, topic)
+ return nil
+ } else {
+ return errors.New(fmt.Sprintf("%s-Topic-not-found", topic))
+ }
+}
+
+func (kp *InterContainerProxy) deleteAllTopicRequestHandlerChannelMap() error {
+ kp.lockTopicRequestHandlerChannelMap.Lock()
+ defer kp.lockTopicRequestHandlerChannelMap.Unlock()
+ var err error
+ for topic, _ := range kp.topicToRequestHandlerChannelMap {
+ // Close the kafka client client first by unsubscribing to this topic
+ if err = kp.kafkaClient.UnSubscribe(&Topic{Name: topic}, kp.topicToRequestHandlerChannelMap[topic].ch); err != nil {
+ log.Errorw("unsubscribing-error", log.Fields{"topic": topic, "error": err})
+ }
+ delete(kp.topicToRequestHandlerChannelMap, topic)
+ }
+ return err
+}
+
+func (kp *InterContainerProxy) addToTransactionIdToChannelMap(id string, topic *Topic, arg chan *ic.InterContainerMessage) {
+ kp.lockTransactionIdToChannelMap.Lock()
+ defer kp.lockTransactionIdToChannelMap.Unlock()
+ if _, exist := kp.transactionIdToChannelMap[id]; !exist {
+ kp.transactionIdToChannelMap[id] = &transactionChannel{topic: topic, ch: arg}
+ }
+}
+
+func (kp *InterContainerProxy) deleteFromTransactionIdToChannelMap(id string) {
+ kp.lockTransactionIdToChannelMap.Lock()
+ defer kp.lockTransactionIdToChannelMap.Unlock()
+ if transChannel, exist := kp.transactionIdToChannelMap[id]; exist {
+ // Close the channel first
+ close(transChannel.ch)
+ delete(kp.transactionIdToChannelMap, id)
+ }
+}
+
+func (kp *InterContainerProxy) deleteTopicTransactionIdToChannelMap(id string) {
+ kp.lockTransactionIdToChannelMap.Lock()
+ defer kp.lockTransactionIdToChannelMap.Unlock()
+ for key, value := range kp.transactionIdToChannelMap {
+ if value.topic.Name == id {
+ close(value.ch)
+ delete(kp.transactionIdToChannelMap, key)
+ }
+ }
+}
+
+func (kp *InterContainerProxy) deleteAllTransactionIdToChannelMap() {
+ kp.lockTransactionIdToChannelMap.Lock()
+ defer kp.lockTransactionIdToChannelMap.Unlock()
+ for key, value := range kp.transactionIdToChannelMap {
+ close(value.ch)
+ delete(kp.transactionIdToChannelMap, key)
+ }
+}
+
+func (kp *InterContainerProxy) DeleteTopic(topic Topic) error {
+ // If we have any consumers on that topic we need to close them
+ if err := kp.deleteFromTopicResponseChannelMap(topic.Name); err != nil {
+ log.Errorw("delete-from-topic-responsechannelmap-failed", log.Fields{"error": err})
+ }
+ if err := kp.deleteFromTopicRequestHandlerChannelMap(topic.Name); err != nil {
+ log.Errorw("delete-from-topic-requesthandlerchannelmap-failed", log.Fields{"error": err})
+ }
+ kp.deleteTopicTransactionIdToChannelMap(topic.Name)
+
+ return kp.kafkaClient.DeleteTopic(&topic)
+}
+
+func encodeReturnedValue(returnedVal interface{}) (*any.Any, error) {
+ // Encode the response argument - needs to be a proto message
+ if returnedVal == nil {
+ return nil, nil
+ }
+ protoValue, ok := returnedVal.(proto.Message)
+ if !ok {
+ log.Warnw("response-value-not-proto-message", log.Fields{"error": ok, "returnVal": returnedVal})
+ err := errors.New("response-value-not-proto-message")
+ return nil, err
+ }
+
+ // Marshal the returned value, if any
+ var marshalledReturnedVal *any.Any
+ var err error
+ if marshalledReturnedVal, err = ptypes.MarshalAny(protoValue); err != nil {
+ log.Warnw("cannot-marshal-returned-val", log.Fields{"error": err})
+ return nil, err
+ }
+ return marshalledReturnedVal, nil
+}
+
+func encodeDefaultFailedResponse(request *ic.InterContainerMessage) *ic.InterContainerMessage {
+ responseHeader := &ic.Header{
+ Id: request.Header.Id,
+ Type: ic.MessageType_RESPONSE,
+ FromTopic: request.Header.ToTopic,
+ ToTopic: request.Header.FromTopic,
+ Timestamp: time.Now().Unix(),
+ }
+ responseBody := &ic.InterContainerResponseBody{
+ Success: false,
+ Result: nil,
+ }
+ var marshalledResponseBody *any.Any
+ var err error
+ // Error should never happen here
+ if marshalledResponseBody, err = ptypes.MarshalAny(responseBody); err != nil {
+ log.Warnw("cannot-marshal-failed-response-body", log.Fields{"error": err})
+ }
+
+ return &ic.InterContainerMessage{
+ Header: responseHeader,
+ Body: marshalledResponseBody,
+ }
+
+}
+
+//formatRequest formats a request to send over kafka and returns an InterContainerMessage message on success
+//or an error on failure
+func encodeResponse(request *ic.InterContainerMessage, success bool, returnedValues ...interface{}) (*ic.InterContainerMessage, error) {
+ //log.Debugw("encodeResponse", log.Fields{"success": success, "returnedValues": returnedValues})
+ responseHeader := &ic.Header{
+ Id: request.Header.Id,
+ Type: ic.MessageType_RESPONSE,
+ FromTopic: request.Header.ToTopic,
+ ToTopic: request.Header.FromTopic,
+ KeyTopic: request.Header.KeyTopic,
+ Timestamp: time.Now().UnixNano(),
+ }
+
+ // Go over all returned values
+ var marshalledReturnedVal *any.Any
+ var err error
+ for _, returnVal := range returnedValues {
+ if marshalledReturnedVal, err = encodeReturnedValue(returnVal); err != nil {
+ log.Warnw("cannot-marshal-response-body", log.Fields{"error": err})
+ }
+ break // for now we support only 1 returned value - (excluding the error)
+ }
+
+ responseBody := &ic.InterContainerResponseBody{
+ Success: success,
+ Result: marshalledReturnedVal,
+ }
+
+ // Marshal the response body
+ var marshalledResponseBody *any.Any
+ if marshalledResponseBody, err = ptypes.MarshalAny(responseBody); err != nil {
+ log.Warnw("cannot-marshal-response-body", log.Fields{"error": err})
+ return nil, err
+ }
+
+ return &ic.InterContainerMessage{
+ Header: responseHeader,
+ Body: marshalledResponseBody,
+ }, nil
+}
+
+func CallFuncByName(myClass interface{}, funcName string, params ...interface{}) (out []reflect.Value, err error) {
+ myClassValue := reflect.ValueOf(myClass)
+ // Capitalize the first letter in the funcName to workaround the first capital letters required to
+ // invoke a function from a different package
+ funcName = strings.Title(funcName)
+ m := myClassValue.MethodByName(funcName)
+ if !m.IsValid() {
+ return make([]reflect.Value, 0), fmt.Errorf("method-not-found \"%s\"", funcName)
+ }
+ in := make([]reflect.Value, len(params))
+ for i, param := range params {
+ in[i] = reflect.ValueOf(param)
+ }
+ out = m.Call(in)
+ return
+}
+
+func (kp *InterContainerProxy) addTransactionId(transactionId string, currentArgs []*ic.Argument) []*ic.Argument {
+ arg := &KVArg{
+ Key: TransactionKey,
+ Value: &ic.StrType{Val: transactionId},
+ }
+
+ var marshalledArg *any.Any
+ var err error
+ if marshalledArg, err = ptypes.MarshalAny(&ic.StrType{Val: transactionId}); err != nil {
+ log.Warnw("cannot-add-transactionId", log.Fields{"error": err})
+ return currentArgs
+ }
+ protoArg := &ic.Argument{
+ Key: arg.Key,
+ Value: marshalledArg,
+ }
+ return append(currentArgs, protoArg)
+}
+
+func (kp *InterContainerProxy) addFromTopic(fromTopic string, currentArgs []*ic.Argument) []*ic.Argument {
+ var marshalledArg *any.Any
+ var err error
+ if marshalledArg, err = ptypes.MarshalAny(&ic.StrType{Val: fromTopic}); err != nil {
+ log.Warnw("cannot-add-transactionId", log.Fields{"error": err})
+ return currentArgs
+ }
+ protoArg := &ic.Argument{
+ Key: FromTopic,
+ Value: marshalledArg,
+ }
+ return append(currentArgs, protoArg)
+}
+
+func (kp *InterContainerProxy) handleMessage(msg *ic.InterContainerMessage, targetInterface interface{}) {
+
+ // First extract the header to know whether this is a request - responses are handled by a different handler
+ if msg.Header.Type == ic.MessageType_REQUEST {
+ var out []reflect.Value
+ var err error
+
+ // Get the request body
+ requestBody := &ic.InterContainerRequestBody{}
+ if err = ptypes.UnmarshalAny(msg.Body, requestBody); err != nil {
+ log.Warnw("cannot-unmarshal-request", log.Fields{"error": err})
+ } else {
+ log.Debugw("received-request", log.Fields{"rpc": requestBody.Rpc, "header": msg.Header})
+ // let the callee unpack the arguments as its the only one that knows the real proto type
+ // Augment the requestBody with the message Id as it will be used in scenarios where cores
+ // are set in pairs and competing
+ requestBody.Args = kp.addTransactionId(msg.Header.Id, requestBody.Args)
+
+ // Augment the requestBody with the From topic name as it will be used in scenarios where a container
+ // needs to send an unsollicited message to the currently requested container
+ requestBody.Args = kp.addFromTopic(msg.Header.FromTopic, requestBody.Args)
+
+ out, err = CallFuncByName(targetInterface, requestBody.Rpc, requestBody.Args)
+ if err != nil {
+ log.Warn(err)
+ }
+ }
+ // Response required?
+ if requestBody.ResponseRequired {
+ // If we already have an error before then just return that
+ var returnError *ic.Error
+ var returnedValues []interface{}
+ var success bool
+ if err != nil {
+ returnError = &ic.Error{Reason: err.Error()}
+ returnedValues = make([]interface{}, 1)
+ returnedValues[0] = returnError
+ } else {
+ returnedValues = make([]interface{}, 0)
+ // Check for errors first
+ lastIndex := len(out) - 1
+ if out[lastIndex].Interface() != nil { // Error
+ if retError, ok := out[lastIndex].Interface().(error); ok {
+ if retError.Error() == ErrorTransactionNotAcquired.Error() {
+ log.Debugw("Ignoring request", log.Fields{"error": retError, "txId": msg.Header.Id})
+ return // Ignore - process is in competing mode and ignored transaction
+ }
+ returnError = &ic.Error{Reason: retError.Error()}
+ returnedValues = append(returnedValues, returnError)
+ } else { // Should never happen
+ returnError = &ic.Error{Reason: "incorrect-error-returns"}
+ returnedValues = append(returnedValues, returnError)
+ }
+ } else if len(out) == 2 && reflect.ValueOf(out[0].Interface()).IsValid() && reflect.ValueOf(out[0].Interface()).IsNil() {
+ log.Warnw("Unexpected response of (nil,nil)", log.Fields{"txId": msg.Header.Id})
+ return // Ignore - should not happen
+ } else { // Non-error case
+ success = true
+ for idx, val := range out {
+ //log.Debugw("returned-api-response-loop", log.Fields{"idx": idx, "val": val.Interface()})
+ if idx != lastIndex {
+ returnedValues = append(returnedValues, val.Interface())
+ }
+ }
+ }
+ }
+
+ var icm *ic.InterContainerMessage
+ if icm, err = encodeResponse(msg, success, returnedValues...); err != nil {
+ log.Warnw("error-encoding-response-returning-failure-result", log.Fields{"error": err})
+ icm = encodeDefaultFailedResponse(msg)
+ }
+ // To preserve ordering of messages, all messages to a given topic are sent to the same partition
+ // by providing a message key. The key is encoded in the topic name. If the deviceId is not
+ // present then the key will be empty, hence all messages for a given topic will be sent to all
+ // partitions.
+ replyTopic := &Topic{Name: msg.Header.FromTopic}
+ key := msg.Header.KeyTopic
+ log.Debugw("sending-response-to-kafka", log.Fields{"rpc": requestBody.Rpc, "header": icm.Header, "key": key})
+ // TODO: handle error response.
+ go kp.kafkaClient.Send(icm, replyTopic, key)
+ }
+ } else if msg.Header.Type == ic.MessageType_RESPONSE {
+ log.Debugw("response-received", log.Fields{"msg-header": msg.Header})
+ go kp.dispatchResponse(msg)
+ } else {
+ log.Warnw("unsupported-message-received", log.Fields{"msg-header": msg.Header})
+ }
+}
+
+func (kp *InterContainerProxy) waitForMessages(ch <-chan *ic.InterContainerMessage, topic Topic, targetInterface interface{}) {
+ // Wait for messages
+ for msg := range ch {
+ //log.Debugw("request-received", log.Fields{"msg": msg, "topic": topic.Name, "target": targetInterface})
+ go kp.handleMessage(msg, targetInterface)
+ }
+}
+
+func (kp *InterContainerProxy) dispatchResponse(msg *ic.InterContainerMessage) {
+ kp.lockTransactionIdToChannelMap.RLock()
+ defer kp.lockTransactionIdToChannelMap.RUnlock()
+ if _, exist := kp.transactionIdToChannelMap[msg.Header.Id]; !exist {
+ log.Debugw("no-waiting-channel", log.Fields{"transaction": msg.Header.Id})
+ return
+ }
+ kp.transactionIdToChannelMap[msg.Header.Id].ch <- msg
+}
+
+// subscribeForResponse allows a caller to subscribe to a given topic when waiting for a response.
+// This method is built to prevent all subscribers to receive all messages as is the case of the Subscribe
+// API. There is one response channel waiting for kafka messages before dispatching the message to the
+// corresponding waiting channel
+func (kp *InterContainerProxy) subscribeForResponse(topic Topic, trnsId string) (chan *ic.InterContainerMessage, error) {
+ log.Debugw("subscribeForResponse", log.Fields{"topic": topic.Name, "trnsid": trnsId})
+
+ // Create a specific channel for this consumers. We cannot use the channel from the kafkaclient as it will
+ // broadcast any message for this topic to all channels waiting on it.
+ ch := make(chan *ic.InterContainerMessage)
+ kp.addToTransactionIdToChannelMap(trnsId, &topic, ch)
+
+ return ch, nil
+}
+
+func (kp *InterContainerProxy) unSubscribeForResponse(trnsId string) error {
+ log.Debugw("unsubscribe-for-response", log.Fields{"trnsId": trnsId})
+ kp.deleteFromTransactionIdToChannelMap(trnsId)
+ return nil
+}
+
+//formatRequest formats a request to send over kafka and returns an InterContainerMessage message on success
+//or an error on failure
+func encodeRequest(rpc string, toTopic *Topic, replyTopic *Topic, key string, kvArgs ...*KVArg) (*ic.InterContainerMessage, error) {
+ requestHeader := &ic.Header{
+ Id: uuid.New().String(),
+ Type: ic.MessageType_REQUEST,
+ FromTopic: replyTopic.Name,
+ ToTopic: toTopic.Name,
+ KeyTopic: key,
+ Timestamp: time.Now().UnixNano(),
+ }
+ requestBody := &ic.InterContainerRequestBody{
+ Rpc: rpc,
+ ResponseRequired: true,
+ ReplyToTopic: replyTopic.Name,
+ }
+
+ for _, arg := range kvArgs {
+ if arg == nil {
+ // In case the caller sends an array with empty args
+ continue
+ }
+ var marshalledArg *any.Any
+ var err error
+ // ascertain the value interface type is a proto.Message
+ protoValue, ok := arg.Value.(proto.Message)
+ if !ok {
+ log.Warnw("argument-value-not-proto-message", log.Fields{"error": ok, "Value": arg.Value})
+ err := errors.New("argument-value-not-proto-message")
+ return nil, err
+ }
+ if marshalledArg, err = ptypes.MarshalAny(protoValue); err != nil {
+ log.Warnw("cannot-marshal-request", log.Fields{"error": err})
+ return nil, err
+ }
+ protoArg := &ic.Argument{
+ Key: arg.Key,
+ Value: marshalledArg,
+ }
+ requestBody.Args = append(requestBody.Args, protoArg)
+ }
+
+ var marshalledData *any.Any
+ var err error
+ if marshalledData, err = ptypes.MarshalAny(requestBody); err != nil {
+ log.Warnw("cannot-marshal-request", log.Fields{"error": err})
+ return nil, err
+ }
+ request := &ic.InterContainerMessage{
+ Header: requestHeader,
+ Body: marshalledData,
+ }
+ return request, nil
+}
+
+func decodeResponse(response *ic.InterContainerMessage) (*ic.InterContainerResponseBody, error) {
+ // Extract the message body
+ responseBody := ic.InterContainerResponseBody{}
+ if err := ptypes.UnmarshalAny(response.Body, &responseBody); err != nil {
+ log.Warnw("cannot-unmarshal-response", log.Fields{"error": err})
+ return nil, err
+ }
+ //log.Debugw("response-decoded-successfully", log.Fields{"response-status": &responseBody.Success})
+
+ return &responseBody, nil
+
+}
diff --git a/vendor/github.com/opencord/voltha-lib-go/pkg/kafka/sarama_client.go b/vendor/github.com/opencord/voltha-lib-go/pkg/kafka/sarama_client.go
new file mode 100644
index 0000000..cec18ec
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/pkg/kafka/sarama_client.go
@@ -0,0 +1,958 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package kafka
+
+import (
+ "errors"
+ "fmt"
+ "github.com/Shopify/sarama"
+ scc "github.com/bsm/sarama-cluster"
+ "github.com/golang/protobuf/proto"
+ "github.com/google/uuid"
+ "github.com/opencord/voltha-lib-go/pkg/log"
+ ic "github.com/opencord/voltha-protos/go/inter_container"
+ "strings"
+ "sync"
+ "time"
+)
+
+func init() {
+ log.AddPackage(log.JSON, log.DebugLevel, nil)
+}
+
+type returnErrorFunction func() error
+
+// consumerChannels represents one or more consumers listening on a kafka topic. Once a message is received on that
+// topic, the consumer(s) broadcasts the message to all the listening channels. The consumer can be a partition
+//consumer or a group consumer
+type consumerChannels struct {
+ consumers []interface{}
+ channels []chan *ic.InterContainerMessage
+}
+
+// SaramaClient represents the messaging proxy
+type SaramaClient struct {
+ cAdmin sarama.ClusterAdmin
+ client sarama.Client
+ KafkaHost string
+ KafkaPort int
+ producer sarama.AsyncProducer
+ consumer sarama.Consumer
+ groupConsumers map[string]*scc.Consumer
+ lockOfGroupConsumers sync.RWMutex
+ consumerGroupPrefix string
+ consumerType int
+ consumerGroupName string
+ producerFlushFrequency int
+ producerFlushMessages int
+ producerFlushMaxmessages int
+ producerRetryMax int
+ producerRetryBackOff time.Duration
+ producerReturnSuccess bool
+ producerReturnErrors bool
+ consumerMaxwait int
+ maxProcessingTime int
+ numPartitions int
+ numReplicas int
+ autoCreateTopic bool
+ doneCh chan int
+ topicToConsumerChannelMap map[string]*consumerChannels
+ lockTopicToConsumerChannelMap sync.RWMutex
+ topicLockMap map[string]*sync.RWMutex
+ lockOfTopicLockMap sync.RWMutex
+ metadataMaxRetry int
+}
+
+type SaramaClientOption func(*SaramaClient)
+
+func Host(host string) SaramaClientOption {
+ return func(args *SaramaClient) {
+ args.KafkaHost = host
+ }
+}
+
+func Port(port int) SaramaClientOption {
+ return func(args *SaramaClient) {
+ args.KafkaPort = port
+ }
+}
+
+func ConsumerGroupPrefix(prefix string) SaramaClientOption {
+ return func(args *SaramaClient) {
+ args.consumerGroupPrefix = prefix
+ }
+}
+
+func ConsumerGroupName(name string) SaramaClientOption {
+ return func(args *SaramaClient) {
+ args.consumerGroupName = name
+ }
+}
+
+func ConsumerType(consumer int) SaramaClientOption {
+ return func(args *SaramaClient) {
+ args.consumerType = consumer
+ }
+}
+
+func ProducerFlushFrequency(frequency int) SaramaClientOption {
+ return func(args *SaramaClient) {
+ args.producerFlushFrequency = frequency
+ }
+}
+
+func ProducerFlushMessages(num int) SaramaClientOption {
+ return func(args *SaramaClient) {
+ args.producerFlushMessages = num
+ }
+}
+
+func ProducerFlushMaxMessages(num int) SaramaClientOption {
+ return func(args *SaramaClient) {
+ args.producerFlushMaxmessages = num
+ }
+}
+
+func ProducerMaxRetries(num int) SaramaClientOption {
+ return func(args *SaramaClient) {
+ args.producerRetryMax = num
+ }
+}
+
+func ProducerRetryBackoff(duration time.Duration) SaramaClientOption {
+ return func(args *SaramaClient) {
+ args.producerRetryBackOff = duration
+ }
+}
+
+func ProducerReturnOnErrors(opt bool) SaramaClientOption {
+ return func(args *SaramaClient) {
+ args.producerReturnErrors = opt
+ }
+}
+
+func ProducerReturnOnSuccess(opt bool) SaramaClientOption {
+ return func(args *SaramaClient) {
+ args.producerReturnSuccess = opt
+ }
+}
+
+func ConsumerMaxWait(wait int) SaramaClientOption {
+ return func(args *SaramaClient) {
+ args.consumerMaxwait = wait
+ }
+}
+
+func MaxProcessingTime(pTime int) SaramaClientOption {
+ return func(args *SaramaClient) {
+ args.maxProcessingTime = pTime
+ }
+}
+
+func NumPartitions(number int) SaramaClientOption {
+ return func(args *SaramaClient) {
+ args.numPartitions = number
+ }
+}
+
+func NumReplicas(number int) SaramaClientOption {
+ return func(args *SaramaClient) {
+ args.numReplicas = number
+ }
+}
+
+func AutoCreateTopic(opt bool) SaramaClientOption {
+ return func(args *SaramaClient) {
+ args.autoCreateTopic = opt
+ }
+}
+
+func MetadatMaxRetries(retry int) SaramaClientOption {
+ return func(args *SaramaClient) {
+ args.metadataMaxRetry = retry
+ }
+}
+
+func NewSaramaClient(opts ...SaramaClientOption) *SaramaClient {
+ client := &SaramaClient{
+ KafkaHost: DefaultKafkaHost,
+ KafkaPort: DefaultKafkaPort,
+ }
+ client.consumerType = DefaultConsumerType
+ client.producerFlushFrequency = DefaultProducerFlushFrequency
+ client.producerFlushMessages = DefaultProducerFlushMessages
+ client.producerFlushMaxmessages = DefaultProducerFlushMaxmessages
+ client.producerReturnErrors = DefaultProducerReturnErrors
+ client.producerReturnSuccess = DefaultProducerReturnSuccess
+ client.producerRetryMax = DefaultProducerRetryMax
+ client.producerRetryBackOff = DefaultProducerRetryBackoff
+ client.consumerMaxwait = DefaultConsumerMaxwait
+ client.maxProcessingTime = DefaultMaxProcessingTime
+ client.numPartitions = DefaultNumberPartitions
+ client.numReplicas = DefaultNumberReplicas
+ client.autoCreateTopic = DefaultAutoCreateTopic
+ client.metadataMaxRetry = DefaultMetadataMaxRetry
+
+ for _, option := range opts {
+ option(client)
+ }
+
+ client.groupConsumers = make(map[string]*scc.Consumer)
+
+ client.lockTopicToConsumerChannelMap = sync.RWMutex{}
+ client.topicLockMap = make(map[string]*sync.RWMutex)
+ client.lockOfTopicLockMap = sync.RWMutex{}
+ client.lockOfGroupConsumers = sync.RWMutex{}
+ return client
+}
+
+func (sc *SaramaClient) Start() error {
+ log.Info("Starting-kafka-sarama-client")
+
+ // Create the Done channel
+ sc.doneCh = make(chan int, 1)
+
+ var err error
+
+ // Add a cleanup in case of failure to startup
+ defer func() {
+ if err != nil {
+ sc.Stop()
+ }
+ }()
+
+ // Create the Cluster Admin
+ if err = sc.createClusterAdmin(); err != nil {
+ log.Errorw("Cannot-create-cluster-admin", log.Fields{"error": err})
+ return err
+ }
+
+ // Create the Publisher
+ if err := sc.createPublisher(); err != nil {
+ log.Errorw("Cannot-create-kafka-publisher", log.Fields{"error": err})
+ return err
+ }
+
+ if sc.consumerType == DefaultConsumerType {
+ // Create the master consumers
+ if err := sc.createConsumer(); err != nil {
+ log.Errorw("Cannot-create-kafka-consumers", log.Fields{"error": err})
+ return err
+ }
+ }
+
+ // Create the topic to consumers/channel map
+ sc.topicToConsumerChannelMap = make(map[string]*consumerChannels)
+
+ log.Info("kafka-sarama-client-started")
+
+ return nil
+}
+
+func (sc *SaramaClient) Stop() {
+ log.Info("stopping-sarama-client")
+
+ //Send a message over the done channel to close all long running routines
+ sc.doneCh <- 1
+
+ if sc.producer != nil {
+ if err := sc.producer.Close(); err != nil {
+ log.Errorw("closing-producer-failed", log.Fields{"error": err})
+ }
+ }
+
+ if sc.consumer != nil {
+ if err := sc.consumer.Close(); err != nil {
+ log.Errorw("closing-partition-consumer-failed", log.Fields{"error": err})
+ }
+ }
+
+ for key, val := range sc.groupConsumers {
+ log.Debugw("closing-group-consumer", log.Fields{"topic": key})
+ if err := val.Close(); err != nil {
+ log.Errorw("closing-group-consumer-failed", log.Fields{"error": err, "topic": key})
+ }
+ }
+
+ if sc.cAdmin != nil {
+ if err := sc.cAdmin.Close(); err != nil {
+ log.Errorw("closing-cluster-admin-failed", log.Fields{"error": err})
+ }
+ }
+
+ //TODO: Clear the consumers map
+ //sc.clearConsumerChannelMap()
+
+ log.Info("sarama-client-stopped")
+}
+
+//createTopic is an internal function to create a topic on the Kafka Broker. No locking is required as
+// the invoking function must hold the lock
+func (sc *SaramaClient) createTopic(topic *Topic, numPartition int, repFactor int) error {
+ // Set the topic details
+ topicDetail := &sarama.TopicDetail{}
+ topicDetail.NumPartitions = int32(numPartition)
+ topicDetail.ReplicationFactor = int16(repFactor)
+ topicDetail.ConfigEntries = make(map[string]*string)
+ topicDetails := make(map[string]*sarama.TopicDetail)
+ topicDetails[topic.Name] = topicDetail
+
+ if err := sc.cAdmin.CreateTopic(topic.Name, topicDetail, false); err != nil {
+ if err == sarama.ErrTopicAlreadyExists {
+ // Not an error
+ log.Debugw("topic-already-exist", log.Fields{"topic": topic.Name})
+ return nil
+ }
+ log.Errorw("create-topic-failure", log.Fields{"error": err})
+ return err
+ }
+ // TODO: Wait until the topic has been created. No API is available in the Sarama clusterAdmin to
+ // do so.
+ log.Debugw("topic-created", log.Fields{"topic": topic, "numPartition": numPartition, "replicationFactor": repFactor})
+ return nil
+}
+
+//CreateTopic is a public API to create a topic on the Kafka Broker. It uses a lock on a specific topic to
+// ensure no two go routines are performing operations on the same topic
+func (sc *SaramaClient) CreateTopic(topic *Topic, numPartition int, repFactor int) error {
+ sc.lockTopic(topic)
+ defer sc.unLockTopic(topic)
+
+ return sc.createTopic(topic, numPartition, repFactor)
+}
+
+//DeleteTopic removes a topic from the kafka Broker
+func (sc *SaramaClient) DeleteTopic(topic *Topic) error {
+ sc.lockTopic(topic)
+ defer sc.unLockTopic(topic)
+
+ // Remove the topic from the broker
+ if err := sc.cAdmin.DeleteTopic(topic.Name); err != nil {
+ if err == sarama.ErrUnknownTopicOrPartition {
+ // Not an error as does not exist
+ log.Debugw("topic-not-exist", log.Fields{"topic": topic.Name})
+ return nil
+ }
+ log.Errorw("delete-topic-failed", log.Fields{"topic": topic, "error": err})
+ return err
+ }
+
+ // Clear the topic from the consumer channel. This will also close any consumers listening on that topic.
+ if err := sc.clearTopicFromConsumerChannelMap(*topic); err != nil {
+ log.Errorw("failure-clearing-channels", log.Fields{"topic": topic, "error": err})
+ return err
+ }
+ return nil
+}
+
+// Subscribe registers a caller to a topic. It returns a channel that the caller can use to receive
+// messages from that topic
+func (sc *SaramaClient) Subscribe(topic *Topic, kvArgs ...*KVArg) (<-chan *ic.InterContainerMessage, error) {
+ sc.lockTopic(topic)
+ defer sc.unLockTopic(topic)
+
+ log.Debugw("subscribe", log.Fields{"topic": topic.Name})
+
+ // If a consumers already exist for that topic then resuse it
+ if consumerCh := sc.getConsumerChannel(topic); consumerCh != nil {
+ log.Debugw("topic-already-subscribed", log.Fields{"topic": topic.Name})
+ // Create a channel specific for that consumers and add it to the consumers channel map
+ ch := make(chan *ic.InterContainerMessage)
+ sc.addChannelToConsumerChannelMap(topic, ch)
+ return ch, nil
+ }
+
+ // Register for the topic and set it up
+ var consumerListeningChannel chan *ic.InterContainerMessage
+ var err error
+
+ // Use the consumerType option to figure out the type of consumer to launch
+ if sc.consumerType == PartitionConsumer {
+ if sc.autoCreateTopic {
+ if err = sc.createTopic(topic, sc.numPartitions, sc.numReplicas); err != nil {
+ log.Errorw("create-topic-failure", log.Fields{"error": err, "topic": topic.Name})
+ return nil, err
+ }
+ }
+ if consumerListeningChannel, err = sc.setupPartitionConsumerChannel(topic, getOffset(kvArgs...)); err != nil {
+ log.Warnw("create-consumers-channel-failure", log.Fields{"error": err, "topic": topic.Name})
+ return nil, err
+ }
+ } else if sc.consumerType == GroupCustomer {
+ // TODO: create topic if auto create is on. There is an issue with the sarama cluster library that
+ // does not consume from a precreated topic in some scenarios
+ //if sc.autoCreateTopic {
+ // if err = sc.createTopic(topic, sc.numPartitions, sc.numReplicas); err != nil {
+ // log.Errorw("create-topic-failure", log.Fields{"error": err, "topic": topic.Name})
+ // return nil, err
+ // }
+ //}
+ //groupId := sc.consumerGroupName
+ groupId := getGroupId(kvArgs...)
+ // Include the group prefix
+ if groupId != "" {
+ groupId = sc.consumerGroupPrefix + groupId
+ } else {
+ // Need to use a unique group Id per topic
+ groupId = sc.consumerGroupPrefix + topic.Name
+ }
+ if consumerListeningChannel, err = sc.setupGroupConsumerChannel(topic, groupId, getOffset(kvArgs...)); err != nil {
+ log.Warnw("create-consumers-channel-failure", log.Fields{"error": err, "topic": topic.Name, "groupId": groupId})
+ return nil, err
+ }
+
+ } else {
+ log.Warnw("unknown-consumer-type", log.Fields{"consumer-type": sc.consumerType})
+ return nil, errors.New("unknown-consumer-type")
+ }
+
+ return consumerListeningChannel, nil
+}
+
+//UnSubscribe unsubscribe a consumer from a given topic
+func (sc *SaramaClient) UnSubscribe(topic *Topic, ch <-chan *ic.InterContainerMessage) error {
+ sc.lockTopic(topic)
+ defer sc.unLockTopic(topic)
+
+ log.Debugw("unsubscribing-channel-from-topic", log.Fields{"topic": topic.Name})
+ var err error
+ if err = sc.removeChannelFromConsumerChannelMap(*topic, ch); err != nil {
+ log.Errorw("failed-removing-channel", log.Fields{"error": err})
+ }
+ if err = sc.deleteFromGroupConsumers(topic.Name); err != nil {
+ log.Errorw("failed-deleting-group-consumer", log.Fields{"error": err})
+ }
+ return err
+}
+
+// send formats and sends the request onto the kafka messaging bus.
+func (sc *SaramaClient) Send(msg interface{}, topic *Topic, keys ...string) error {
+
+ // Assert message is a proto message
+ var protoMsg proto.Message
+ var ok bool
+ // ascertain the value interface type is a proto.Message
+ if protoMsg, ok = msg.(proto.Message); !ok {
+ log.Warnw("message-not-proto-message", log.Fields{"msg": msg})
+ return errors.New(fmt.Sprintf("not-a-proto-msg-%s", msg))
+ }
+
+ var marshalled []byte
+ var err error
+ // Create the Sarama producer message
+ if marshalled, err = proto.Marshal(protoMsg); err != nil {
+ log.Errorw("marshalling-failed", log.Fields{"msg": protoMsg, "error": err})
+ return err
+ }
+ key := ""
+ if len(keys) > 0 {
+ key = keys[0] // Only the first key is relevant
+ }
+ kafkaMsg := &sarama.ProducerMessage{
+ Topic: topic.Name,
+ Key: sarama.StringEncoder(key),
+ Value: sarama.ByteEncoder(marshalled),
+ }
+
+ // Send message to kafka
+ sc.producer.Input() <- kafkaMsg
+ // Wait for result
+ // TODO: Use a lock or a different mechanism to ensure the response received corresponds to the message sent.
+ select {
+ case ok := <-sc.producer.Successes():
+ log.Debugw("message-sent", log.Fields{"status": ok.Topic})
+ case notOk := <-sc.producer.Errors():
+ log.Debugw("error-sending", log.Fields{"status": notOk})
+ return notOk
+ }
+ return nil
+}
+
+// getGroupId returns the group id from the key-value args.
+func getGroupId(kvArgs ...*KVArg) string {
+ for _, arg := range kvArgs {
+ if arg.Key == GroupIdKey {
+ return arg.Value.(string)
+ }
+ }
+ return ""
+}
+
+// getOffset returns the offset from the key-value args.
+func getOffset(kvArgs ...*KVArg) int64 {
+ for _, arg := range kvArgs {
+ if arg.Key == Offset {
+ return arg.Value.(int64)
+ }
+ }
+ return sarama.OffsetNewest
+}
+
+func (sc *SaramaClient) createClusterAdmin() error {
+ kafkaFullAddr := fmt.Sprintf("%s:%d", sc.KafkaHost, sc.KafkaPort)
+ config := sarama.NewConfig()
+ config.Version = sarama.V1_0_0_0
+
+ // Create a cluster Admin
+ var cAdmin sarama.ClusterAdmin
+ var err error
+ if cAdmin, err = sarama.NewClusterAdmin([]string{kafkaFullAddr}, config); err != nil {
+ log.Errorw("cluster-admin-failure", log.Fields{"error": err, "broker-address": kafkaFullAddr})
+ return err
+ }
+ sc.cAdmin = cAdmin
+ return nil
+}
+
+func (sc *SaramaClient) lockTopic(topic *Topic) {
+ sc.lockOfTopicLockMap.Lock()
+ if _, exist := sc.topicLockMap[topic.Name]; exist {
+ sc.lockOfTopicLockMap.Unlock()
+ sc.topicLockMap[topic.Name].Lock()
+ } else {
+ sc.topicLockMap[topic.Name] = &sync.RWMutex{}
+ sc.lockOfTopicLockMap.Unlock()
+ sc.topicLockMap[topic.Name].Lock()
+ }
+}
+
+func (sc *SaramaClient) unLockTopic(topic *Topic) {
+ sc.lockOfTopicLockMap.Lock()
+ defer sc.lockOfTopicLockMap.Unlock()
+ if _, exist := sc.topicLockMap[topic.Name]; exist {
+ sc.topicLockMap[topic.Name].Unlock()
+ }
+}
+
+func (sc *SaramaClient) addTopicToConsumerChannelMap(id string, arg *consumerChannels) {
+ sc.lockTopicToConsumerChannelMap.Lock()
+ defer sc.lockTopicToConsumerChannelMap.Unlock()
+ if _, exist := sc.topicToConsumerChannelMap[id]; !exist {
+ sc.topicToConsumerChannelMap[id] = arg
+ }
+}
+
+func (sc *SaramaClient) deleteFromTopicToConsumerChannelMap(id string) {
+ sc.lockTopicToConsumerChannelMap.Lock()
+ defer sc.lockTopicToConsumerChannelMap.Unlock()
+ if _, exist := sc.topicToConsumerChannelMap[id]; exist {
+ delete(sc.topicToConsumerChannelMap, id)
+ }
+}
+
+func (sc *SaramaClient) getConsumerChannel(topic *Topic) *consumerChannels {
+ sc.lockTopicToConsumerChannelMap.RLock()
+ defer sc.lockTopicToConsumerChannelMap.RUnlock()
+
+ if consumerCh, exist := sc.topicToConsumerChannelMap[topic.Name]; exist {
+ return consumerCh
+ }
+ return nil
+}
+
+func (sc *SaramaClient) addChannelToConsumerChannelMap(topic *Topic, ch chan *ic.InterContainerMessage) {
+ sc.lockTopicToConsumerChannelMap.Lock()
+ defer sc.lockTopicToConsumerChannelMap.Unlock()
+ if consumerCh, exist := sc.topicToConsumerChannelMap[topic.Name]; exist {
+ consumerCh.channels = append(consumerCh.channels, ch)
+ return
+ }
+ log.Warnw("consumers-channel-not-exist", log.Fields{"topic": topic.Name})
+}
+
+//closeConsumers closes a list of sarama consumers. The consumers can either be a partition consumers or a group consumers
+func closeConsumers(consumers []interface{}) error {
+ var err error
+ for _, consumer := range consumers {
+ // Is it a partition consumers?
+ if partionConsumer, ok := consumer.(sarama.PartitionConsumer); ok {
+ if errTemp := partionConsumer.Close(); errTemp != nil {
+ log.Debugw("partition!!!", log.Fields{"err": errTemp})
+ if strings.Compare(errTemp.Error(), sarama.ErrUnknownTopicOrPartition.Error()) == 0 {
+ // This can occur on race condition
+ err = nil
+ } else {
+ err = errTemp
+ }
+ }
+ } else if groupConsumer, ok := consumer.(*scc.Consumer); ok {
+ if errTemp := groupConsumer.Close(); errTemp != nil {
+ if strings.Compare(errTemp.Error(), sarama.ErrUnknownTopicOrPartition.Error()) == 0 {
+ // This can occur on race condition
+ err = nil
+ } else {
+ err = errTemp
+ }
+ }
+ }
+ }
+ return err
+}
+
+func (sc *SaramaClient) removeChannelFromConsumerChannelMap(topic Topic, ch <-chan *ic.InterContainerMessage) error {
+ sc.lockTopicToConsumerChannelMap.Lock()
+ defer sc.lockTopicToConsumerChannelMap.Unlock()
+ if consumerCh, exist := sc.topicToConsumerChannelMap[topic.Name]; exist {
+ // Channel will be closed in the removeChannel method
+ consumerCh.channels = removeChannel(consumerCh.channels, ch)
+ // If there are no more channels then we can close the consumers itself
+ if len(consumerCh.channels) == 0 {
+ log.Debugw("closing-consumers", log.Fields{"topic": topic})
+ err := closeConsumers(consumerCh.consumers)
+ //err := consumerCh.consumers.Close()
+ delete(sc.topicToConsumerChannelMap, topic.Name)
+ return err
+ }
+ return nil
+ }
+ log.Warnw("topic-does-not-exist", log.Fields{"topic": topic.Name})
+ return errors.New("topic-does-not-exist")
+}
+
+func (sc *SaramaClient) clearTopicFromConsumerChannelMap(topic Topic) error {
+ sc.lockTopicToConsumerChannelMap.Lock()
+ defer sc.lockTopicToConsumerChannelMap.Unlock()
+ if consumerCh, exist := sc.topicToConsumerChannelMap[topic.Name]; exist {
+ for _, ch := range consumerCh.channels {
+ // Channel will be closed in the removeChannel method
+ removeChannel(consumerCh.channels, ch)
+ }
+ err := closeConsumers(consumerCh.consumers)
+ //if err == sarama.ErrUnknownTopicOrPartition {
+ // // Not an error
+ // err = nil
+ //}
+ //err := consumerCh.consumers.Close()
+ delete(sc.topicToConsumerChannelMap, topic.Name)
+ return err
+ }
+ log.Debugw("topic-does-not-exist", log.Fields{"topic": topic.Name})
+ return nil
+}
+
+func (sc *SaramaClient) clearConsumerChannelMap() error {
+ sc.lockTopicToConsumerChannelMap.Lock()
+ defer sc.lockTopicToConsumerChannelMap.Unlock()
+ var err error
+ for topic, consumerCh := range sc.topicToConsumerChannelMap {
+ for _, ch := range consumerCh.channels {
+ // Channel will be closed in the removeChannel method
+ removeChannel(consumerCh.channels, ch)
+ }
+ if errTemp := closeConsumers(consumerCh.consumers); errTemp != nil {
+ err = errTemp
+ }
+ //err = consumerCh.consumers.Close()
+ delete(sc.topicToConsumerChannelMap, topic)
+ }
+ return err
+}
+
+//createPublisher creates the publisher which is used to send a message onto kafka
+func (sc *SaramaClient) createPublisher() error {
+ // This Creates the publisher
+ config := sarama.NewConfig()
+ config.Producer.Partitioner = sarama.NewRandomPartitioner
+ config.Producer.Flush.Frequency = time.Duration(sc.producerFlushFrequency)
+ config.Producer.Flush.Messages = sc.producerFlushMessages
+ config.Producer.Flush.MaxMessages = sc.producerFlushMaxmessages
+ config.Producer.Return.Errors = sc.producerReturnErrors
+ config.Producer.Return.Successes = sc.producerReturnSuccess
+ //config.Producer.RequiredAcks = sarama.WaitForAll
+ config.Producer.RequiredAcks = sarama.WaitForLocal
+
+ kafkaFullAddr := fmt.Sprintf("%s:%d", sc.KafkaHost, sc.KafkaPort)
+ brokers := []string{kafkaFullAddr}
+
+ if producer, err := sarama.NewAsyncProducer(brokers, config); err != nil {
+ log.Errorw("error-starting-publisher", log.Fields{"error": err})
+ return err
+ } else {
+ sc.producer = producer
+ }
+ log.Info("Kafka-publisher-created")
+ return nil
+}
+
+func (sc *SaramaClient) createConsumer() error {
+ config := sarama.NewConfig()
+ config.Consumer.Return.Errors = true
+ config.Consumer.Fetch.Min = 1
+ config.Consumer.MaxWaitTime = time.Duration(sc.consumerMaxwait) * time.Millisecond
+ config.Consumer.MaxProcessingTime = time.Duration(sc.maxProcessingTime) * time.Millisecond
+ config.Consumer.Offsets.Initial = sarama.OffsetNewest
+ config.Metadata.Retry.Max = sc.metadataMaxRetry
+ kafkaFullAddr := fmt.Sprintf("%s:%d", sc.KafkaHost, sc.KafkaPort)
+ brokers := []string{kafkaFullAddr}
+
+ if consumer, err := sarama.NewConsumer(brokers, config); err != nil {
+ log.Errorw("error-starting-consumers", log.Fields{"error": err})
+ return err
+ } else {
+ sc.consumer = consumer
+ }
+ log.Info("Kafka-consumers-created")
+ return nil
+}
+
+// createGroupConsumer creates a consumers group
+func (sc *SaramaClient) createGroupConsumer(topic *Topic, groupId string, initialOffset int64, retries int) (*scc.Consumer, error) {
+ config := scc.NewConfig()
+ config.ClientID = uuid.New().String()
+ config.Group.Mode = scc.ConsumerModeMultiplex
+ //config.Consumer.Return.Errors = true
+ //config.Group.Return.Notifications = false
+ //config.Consumer.MaxWaitTime = time.Duration(DefaultConsumerMaxwait) * time.Millisecond
+ //config.Consumer.MaxProcessingTime = time.Duration(DefaultMaxProcessingTime) * time.Millisecond
+ config.Consumer.Offsets.Initial = initialOffset
+ //config.Consumer.Offsets.Initial = sarama.OffsetOldest
+ kafkaFullAddr := fmt.Sprintf("%s:%d", sc.KafkaHost, sc.KafkaPort)
+ brokers := []string{kafkaFullAddr}
+
+ topics := []string{topic.Name}
+ var consumer *scc.Consumer
+ var err error
+
+ if consumer, err = scc.NewConsumer(brokers, groupId, topics, config); err != nil {
+ log.Errorw("create-group-consumers-failure", log.Fields{"error": err, "topic": topic.Name, "groupId": groupId})
+ return nil, err
+ }
+ log.Debugw("create-group-consumers-success", log.Fields{"topic": topic.Name, "groupId": groupId})
+
+ //sc.groupConsumers[topic.Name] = consumer
+ sc.addToGroupConsumers(topic.Name, consumer)
+ return consumer, nil
+}
+
+// dispatchToConsumers sends the intercontainermessage received on a given topic to all subscribers for that
+// topic via the unique channel each subscriber received during subscription
+func (sc *SaramaClient) dispatchToConsumers(consumerCh *consumerChannels, protoMessage *ic.InterContainerMessage) {
+ // Need to go over all channels and publish messages to them - do we need to copy msg?
+ sc.lockTopicToConsumerChannelMap.RLock()
+ defer sc.lockTopicToConsumerChannelMap.RUnlock()
+ for _, ch := range consumerCh.channels {
+ go func(c chan *ic.InterContainerMessage) {
+ c <- protoMessage
+ }(ch)
+ }
+}
+
+func (sc *SaramaClient) consumeFromAPartition(topic *Topic, consumer sarama.PartitionConsumer, consumerChnls *consumerChannels) {
+ log.Debugw("starting-partition-consumption-loop", log.Fields{"topic": topic.Name})
+startloop:
+ for {
+ select {
+ case err, ok := <-consumer.Errors():
+ if ok {
+ log.Warnw("partition-consumers-error", log.Fields{"error": err})
+ } else {
+ // Channel is closed
+ break startloop
+ }
+ case msg, ok := <-consumer.Messages():
+ //log.Debugw("message-received", log.Fields{"msg": msg, "receivedTopic": msg.Topic})
+ if !ok {
+ // channel is closed
+ break startloop
+ }
+ msgBody := msg.Value
+ icm := &ic.InterContainerMessage{}
+ if err := proto.Unmarshal(msgBody, icm); err != nil {
+ log.Warnw("partition-invalid-message", log.Fields{"error": err})
+ continue
+ }
+ go sc.dispatchToConsumers(consumerChnls, icm)
+ case <-sc.doneCh:
+ log.Infow("partition-received-exit-signal", log.Fields{"topic": topic.Name})
+ break startloop
+ }
+ }
+ log.Infow("partition-consumer-stopped", log.Fields{"topic": topic.Name})
+}
+
+func (sc *SaramaClient) consumeGroupMessages(topic *Topic, consumer *scc.Consumer, consumerChnls *consumerChannels) {
+ log.Debugw("starting-group-consumption-loop", log.Fields{"topic": topic.Name})
+
+startloop:
+ for {
+ select {
+ case err, ok := <-consumer.Errors():
+ if ok {
+ log.Warnw("group-consumers-error", log.Fields{"topic": topic.Name, "error": err})
+ } else {
+ // channel is closed
+ break startloop
+ }
+ case msg, ok := <-consumer.Messages():
+ if !ok {
+ // Channel closed
+ break startloop
+ }
+ log.Debugw("message-received", log.Fields{"timestamp": msg.Timestamp, "receivedTopic": msg.Topic})
+ msgBody := msg.Value
+ icm := &ic.InterContainerMessage{}
+ if err := proto.Unmarshal(msgBody, icm); err != nil {
+ log.Warnw("invalid-message", log.Fields{"error": err})
+ continue
+ }
+ go sc.dispatchToConsumers(consumerChnls, icm)
+ consumer.MarkOffset(msg, "")
+ case ntf := <-consumer.Notifications():
+ log.Debugw("group-received-notification", log.Fields{"notification": ntf})
+ case <-sc.doneCh:
+ log.Infow("group-received-exit-signal", log.Fields{"topic": topic.Name})
+ break startloop
+ }
+ }
+ log.Infow("group-consumer-stopped", log.Fields{"topic": topic.Name})
+}
+
+func (sc *SaramaClient) startConsumers(topic *Topic) error {
+ log.Debugw("starting-consumers", log.Fields{"topic": topic.Name})
+ var consumerCh *consumerChannels
+ if consumerCh = sc.getConsumerChannel(topic); consumerCh == nil {
+ log.Errorw("consumers-not-exist", log.Fields{"topic": topic.Name})
+ return errors.New("consumers-not-exist")
+ }
+ // For each consumer listening for that topic, start a consumption loop
+ for _, consumer := range consumerCh.consumers {
+ if pConsumer, ok := consumer.(sarama.PartitionConsumer); ok {
+ go sc.consumeFromAPartition(topic, pConsumer, consumerCh)
+ } else if gConsumer, ok := consumer.(*scc.Consumer); ok {
+ go sc.consumeGroupMessages(topic, gConsumer, consumerCh)
+ } else {
+ log.Errorw("invalid-consumer", log.Fields{"topic": topic})
+ return errors.New("invalid-consumer")
+ }
+ }
+ return nil
+}
+
+//// setupConsumerChannel creates a consumerChannels object for that topic and add it to the consumerChannels map
+//// for that topic. It also starts the routine that listens for messages on that topic.
+func (sc *SaramaClient) setupPartitionConsumerChannel(topic *Topic, initialOffset int64) (chan *ic.InterContainerMessage, error) {
+ var pConsumers []sarama.PartitionConsumer
+ var err error
+
+ if pConsumers, err = sc.createPartitionConsumers(topic, initialOffset); err != nil {
+ log.Errorw("creating-partition-consumers-failure", log.Fields{"error": err, "topic": topic.Name})
+ return nil, err
+ }
+
+ consumersIf := make([]interface{}, 0)
+ for _, pConsumer := range pConsumers {
+ consumersIf = append(consumersIf, pConsumer)
+ }
+
+ // Create the consumers/channel structure and set the consumers and create a channel on that topic - for now
+ // unbuffered to verify race conditions.
+ consumerListeningChannel := make(chan *ic.InterContainerMessage)
+ cc := &consumerChannels{
+ consumers: consumersIf,
+ channels: []chan *ic.InterContainerMessage{consumerListeningChannel},
+ }
+
+ // Add the consumers channel to the map
+ sc.addTopicToConsumerChannelMap(topic.Name, cc)
+
+ //Start a consumers to listen on that specific topic
+ go sc.startConsumers(topic)
+
+ return consumerListeningChannel, nil
+}
+
+// setupConsumerChannel creates a consumerChannels object for that topic and add it to the consumerChannels map
+// for that topic. It also starts the routine that listens for messages on that topic.
+func (sc *SaramaClient) setupGroupConsumerChannel(topic *Topic, groupId string, initialOffset int64) (chan *ic.InterContainerMessage, error) {
+ // TODO: Replace this development partition consumers with a group consumers
+ var pConsumer *scc.Consumer
+ var err error
+ if pConsumer, err = sc.createGroupConsumer(topic, groupId, initialOffset, DefaultMaxRetries); err != nil {
+ log.Errorw("creating-partition-consumers-failure", log.Fields{"error": err, "topic": topic.Name})
+ return nil, err
+ }
+ // Create the consumers/channel structure and set the consumers and create a channel on that topic - for now
+ // unbuffered to verify race conditions.
+ consumerListeningChannel := make(chan *ic.InterContainerMessage)
+ cc := &consumerChannels{
+ consumers: []interface{}{pConsumer},
+ channels: []chan *ic.InterContainerMessage{consumerListeningChannel},
+ }
+
+ // Add the consumers channel to the map
+ sc.addTopicToConsumerChannelMap(topic.Name, cc)
+
+ //Start a consumers to listen on that specific topic
+ go sc.startConsumers(topic)
+
+ return consumerListeningChannel, nil
+}
+
+func (sc *SaramaClient) createPartitionConsumers(topic *Topic, initialOffset int64) ([]sarama.PartitionConsumer, error) {
+ log.Debugw("creating-partition-consumers", log.Fields{"topic": topic.Name})
+ partitionList, err := sc.consumer.Partitions(topic.Name)
+ if err != nil {
+ log.Warnw("get-partition-failure", log.Fields{"error": err, "topic": topic.Name})
+ return nil, err
+ }
+
+ pConsumers := make([]sarama.PartitionConsumer, 0)
+ for _, partition := range partitionList {
+ var pConsumer sarama.PartitionConsumer
+ if pConsumer, err = sc.consumer.ConsumePartition(topic.Name, partition, initialOffset); err != nil {
+ log.Warnw("consumers-partition-failure", log.Fields{"error": err, "topic": topic.Name})
+ return nil, err
+ }
+ pConsumers = append(pConsumers, pConsumer)
+ }
+ return pConsumers, nil
+}
+
+func removeChannel(channels []chan *ic.InterContainerMessage, ch <-chan *ic.InterContainerMessage) []chan *ic.InterContainerMessage {
+ var i int
+ var channel chan *ic.InterContainerMessage
+ for i, channel = range channels {
+ if channel == ch {
+ channels[len(channels)-1], channels[i] = channels[i], channels[len(channels)-1]
+ close(channel)
+ log.Debug("channel-closed")
+ return channels[:len(channels)-1]
+ }
+ }
+ return channels
+}
+
+func (sc *SaramaClient) addToGroupConsumers(topic string, consumer *scc.Consumer) {
+ sc.lockOfGroupConsumers.Lock()
+ defer sc.lockOfGroupConsumers.Unlock()
+ if _, exist := sc.groupConsumers[topic]; !exist {
+ sc.groupConsumers[topic] = consumer
+ }
+}
+
+func (sc *SaramaClient) deleteFromGroupConsumers(topic string) error {
+ sc.lockOfGroupConsumers.Lock()
+ defer sc.lockOfGroupConsumers.Unlock()
+ if _, exist := sc.groupConsumers[topic]; exist {
+ consumer := sc.groupConsumers[topic]
+ delete(sc.groupConsumers, topic)
+ if err := consumer.Close(); err != nil {
+ log.Errorw("failure-closing-consumer", log.Fields{"error": err})
+ return err
+ }
+ }
+ return nil
+}
diff --git a/vendor/github.com/opencord/voltha-lib-go/pkg/kafka/utils.go b/vendor/github.com/opencord/voltha-lib-go/pkg/kafka/utils.go
new file mode 100644
index 0000000..0cb9535
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/pkg/kafka/utils.go
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package kafka
+
+import "strings"
+
+const (
+ TopicSeparator = "_"
+ DeviceIdLength = 24
+)
+
+// A Topic definition - may be augmented with additional attributes eventually
+type Topic struct {
+ // The name of the topic. It must start with a letter,
+ // and contain only letters (`[A-Za-z]`), numbers (`[0-9]`), dashes (`-`),
+ // underscores (`_`), periods (`.`), tildes (`~`), plus (`+`) or percent
+ // signs (`%`).
+ Name string
+}
+
+type KVArg struct {
+ Key string
+ Value interface{}
+}
+
+// TODO: Remove and provide better may to get the device id
+// GetDeviceIdFromTopic extract the deviceId from the topic name. The topic name is formatted either as:
+// <any string> or <any string>_<deviceId>. The device Id is 24 characters long.
+func GetDeviceIdFromTopic(topic Topic) string {
+ pos := strings.LastIndex(topic.Name, TopicSeparator)
+ if pos == -1 {
+ return ""
+ }
+ adjustedPos := pos + len(TopicSeparator)
+ if adjustedPos >= len(topic.Name) {
+ return ""
+ }
+ deviceId := topic.Name[adjustedPos:len(topic.Name)]
+ if len(deviceId) != DeviceIdLength {
+ return ""
+ }
+ return deviceId
+}
diff --git a/vendor/github.com/opencord/voltha-lib-go/pkg/log/log.go b/vendor/github.com/opencord/voltha-lib-go/pkg/log/log.go
new file mode 100644
index 0000000..fe3a4e0
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/pkg/log/log.go
@@ -0,0 +1,763 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//Package log provides a structured Logger interface implemented using zap logger. It provides the following capabilities:
+//1. Package level logging - a go package can register itself (AddPackage) and have a logger created for that package.
+//2. Dynamic log level change - for all registered packages (SetAllLogLevel)
+//3. Dynamic log level change - for a given package (SetPackageLogLevel)
+//4. Provides a default logger for unregistered packages
+//5. Allow key-value pairs to be added to a logger(UpdateLogger) or all loggers (UpdateAllLoggers) at run time
+//6. Add to the log output the location where the log was invoked (filename.functionname.linenumber)
+//
+// Using package-level logging (recommended approach). In the examples below, log refers to this log package.
+// 1. In the appropriate package add the following in the init section of the package. The log level can be changed
+// and any number of default fields can be added as well. The log level specifies the lowest log level that will be
+// in the output while the fields will be automatically added to all log printouts.
+//
+// log.AddPackage(mylog.JSON, log.WarnLevel, log.Fields{"anyFieldName": "any value"})
+//
+//2. In the calling package, just invoke any of the publicly available functions of the logger. Here is an example
+// to write an Info log with additional fields:
+//
+//log.Infow("An example", mylog.Fields{"myStringOutput": "output", "myIntOutput": 2})
+//
+//3. To dynamically change the log level, you can use 1)SetLogLevel from inside your package or 2) SetPackageLogLevel
+// from anywhere or 3) SetAllLogLevel from anywhere.
+//
+
+package log
+
+import (
+ "errors"
+ "fmt"
+ zp "go.uber.org/zap"
+ zc "go.uber.org/zap/zapcore"
+ "path"
+ "runtime"
+ "strings"
+)
+
+const (
+ // DebugLevel logs a message at debug level
+ DebugLevel = iota
+ // InfoLevel logs a message at info level
+ InfoLevel
+ // WarnLevel logs a message at warning level
+ WarnLevel
+ // ErrorLevel logs a message at error level
+ ErrorLevel
+ // PanicLevel logs a message, then panics.
+ PanicLevel
+ // FatalLevel logs a message, then calls os.Exit(1).
+ FatalLevel
+)
+
+// CONSOLE formats the log for the console, mostly used during development
+const CONSOLE = "console"
+
+// JSON formats the log using json format, mostly used by an automated logging system consumption
+const JSON = "json"
+
+// Logger represents an abstract logging interface. Any logging implementation used
+// will need to abide by this interface
+type Logger interface {
+ Debug(...interface{})
+ Debugln(...interface{})
+ Debugf(string, ...interface{})
+ Debugw(string, Fields)
+
+ Info(...interface{})
+ Infoln(...interface{})
+ Infof(string, ...interface{})
+ Infow(string, Fields)
+
+ Warn(...interface{})
+ Warnln(...interface{})
+ Warnf(string, ...interface{})
+ Warnw(string, Fields)
+
+ Error(...interface{})
+ Errorln(...interface{})
+ Errorf(string, ...interface{})
+ Errorw(string, Fields)
+
+ Fatal(...interface{})
+ Fatalln(...interface{})
+ Fatalf(string, ...interface{})
+ Fatalw(string, Fields)
+
+ With(Fields) Logger
+
+ // The following are added to be able to use this logger as a gRPC LoggerV2 if needed
+ //
+ Warning(...interface{})
+ Warningln(...interface{})
+ Warningf(string, ...interface{})
+
+ // V reports whether verbosity level l is at least the requested verbose level.
+ V(l int) bool
+}
+
+// Fields is used as key-value pairs for structured logging
+type Fields map[string]interface{}
+
+var defaultLogger *logger
+var cfg zp.Config
+
+var loggers map[string]*logger
+var cfgs map[string]zp.Config
+
+type logger struct {
+ log *zp.SugaredLogger
+ parent *zp.Logger
+}
+
+func intToAtomicLevel(l int) zp.AtomicLevel {
+ switch l {
+ case DebugLevel:
+ return zp.NewAtomicLevelAt(zc.DebugLevel)
+ case InfoLevel:
+ return zp.NewAtomicLevelAt(zc.InfoLevel)
+ case WarnLevel:
+ return zp.NewAtomicLevelAt(zc.WarnLevel)
+ case ErrorLevel:
+ return zp.NewAtomicLevelAt(zc.ErrorLevel)
+ case PanicLevel:
+ return zp.NewAtomicLevelAt(zc.PanicLevel)
+ case FatalLevel:
+ return zp.NewAtomicLevelAt(zc.FatalLevel)
+ }
+ return zp.NewAtomicLevelAt(zc.ErrorLevel)
+}
+
+func intToLevel(l int) zc.Level {
+ switch l {
+ case DebugLevel:
+ return zc.DebugLevel
+ case InfoLevel:
+ return zc.InfoLevel
+ case WarnLevel:
+ return zc.WarnLevel
+ case ErrorLevel:
+ return zc.ErrorLevel
+ case PanicLevel:
+ return zc.PanicLevel
+ case FatalLevel:
+ return zc.FatalLevel
+ }
+ return zc.ErrorLevel
+}
+
+func levelToInt(l zc.Level) int {
+ switch l {
+ case zc.DebugLevel:
+ return DebugLevel
+ case zc.InfoLevel:
+ return InfoLevel
+ case zc.WarnLevel:
+ return WarnLevel
+ case zc.ErrorLevel:
+ return ErrorLevel
+ case zc.PanicLevel:
+ return PanicLevel
+ case FatalLevel:
+ return FatalLevel
+ }
+ return ErrorLevel
+}
+
+func getDefaultConfig(outputType string, level int, defaultFields Fields) zp.Config {
+ return zp.Config{
+ Level: intToAtomicLevel(level),
+ Encoding: outputType,
+ Development: true,
+ OutputPaths: []string{"stdout"},
+ ErrorOutputPaths: []string{"stderr"},
+ InitialFields: defaultFields,
+ EncoderConfig: zc.EncoderConfig{
+ LevelKey: "level",
+ MessageKey: "msg",
+ TimeKey: "ts",
+ StacktraceKey: "stacktrace",
+ LineEnding: zc.DefaultLineEnding,
+ EncodeLevel: zc.LowercaseLevelEncoder,
+ EncodeTime: zc.ISO8601TimeEncoder,
+ EncodeDuration: zc.SecondsDurationEncoder,
+ EncodeCaller: zc.ShortCallerEncoder,
+ },
+ }
+}
+
+// SetLogger needs to be invoked before the logger API can be invoked. This function
+// initialize the default logger (zap's sugaredlogger)
+func SetDefaultLogger(outputType string, level int, defaultFields Fields) (Logger, error) {
+ // Build a custom config using zap
+ cfg = getDefaultConfig(outputType, level, defaultFields)
+
+ l, err := cfg.Build()
+ if err != nil {
+ return nil, err
+ }
+
+ defaultLogger = &logger{
+ log: l.Sugar(),
+ parent: l,
+ }
+
+ return defaultLogger, nil
+}
+
+// AddPackage registers a package to the log map. Each package gets its own logger which allows
+// its config (loglevel) to be changed dynamically without interacting with the other packages.
+// outputType is JSON, level is the lowest level log to output with this logger and defaultFields is a map of
+// key-value pairs to always add to the output.
+// Note: AddPackage also returns a reference to the actual logger. If a calling package uses this reference directly
+//instead of using the publicly available functions in this log package then a number of functionalities will not
+// be available to it, notably log tracing with filename.functionname.linenumber annotation.
+//
+// pkgNames parameter should be used for testing only as this function detects the caller's package.
+func AddPackage(outputType string, level int, defaultFields Fields, pkgNames ...string) (Logger, error) {
+ if cfgs == nil {
+ cfgs = make(map[string]zp.Config)
+ }
+ if loggers == nil {
+ loggers = make(map[string]*logger)
+ }
+
+ var pkgName string
+ for _, name := range pkgNames {
+ pkgName = name
+ break
+ }
+ if pkgName == "" {
+ pkgName, _, _, _ = getCallerInfo()
+ }
+
+ if _, exist := loggers[pkgName]; exist {
+ return loggers[pkgName], nil
+ }
+
+ cfgs[pkgName] = getDefaultConfig(outputType, level, defaultFields)
+
+ l, err := cfgs[pkgName].Build()
+ if err != nil {
+ return nil, err
+ }
+
+ loggers[pkgName] = &logger{
+ log: l.Sugar(),
+ parent: l,
+ }
+ return loggers[pkgName], nil
+}
+
+//UpdateAllLoggers create new loggers for all registered pacakges with the defaultFields.
+func UpdateAllLoggers(defaultFields Fields) error {
+ for pkgName, cfg := range cfgs {
+ for k, v := range defaultFields {
+ if cfg.InitialFields == nil {
+ cfg.InitialFields = make(map[string]interface{})
+ }
+ cfg.InitialFields[k] = v
+ }
+ l, err := cfg.Build()
+ if err != nil {
+ return err
+ }
+
+ loggers[pkgName] = &logger{
+ log: l.Sugar(),
+ parent: l,
+ }
+ }
+ return nil
+}
+
+// Return a list of all packages that have individually-configured loggers
+func GetPackageNames() []string {
+ i := 0
+ keys := make([]string, len(loggers))
+ for k := range loggers {
+ keys[i] = k
+ i++
+ }
+ return keys
+}
+
+// UpdateLogger deletes the logger associated with a caller's package and creates a new logger with the
+// defaultFields. If a calling package is holding on to a Logger reference obtained from AddPackage invocation, then
+// that package needs to invoke UpdateLogger if it needs to make changes to the default fields and obtain a new logger
+// reference
+func UpdateLogger(defaultFields Fields) (Logger, error) {
+ pkgName, _, _, _ := getCallerInfo()
+ if _, exist := loggers[pkgName]; !exist {
+ return nil, errors.New(fmt.Sprintf("package-%s-not-registered", pkgName))
+ }
+
+ // Build a new logger
+ if _, exist := cfgs[pkgName]; !exist {
+ return nil, errors.New(fmt.Sprintf("config-%s-not-registered", pkgName))
+ }
+
+ cfg := cfgs[pkgName]
+ for k, v := range defaultFields {
+ if cfg.InitialFields == nil {
+ cfg.InitialFields = make(map[string]interface{})
+ }
+ cfg.InitialFields[k] = v
+ }
+ l, err := cfg.Build()
+ if err != nil {
+ return nil, err
+ }
+
+ // Set the logger
+ loggers[pkgName] = &logger{
+ log: l.Sugar(),
+ parent: l,
+ }
+ return loggers[pkgName], nil
+}
+
+func setLevel(cfg zp.Config, level int) {
+ switch level {
+ case DebugLevel:
+ cfg.Level.SetLevel(zc.DebugLevel)
+ case InfoLevel:
+ cfg.Level.SetLevel(zc.InfoLevel)
+ case WarnLevel:
+ cfg.Level.SetLevel(zc.WarnLevel)
+ case ErrorLevel:
+ cfg.Level.SetLevel(zc.ErrorLevel)
+ case PanicLevel:
+ cfg.Level.SetLevel(zc.PanicLevel)
+ case FatalLevel:
+ cfg.Level.SetLevel(zc.FatalLevel)
+ default:
+ cfg.Level.SetLevel(zc.ErrorLevel)
+ }
+}
+
+//SetPackageLogLevel dynamically sets the log level of a given package to level. This is typically invoked at an
+// application level during debugging
+func SetPackageLogLevel(packageName string, level int) {
+ // Get proper config
+ if cfg, ok := cfgs[packageName]; ok {
+ setLevel(cfg, level)
+ }
+}
+
+//SetAllLogLevel sets the log level of all registered packages to level
+func SetAllLogLevel(level int) {
+ // Get proper config
+ for _, cfg := range cfgs {
+ setLevel(cfg, level)
+ }
+}
+
+//GetPackageLogLevel returns the current log level of a package.
+func GetPackageLogLevel(packageName ...string) (int, error) {
+ var name string
+ if len(packageName) == 1 {
+ name = packageName[0]
+ } else {
+ name, _, _, _ = getCallerInfo()
+ }
+ if cfg, ok := cfgs[name]; ok {
+ return levelToInt(cfg.Level.Level()), nil
+ }
+ return 0, errors.New(fmt.Sprintf("unknown-package-%s", name))
+}
+
+//GetDefaultLogLevel gets the log level used for packages that don't have specific loggers
+func GetDefaultLogLevel() int {
+ return levelToInt(cfg.Level.Level())
+}
+
+//SetLogLevel sets the log level for the logger corresponding to the caller's package
+func SetLogLevel(level int) error {
+ pkgName, _, _, _ := getCallerInfo()
+ if _, exist := cfgs[pkgName]; !exist {
+ return errors.New(fmt.Sprintf("unregistered-package-%s", pkgName))
+ }
+ cfg := cfgs[pkgName]
+ setLevel(cfg, level)
+ return nil
+}
+
+//SetDefaultLogLevel sets the log level used for packages that don't have specific loggers
+func SetDefaultLogLevel(level int) {
+ setLevel(cfg, level)
+}
+
+// CleanUp flushed any buffered log entries. Applications should take care to call
+// CleanUp before exiting.
+func CleanUp() error {
+ for _, logger := range loggers {
+ if logger != nil {
+ if logger.parent != nil {
+ if err := logger.parent.Sync(); err != nil {
+ return err
+ }
+ }
+ }
+ }
+ if defaultLogger != nil {
+ if defaultLogger.parent != nil {
+ if err := defaultLogger.parent.Sync(); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+func getCallerInfo() (string, string, string, int) {
+ // Since the caller of a log function is one stack frame before (in terms of stack higher level) the log.go
+ // filename, then first look for the last log.go filename and then grab the caller info one level higher.
+ maxLevel := 3
+ skiplevel := 3 // Level with the most empirical success to see the last log.go stack frame.
+ pc := make([]uintptr, maxLevel)
+ n := runtime.Callers(skiplevel, pc)
+ packageName := ""
+ funcName := ""
+ fileName := ""
+ var line int
+ if n == 0 {
+ return packageName, fileName, funcName, line
+ }
+ frames := runtime.CallersFrames(pc[:n])
+ var frame runtime.Frame
+ var foundFrame runtime.Frame
+ more := true
+ for more {
+ frame, more = frames.Next()
+ _, fileName = path.Split(frame.File)
+ if fileName != "log.go" {
+ foundFrame = frame // First frame after log.go in the frame stack
+ break
+ }
+ }
+ parts := strings.Split(foundFrame.Function, ".")
+ pl := len(parts)
+ if pl >= 2 {
+ funcName = parts[pl-1]
+ if parts[pl-2][0] == '(' {
+ packageName = strings.Join(parts[0:pl-2], ".")
+ } else {
+ packageName = strings.Join(parts[0:pl-1], ".")
+ }
+ }
+
+ if strings.HasSuffix(packageName, ".init") {
+ packageName = strings.TrimSuffix(packageName, ".init")
+ }
+
+ if strings.HasSuffix(fileName, ".go") {
+ fileName = strings.TrimSuffix(fileName, ".go")
+ }
+
+ return packageName, fileName, funcName, foundFrame.Line
+}
+
+func getPackageLevelSugaredLogger() *zp.SugaredLogger {
+ pkgName, fileName, funcName, line := getCallerInfo()
+ if _, exist := loggers[pkgName]; exist {
+ return loggers[pkgName].log.With("caller", fmt.Sprintf("%s.%s:%d", fileName, funcName, line))
+ }
+ return defaultLogger.log.With("caller", fmt.Sprintf("%s.%s:%d", fileName, funcName, line))
+}
+
+func getPackageLevelLogger() Logger {
+ pkgName, _, _, _ := getCallerInfo()
+ if _, exist := loggers[pkgName]; exist {
+ return loggers[pkgName]
+ }
+ return defaultLogger
+}
+
+func serializeMap(fields Fields) []interface{} {
+ data := make([]interface{}, len(fields)*2)
+ i := 0
+ for k, v := range fields {
+ data[i] = k
+ data[i+1] = v
+ i = i + 2
+ }
+ return data
+}
+
+// With returns a logger initialized with the key-value pairs
+func (l logger) With(keysAndValues Fields) Logger {
+ return logger{log: l.log.With(serializeMap(keysAndValues)...), parent: l.parent}
+}
+
+// Debug logs a message at level Debug on the standard logger.
+func (l logger) Debug(args ...interface{}) {
+ l.log.Debug(args...)
+}
+
+// Debugln logs a message at level Debug on the standard logger with a line feed. Default in any case.
+func (l logger) Debugln(args ...interface{}) {
+ l.log.Debug(args...)
+}
+
+// Debugw logs a message at level Debug on the standard logger.
+func (l logger) Debugf(format string, args ...interface{}) {
+ l.log.Debugf(format, args...)
+}
+
+// Debugw logs a message with some additional context. The variadic key-value
+// pairs are treated as they are in With.
+func (l logger) Debugw(msg string, keysAndValues Fields) {
+ l.log.Debugw(msg, serializeMap(keysAndValues)...)
+}
+
+// Info logs a message at level Info on the standard logger.
+func (l logger) Info(args ...interface{}) {
+ l.log.Info(args...)
+}
+
+// Infoln logs a message at level Info on the standard logger with a line feed. Default in any case.
+func (l logger) Infoln(args ...interface{}) {
+ l.log.Info(args...)
+ //msg := fmt.Sprintln(args...)
+ //l.sourced().Info(msg[:len(msg)-1])
+}
+
+// Infof logs a message at level Info on the standard logger.
+func (l logger) Infof(format string, args ...interface{}) {
+ l.log.Infof(format, args...)
+}
+
+// Infow logs a message with some additional context. The variadic key-value
+// pairs are treated as they are in With.
+func (l logger) Infow(msg string, keysAndValues Fields) {
+ l.log.Infow(msg, serializeMap(keysAndValues)...)
+}
+
+// Warn logs a message at level Warn on the standard logger.
+func (l logger) Warn(args ...interface{}) {
+ l.log.Warn(args...)
+}
+
+// Warnln logs a message at level Warn on the standard logger with a line feed. Default in any case.
+func (l logger) Warnln(args ...interface{}) {
+ l.log.Warn(args...)
+}
+
+// Warnf logs a message at level Warn on the standard logger.
+func (l logger) Warnf(format string, args ...interface{}) {
+ l.log.Warnf(format, args...)
+}
+
+// Warnw logs a message with some additional context. The variadic key-value
+// pairs are treated as they are in With.
+func (l logger) Warnw(msg string, keysAndValues Fields) {
+ l.log.Warnw(msg, serializeMap(keysAndValues)...)
+}
+
+// Error logs a message at level Error on the standard logger.
+func (l logger) Error(args ...interface{}) {
+ l.log.Error(args...)
+}
+
+// Errorln logs a message at level Error on the standard logger with a line feed. Default in any case.
+func (l logger) Errorln(args ...interface{}) {
+ l.log.Error(args...)
+}
+
+// Errorf logs a message at level Error on the standard logger.
+func (l logger) Errorf(format string, args ...interface{}) {
+ l.log.Errorf(format, args...)
+}
+
+// Errorw logs a message with some additional context. The variadic key-value
+// pairs are treated as they are in With.
+func (l logger) Errorw(msg string, keysAndValues Fields) {
+ l.log.Errorw(msg, serializeMap(keysAndValues)...)
+}
+
+// Fatal logs a message at level Fatal on the standard logger.
+func (l logger) Fatal(args ...interface{}) {
+ l.log.Fatal(args...)
+}
+
+// Fatalln logs a message at level Fatal on the standard logger with a line feed. Default in any case.
+func (l logger) Fatalln(args ...interface{}) {
+ l.log.Fatal(args...)
+}
+
+// Fatalf logs a message at level Fatal on the standard logger.
+func (l logger) Fatalf(format string, args ...interface{}) {
+ l.log.Fatalf(format, args...)
+}
+
+// Fatalw logs a message with some additional context. The variadic key-value
+// pairs are treated as they are in With.
+func (l logger) Fatalw(msg string, keysAndValues Fields) {
+ l.log.Fatalw(msg, serializeMap(keysAndValues)...)
+}
+
+// Warning logs a message at level Warn on the standard logger.
+func (l logger) Warning(args ...interface{}) {
+ l.log.Warn(args...)
+}
+
+// Warningln logs a message at level Warn on the standard logger with a line feed. Default in any case.
+func (l logger) Warningln(args ...interface{}) {
+ l.log.Warn(args...)
+}
+
+// Warningf logs a message at level Warn on the standard logger.
+func (l logger) Warningf(format string, args ...interface{}) {
+ l.log.Warnf(format, args...)
+}
+
+// V reports whether verbosity level l is at least the requested verbose level.
+func (l logger) V(level int) bool {
+ return l.parent.Core().Enabled(intToLevel(level))
+}
+
+// With returns a logger initialized with the key-value pairs
+func With(keysAndValues Fields) Logger {
+ return logger{log: getPackageLevelSugaredLogger().With(serializeMap(keysAndValues)...), parent: defaultLogger.parent}
+}
+
+// Debug logs a message at level Debug on the standard logger.
+func Debug(args ...interface{}) {
+ getPackageLevelSugaredLogger().Debug(args...)
+}
+
+// Debugln logs a message at level Debug on the standard logger.
+func Debugln(args ...interface{}) {
+ getPackageLevelSugaredLogger().Debug(args...)
+}
+
+// Debugf logs a message at level Debug on the standard logger.
+func Debugf(format string, args ...interface{}) {
+ getPackageLevelSugaredLogger().Debugf(format, args...)
+}
+
+// Debugw logs a message with some additional context. The variadic key-value
+// pairs are treated as they are in With.
+func Debugw(msg string, keysAndValues Fields) {
+ getPackageLevelSugaredLogger().Debugw(msg, serializeMap(keysAndValues)...)
+}
+
+// Info logs a message at level Info on the standard logger.
+func Info(args ...interface{}) {
+ getPackageLevelSugaredLogger().Info(args...)
+}
+
+// Infoln logs a message at level Info on the standard logger.
+func Infoln(args ...interface{}) {
+ getPackageLevelSugaredLogger().Info(args...)
+}
+
+// Infof logs a message at level Info on the standard logger.
+func Infof(format string, args ...interface{}) {
+ getPackageLevelSugaredLogger().Infof(format, args...)
+}
+
+//Infow logs a message with some additional context. The variadic key-value
+//pairs are treated as they are in With.
+func Infow(msg string, keysAndValues Fields) {
+ getPackageLevelSugaredLogger().Infow(msg, serializeMap(keysAndValues)...)
+}
+
+// Warn logs a message at level Warn on the standard logger.
+func Warn(args ...interface{}) {
+ getPackageLevelSugaredLogger().Warn(args...)
+}
+
+// Warnln logs a message at level Warn on the standard logger.
+func Warnln(args ...interface{}) {
+ getPackageLevelSugaredLogger().Warn(args...)
+}
+
+// Warnf logs a message at level Warn on the standard logger.
+func Warnf(format string, args ...interface{}) {
+ getPackageLevelSugaredLogger().Warnf(format, args...)
+}
+
+// Warnw logs a message with some additional context. The variadic key-value
+// pairs are treated as they are in With.
+func Warnw(msg string, keysAndValues Fields) {
+ getPackageLevelSugaredLogger().Warnw(msg, serializeMap(keysAndValues)...)
+}
+
+// Error logs a message at level Error on the standard logger.
+func Error(args ...interface{}) {
+ getPackageLevelSugaredLogger().Error(args...)
+}
+
+// Errorln logs a message at level Error on the standard logger.
+func Errorln(args ...interface{}) {
+ getPackageLevelSugaredLogger().Error(args...)
+}
+
+// Errorf logs a message at level Error on the standard logger.
+func Errorf(format string, args ...interface{}) {
+ getPackageLevelSugaredLogger().Errorf(format, args...)
+}
+
+// Errorw logs a message with some additional context. The variadic key-value
+// pairs are treated as they are in With.
+func Errorw(msg string, keysAndValues Fields) {
+ getPackageLevelSugaredLogger().Errorw(msg, serializeMap(keysAndValues)...)
+}
+
+// Fatal logs a message at level Fatal on the standard logger.
+func Fatal(args ...interface{}) {
+ getPackageLevelSugaredLogger().Fatal(args...)
+}
+
+// Fatalln logs a message at level Fatal on the standard logger.
+func Fatalln(args ...interface{}) {
+ getPackageLevelSugaredLogger().Fatal(args...)
+}
+
+// Fatalf logs a message at level Fatal on the standard logger.
+func Fatalf(format string, args ...interface{}) {
+ getPackageLevelSugaredLogger().Fatalf(format, args...)
+}
+
+// Fatalw logs a message with some additional context. The variadic key-value
+// pairs are treated as they are in With.
+func Fatalw(msg string, keysAndValues Fields) {
+ getPackageLevelSugaredLogger().Fatalw(msg, serializeMap(keysAndValues)...)
+}
+
+// Warning logs a message at level Warn on the standard logger.
+func Warning(args ...interface{}) {
+ getPackageLevelSugaredLogger().Warn(args...)
+}
+
+// Warningln logs a message at level Warn on the standard logger.
+func Warningln(args ...interface{}) {
+ getPackageLevelSugaredLogger().Warn(args...)
+}
+
+// Warningf logs a message at level Warn on the standard logger.
+func Warningf(format string, args ...interface{}) {
+ getPackageLevelSugaredLogger().Warnf(format, args...)
+}
+
+// V reports whether verbosity level l is at least the requested verbose level.
+func V(level int) bool {
+ return getPackageLevelLogger().V(level)
+}
diff --git a/vendor/github.com/opencord/voltha-lib-go/pkg/probe/probe.go b/vendor/github.com/opencord/voltha-lib-go/pkg/probe/probe.go
new file mode 100644
index 0000000..192c7ca
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/pkg/probe/probe.go
@@ -0,0 +1,253 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package probe
+
+import (
+ "context"
+ "fmt"
+ "github.com/opencord/voltha-lib-go/pkg/log"
+ "net/http"
+ "sync"
+)
+
+// ProbeContextKey used to fetch the Probe instance from a context
+type ProbeContextKeyType string
+
+// ServiceStatus typed values for service status
+type ServiceStatus int
+
+const (
+ // ServiceStatusUnknown initial state of services
+ ServiceStatusUnknown ServiceStatus = iota
+
+ // ServiceStatusPreparing to optionally be used for prep, such as connecting
+ ServiceStatusPreparing
+
+ // ServiceStatusPrepared to optionally be used when prep is complete, but before run
+ ServiceStatusPrepared
+
+ // ServiceStatusRunning service is functional
+ ServiceStatusRunning
+
+ // ServiceStatusStopped service has stopped, but not because of error
+ ServiceStatusStopped
+
+ // ServiceStatusFailed service has stopped because of an error
+ ServiceStatusFailed
+)
+
+const (
+ // ProbeContextKey value of context key to fetch probe
+ ProbeContextKey = ProbeContextKeyType("status-update-probe")
+)
+
+// String convert ServiceStatus values to strings
+func (s ServiceStatus) String() string {
+ switch s {
+ default:
+ fallthrough
+ case ServiceStatusUnknown:
+ return "Unknown"
+ case ServiceStatusPreparing:
+ return "Preparing"
+ case ServiceStatusPrepared:
+ return "Prepared"
+ case ServiceStatusRunning:
+ return "Running"
+ case ServiceStatusStopped:
+ return "Stopped"
+ case ServiceStatusFailed:
+ return "Failed"
+ }
+}
+
+// ServiceStatusUpdate status update event
+type ServiceStatusUpdate struct {
+ Name string
+ Status ServiceStatus
+}
+
+// Probe reciever on which to implement probe capabilities
+type Probe struct {
+ readyFunc func(map[string]ServiceStatus) bool
+ healthFunc func(map[string]ServiceStatus) bool
+
+ mutex sync.RWMutex
+ status map[string]ServiceStatus
+ isReady bool
+ isHealthy bool
+}
+
+// WithReadyFunc override the default ready calculation function
+func (p *Probe) WithReadyFunc(readyFunc func(map[string]ServiceStatus) bool) *Probe {
+ p.readyFunc = readyFunc
+ return p
+}
+
+// WithHealthFunc override the default health calculation function
+func (p *Probe) WithHealthFunc(healthFunc func(map[string]ServiceStatus) bool) *Probe {
+ p.healthFunc = healthFunc
+ return p
+}
+
+// RegisterService register one or more service names with the probe, status will be track against service name
+func (p *Probe) RegisterService(names ...string) {
+ p.mutex.Lock()
+ defer p.mutex.Unlock()
+ if p.status == nil {
+ p.status = make(map[string]ServiceStatus)
+ }
+ for _, name := range names {
+ if _, ok := p.status[name]; !ok {
+ p.status[name] = ServiceStatusUnknown
+ log.Debugw("probe-service-registered", log.Fields{"service-name": name})
+ }
+ }
+
+ if p.readyFunc != nil {
+ p.isReady = p.readyFunc(p.status)
+ } else {
+ p.isReady = defaultReadyFunc(p.status)
+ }
+
+ if p.healthFunc != nil {
+ p.isHealthy = p.healthFunc(p.status)
+ } else {
+ p.isHealthy = defaultHealthFunc(p.status)
+ }
+}
+
+// UpdateStatus utility function to send a service update to the probe
+func (p *Probe) UpdateStatus(name string, status ServiceStatus) {
+ p.mutex.Lock()
+ defer p.mutex.Unlock()
+ if p.status == nil {
+ p.status = make(map[string]ServiceStatus)
+ }
+ p.status[name] = status
+ if p.readyFunc != nil {
+ p.isReady = p.readyFunc(p.status)
+ } else {
+ p.isReady = defaultReadyFunc(p.status)
+ }
+
+ if p.healthFunc != nil {
+ p.isHealthy = p.healthFunc(p.status)
+ } else {
+ p.isHealthy = defaultHealthFunc(p.status)
+ }
+ log.Debugw("probe-service-status-updated",
+ log.Fields{
+ "service-name": name,
+ "status": status.String(),
+ "ready": p.isReady,
+ "health": p.isHealthy,
+ })
+}
+
+// UpdateStatusFromContext a convenience function to pull the Probe reference from the
+// Context, if it exists, and then calling UpdateStatus on that Probe reference. If Context
+// is nil or if a Probe reference is not associated with the ProbeContextKey then nothing
+// happens
+func UpdateStatusFromContext(ctx context.Context, name string, status ServiceStatus) {
+ if ctx != nil {
+ if value := ctx.Value(ProbeContextKey); value != nil {
+ if p, ok := value.(*Probe); ok {
+ p.UpdateStatus(name, status)
+ }
+ }
+ }
+}
+
+// pulled out to a function to help better enable unit testing
+func (p *Probe) readzFunc(w http.ResponseWriter, req *http.Request) {
+ p.mutex.RLock()
+ defer p.mutex.RUnlock()
+ if p.isReady {
+ w.WriteHeader(http.StatusOK)
+ } else {
+ w.WriteHeader(http.StatusTeapot)
+ }
+}
+func (p *Probe) healthzFunc(w http.ResponseWriter, req *http.Request) {
+ p.mutex.RLock()
+ defer p.mutex.RUnlock()
+ if p.isHealthy {
+ w.WriteHeader(http.StatusOK)
+ } else {
+ w.WriteHeader(http.StatusTeapot)
+ }
+}
+func (p *Probe) detailzFunc(w http.ResponseWriter, req *http.Request) {
+ p.mutex.RLock()
+ defer p.mutex.RUnlock()
+ w.Header().Set("Content-Type", "application/json")
+ w.Write([]byte("{"))
+ comma := ""
+ for c, s := range p.status {
+ w.Write([]byte(fmt.Sprintf("%s\"%s\": \"%s\"", comma, c, s.String())))
+ comma = ", "
+ }
+ w.Write([]byte("}"))
+ w.WriteHeader(http.StatusOK)
+
+}
+
+// ListenAndServe implements 3 HTTP endpoints on the given port for healthz, readz, and detailz. Returns only on error
+func (p *Probe) ListenAndServe(address string) {
+ mux := http.NewServeMux()
+
+ // Returns the result of the readyFunc calculation
+ mux.HandleFunc("/readz", p.readzFunc)
+
+ // Returns the result of the healthFunc calculation
+ mux.HandleFunc("/healthz", p.healthzFunc)
+
+ // Returns the details of the services and their status as JSON
+ mux.HandleFunc("/detailz", p.detailzFunc)
+ s := &http.Server{
+ Addr: address,
+ Handler: mux,
+ }
+ log.Fatal(s.ListenAndServe())
+}
+
+// defaultReadyFunc if all services are running then ready, else not
+func defaultReadyFunc(services map[string]ServiceStatus) bool {
+ if len(services) == 0 {
+ return false
+ }
+ for _, status := range services {
+ if status != ServiceStatusRunning {
+ return false
+ }
+ }
+ return true
+}
+
+// defaultHealthFunc if no service is stopped or failed, then healthy, else not.
+// service is start as unknown, so they are considered healthy
+func defaultHealthFunc(services map[string]ServiceStatus) bool {
+ if len(services) == 0 {
+ return false
+ }
+ for _, status := range services {
+ if status == ServiceStatusStopped || status == ServiceStatusFailed {
+ return false
+ }
+ }
+ return true
+}
diff --git a/vendor/github.com/opencord/voltha-lib-go/pkg/version/version.go b/vendor/github.com/opencord/voltha-lib-go/pkg/version/version.go
new file mode 100644
index 0000000..49c0b10
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/pkg/version/version.go
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package version
+
+import (
+ "fmt"
+ "strings"
+)
+
+// Default build-time variable.
+// These values can (should) be overridden via ldflags when built with
+// `make`
+var (
+ version = "unknown-version"
+ goVersion = "unknown-goversion"
+ vcsRef = "unknown-vcsref"
+ vcsDirty = "unknown-vcsdirty"
+ buildTime = "unknown-buildtime"
+ os = "unknown-os"
+ arch = "unknown-arch"
+)
+
+type VersionInfoType struct {
+ Version string `json:"version"`
+ GoVersion string `json:"goversion"`
+ VcsRef string `json:"vcsref"`
+ VcsDirty string `json:"vcsdirty"`
+ BuildTime string `json:"buildtime"`
+ Os string `json:"os"`
+ Arch string `json:"arch"`
+}
+
+var VersionInfo VersionInfoType
+
+func init() {
+ VersionInfo = VersionInfoType{
+ Version: version,
+ VcsRef: vcsRef,
+ VcsDirty: vcsDirty,
+ GoVersion: goVersion,
+ Os: os,
+ Arch: arch,
+ BuildTime: buildTime,
+ }
+}
+
+func (v VersionInfoType) String(indent string) string {
+ builder := strings.Builder{}
+
+ builder.WriteString(fmt.Sprintf("%sVersion: %s\n", indent, VersionInfo.Version))
+ builder.WriteString(fmt.Sprintf("%sGoVersion: %s\n", indent, VersionInfo.GoVersion))
+ builder.WriteString(fmt.Sprintf("%sVCS Ref: %s\n", indent, VersionInfo.VcsRef))
+ builder.WriteString(fmt.Sprintf("%sVCS Dirty: %s\n", indent, VersionInfo.VcsDirty))
+ builder.WriteString(fmt.Sprintf("%sBuilt: %s\n", indent, VersionInfo.BuildTime))
+ builder.WriteString(fmt.Sprintf("%sOS/Arch: %s/%s\n", indent, VersionInfo.Os, VersionInfo.Arch))
+ return builder.String()
+}
diff --git a/vendor/github.com/opencord/voltha-protos/go/tech_profile/tech_profile.pb.go b/vendor/github.com/opencord/voltha-protos/go/tech_profile/tech_profile.pb.go
deleted file mode 100644
index 4c6d4b3..0000000
--- a/vendor/github.com/opencord/voltha-protos/go/tech_profile/tech_profile.pb.go
+++ /dev/null
@@ -1,971 +0,0 @@
-// Code generated by protoc-gen-go. DO NOT EDIT.
-// source: voltha_protos/tech_profile.proto
-
-package tech_profile
-
-import (
- fmt "fmt"
- proto "github.com/golang/protobuf/proto"
- _ "google.golang.org/genproto/googleapis/api/annotations"
- math "math"
-)
-
-// Reference imports to suppress errors if they are not otherwise used.
-var _ = proto.Marshal
-var _ = fmt.Errorf
-var _ = math.Inf
-
-// This is a compile-time assertion to ensure that this generated file
-// is compatible with the proto package it is being compiled against.
-// A compilation error at this line likely means your copy of the
-// proto package needs to be updated.
-const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
-
-type Direction int32
-
-const (
- Direction_UPSTREAM Direction = 0
- Direction_DOWNSTREAM Direction = 1
- Direction_BIDIRECTIONAL Direction = 2
-)
-
-var Direction_name = map[int32]string{
- 0: "UPSTREAM",
- 1: "DOWNSTREAM",
- 2: "BIDIRECTIONAL",
-}
-
-var Direction_value = map[string]int32{
- "UPSTREAM": 0,
- "DOWNSTREAM": 1,
- "BIDIRECTIONAL": 2,
-}
-
-func (x Direction) String() string {
- return proto.EnumName(Direction_name, int32(x))
-}
-
-func (Direction) EnumDescriptor() ([]byte, []int) {
- return fileDescriptor_d019a68bffe14cae, []int{0}
-}
-
-type SchedulingPolicy int32
-
-const (
- SchedulingPolicy_WRR SchedulingPolicy = 0
- SchedulingPolicy_StrictPriority SchedulingPolicy = 1
- SchedulingPolicy_Hybrid SchedulingPolicy = 2
-)
-
-var SchedulingPolicy_name = map[int32]string{
- 0: "WRR",
- 1: "StrictPriority",
- 2: "Hybrid",
-}
-
-var SchedulingPolicy_value = map[string]int32{
- "WRR": 0,
- "StrictPriority": 1,
- "Hybrid": 2,
-}
-
-func (x SchedulingPolicy) String() string {
- return proto.EnumName(SchedulingPolicy_name, int32(x))
-}
-
-func (SchedulingPolicy) EnumDescriptor() ([]byte, []int) {
- return fileDescriptor_d019a68bffe14cae, []int{1}
-}
-
-type AdditionalBW int32
-
-const (
- AdditionalBW_AdditionalBW_None AdditionalBW = 0
- AdditionalBW_AdditionalBW_NA AdditionalBW = 1
- AdditionalBW_AdditionalBW_BestEffort AdditionalBW = 2
- AdditionalBW_AdditionalBW_Auto AdditionalBW = 3
-)
-
-var AdditionalBW_name = map[int32]string{
- 0: "AdditionalBW_None",
- 1: "AdditionalBW_NA",
- 2: "AdditionalBW_BestEffort",
- 3: "AdditionalBW_Auto",
-}
-
-var AdditionalBW_value = map[string]int32{
- "AdditionalBW_None": 0,
- "AdditionalBW_NA": 1,
- "AdditionalBW_BestEffort": 2,
- "AdditionalBW_Auto": 3,
-}
-
-func (x AdditionalBW) String() string {
- return proto.EnumName(AdditionalBW_name, int32(x))
-}
-
-func (AdditionalBW) EnumDescriptor() ([]byte, []int) {
- return fileDescriptor_d019a68bffe14cae, []int{2}
-}
-
-type DiscardPolicy int32
-
-const (
- DiscardPolicy_TailDrop DiscardPolicy = 0
- DiscardPolicy_WTailDrop DiscardPolicy = 1
- DiscardPolicy_Red DiscardPolicy = 2
- DiscardPolicy_WRed DiscardPolicy = 3
-)
-
-var DiscardPolicy_name = map[int32]string{
- 0: "TailDrop",
- 1: "WTailDrop",
- 2: "Red",
- 3: "WRed",
-}
-
-var DiscardPolicy_value = map[string]int32{
- "TailDrop": 0,
- "WTailDrop": 1,
- "Red": 2,
- "WRed": 3,
-}
-
-func (x DiscardPolicy) String() string {
- return proto.EnumName(DiscardPolicy_name, int32(x))
-}
-
-func (DiscardPolicy) EnumDescriptor() ([]byte, []int) {
- return fileDescriptor_d019a68bffe14cae, []int{3}
-}
-
-type InferredAdditionBWIndication int32
-
-const (
- InferredAdditionBWIndication_InferredAdditionBWIndication_None InferredAdditionBWIndication = 0
- InferredAdditionBWIndication_InferredAdditionBWIndication_Assured InferredAdditionBWIndication = 1
- InferredAdditionBWIndication_InferredAdditionBWIndication_BestEffort InferredAdditionBWIndication = 2
-)
-
-var InferredAdditionBWIndication_name = map[int32]string{
- 0: "InferredAdditionBWIndication_None",
- 1: "InferredAdditionBWIndication_Assured",
- 2: "InferredAdditionBWIndication_BestEffort",
-}
-
-var InferredAdditionBWIndication_value = map[string]int32{
- "InferredAdditionBWIndication_None": 0,
- "InferredAdditionBWIndication_Assured": 1,
- "InferredAdditionBWIndication_BestEffort": 2,
-}
-
-func (x InferredAdditionBWIndication) String() string {
- return proto.EnumName(InferredAdditionBWIndication_name, int32(x))
-}
-
-func (InferredAdditionBWIndication) EnumDescriptor() ([]byte, []int) {
- return fileDescriptor_d019a68bffe14cae, []int{4}
-}
-
-type SchedulerConfig struct {
- Direction Direction `protobuf:"varint,1,opt,name=direction,proto3,enum=tech_profile.Direction" json:"direction,omitempty"`
- AdditionalBw AdditionalBW `protobuf:"varint,2,opt,name=additional_bw,json=additionalBw,proto3,enum=tech_profile.AdditionalBW" json:"additional_bw,omitempty"`
- Priority uint32 `protobuf:"fixed32,3,opt,name=priority,proto3" json:"priority,omitempty"`
- Weight uint32 `protobuf:"fixed32,4,opt,name=weight,proto3" json:"weight,omitempty"`
- SchedPolicy SchedulingPolicy `protobuf:"varint,5,opt,name=sched_policy,json=schedPolicy,proto3,enum=tech_profile.SchedulingPolicy" json:"sched_policy,omitempty"`
- XXX_NoUnkeyedLiteral struct{} `json:"-"`
- XXX_unrecognized []byte `json:"-"`
- XXX_sizecache int32 `json:"-"`
-}
-
-func (m *SchedulerConfig) Reset() { *m = SchedulerConfig{} }
-func (m *SchedulerConfig) String() string { return proto.CompactTextString(m) }
-func (*SchedulerConfig) ProtoMessage() {}
-func (*SchedulerConfig) Descriptor() ([]byte, []int) {
- return fileDescriptor_d019a68bffe14cae, []int{0}
-}
-
-func (m *SchedulerConfig) XXX_Unmarshal(b []byte) error {
- return xxx_messageInfo_SchedulerConfig.Unmarshal(m, b)
-}
-func (m *SchedulerConfig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
- return xxx_messageInfo_SchedulerConfig.Marshal(b, m, deterministic)
-}
-func (m *SchedulerConfig) XXX_Merge(src proto.Message) {
- xxx_messageInfo_SchedulerConfig.Merge(m, src)
-}
-func (m *SchedulerConfig) XXX_Size() int {
- return xxx_messageInfo_SchedulerConfig.Size(m)
-}
-func (m *SchedulerConfig) XXX_DiscardUnknown() {
- xxx_messageInfo_SchedulerConfig.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_SchedulerConfig proto.InternalMessageInfo
-
-func (m *SchedulerConfig) GetDirection() Direction {
- if m != nil {
- return m.Direction
- }
- return Direction_UPSTREAM
-}
-
-func (m *SchedulerConfig) GetAdditionalBw() AdditionalBW {
- if m != nil {
- return m.AdditionalBw
- }
- return AdditionalBW_AdditionalBW_None
-}
-
-func (m *SchedulerConfig) GetPriority() uint32 {
- if m != nil {
- return m.Priority
- }
- return 0
-}
-
-func (m *SchedulerConfig) GetWeight() uint32 {
- if m != nil {
- return m.Weight
- }
- return 0
-}
-
-func (m *SchedulerConfig) GetSchedPolicy() SchedulingPolicy {
- if m != nil {
- return m.SchedPolicy
- }
- return SchedulingPolicy_WRR
-}
-
-type TrafficShapingInfo struct {
- Cir uint32 `protobuf:"fixed32,1,opt,name=cir,proto3" json:"cir,omitempty"`
- Cbs uint32 `protobuf:"fixed32,2,opt,name=cbs,proto3" json:"cbs,omitempty"`
- Pir uint32 `protobuf:"fixed32,3,opt,name=pir,proto3" json:"pir,omitempty"`
- Pbs uint32 `protobuf:"fixed32,4,opt,name=pbs,proto3" json:"pbs,omitempty"`
- Gir uint32 `protobuf:"fixed32,5,opt,name=gir,proto3" json:"gir,omitempty"`
- AddBwInd InferredAdditionBWIndication `protobuf:"varint,6,opt,name=add_bw_ind,json=addBwInd,proto3,enum=tech_profile.InferredAdditionBWIndication" json:"add_bw_ind,omitempty"`
- XXX_NoUnkeyedLiteral struct{} `json:"-"`
- XXX_unrecognized []byte `json:"-"`
- XXX_sizecache int32 `json:"-"`
-}
-
-func (m *TrafficShapingInfo) Reset() { *m = TrafficShapingInfo{} }
-func (m *TrafficShapingInfo) String() string { return proto.CompactTextString(m) }
-func (*TrafficShapingInfo) ProtoMessage() {}
-func (*TrafficShapingInfo) Descriptor() ([]byte, []int) {
- return fileDescriptor_d019a68bffe14cae, []int{1}
-}
-
-func (m *TrafficShapingInfo) XXX_Unmarshal(b []byte) error {
- return xxx_messageInfo_TrafficShapingInfo.Unmarshal(m, b)
-}
-func (m *TrafficShapingInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
- return xxx_messageInfo_TrafficShapingInfo.Marshal(b, m, deterministic)
-}
-func (m *TrafficShapingInfo) XXX_Merge(src proto.Message) {
- xxx_messageInfo_TrafficShapingInfo.Merge(m, src)
-}
-func (m *TrafficShapingInfo) XXX_Size() int {
- return xxx_messageInfo_TrafficShapingInfo.Size(m)
-}
-func (m *TrafficShapingInfo) XXX_DiscardUnknown() {
- xxx_messageInfo_TrafficShapingInfo.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_TrafficShapingInfo proto.InternalMessageInfo
-
-func (m *TrafficShapingInfo) GetCir() uint32 {
- if m != nil {
- return m.Cir
- }
- return 0
-}
-
-func (m *TrafficShapingInfo) GetCbs() uint32 {
- if m != nil {
- return m.Cbs
- }
- return 0
-}
-
-func (m *TrafficShapingInfo) GetPir() uint32 {
- if m != nil {
- return m.Pir
- }
- return 0
-}
-
-func (m *TrafficShapingInfo) GetPbs() uint32 {
- if m != nil {
- return m.Pbs
- }
- return 0
-}
-
-func (m *TrafficShapingInfo) GetGir() uint32 {
- if m != nil {
- return m.Gir
- }
- return 0
-}
-
-func (m *TrafficShapingInfo) GetAddBwInd() InferredAdditionBWIndication {
- if m != nil {
- return m.AddBwInd
- }
- return InferredAdditionBWIndication_InferredAdditionBWIndication_None
-}
-
-type TrafficScheduler struct {
- Direction Direction `protobuf:"varint,1,opt,name=direction,proto3,enum=tech_profile.Direction" json:"direction,omitempty"`
- AllocId uint32 `protobuf:"fixed32,2,opt,name=alloc_id,json=allocId,proto3" json:"alloc_id,omitempty"`
- Scheduler *SchedulerConfig `protobuf:"bytes,3,opt,name=scheduler,proto3" json:"scheduler,omitempty"`
- TrafficShapingInfo *TrafficShapingInfo `protobuf:"bytes,4,opt,name=traffic_shaping_info,json=trafficShapingInfo,proto3" json:"traffic_shaping_info,omitempty"`
- XXX_NoUnkeyedLiteral struct{} `json:"-"`
- XXX_unrecognized []byte `json:"-"`
- XXX_sizecache int32 `json:"-"`
-}
-
-func (m *TrafficScheduler) Reset() { *m = TrafficScheduler{} }
-func (m *TrafficScheduler) String() string { return proto.CompactTextString(m) }
-func (*TrafficScheduler) ProtoMessage() {}
-func (*TrafficScheduler) Descriptor() ([]byte, []int) {
- return fileDescriptor_d019a68bffe14cae, []int{2}
-}
-
-func (m *TrafficScheduler) XXX_Unmarshal(b []byte) error {
- return xxx_messageInfo_TrafficScheduler.Unmarshal(m, b)
-}
-func (m *TrafficScheduler) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
- return xxx_messageInfo_TrafficScheduler.Marshal(b, m, deterministic)
-}
-func (m *TrafficScheduler) XXX_Merge(src proto.Message) {
- xxx_messageInfo_TrafficScheduler.Merge(m, src)
-}
-func (m *TrafficScheduler) XXX_Size() int {
- return xxx_messageInfo_TrafficScheduler.Size(m)
-}
-func (m *TrafficScheduler) XXX_DiscardUnknown() {
- xxx_messageInfo_TrafficScheduler.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_TrafficScheduler proto.InternalMessageInfo
-
-func (m *TrafficScheduler) GetDirection() Direction {
- if m != nil {
- return m.Direction
- }
- return Direction_UPSTREAM
-}
-
-func (m *TrafficScheduler) GetAllocId() uint32 {
- if m != nil {
- return m.AllocId
- }
- return 0
-}
-
-func (m *TrafficScheduler) GetScheduler() *SchedulerConfig {
- if m != nil {
- return m.Scheduler
- }
- return nil
-}
-
-func (m *TrafficScheduler) GetTrafficShapingInfo() *TrafficShapingInfo {
- if m != nil {
- return m.TrafficShapingInfo
- }
- return nil
-}
-
-type TrafficSchedulers struct {
- IntfId uint32 `protobuf:"fixed32,1,opt,name=intf_id,json=intfId,proto3" json:"intf_id,omitempty"`
- OnuId uint32 `protobuf:"fixed32,2,opt,name=onu_id,json=onuId,proto3" json:"onu_id,omitempty"`
- UniId uint32 `protobuf:"fixed32,4,opt,name=uni_id,json=uniId,proto3" json:"uni_id,omitempty"`
- PortNo uint32 `protobuf:"fixed32,5,opt,name=port_no,json=portNo,proto3" json:"port_no,omitempty"`
- TrafficScheds []*TrafficScheduler `protobuf:"bytes,3,rep,name=traffic_scheds,json=trafficScheds,proto3" json:"traffic_scheds,omitempty"`
- XXX_NoUnkeyedLiteral struct{} `json:"-"`
- XXX_unrecognized []byte `json:"-"`
- XXX_sizecache int32 `json:"-"`
-}
-
-func (m *TrafficSchedulers) Reset() { *m = TrafficSchedulers{} }
-func (m *TrafficSchedulers) String() string { return proto.CompactTextString(m) }
-func (*TrafficSchedulers) ProtoMessage() {}
-func (*TrafficSchedulers) Descriptor() ([]byte, []int) {
- return fileDescriptor_d019a68bffe14cae, []int{3}
-}
-
-func (m *TrafficSchedulers) XXX_Unmarshal(b []byte) error {
- return xxx_messageInfo_TrafficSchedulers.Unmarshal(m, b)
-}
-func (m *TrafficSchedulers) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
- return xxx_messageInfo_TrafficSchedulers.Marshal(b, m, deterministic)
-}
-func (m *TrafficSchedulers) XXX_Merge(src proto.Message) {
- xxx_messageInfo_TrafficSchedulers.Merge(m, src)
-}
-func (m *TrafficSchedulers) XXX_Size() int {
- return xxx_messageInfo_TrafficSchedulers.Size(m)
-}
-func (m *TrafficSchedulers) XXX_DiscardUnknown() {
- xxx_messageInfo_TrafficSchedulers.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_TrafficSchedulers proto.InternalMessageInfo
-
-func (m *TrafficSchedulers) GetIntfId() uint32 {
- if m != nil {
- return m.IntfId
- }
- return 0
-}
-
-func (m *TrafficSchedulers) GetOnuId() uint32 {
- if m != nil {
- return m.OnuId
- }
- return 0
-}
-
-func (m *TrafficSchedulers) GetUniId() uint32 {
- if m != nil {
- return m.UniId
- }
- return 0
-}
-
-func (m *TrafficSchedulers) GetPortNo() uint32 {
- if m != nil {
- return m.PortNo
- }
- return 0
-}
-
-func (m *TrafficSchedulers) GetTrafficScheds() []*TrafficScheduler {
- if m != nil {
- return m.TrafficScheds
- }
- return nil
-}
-
-type TailDropDiscardConfig struct {
- QueueSize uint32 `protobuf:"fixed32,1,opt,name=queue_size,json=queueSize,proto3" json:"queue_size,omitempty"`
- XXX_NoUnkeyedLiteral struct{} `json:"-"`
- XXX_unrecognized []byte `json:"-"`
- XXX_sizecache int32 `json:"-"`
-}
-
-func (m *TailDropDiscardConfig) Reset() { *m = TailDropDiscardConfig{} }
-func (m *TailDropDiscardConfig) String() string { return proto.CompactTextString(m) }
-func (*TailDropDiscardConfig) ProtoMessage() {}
-func (*TailDropDiscardConfig) Descriptor() ([]byte, []int) {
- return fileDescriptor_d019a68bffe14cae, []int{4}
-}
-
-func (m *TailDropDiscardConfig) XXX_Unmarshal(b []byte) error {
- return xxx_messageInfo_TailDropDiscardConfig.Unmarshal(m, b)
-}
-func (m *TailDropDiscardConfig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
- return xxx_messageInfo_TailDropDiscardConfig.Marshal(b, m, deterministic)
-}
-func (m *TailDropDiscardConfig) XXX_Merge(src proto.Message) {
- xxx_messageInfo_TailDropDiscardConfig.Merge(m, src)
-}
-func (m *TailDropDiscardConfig) XXX_Size() int {
- return xxx_messageInfo_TailDropDiscardConfig.Size(m)
-}
-func (m *TailDropDiscardConfig) XXX_DiscardUnknown() {
- xxx_messageInfo_TailDropDiscardConfig.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_TailDropDiscardConfig proto.InternalMessageInfo
-
-func (m *TailDropDiscardConfig) GetQueueSize() uint32 {
- if m != nil {
- return m.QueueSize
- }
- return 0
-}
-
-type RedDiscardConfig struct {
- MinThreshold uint32 `protobuf:"fixed32,1,opt,name=min_threshold,json=minThreshold,proto3" json:"min_threshold,omitempty"`
- MaxThreshold uint32 `protobuf:"fixed32,2,opt,name=max_threshold,json=maxThreshold,proto3" json:"max_threshold,omitempty"`
- MaxProbability uint32 `protobuf:"fixed32,3,opt,name=max_probability,json=maxProbability,proto3" json:"max_probability,omitempty"`
- XXX_NoUnkeyedLiteral struct{} `json:"-"`
- XXX_unrecognized []byte `json:"-"`
- XXX_sizecache int32 `json:"-"`
-}
-
-func (m *RedDiscardConfig) Reset() { *m = RedDiscardConfig{} }
-func (m *RedDiscardConfig) String() string { return proto.CompactTextString(m) }
-func (*RedDiscardConfig) ProtoMessage() {}
-func (*RedDiscardConfig) Descriptor() ([]byte, []int) {
- return fileDescriptor_d019a68bffe14cae, []int{5}
-}
-
-func (m *RedDiscardConfig) XXX_Unmarshal(b []byte) error {
- return xxx_messageInfo_RedDiscardConfig.Unmarshal(m, b)
-}
-func (m *RedDiscardConfig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
- return xxx_messageInfo_RedDiscardConfig.Marshal(b, m, deterministic)
-}
-func (m *RedDiscardConfig) XXX_Merge(src proto.Message) {
- xxx_messageInfo_RedDiscardConfig.Merge(m, src)
-}
-func (m *RedDiscardConfig) XXX_Size() int {
- return xxx_messageInfo_RedDiscardConfig.Size(m)
-}
-func (m *RedDiscardConfig) XXX_DiscardUnknown() {
- xxx_messageInfo_RedDiscardConfig.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_RedDiscardConfig proto.InternalMessageInfo
-
-func (m *RedDiscardConfig) GetMinThreshold() uint32 {
- if m != nil {
- return m.MinThreshold
- }
- return 0
-}
-
-func (m *RedDiscardConfig) GetMaxThreshold() uint32 {
- if m != nil {
- return m.MaxThreshold
- }
- return 0
-}
-
-func (m *RedDiscardConfig) GetMaxProbability() uint32 {
- if m != nil {
- return m.MaxProbability
- }
- return 0
-}
-
-type WRedDiscardConfig struct {
- Green *RedDiscardConfig `protobuf:"bytes,1,opt,name=green,proto3" json:"green,omitempty"`
- Yellow *RedDiscardConfig `protobuf:"bytes,2,opt,name=yellow,proto3" json:"yellow,omitempty"`
- Red *RedDiscardConfig `protobuf:"bytes,3,opt,name=red,proto3" json:"red,omitempty"`
- XXX_NoUnkeyedLiteral struct{} `json:"-"`
- XXX_unrecognized []byte `json:"-"`
- XXX_sizecache int32 `json:"-"`
-}
-
-func (m *WRedDiscardConfig) Reset() { *m = WRedDiscardConfig{} }
-func (m *WRedDiscardConfig) String() string { return proto.CompactTextString(m) }
-func (*WRedDiscardConfig) ProtoMessage() {}
-func (*WRedDiscardConfig) Descriptor() ([]byte, []int) {
- return fileDescriptor_d019a68bffe14cae, []int{6}
-}
-
-func (m *WRedDiscardConfig) XXX_Unmarshal(b []byte) error {
- return xxx_messageInfo_WRedDiscardConfig.Unmarshal(m, b)
-}
-func (m *WRedDiscardConfig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
- return xxx_messageInfo_WRedDiscardConfig.Marshal(b, m, deterministic)
-}
-func (m *WRedDiscardConfig) XXX_Merge(src proto.Message) {
- xxx_messageInfo_WRedDiscardConfig.Merge(m, src)
-}
-func (m *WRedDiscardConfig) XXX_Size() int {
- return xxx_messageInfo_WRedDiscardConfig.Size(m)
-}
-func (m *WRedDiscardConfig) XXX_DiscardUnknown() {
- xxx_messageInfo_WRedDiscardConfig.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_WRedDiscardConfig proto.InternalMessageInfo
-
-func (m *WRedDiscardConfig) GetGreen() *RedDiscardConfig {
- if m != nil {
- return m.Green
- }
- return nil
-}
-
-func (m *WRedDiscardConfig) GetYellow() *RedDiscardConfig {
- if m != nil {
- return m.Yellow
- }
- return nil
-}
-
-func (m *WRedDiscardConfig) GetRed() *RedDiscardConfig {
- if m != nil {
- return m.Red
- }
- return nil
-}
-
-type DiscardConfig struct {
- DiscardPolicy DiscardPolicy `protobuf:"varint,1,opt,name=discard_policy,json=discardPolicy,proto3,enum=tech_profile.DiscardPolicy" json:"discard_policy,omitempty"`
- // Types that are valid to be assigned to DiscardConfig:
- // *DiscardConfig_TailDropDiscardConfig
- // *DiscardConfig_RedDiscardConfig
- // *DiscardConfig_WredDiscardConfig
- DiscardConfig isDiscardConfig_DiscardConfig `protobuf_oneof:"discard_config"`
- XXX_NoUnkeyedLiteral struct{} `json:"-"`
- XXX_unrecognized []byte `json:"-"`
- XXX_sizecache int32 `json:"-"`
-}
-
-func (m *DiscardConfig) Reset() { *m = DiscardConfig{} }
-func (m *DiscardConfig) String() string { return proto.CompactTextString(m) }
-func (*DiscardConfig) ProtoMessage() {}
-func (*DiscardConfig) Descriptor() ([]byte, []int) {
- return fileDescriptor_d019a68bffe14cae, []int{7}
-}
-
-func (m *DiscardConfig) XXX_Unmarshal(b []byte) error {
- return xxx_messageInfo_DiscardConfig.Unmarshal(m, b)
-}
-func (m *DiscardConfig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
- return xxx_messageInfo_DiscardConfig.Marshal(b, m, deterministic)
-}
-func (m *DiscardConfig) XXX_Merge(src proto.Message) {
- xxx_messageInfo_DiscardConfig.Merge(m, src)
-}
-func (m *DiscardConfig) XXX_Size() int {
- return xxx_messageInfo_DiscardConfig.Size(m)
-}
-func (m *DiscardConfig) XXX_DiscardUnknown() {
- xxx_messageInfo_DiscardConfig.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_DiscardConfig proto.InternalMessageInfo
-
-func (m *DiscardConfig) GetDiscardPolicy() DiscardPolicy {
- if m != nil {
- return m.DiscardPolicy
- }
- return DiscardPolicy_TailDrop
-}
-
-type isDiscardConfig_DiscardConfig interface {
- isDiscardConfig_DiscardConfig()
-}
-
-type DiscardConfig_TailDropDiscardConfig struct {
- TailDropDiscardConfig *TailDropDiscardConfig `protobuf:"bytes,2,opt,name=tail_drop_discard_config,json=tailDropDiscardConfig,proto3,oneof"`
-}
-
-type DiscardConfig_RedDiscardConfig struct {
- RedDiscardConfig *RedDiscardConfig `protobuf:"bytes,3,opt,name=red_discard_config,json=redDiscardConfig,proto3,oneof"`
-}
-
-type DiscardConfig_WredDiscardConfig struct {
- WredDiscardConfig *WRedDiscardConfig `protobuf:"bytes,4,opt,name=wred_discard_config,json=wredDiscardConfig,proto3,oneof"`
-}
-
-func (*DiscardConfig_TailDropDiscardConfig) isDiscardConfig_DiscardConfig() {}
-
-func (*DiscardConfig_RedDiscardConfig) isDiscardConfig_DiscardConfig() {}
-
-func (*DiscardConfig_WredDiscardConfig) isDiscardConfig_DiscardConfig() {}
-
-func (m *DiscardConfig) GetDiscardConfig() isDiscardConfig_DiscardConfig {
- if m != nil {
- return m.DiscardConfig
- }
- return nil
-}
-
-func (m *DiscardConfig) GetTailDropDiscardConfig() *TailDropDiscardConfig {
- if x, ok := m.GetDiscardConfig().(*DiscardConfig_TailDropDiscardConfig); ok {
- return x.TailDropDiscardConfig
- }
- return nil
-}
-
-func (m *DiscardConfig) GetRedDiscardConfig() *RedDiscardConfig {
- if x, ok := m.GetDiscardConfig().(*DiscardConfig_RedDiscardConfig); ok {
- return x.RedDiscardConfig
- }
- return nil
-}
-
-func (m *DiscardConfig) GetWredDiscardConfig() *WRedDiscardConfig {
- if x, ok := m.GetDiscardConfig().(*DiscardConfig_WredDiscardConfig); ok {
- return x.WredDiscardConfig
- }
- return nil
-}
-
-// XXX_OneofWrappers is for the internal use of the proto package.
-func (*DiscardConfig) XXX_OneofWrappers() []interface{} {
- return []interface{}{
- (*DiscardConfig_TailDropDiscardConfig)(nil),
- (*DiscardConfig_RedDiscardConfig)(nil),
- (*DiscardConfig_WredDiscardConfig)(nil),
- }
-}
-
-type TrafficQueue struct {
- Direction Direction `protobuf:"varint,1,opt,name=direction,proto3,enum=tech_profile.Direction" json:"direction,omitempty"`
- GemportId uint32 `protobuf:"fixed32,2,opt,name=gemport_id,json=gemportId,proto3" json:"gemport_id,omitempty"`
- PbitMap string `protobuf:"bytes,3,opt,name=pbit_map,json=pbitMap,proto3" json:"pbit_map,omitempty"`
- AesEncryption bool `protobuf:"varint,4,opt,name=aes_encryption,json=aesEncryption,proto3" json:"aes_encryption,omitempty"`
- SchedPolicy SchedulingPolicy `protobuf:"varint,5,opt,name=sched_policy,json=schedPolicy,proto3,enum=tech_profile.SchedulingPolicy" json:"sched_policy,omitempty"`
- Priority uint32 `protobuf:"fixed32,6,opt,name=priority,proto3" json:"priority,omitempty"`
- Weight uint32 `protobuf:"fixed32,7,opt,name=weight,proto3" json:"weight,omitempty"`
- DiscardPolicy DiscardPolicy `protobuf:"varint,8,opt,name=discard_policy,json=discardPolicy,proto3,enum=tech_profile.DiscardPolicy" json:"discard_policy,omitempty"`
- DiscardConfig *DiscardConfig `protobuf:"bytes,9,opt,name=discard_config,json=discardConfig,proto3" json:"discard_config,omitempty"`
- XXX_NoUnkeyedLiteral struct{} `json:"-"`
- XXX_unrecognized []byte `json:"-"`
- XXX_sizecache int32 `json:"-"`
-}
-
-func (m *TrafficQueue) Reset() { *m = TrafficQueue{} }
-func (m *TrafficQueue) String() string { return proto.CompactTextString(m) }
-func (*TrafficQueue) ProtoMessage() {}
-func (*TrafficQueue) Descriptor() ([]byte, []int) {
- return fileDescriptor_d019a68bffe14cae, []int{8}
-}
-
-func (m *TrafficQueue) XXX_Unmarshal(b []byte) error {
- return xxx_messageInfo_TrafficQueue.Unmarshal(m, b)
-}
-func (m *TrafficQueue) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
- return xxx_messageInfo_TrafficQueue.Marshal(b, m, deterministic)
-}
-func (m *TrafficQueue) XXX_Merge(src proto.Message) {
- xxx_messageInfo_TrafficQueue.Merge(m, src)
-}
-func (m *TrafficQueue) XXX_Size() int {
- return xxx_messageInfo_TrafficQueue.Size(m)
-}
-func (m *TrafficQueue) XXX_DiscardUnknown() {
- xxx_messageInfo_TrafficQueue.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_TrafficQueue proto.InternalMessageInfo
-
-func (m *TrafficQueue) GetDirection() Direction {
- if m != nil {
- return m.Direction
- }
- return Direction_UPSTREAM
-}
-
-func (m *TrafficQueue) GetGemportId() uint32 {
- if m != nil {
- return m.GemportId
- }
- return 0
-}
-
-func (m *TrafficQueue) GetPbitMap() string {
- if m != nil {
- return m.PbitMap
- }
- return ""
-}
-
-func (m *TrafficQueue) GetAesEncryption() bool {
- if m != nil {
- return m.AesEncryption
- }
- return false
-}
-
-func (m *TrafficQueue) GetSchedPolicy() SchedulingPolicy {
- if m != nil {
- return m.SchedPolicy
- }
- return SchedulingPolicy_WRR
-}
-
-func (m *TrafficQueue) GetPriority() uint32 {
- if m != nil {
- return m.Priority
- }
- return 0
-}
-
-func (m *TrafficQueue) GetWeight() uint32 {
- if m != nil {
- return m.Weight
- }
- return 0
-}
-
-func (m *TrafficQueue) GetDiscardPolicy() DiscardPolicy {
- if m != nil {
- return m.DiscardPolicy
- }
- return DiscardPolicy_TailDrop
-}
-
-func (m *TrafficQueue) GetDiscardConfig() *DiscardConfig {
- if m != nil {
- return m.DiscardConfig
- }
- return nil
-}
-
-type TrafficQueues struct {
- IntfId uint32 `protobuf:"fixed32,1,opt,name=intf_id,json=intfId,proto3" json:"intf_id,omitempty"`
- OnuId uint32 `protobuf:"fixed32,2,opt,name=onu_id,json=onuId,proto3" json:"onu_id,omitempty"`
- UniId uint32 `protobuf:"fixed32,4,opt,name=uni_id,json=uniId,proto3" json:"uni_id,omitempty"`
- PortNo uint32 `protobuf:"fixed32,5,opt,name=port_no,json=portNo,proto3" json:"port_no,omitempty"`
- TrafficQueues []*TrafficQueue `protobuf:"bytes,6,rep,name=traffic_queues,json=trafficQueues,proto3" json:"traffic_queues,omitempty"`
- XXX_NoUnkeyedLiteral struct{} `json:"-"`
- XXX_unrecognized []byte `json:"-"`
- XXX_sizecache int32 `json:"-"`
-}
-
-func (m *TrafficQueues) Reset() { *m = TrafficQueues{} }
-func (m *TrafficQueues) String() string { return proto.CompactTextString(m) }
-func (*TrafficQueues) ProtoMessage() {}
-func (*TrafficQueues) Descriptor() ([]byte, []int) {
- return fileDescriptor_d019a68bffe14cae, []int{9}
-}
-
-func (m *TrafficQueues) XXX_Unmarshal(b []byte) error {
- return xxx_messageInfo_TrafficQueues.Unmarshal(m, b)
-}
-func (m *TrafficQueues) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
- return xxx_messageInfo_TrafficQueues.Marshal(b, m, deterministic)
-}
-func (m *TrafficQueues) XXX_Merge(src proto.Message) {
- xxx_messageInfo_TrafficQueues.Merge(m, src)
-}
-func (m *TrafficQueues) XXX_Size() int {
- return xxx_messageInfo_TrafficQueues.Size(m)
-}
-func (m *TrafficQueues) XXX_DiscardUnknown() {
- xxx_messageInfo_TrafficQueues.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_TrafficQueues proto.InternalMessageInfo
-
-func (m *TrafficQueues) GetIntfId() uint32 {
- if m != nil {
- return m.IntfId
- }
- return 0
-}
-
-func (m *TrafficQueues) GetOnuId() uint32 {
- if m != nil {
- return m.OnuId
- }
- return 0
-}
-
-func (m *TrafficQueues) GetUniId() uint32 {
- if m != nil {
- return m.UniId
- }
- return 0
-}
-
-func (m *TrafficQueues) GetPortNo() uint32 {
- if m != nil {
- return m.PortNo
- }
- return 0
-}
-
-func (m *TrafficQueues) GetTrafficQueues() []*TrafficQueue {
- if m != nil {
- return m.TrafficQueues
- }
- return nil
-}
-
-func init() {
- proto.RegisterEnum("tech_profile.Direction", Direction_name, Direction_value)
- proto.RegisterEnum("tech_profile.SchedulingPolicy", SchedulingPolicy_name, SchedulingPolicy_value)
- proto.RegisterEnum("tech_profile.AdditionalBW", AdditionalBW_name, AdditionalBW_value)
- proto.RegisterEnum("tech_profile.DiscardPolicy", DiscardPolicy_name, DiscardPolicy_value)
- proto.RegisterEnum("tech_profile.InferredAdditionBWIndication", InferredAdditionBWIndication_name, InferredAdditionBWIndication_value)
- proto.RegisterType((*SchedulerConfig)(nil), "tech_profile.SchedulerConfig")
- proto.RegisterType((*TrafficShapingInfo)(nil), "tech_profile.TrafficShapingInfo")
- proto.RegisterType((*TrafficScheduler)(nil), "tech_profile.TrafficScheduler")
- proto.RegisterType((*TrafficSchedulers)(nil), "tech_profile.TrafficSchedulers")
- proto.RegisterType((*TailDropDiscardConfig)(nil), "tech_profile.TailDropDiscardConfig")
- proto.RegisterType((*RedDiscardConfig)(nil), "tech_profile.RedDiscardConfig")
- proto.RegisterType((*WRedDiscardConfig)(nil), "tech_profile.WRedDiscardConfig")
- proto.RegisterType((*DiscardConfig)(nil), "tech_profile.DiscardConfig")
- proto.RegisterType((*TrafficQueue)(nil), "tech_profile.TrafficQueue")
- proto.RegisterType((*TrafficQueues)(nil), "tech_profile.TrafficQueues")
-}
-
-func init() { proto.RegisterFile("voltha_protos/tech_profile.proto", fileDescriptor_d019a68bffe14cae) }
-
-var fileDescriptor_d019a68bffe14cae = []byte{
- // 1103 bytes of a gzipped FileDescriptorProto
- 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x56, 0xdd, 0x6e, 0x1b, 0x45,
- 0x14, 0xf6, 0xda, 0x8d, 0x7f, 0x4e, 0x6c, 0x77, 0x33, 0x25, 0xd4, 0xa4, 0x0d, 0x04, 0x97, 0xaa,
- 0x91, 0x11, 0x09, 0xa4, 0xd0, 0x9b, 0x22, 0x55, 0x76, 0x13, 0x29, 0x96, 0x68, 0x9a, 0x4e, 0x82,
- 0x2c, 0x71, 0xc1, 0x6a, 0xbc, 0x33, 0xb6, 0x47, 0x5a, 0xcf, 0x2c, 0xb3, 0x63, 0x9c, 0xf4, 0x8a,
- 0x1b, 0xde, 0x82, 0x5b, 0x5e, 0x00, 0x6e, 0x10, 0x4f, 0xc4, 0x0b, 0x70, 0x8f, 0x66, 0x76, 0xd7,
- 0xf6, 0xda, 0x26, 0x85, 0x0a, 0xee, 0xe6, 0x7c, 0xfb, 0xcd, 0x99, 0xf3, 0xcd, 0xf9, 0xd9, 0x81,
- 0xbd, 0xef, 0x65, 0xa0, 0x47, 0xc4, 0x0b, 0x95, 0xd4, 0x32, 0x3a, 0xd4, 0xcc, 0x1f, 0x99, 0xf5,
- 0x80, 0x07, 0xec, 0xc0, 0x62, 0xa8, 0xba, 0x88, 0xed, 0xdc, 0x1f, 0x4a, 0x39, 0x0c, 0xd8, 0x21,
- 0x09, 0xf9, 0x21, 0x11, 0x42, 0x6a, 0xa2, 0xb9, 0x14, 0x51, 0xcc, 0x6d, 0xfe, 0x90, 0x87, 0xdb,
- 0x17, 0xfe, 0x88, 0xd1, 0x49, 0xc0, 0xd4, 0x73, 0x29, 0x06, 0x7c, 0x88, 0xbe, 0x80, 0x0a, 0xe5,
- 0x8a, 0xf9, 0x86, 0xd7, 0x70, 0xf6, 0x9c, 0xfd, 0xfa, 0xd1, 0xdd, 0x83, 0xcc, 0x39, 0xc7, 0xe9,
- 0x67, 0x3c, 0x67, 0xa2, 0x67, 0x50, 0x23, 0x94, 0x72, 0xb3, 0x26, 0x81, 0xd7, 0x9f, 0x36, 0xf2,
- 0x76, 0xeb, 0x4e, 0x76, 0x6b, 0x7b, 0x46, 0xe9, 0xf4, 0x70, 0x75, 0xbe, 0xa1, 0x33, 0x45, 0x3b,
- 0x50, 0x0e, 0x15, 0x97, 0x8a, 0xeb, 0xeb, 0x46, 0x61, 0xcf, 0xd9, 0x2f, 0xe1, 0x99, 0x8d, 0xde,
- 0x85, 0xe2, 0x94, 0xf1, 0xe1, 0x48, 0x37, 0x6e, 0xd9, 0x2f, 0x89, 0x85, 0xda, 0x50, 0x8d, 0x4c,
- 0xf8, 0x5e, 0x28, 0x03, 0xee, 0x5f, 0x37, 0x36, 0xec, 0x99, 0xef, 0x67, 0xcf, 0x4c, 0x04, 0x72,
- 0x31, 0x3c, 0xb7, 0x2c, 0xbc, 0x69, 0xf7, 0xc4, 0x46, 0xf3, 0x37, 0x07, 0xd0, 0xa5, 0x22, 0x83,
- 0x01, 0xf7, 0x2f, 0x46, 0x24, 0xe4, 0x62, 0xd8, 0x15, 0x03, 0x89, 0x5c, 0x28, 0xf8, 0x5c, 0x59,
- 0xfd, 0x25, 0x6c, 0x96, 0x16, 0xe9, 0x47, 0x56, 0x96, 0x41, 0xfa, 0x91, 0x41, 0x42, 0xae, 0x92,
- 0x60, 0xcd, 0xd2, 0x22, 0xfd, 0x28, 0x09, 0xd2, 0x2c, 0x0d, 0x32, 0xe4, 0xca, 0x06, 0x56, 0xc2,
- 0x66, 0x89, 0x4e, 0x01, 0x08, 0xa5, 0x5e, 0x7f, 0xea, 0x71, 0x41, 0x1b, 0x45, 0x1b, 0x71, 0x2b,
- 0x1b, 0x71, 0x57, 0x0c, 0x98, 0x52, 0x8c, 0xa6, 0xb7, 0xd5, 0xe9, 0x75, 0x05, 0xe5, 0xbe, 0x4d,
- 0x1d, 0x2e, 0x13, 0x4a, 0x3b, 0xd3, 0xae, 0xa0, 0xcd, 0x3f, 0x1d, 0x70, 0xd3, 0xd0, 0xd3, 0x24,
- 0xbe, 0x6d, 0xfa, 0xde, 0x83, 0x32, 0x09, 0x02, 0xe9, 0x7b, 0x9c, 0x26, 0x12, 0x4b, 0xd6, 0xee,
- 0x52, 0xf4, 0x14, 0x2a, 0x51, 0xea, 0xde, 0x8a, 0xdd, 0x3c, 0xda, 0x5d, 0x7b, 0xc3, 0x69, 0x09,
- 0xe1, 0x39, 0x1f, 0x61, 0x78, 0x47, 0xc7, 0x21, 0x7a, 0x51, 0x7c, 0xbd, 0x1e, 0x17, 0x03, 0x69,
- 0xaf, 0x68, 0xf3, 0x68, 0x2f, 0xeb, 0x67, 0x35, 0x0f, 0x18, 0xe9, 0x15, 0xac, 0xf9, 0xbb, 0x03,
- 0x5b, 0xcb, 0xba, 0x23, 0x74, 0x17, 0x4a, 0x5c, 0xe8, 0x81, 0x11, 0x10, 0x67, 0xad, 0x68, 0xcc,
- 0x2e, 0x45, 0xdb, 0x50, 0x94, 0x62, 0x32, 0x17, 0xb6, 0x21, 0xc5, 0x24, 0x86, 0x27, 0x82, 0x1b,
- 0x38, 0x4e, 0xd7, 0xc6, 0x44, 0xf0, 0x2e, 0x35, 0x6e, 0x42, 0xa9, 0xb4, 0x27, 0x64, 0x92, 0xb4,
- 0xa2, 0x31, 0xcf, 0x24, 0x3a, 0x81, 0xfa, 0x4c, 0x89, 0x39, 0x35, 0x6a, 0x14, 0xf6, 0x0a, 0xfb,
- 0x9b, 0xcb, 0xd5, 0xb6, 0x1c, 0x18, 0xae, 0xe9, 0x05, 0x24, 0x6a, 0x3e, 0x81, 0xed, 0x4b, 0xc2,
- 0x83, 0x63, 0x25, 0xc3, 0x63, 0x1e, 0xf9, 0x44, 0xd1, 0xa4, 0xef, 0x76, 0x01, 0xbe, 0x9b, 0xb0,
- 0x09, 0xf3, 0x22, 0xfe, 0x9a, 0x25, 0x12, 0x2a, 0x16, 0xb9, 0xe0, 0xaf, 0x59, 0xf3, 0x47, 0x07,
- 0x5c, 0xcc, 0x68, 0x76, 0xcf, 0x03, 0xa8, 0x8d, 0xb9, 0xf0, 0xf4, 0x48, 0xb1, 0x68, 0x24, 0x83,
- 0x54, 0x79, 0x75, 0xcc, 0xc5, 0x65, 0x8a, 0x59, 0x12, 0xb9, 0x5a, 0x20, 0xe5, 0x13, 0x12, 0xb9,
- 0x9a, 0x93, 0x1e, 0xc1, 0x6d, 0x43, 0x0a, 0x95, 0xec, 0x93, 0x3e, 0x0f, 0xe6, 0x4d, 0x58, 0x1f,
- 0x93, 0xab, 0xf3, 0x39, 0xda, 0xfc, 0xd5, 0x81, 0xad, 0xde, 0x4a, 0x20, 0x9f, 0xc3, 0xc6, 0x50,
- 0x31, 0x16, 0x57, 0xdc, 0xca, 0x9d, 0x2c, 0xd3, 0x71, 0x4c, 0x46, 0x4f, 0xa0, 0x78, 0xcd, 0x82,
- 0x40, 0xc6, 0xc3, 0xe2, 0xcd, 0xdb, 0x12, 0x36, 0xfa, 0x14, 0x0a, 0x8a, 0xd1, 0xa4, 0x16, 0xdf,
- 0xb4, 0xc9, 0x50, 0x9b, 0x7f, 0xe4, 0xa1, 0x96, 0x8d, 0xb8, 0x03, 0x75, 0x1a, 0x03, 0xe9, 0xf0,
- 0x88, 0x9b, 0xe5, 0xde, 0x72, 0xb3, 0x58, 0x4e, 0x32, 0x39, 0x6a, 0x74, 0xd1, 0x44, 0xdf, 0x42,
- 0x43, 0x13, 0x1e, 0x78, 0x54, 0xc9, 0xd0, 0x4b, 0xbd, 0xf9, 0xd6, 0x7f, 0xa2, 0xe8, 0xc1, 0x52,
- 0x71, 0xac, 0xcb, 0xfc, 0x69, 0x0e, 0x6f, 0xeb, 0xb5, 0x25, 0x71, 0x06, 0x48, 0x31, 0xba, 0xec,
- 0xf9, 0x1f, 0xc9, 0x3e, 0xcd, 0x61, 0x57, 0x2d, 0x67, 0xe9, 0x15, 0xdc, 0x99, 0xae, 0x71, 0x18,
- 0xf7, 0xe2, 0x07, 0x59, 0x87, 0xbd, 0x35, 0x1e, 0xb7, 0xa6, 0xcb, 0x2e, 0x3b, 0xee, 0xfc, 0x1a,
- 0x63, 0x6f, 0xcd, 0x9f, 0x0b, 0x50, 0x4d, 0x9a, 0xe0, 0x95, 0xa9, 0xde, 0xb7, 0x9d, 0x48, 0xbb,
- 0x00, 0x43, 0x36, 0xb6, 0xbd, 0x38, 0x6b, 0xdd, 0x4a, 0x82, 0x74, 0xa9, 0x19, 0x58, 0x61, 0x9f,
- 0x6b, 0x6f, 0x4c, 0x42, 0x7b, 0x23, 0x15, 0x5c, 0x32, 0xf6, 0x0b, 0x12, 0xa2, 0x87, 0x50, 0x27,
- 0x2c, 0xf2, 0x98, 0xf0, 0xd5, 0x75, 0x68, 0x4f, 0x35, 0x0a, 0xcb, 0xb8, 0x46, 0x58, 0x74, 0x32,
- 0x03, 0xff, 0x83, 0x9f, 0x47, 0xe6, 0x9f, 0x55, 0xfc, 0xdb, 0x7f, 0x56, 0x29, 0xf3, 0xcf, 0x5a,
- 0x2d, 0xbc, 0xf2, 0xbf, 0x2e, 0xbc, 0xce, 0xf2, 0xad, 0x37, 0x2a, 0x36, 0x87, 0xeb, 0x7d, 0x24,
- 0x8d, 0x90, 0xfa, 0x88, 0xcd, 0xe6, 0x2f, 0x0e, 0xd4, 0x16, 0xf3, 0xf4, 0xff, 0x4f, 0xd0, 0xf6,
- 0x7c, 0x82, 0xda, 0xb9, 0x16, 0x35, 0x8a, 0x76, 0x82, 0xee, 0xac, 0x9d, 0xa0, 0x36, 0xa8, 0xd9,
- 0xf4, 0x8c, 0x43, 0x6c, 0x7d, 0x09, 0x95, 0x59, 0xb1, 0xa0, 0x2a, 0x94, 0xbf, 0x3e, 0xbf, 0xb8,
- 0xc4, 0x27, 0xed, 0x17, 0x6e, 0x0e, 0xd5, 0x01, 0x8e, 0x5f, 0xf6, 0xce, 0x12, 0xdb, 0x41, 0x5b,
- 0x50, 0xeb, 0x74, 0x8f, 0xbb, 0xf8, 0xe4, 0xf9, 0x65, 0xf7, 0xe5, 0x59, 0xfb, 0x2b, 0x37, 0xdf,
- 0x7a, 0x0a, 0xee, 0x72, 0x3e, 0x51, 0x09, 0x0a, 0x3d, 0x8c, 0xdd, 0x1c, 0x42, 0x50, 0xbf, 0xd0,
- 0x8a, 0xfb, 0xfa, 0x3c, 0xc9, 0xa0, 0xeb, 0x20, 0x80, 0xe2, 0xe9, 0x75, 0x5f, 0x71, 0xea, 0xe6,
- 0x5b, 0x02, 0xaa, 0x8b, 0xaf, 0x17, 0xb4, 0x0d, 0x5b, 0x8b, 0xb6, 0x77, 0x26, 0x05, 0x73, 0x73,
- 0xe8, 0x0e, 0xdc, 0xce, 0xc2, 0x6d, 0xd7, 0x41, 0xf7, 0xe0, 0x6e, 0x06, 0xec, 0xb0, 0x48, 0x9f,
- 0x0c, 0x06, 0x52, 0x69, 0x37, 0xbf, 0xe2, 0xa8, 0x3d, 0xd1, 0xd2, 0x2d, 0xb4, 0x9e, 0xcd, 0x26,
- 0x56, 0x12, 0x69, 0x15, 0xca, 0xe9, 0xfc, 0x70, 0x73, 0xa8, 0x06, 0x95, 0xde, 0xcc, 0x74, 0x8c,
- 0x0c, 0xcc, 0xa8, 0x9b, 0x47, 0x65, 0xb8, 0x65, 0x5a, 0xd7, 0x2d, 0xb4, 0x7e, 0x72, 0xe0, 0xfe,
- 0x4d, 0x2f, 0x09, 0xf4, 0x10, 0x3e, 0xbc, 0xe9, 0x7b, 0xaa, 0x68, 0x1f, 0x3e, 0xba, 0x91, 0xd6,
- 0x8e, 0xa2, 0x89, 0x62, 0xd4, 0x75, 0xd0, 0xc7, 0xf0, 0xe8, 0x46, 0xe6, 0xa2, 0xec, 0xce, 0xe3,
- 0x6f, 0x3e, 0x1b, 0x72, 0x3d, 0x9a, 0xf4, 0x0f, 0x7c, 0x39, 0x3e, 0x94, 0x21, 0x13, 0xbe, 0x54,
- 0xf4, 0x30, 0x7e, 0xdf, 0x7e, 0x92, 0xbc, 0x6f, 0x87, 0x32, 0xf3, 0xc4, 0xed, 0x17, 0x2d, 0xfe,
- 0xf8, 0xaf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x5f, 0x7b, 0x91, 0x72, 0x07, 0x0b, 0x00, 0x00,
-}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 251fedd..c0ad304 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -6,8 +6,6 @@
github.com/armon/go-metrics
# github.com/bclicn/color v0.0.0-20180711051946-108f2023dc84
github.com/bclicn/color
-# github.com/boljen/go-bitmap v0.0.0-20151001105940-23cd2fb0ce7d
-github.com/boljen/go-bitmap
# github.com/bsm/sarama-cluster v2.1.15+incompatible
github.com/bsm/sarama-cluster
# github.com/cevaris/ordered_map v0.0.0-20190319150403-3adeae072e73
@@ -29,12 +27,12 @@
github.com/gogo/protobuf/gogoproto
github.com/gogo/protobuf/protoc-gen-gogo/descriptor
# github.com/golang/protobuf v1.3.2
-github.com/golang/protobuf/proto
+github.com/golang/protobuf/ptypes/empty
github.com/golang/protobuf/ptypes
github.com/golang/protobuf/ptypes/any
-github.com/golang/protobuf/ptypes/empty
-github.com/golang/protobuf/descriptor
+github.com/golang/protobuf/proto
github.com/golang/protobuf/protoc-gen-go/descriptor
+github.com/golang/protobuf/descriptor
github.com/golang/protobuf/ptypes/duration
github.com/golang/protobuf/ptypes/timestamp
# github.com/golang/snappy v0.0.1
@@ -64,12 +62,22 @@
github.com/mitchellh/go-homedir
# github.com/mitchellh/mapstructure v1.1.2
github.com/mitchellh/mapstructure
+# github.com/opencord/voltha-lib-go v0.0.0-20191017201200-e73f91e306e9
+github.com/opencord/voltha-lib-go/pkg/log
+github.com/opencord/voltha-lib-go/pkg/db/kvstore
+github.com/opencord/voltha-lib-go/pkg/grpc
+github.com/opencord/voltha-lib-go/pkg/probe
+github.com/opencord/voltha-lib-go/pkg/version
+github.com/opencord/voltha-lib-go/pkg/db/model
+github.com/opencord/voltha-lib-go/pkg/kafka
+github.com/opencord/voltha-lib-go/pkg/adapters/common
+github.com/opencord/voltha-lib-go/pkg/adapters
+github.com/opencord/voltha-lib-go/pkg/adapters/adapterif
# github.com/opencord/voltha-protos v1.0.3
-github.com/opencord/voltha-protos/go/inter_container
-github.com/opencord/voltha-protos/go/openflow_13
-github.com/opencord/voltha-protos/go/voltha
github.com/opencord/voltha-protos/go/common
-github.com/opencord/voltha-protos/go/tech_profile
+github.com/opencord/voltha-protos/go/voltha
+github.com/opencord/voltha-protos/go/openflow_13
+github.com/opencord/voltha-protos/go/inter_container
github.com/opencord/voltha-protos/go/omci
# github.com/pierrec/lz4 v2.3.0+incompatible
github.com/pierrec/lz4
@@ -116,14 +124,14 @@
golang.org/x/crypto/pbkdf2
# golang.org/x/net v0.0.0-20190930134127-c5a3c61f89f3
golang.org/x/net/trace
-golang.org/x/net/proxy
golang.org/x/net/internal/timeseries
golang.org/x/net/http2
golang.org/x/net/http2/hpack
-golang.org/x/net/context
-golang.org/x/net/internal/socks
+golang.org/x/net/proxy
golang.org/x/net/http/httpguts
golang.org/x/net/idna
+golang.org/x/net/context
+golang.org/x/net/internal/socks
# golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24
golang.org/x/sys/unix
# golang.org/x/text v0.3.2
@@ -135,18 +143,18 @@
google.golang.org/genproto/googleapis/api/annotations
google.golang.org/genproto/googleapis/rpc/status
# google.golang.org/grpc v1.24.0
-google.golang.org/grpc/codes
-google.golang.org/grpc/status
google.golang.org/grpc
-google.golang.org/grpc/credentials
+google.golang.org/grpc/codes
google.golang.org/grpc/metadata
-google.golang.org/grpc/internal
+google.golang.org/grpc/status
google.golang.org/grpc/balancer
google.golang.org/grpc/balancer/roundrobin
google.golang.org/grpc/connectivity
+google.golang.org/grpc/credentials
google.golang.org/grpc/encoding
google.golang.org/grpc/encoding/proto
google.golang.org/grpc/grpclog
+google.golang.org/grpc/internal
google.golang.org/grpc/internal/backoff
google.golang.org/grpc/internal/balancerload
google.golang.org/grpc/internal/binarylog
@@ -164,8 +172,8 @@
google.golang.org/grpc/serviceconfig
google.golang.org/grpc/stats
google.golang.org/grpc/tap
-google.golang.org/grpc/credentials/internal
google.golang.org/grpc/balancer/base
+google.golang.org/grpc/credentials/internal
google.golang.org/grpc/binarylog/grpc_binarylog_v1
google.golang.org/grpc/internal/syscall
# gopkg.in/jcmturner/aescts.v1 v1.0.1