VOL-1967 move api-server to separate repository
Current with voltha-go acf0adaf2d91ae72b55192cc8a939e0485918d16
Change-Id: I000ea6be0789e20c922bd671562b58a7120892ae
diff --git a/internal/pkg/afrouter/cluster.go b/internal/pkg/afrouter/cluster.go
new file mode 100644
index 0000000..859d92f
--- /dev/null
+++ b/internal/pkg/afrouter/cluster.go
@@ -0,0 +1,172 @@
+/*
+ * 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 afrouter
+
+import (
+ "errors"
+ "fmt"
+ "github.com/google/uuid"
+ "github.com/opencord/voltha-go/common/log"
+ "google.golang.org/grpc"
+)
+
+var clusters = make(map[string]*cluster)
+
+// cluster a collection of HA backends
+type cluster struct {
+ name string
+ //backends map[string]*backend
+ backends []*backend
+ backendIDMap map[*backend]int
+}
+
+//TODO: Move the backend type (active/active etc) to the cluster
+// level. All backends should really be of the same type.
+// Create a new backend cluster
+func newBackendCluster(conf *BackendClusterConfig) (*cluster, error) {
+ var err error = nil
+ var rtrn_err = false
+ var be *backend
+ log.Debugf("Creating a backend cluster with %v", conf)
+ // Validate the configuration
+ if conf.Name == "" {
+ log.Error("A backend cluster must have a name")
+ rtrn_err = true
+ }
+ //bc := &cluster{name:conf.Name,backends:make(map[string]*backend)}
+ bc := &cluster{name: conf.Name, backendIDMap: make(map[*backend]int)}
+ clusters[bc.name] = bc
+ idx := 0
+ for _, bec := range conf.Backends {
+ if bec.Name == "" {
+ log.Errorf("A backend must have a name in cluster %s\n", conf.Name)
+ rtrn_err = true
+ }
+ if be, err = newBackend(&bec, conf.Name); err != nil {
+ log.Errorf("Error creating backend %s", bec.Name)
+ rtrn_err = true
+ }
+ bc.backends = append(bc.backends, be)
+ bc.backendIDMap[bc.backends[idx]] = idx
+ idx++
+ }
+ if rtrn_err {
+ return nil, errors.New("Error creating backend(s)")
+ }
+ return bc, nil
+}
+
+func (c *cluster) getBackend(name string) *backend {
+ for _, v := range c.backends {
+ if v.name == name {
+ return v
+ }
+ }
+ return nil
+}
+
+func (c *cluster) allocateSerialNumber() string {
+ return uuid.New().String()
+}
+
+func (c *cluster) nextBackend(be *backend, seq backendSequence) (*backend, error) {
+ switch seq {
+ case BackendSequenceRoundRobin: // Round robin
+ in := be
+ // If no backend is found having a connection
+ // then return nil.
+ if be == nil {
+ log.Debug("Previous backend is nil")
+ be = c.backends[0]
+ in = be
+ if be.NumOpenConnections() != 0 {
+ return be, nil
+ }
+ }
+ for {
+ log.Debugf("Requesting a new backend starting from %s", be.name)
+ cur := c.backendIDMap[be]
+ cur++
+ if cur >= len(c.backends) {
+ cur = 0
+ }
+ log.Debugf("Next backend is %d:%s", cur, c.backends[cur].name)
+ if c.backends[cur].NumOpenConnections() > 0 {
+ return c.backends[cur], nil
+ }
+ if c.backends[cur] == in {
+ err := fmt.Errorf("No backend with open connections found")
+ log.Debug(err)
+ return nil, err
+ }
+ be = c.backends[cur]
+ log.Debugf("Backend '%s' has no open connections, trying next", c.backends[cur].name)
+ }
+ default: // Invalid, default to round robin
+ log.Errorf("Invalid backend sequence %d. Defaulting to round robin", seq)
+ return c.nextBackend(be, BackendSequenceRoundRobin)
+ }
+}
+
+func (c *cluster) handler(srv interface{}, serverStream grpc.ServerStream, r Router, methodInfo methodDetails,
+ mk string, mv string) error {
+ //func (c *cluster) handler(nbR * nbRequest) error {
+
+ // The final backend cluster needs to be determined here. With non-affinity routed backends it could
+ // just be determined here and for affinity routed backends the first message must be received
+ // before the backend is determined. In order to keep things simple, the same approach is taken for
+ // now.
+
+ // Get the backend to use.
+ // Allocate the requestFrame here since it holds the "context" of this communication
+ nf := &requestFrame{router: r, methodInfo: methodInfo, serialNo: c.allocateSerialNumber(), metaKey: mk, metaVal: mv}
+ log.Debugf("Nb frame allocate with method %s", nf.methodInfo.method)
+
+ if be, err := c.assignBackend(serverStream, nf); err != nil {
+ // At this point, no backend streams have been initiated
+ // so just return the error.
+ return err
+ } else {
+ log.Debugf("Backend '%s' selected", be.name)
+ // Allocate a responseFrame here because it might be needed for return value intercept
+ sf := &responseFrame{router: r, backend: be, method: nf.methodInfo.method, metaKey: mk, metaVal: mv}
+ log.Debugf("Sb frame allocated with router %s", r.Name())
+ return be.handler(srv, serverStream, nf, sf)
+ }
+}
+
+func (c *cluster) assignBackend(src grpc.ServerStream, f *requestFrame) (*backend, error) {
+ // Receive the first message from the server. This calls the assigned codec in which
+ // Unmarshal gets executed. That will use the assigned router to select a backend
+ // and add it to the frame
+ if err := src.RecvMsg(f); err != nil {
+ return nil, err
+ }
+ // 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 {
+ err := fmt.Errorf("Unable to route method '%s'", f.methodInfo.method)
+ log.Error(err)
+ return nil, err
+ } else if len(f.backend.openConns) == 0 {
+ err := fmt.Errorf("No open connections on backend '%s'", f.backend.name)
+ log.Error(err)
+ return f.backend, err
+ }
+ log.Debugf("Assigned backend %s", f.backend.name)
+ return f.backend, nil
+}