VOL-1848 API for setting and querying loglevel of api-server;
Add source-router to support routing UpdateLogLevel to cores;
Add logging endpoints to rocore
Change-Id: I89eea3599ea3006fe92e6917221cd1fd235ec5e4
diff --git a/afrouter/afrouter/affinity-router.go b/afrouter/afrouter/affinity-router.go
index 583d0a7..7a3168e 100644
--- a/afrouter/afrouter/affinity-router.go
+++ b/afrouter/afrouter/affinity-router.go
@@ -26,6 +26,7 @@
"strconv"
)
+// TODO: Used in multiple routers, should move to common file
const (
PKG_MTHD_PKG int = 1
PKG_MTHD_MTHD int = 2
@@ -180,6 +181,7 @@
return false
}
+// TODO: Used in multiple routers, should move to common file
func needMethod(mthd string, conf *RouteConfig) bool {
for _, m := range conf.Methods {
if mthd == m {
@@ -265,7 +267,7 @@
}
}
-func (ar AffinityRouter) Route(sel interface{}) *backend {
+func (ar AffinityRouter) Route(sel interface{}) (*backend, *connection) {
switch sl := sel.(type) {
case *requestFrame:
log.Debugf("Route called for requestFrame with method %s", sl.methodInfo.method)
@@ -276,17 +278,17 @@
log.Debugf("Method '%s' affinity binds on reply", sl.methodInfo.method)
// Just round robin route the southbound request
if *ar.currentBackend, err = ar.cluster.nextBackend(*ar.currentBackend, BackendSequenceRoundRobin); err == nil {
- return *ar.currentBackend
+ return *ar.currentBackend, nil
} else {
sl.err = err
- return nil
+ return nil, nil
}
}
// Not a south affinity binding method, proceed with north affinity binding.
if selector, err := ar.decodeProtoField(sl.payload, ar.methodMap[sl.methodInfo.method]); err == nil {
log.Debugf("Establishing affinity for selector: %s", selector)
if rtrn, ok := ar.affinity[selector]; ok {
- return rtrn
+ return rtrn, nil
} else {
// The selector isn't in the map, create a new affinity mapping
log.Debugf("MUST CREATE A NEW AFFINITY MAP ENTRY!!")
@@ -295,19 +297,19 @@
ar.setAffinity(selector, *ar.currentBackend)
//ar.affinity[selector] = *ar.currentBackend
//log.Debugf("New affinity set to backend %s",(*ar.currentBackend).name)
- return *ar.currentBackend
+ return *ar.currentBackend, nil
} else {
sl.err = err
- return nil
+ return nil, nil
}
}
}
default:
log.Errorf("Internal: invalid data type in Route call %v", sel)
- return nil
+ return nil, nil
}
log.Errorf("Bad lookup in affinity map %v", ar.affinity)
- return nil
+ return nil, nil
}
func (ar AffinityRouter) GetMetaKeyVal(serverStream grpc.ServerStream) (string, string, error) {
diff --git a/afrouter/afrouter/api.go b/afrouter/afrouter/api.go
index 3e47ef8..8b72897 100644
--- a/afrouter/afrouter/api.go
+++ b/afrouter/afrouter/api.go
@@ -21,6 +21,7 @@
"fmt"
"github.com/opencord/voltha-go/common/log"
pb "github.com/opencord/voltha-protos/go/afrouter"
+ common_pb "github.com/opencord/voltha-protos/go/common"
"golang.org/x/net/context"
"google.golang.org/grpc"
"net"
@@ -210,6 +211,47 @@
return &pb.Count{Count: uint32(runtime.NumGoroutine())}, nil
}
+func (aa ArouterApi) UpdateLogLevel(ctx context.Context, in *common_pb.Logging) (*pb.Empty, error) {
+ intLevel := int(in.Level)
+
+ if in.PackageName == "" {
+ log.SetAllLogLevel(intLevel)
+ log.SetDefaultLogLevel(intLevel)
+ } else if in.PackageName == "default" {
+ log.SetDefaultLogLevel(intLevel)
+ } else {
+ log.SetPackageLogLevel(in.PackageName, intLevel)
+ }
+
+ return &pb.Empty{}, nil
+}
+
+func (aa ArouterApi) GetLogLevels(ctx context.Context, in *common_pb.LoggingComponent) (*common_pb.Loggings, error) {
+ logLevels := &common_pb.Loggings{}
+
+ // do the per-package log levels
+ for _, packageName := range log.GetPackageNames() {
+ level, err := log.GetPackageLogLevel(packageName)
+ if err != nil {
+ return nil, err
+ }
+ logLevel := &common_pb.Logging{
+ ComponentName: in.ComponentName,
+ PackageName: packageName,
+ Level: common_pb.LogLevel_LogLevel(level)}
+ logLevels.Items = append(logLevels.Items, logLevel)
+ }
+
+ // now do the default log level
+ logLevel := &common_pb.Logging{
+ ComponentName: in.ComponentName,
+ PackageName: "default",
+ Level: common_pb.LogLevel_LogLevel(log.GetDefaultLogLevel())}
+ logLevels.Items = append(logLevels.Items, logLevel)
+
+ return logLevels, nil
+}
+
func (aa *ArouterApi) serve() {
// Start a serving thread
go func() {
diff --git a/afrouter/afrouter/backend.go b/afrouter/afrouter/backend.go
index 3c7815d..3db98c5 100644
--- a/afrouter/afrouter/backend.go
+++ b/afrouter/afrouter/backend.go
@@ -111,8 +111,18 @@
// connections are non-existent.
var atLeastOne = false
var errStr strings.Builder
+
log.Debugf("There are %d/%d streams to open", len(be.openConns), len(be.connections))
+ if nf.connection != nil {
+ // Debug statement triggered by source router. Other routers have no connection preference.
+ log.Debugf("Looking for connection %s", nf.connection.name)
+ }
for cn, conn := range be.openConns {
+ // If source-router was used, it will indicate a specific connection to be used
+ if nf.connection != nil && nf.connection != cn {
+ continue
+ }
+
log.Debugf("Opening stream for connection '%s'", cn.name)
if stream, err := grpc.NewClientStream(r.ctx, clientStreamDescForProxying, conn, r.methodInfo.all); err != nil {
log.Debugf("Failed to create a client stream '%s', %v", cn.name, err)
diff --git a/afrouter/afrouter/binding-router.go b/afrouter/afrouter/binding-router.go
index dd73756..2e72571 100644
--- a/afrouter/afrouter/binding-router.go
+++ b/afrouter/afrouter/binding-router.go
@@ -80,37 +80,37 @@
func (br BindingRouter) ReplyHandler(v interface{}) error {
return nil
}
-func (br BindingRouter) Route(sel interface{}) *backend {
+func (br BindingRouter) Route(sel interface{}) (*backend, *connection) {
var err error
switch sl := sel.(type) {
case *requestFrame:
if b, ok := br.bindings[sl.metaVal]; ok == true { // binding exists, just return it
- return b
+ return b, nil
} else { // establish a new binding or error.
if sl.metaVal != "" {
err = errors.New(fmt.Sprintf("Attempt to route on non-existent metadata value '%s' in key '%s'",
sl.metaVal, sl.metaKey))
log.Error(err)
sl.err = err
- return nil
+ return nil, nil
}
if sl.methodInfo.method != br.bindingMethod {
err = errors.New(fmt.Sprintf("Binding must occur with method %s but attempted with method %s",
br.bindingMethod, sl.methodInfo.method))
log.Error(err)
sl.err = err
- return nil
+ return nil, nil
}
log.Debugf("MUST CREATE A NEW BINDING MAP ENTRY!!")
if len(br.bindings) < len(br.beCluster.backends) {
if *br.currentBackend, err = br.beCluster.nextBackend(*br.currentBackend, BackendSequenceRoundRobin); err == nil {
// Use the name of the backend as the metaVal for this new binding
br.bindings[(*br.currentBackend).name] = *br.currentBackend
- return *br.currentBackend
+ return *br.currentBackend, nil
} else {
log.Error(err)
sl.err = err
- return nil
+ return nil, nil
}
} else {
err = errors.New(fmt.Sprintf("Backends exhausted in attempt to bind for metakey '%s' with value '%s'",
@@ -119,9 +119,9 @@
sl.err = err
}
}
- return nil
+ return nil, nil
default:
- return nil
+ return nil, nil
}
}
diff --git a/afrouter/afrouter/cluster.go b/afrouter/afrouter/cluster.go
index e33db67..7fc581b 100644
--- a/afrouter/afrouter/cluster.go
+++ b/afrouter/afrouter/cluster.go
@@ -157,6 +157,7 @@
if err := src.RecvMsg(f); err != nil {
return nil, err
}
+ log.Debugf("Assigned backend %s", f.backend.name)
// Check that the backend was routable and actually has connections open.
// If it doesn't then return a nil backend to indicate this
if f.backend == nil {
diff --git a/afrouter/afrouter/codec.go b/afrouter/afrouter/codec.go
index 7ea6ef2..27f4619 100644
--- a/afrouter/afrouter/codec.go
+++ b/afrouter/afrouter/codec.go
@@ -50,6 +50,7 @@
payload []byte
router Router
backend *backend
+ connection *connection // optional, if the router preferred one connection over another
err error
methodInfo methodDetails
serialNo uint64
@@ -79,12 +80,13 @@
t.payload = data
// This is were the afinity value is pulled from the payload
// and the backend selected.
- t.backend = t.router.Route(v)
+ t.backend, t.connection = t.router.Route(v)
name := "<nil>"
if t.backend != nil {
name = t.backend.name
}
log.Debugf("Routing returned %s for method %s", name, t.methodInfo.method)
+
return nil
default:
return cdc.parentCodec.Unmarshal(data, v)
diff --git a/afrouter/afrouter/enums.go b/afrouter/afrouter/enums.go
index 02b1d3d..1847191 100644
--- a/afrouter/afrouter/enums.go
+++ b/afrouter/afrouter/enums.go
@@ -133,11 +133,12 @@
RouteTypeRpcAffinityHeader
RouteTypeBinding
RouteTypeRoundRobin
+ RouteTypeSource
)
// String names for display in error messages.
-var stringToRouteType = map[string]routeType{"rpc_affinity_message": RouteTypeRpcAffinityMessage, "rpc_affinity_header": RouteTypeRpcAffinityHeader, "binding": RouteTypeBinding, "round_robin": RouteTypeRoundRobin}
-var routeTypeToString = map[routeType]string{RouteTypeRpcAffinityMessage: "rpc_affinity_message", RouteTypeRpcAffinityHeader: "rpc_affinity_header", RouteTypeBinding: "binding", RouteTypeRoundRobin: "round_robin"}
+var stringToRouteType = map[string]routeType{"rpc_affinity_message": RouteTypeRpcAffinityMessage, "rpc_affinity_header": RouteTypeRpcAffinityHeader, "binding": RouteTypeBinding, "round_robin": RouteTypeRoundRobin, "source": RouteTypeSource}
+var routeTypeToString = map[routeType]string{RouteTypeRpcAffinityMessage: "rpc_affinity_message", RouteTypeRpcAffinityHeader: "rpc_affinity_header", RouteTypeBinding: "binding", RouteTypeRoundRobin: "round_robin", RouteTypeSource: "source"}
func (t routeType) String() string {
if str, have := routeTypeToString[t]; have {
diff --git a/afrouter/afrouter/method-router.go b/afrouter/afrouter/method-router.go
index fd3b974..56d025e 100644
--- a/afrouter/afrouter/method-router.go
+++ b/afrouter/afrouter/method-router.go
@@ -176,16 +176,16 @@
return nil
}
-func (mr MethodRouter) Route(sel interface{}) *backend {
+func (mr MethodRouter) Route(sel interface{}) (*backend, *connection) {
switch sl := sel.(type) {
case *requestFrame:
if r, ok := mr.methodRouter[sl.metaKey][sl.methodInfo.method]; ok {
return r.Route(sel)
}
log.Errorf("Attept to route on non-existent method '%s'", sl.methodInfo.method)
- return nil
+ return nil, nil
default:
- return nil
+ return nil, nil
}
}
diff --git a/afrouter/afrouter/round-robin-router.go b/afrouter/afrouter/round-robin-router.go
index 60ff457..b5f031e 100644
--- a/afrouter/afrouter/round-robin-router.go
+++ b/afrouter/afrouter/round-robin-router.go
@@ -92,20 +92,20 @@
return rr.name
}
-func (rr RoundRobinRouter) Route(sel interface{}) *backend {
+func (rr RoundRobinRouter) Route(sel interface{}) (*backend, *connection) {
var err error
switch sl := sel.(type) {
case *requestFrame:
// Since this is a round robin router just get the next backend
if *rr.currentBackend, err = rr.cluster.nextBackend(*rr.currentBackend, BackendSequenceRoundRobin); err == nil {
- return *rr.currentBackend
+ return *rr.currentBackend, nil
} else {
sl.err = err
- return nil
+ return nil, nil
}
default:
log.Errorf("Internal: invalid data type in Route call %v", sel)
- return nil
+ return nil, nil
}
}
diff --git a/afrouter/afrouter/router.go b/afrouter/afrouter/router.go
index 1bd2d6e..7abbceb 100644
--- a/afrouter/afrouter/router.go
+++ b/afrouter/afrouter/router.go
@@ -27,7 +27,11 @@
// The router interface
type Router interface {
Name() string
- Route(interface{}) *backend
+
+ // Route() returns a backend and a connection. The connection is optional and if unspecified, then any
+ // connection on the backend may be used.
+ Route(interface{}) (*backend, *connection)
+
Service() string
IsStreaming(string) (bool, bool)
BackendCluster(string, string) (*cluster, error)
@@ -64,6 +68,12 @@
allRouters[rconf.Name+config.Name] = r
}
return r, err
+ case RouteTypeSource:
+ r, err := newSourceRouter(rconf, config)
+ if err == nil {
+ allRouters[rconf.Name+config.Name] = r
+ }
+ return r, err
default:
return nil, errors.New(fmt.Sprintf("Internal error, undefined router type: %s", config.Type))
}
diff --git a/afrouter/afrouter/source-router.go b/afrouter/afrouter/source-router.go
new file mode 100644
index 0000000..c9f94ef
--- /dev/null
+++ b/afrouter/afrouter/source-router.go
@@ -0,0 +1,320 @@
+/*
+ * 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 afrouter
+
+/* Source-Router
+
+ This router implements source routing where the caller identifies the
+ component the message should be routed to. The `RouteField` should be
+ configured with the gRPC field name to inspect to determine the
+ destination. This field is assumed to be a string. That string will
+ then be used to identify a particular connection on a particular
+ backend.
+
+ The source-router must be configured with a backend cluster, as all routers
+ must identify a backend cluster. However, that backend cluster
+ is merely a placeholder and is not used by the source-router. The
+ source-router's Route() function will return whatever backend cluster is
+ specified by the `RouteField`.
+*/
+
+import (
+ "errors"
+ "fmt"
+ "github.com/golang/protobuf/proto"
+ pb "github.com/golang/protobuf/protoc-gen-go/descriptor"
+ "github.com/opencord/voltha-go/common/log"
+ "google.golang.org/grpc"
+ "io/ioutil"
+ "regexp"
+ "strconv"
+)
+
+type SourceRouter struct {
+ name string
+ //association associationType
+ routingField string
+ grpcService string
+ protoDescriptor *pb.FileDescriptorSet
+ methodMap map[string]byte
+ cluster *cluster
+}
+
+func newSourceRouter(rconf *RouterConfig, config *RouteConfig) (Router, error) {
+ var err error = nil
+ var rtrn_err = false
+ var pkg_re = regexp.MustCompile(`^(\.[^.]+\.)(.+)$`)
+ // Validate the configuration
+
+ // A name must exist
+ if config.Name == "" {
+ log.Error("A router 'name' must be specified")
+ rtrn_err = true
+ }
+
+ if rconf.ProtoPackage == "" {
+ log.Error("A 'package' must be specified")
+ rtrn_err = true
+ }
+
+ if rconf.ProtoService == "" {
+ log.Error("A 'service' must be specified")
+ rtrn_err = true
+ }
+
+ if config.RouteField == "" {
+ log.Error("A 'routing_field' must be specified")
+ rtrn_err = true
+ }
+
+ // TODO The overrieds section is currently not being used
+ // so the router will route all methods based on the
+ // routing_field. This needs to be added so that methods
+ // can have different routing fields.
+ dr := SourceRouter{
+ name: config.Name,
+ grpcService: rconf.ProtoService,
+ methodMap: make(map[string]byte),
+ }
+
+ // Load the protobuf descriptor file
+ dr.protoDescriptor = &pb.FileDescriptorSet{}
+ fb, err := ioutil.ReadFile(rconf.ProtoFile)
+ if err != nil {
+ log.Errorf("Could not open proto file '%s'", rconf.ProtoFile)
+ rtrn_err = true
+ }
+ err = proto.Unmarshal(fb, dr.protoDescriptor)
+ if err != nil {
+ log.Errorf("Could not unmarshal %s, %v", "proto.pb", err)
+ rtrn_err = true
+ }
+
+ // Build the routing structure based on the loaded protobuf
+ // descriptor file and the config information.
+ type key struct {
+ method string
+ field string
+ }
+ var msgs = make(map[key]byte)
+ for _, f := range dr.protoDescriptor.File {
+ // Build a temporary map of message types by name.
+ for _, m := range f.MessageType {
+ for _, fld := range m.Field {
+ log.Debugf("Processing message '%s', field '%s'", *m.Name, *fld.Name)
+ msgs[key{*m.Name, *fld.Name}] = byte(*fld.Number)
+ }
+ }
+ }
+ log.Debugf("The map contains: %v", msgs)
+ for _, f := range dr.protoDescriptor.File {
+ if *f.Package == rconf.ProtoPackage {
+ for _, s := range f.Service {
+ if *s.Name == rconf.ProtoService {
+ log.Debugf("Loading package data '%s' for service '%s' for router '%s'", *f.Package, *s.Name, dr.name)
+ // Now create a map keyed by method name with the value being the
+ // field number of the route selector.
+ var ok bool
+ for _, m := range s.Method {
+ // Find the input type in the messages and extract the
+ // field number and save it for future reference.
+ log.Debugf("Processing method '%s'", *m.Name)
+ // Determine if this is a method we're supposed to be processing.
+ if needMethod(*m.Name, config) {
+ log.Debugf("Enabling method '%s'", *m.Name)
+ pkg_methd := pkg_re.FindStringSubmatch(*m.InputType)
+ if pkg_methd == nil {
+ log.Errorf("Regular expression didn't match input type '%s'", *m.InputType)
+ rtrn_err = true
+ }
+ // The input type has the package name prepended to it. Remove it.
+ //in := (*m.InputType)[len(rconf.ProtoPackage)+2:]
+ in := pkg_methd[PKG_MTHD_MTHD]
+ dr.methodMap[*m.Name], ok = msgs[key{in, config.RouteField}]
+ if !ok {
+ log.Errorf("Method '%s' has no field named '%s' in it's parameter message '%s'",
+ *m.Name, config.RouteField, in)
+ rtrn_err = true
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // We need to pick a cluster, because server will call cluster.handler. The choice we make doesn't
+ // matter, as we can return a different cluster from Route().
+ ok := true
+ if dr.cluster, ok = clusters[config.backendCluster.Name]; !ok {
+ if dr.cluster, err = newBackendCluster(config.backendCluster); err != nil {
+ log.Errorf("Could not create a backend for router %s", config.Name)
+ rtrn_err = true
+ }
+ }
+
+ if rtrn_err {
+ return dr, errors.New(fmt.Sprintf("Failed to create a new router '%s'", dr.name))
+ }
+
+ return dr, nil
+}
+
+func (ar SourceRouter) Service() string {
+ return ar.grpcService
+}
+
+func (ar SourceRouter) Name() string {
+ return ar.name
+}
+
+func (ar SourceRouter) skipField(data *[]byte, idx *int) error {
+ switch (*data)[*idx] & 3 {
+ case 0: // Varint
+ // skip the field number/type
+ *idx++
+ // if the msb is set, then more bytes to follow
+ for (*data)[*idx] >= 128 {
+ *idx++
+ }
+ // the last byte doesn't have the msb set
+ *idx++
+ case 1: // 64 bit
+ *idx += 9
+ case 2: // Length delimited
+ // skip the field number / type
+ *idx++
+ // read a varint that tells length of string
+ b := proto.NewBuffer((*data)[*idx:])
+ t, _ := b.DecodeVarint()
+ // skip the length varint and the string bytes
+ // TODO: This assumes the varint was one byte long -- max string length is 127 bytes
+ *idx += int(t) + 1
+ case 3: // Deprecated
+ case 4: // Deprecated
+ case 5: // 32 bit
+ *idx += 5
+ }
+ return nil
+}
+
+func (ar SourceRouter) decodeProtoField(payload []byte, fieldId byte) (string, error) {
+ idx := 0
+ b := proto.NewBuffer([]byte{})
+ //b.DebugPrint("The Buffer", payload)
+ for { // Find the route selector field
+ log.Debugf("Decoding source value attributeNumber: %d from %v at index %d", fieldId, payload, idx)
+ log.Debugf("Attempting match with payload: %d, methodTable: %d", payload[idx], fieldId)
+ if payload[idx]>>3 == fieldId {
+ log.Debugf("Method match with payload: %d, methodTable: %d", payload[idx], fieldId)
+ // TODO: Consider supporting other selector types.... Way, way in the future
+ // ok, the future is now, support strings as well... ugh.
+ var selector string
+ switch payload[idx] & 3 {
+ case 0: // Integer
+ b.SetBuf(payload[idx+1:])
+ v, e := b.DecodeVarint()
+ if e == nil {
+ log.Debugf("Decoded the ing field: %v", v)
+ selector = strconv.Itoa(int(v))
+ } else {
+ log.Errorf("Failed to decode varint %v", e)
+ return "", e
+ }
+ case 2: // Length delimited AKA string
+ b.SetBuf(payload[idx+1:])
+ v, e := b.DecodeStringBytes()
+ if e == nil {
+ log.Debugf("Decoded the string field: %v", v)
+ selector = v
+ } else {
+ log.Errorf("Failed to decode string %v", e)
+ return "", e
+ }
+ default:
+ err := errors.New(fmt.Sprintf("Only integer and string route selectors are permitted"))
+ log.Error(err)
+ return "", err
+ }
+ return selector, nil
+ } else if err := ar.skipField(&payload, &idx); err != nil {
+ log.Errorf("Parsing message failed %v", err)
+ return "", err
+ }
+ }
+}
+
+func (ar SourceRouter) Route(sel interface{}) (*backend, *connection) {
+ log.Debugf("SourceRouter sel %v", sel)
+ switch sl := sel.(type) {
+ case *requestFrame:
+ log.Debugf("Route called for nbFrame with method %s", sl.methodInfo.method)
+ // Not a south affinity binding method, proceed with north affinity binding.
+ if selector, err := ar.decodeProtoField(sl.payload, ar.methodMap[sl.methodInfo.method]); err == nil {
+ // selector is
+
+ for _, cluster := range clusters {
+ for _, backend := range cluster.backends {
+ log.Debugf("Checking backend %s", backend.name)
+ for _, connection := range backend.connections {
+ log.Debugf("Checking connection %s", connection.name)
+ // caller specified a backend and a connection
+ if backend.name+"."+connection.name == selector {
+ return backend, connection
+ }
+ }
+ // caller specified just a backend
+ if backend.name == selector {
+ return backend, nil
+ }
+ }
+ }
+ sl.err = fmt.Errorf("Backend %s not found", selector)
+ return nil, nil
+ }
+ default:
+ log.Errorf("Internal: invalid data type in Route call %v", sel)
+ return nil, nil
+ }
+ log.Errorf("Bad routing in SourceRouter:Route")
+ return nil, nil
+}
+
+func (ar SourceRouter) GetMetaKeyVal(serverStream grpc.ServerStream) (string, string, error) {
+ return "", "", nil
+}
+
+func (ar SourceRouter) IsStreaming(_ string) (bool, bool) {
+ panic("not implemented")
+}
+
+func (ar SourceRouter) BackendCluster(mthd string, metaKey string) (*cluster, error) {
+ // unsupported?
+ return ar.cluster, nil
+}
+
+func (ar SourceRouter) FindBackendCluster(beName string) *cluster {
+ // unsupported?
+ if beName == ar.cluster.name {
+ return ar.cluster
+ }
+ return nil
+}
+
+func (rr SourceRouter) ReplyHandler(sel interface{}) error { // This is a no-op
+ return nil
+}
diff --git a/afrouter/afrouter/source-router_test.go b/afrouter/afrouter/source-router_test.go
new file mode 100644
index 0000000..3d35d6e
--- /dev/null
+++ b/afrouter/afrouter/source-router_test.go
@@ -0,0 +1,140 @@
+/*
+ * Portions copyright 2019-present Open Networking Foundation
+ * Original copyright 2019-present Ciena Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the"github.com/stretchr/testify/assert" "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 afrouter
+
+import (
+ // "github.com/fullstorydev/grpcurl"
+ "github.com/golang/protobuf/proto"
+ // descpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
+ // "github.com/jhump/protoreflect/dynamic"
+ "github.com/opencord/voltha-go/common/log"
+ common_pb "github.com/opencord/voltha-protos/go/common"
+ "github.com/stretchr/testify/assert"
+ //"io/ioutil"
+ "testing"
+)
+
+const (
+ PROTOFILE = "../../vendor/github.com/opencord/voltha-protos/go/voltha.pb"
+)
+
+func init() {
+ log.SetDefaultLogger(log.JSON, log.DebugLevel, nil)
+ log.AddPackage(log.JSON, log.WarnLevel, nil)
+}
+
+func MakeTestConfig() (*ConnectionConfig, *BackendConfig, *BackendClusterConfig, *RouteConfig, *RouterConfig) {
+ connectionConfig := ConnectionConfig{
+ Name: "ro_vcore01",
+ Addr: "foo",
+ Port: "123",
+ }
+
+ backendConfig := BackendConfig{
+ Name: "ro_vcore0",
+ Type: BackendSingleServer,
+ Connections: []ConnectionConfig{connectionConfig},
+ }
+
+ backendClusterConfig := BackendClusterConfig{
+ Name: "ro_vcore",
+ Backends: []BackendConfig{backendConfig},
+ }
+
+ routeConfig := RouteConfig{
+ Name: "logger",
+ Type: RouteTypeSource,
+ RouteField: "component_name",
+ BackendCluster: "ro_vcore",
+ backendCluster: &backendClusterConfig,
+ Methods: []string{"UpdateLogLevel", "GetLogLevel"},
+ }
+
+ routerConfig := RouterConfig{
+ Name: "vcore",
+ ProtoService: "VolthaService",
+ ProtoPackage: "voltha",
+ Routes: []RouteConfig{routeConfig},
+ ProtoFile: PROTOFILE,
+ }
+ return &connectionConfig, &backendConfig, &backendClusterConfig, &routeConfig, &routerConfig
+}
+
+func TestSourceRouterInit(t *testing.T) {
+ _, _, _, routeConfig, routerConfig := MakeTestConfig()
+
+ router, err := newSourceRouter(routerConfig, routeConfig)
+
+ assert.NotEqual(t, router, nil)
+ assert.Equal(t, err, nil)
+
+ assert.Equal(t, router.Service(), "VolthaService")
+ assert.Equal(t, router.Name(), "logger")
+
+ cluster, err := router.BackendCluster("foo", "bar")
+ assert.Equal(t, cluster, clusters["ro_vcore"])
+ assert.Equal(t, err, nil)
+
+ assert.Equal(t, router.FindBackendCluster("ro_vcore"), clusters["ro_vcore"])
+ assert.Equal(t, router.ReplyHandler("foo"), nil)
+}
+
+func TestDecodeProtoField(t *testing.T) {
+ _, _, _, routeConfig, routerConfig := MakeTestConfig()
+
+ router, err := newSourceRouter(routerConfig, routeConfig)
+ assert.Equal(t, err, nil)
+
+ loggingMessage := &common_pb.Logging{Level: 1,
+ PackageName: "default",
+ ComponentName: "ro_vcore0.ro_vcore01"}
+
+ loggingData, err := proto.Marshal(loggingMessage)
+ assert.Equal(t, err, nil)
+
+ s, err := router.(SourceRouter).decodeProtoField(loggingData, 2) // field 2 is package_name
+ assert.Equal(t, s, "default")
+
+ s, err = router.(SourceRouter).decodeProtoField(loggingData, 3) // field 2 is component_name
+ assert.Equal(t, s, "ro_vcore0.ro_vcore01")
+}
+
+func TestRoute(t *testing.T) {
+ _, _, _, routeConfig, routerConfig := MakeTestConfig()
+
+ router, err := newSourceRouter(routerConfig, routeConfig)
+ assert.Equal(t, err, nil)
+
+ loggingMessage := &common_pb.Logging{Level: 1,
+ PackageName: "default",
+ ComponentName: "ro_vcore0.ro_vcore01"}
+
+ loggingData, err := proto.Marshal(loggingMessage)
+ assert.Equal(t, err, nil)
+
+ sel := &requestFrame{payload: loggingData,
+ err: nil,
+ methodInfo: newMethodDetails("/volta.VolthaService/UpdateLogLevel")}
+
+ backend, connection := router.Route(sel)
+
+ assert.Equal(t, sel.err, nil)
+ assert.NotEqual(t, backend, nil)
+ assert.Equal(t, backend.name, "ro_vcore0")
+ assert.NotEqual(t, connection, nil)
+ assert.Equal(t, connection.name, "ro_vcore01")
+}
diff --git a/afrouter/arouter.json b/afrouter/arouter.json
index 5036402..debfcd7 100644
--- a/afrouter/arouter.json
+++ b/afrouter/arouter.json
@@ -79,6 +79,17 @@
]
},
{
+ "name": "logger",
+ "proto_descriptor": "voltha.pb",
+ "type": "source",
+ "routing_field": "component_name",
+ "backend_cluster": "ro_vcore",
+ "methods": [
+ "UpdateLogLevel",
+ "GetLogLevels"
+ ]
+ },
+ {
"name": "read_only",
"type": "round_robin",
"association": "round_robin",