[VOL-1349] EPON OLT adapter (package B)

Change-Id: I634ef62c53813dcf4456f54948f13e06358e263c
diff --git a/cmd/openolt-adapter/main.go b/cmd/openolt-adapter/main.go
new file mode 100644
index 0000000..19da3d7
--- /dev/null
+++ b/cmd/openolt-adapter/main.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 main invokes the application
+package main
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"os"
+	"os/signal"
+	"syscall"
+	"time"
+
+	"github.com/opencord/voltha-lib-go/v3/pkg/adapters/adapterif"
+
+	"github.com/opencord/voltha-lib-go/v3/pkg/adapters"
+	com "github.com/opencord/voltha-lib-go/v3/pkg/adapters/common"
+	conf "github.com/opencord/voltha-lib-go/v3/pkg/config"
+	"github.com/opencord/voltha-lib-go/v3/pkg/db/kvstore"
+	"github.com/opencord/voltha-lib-go/v3/pkg/kafka"
+	"github.com/opencord/voltha-lib-go/v3/pkg/log"
+	"github.com/opencord/voltha-lib-go/v3/pkg/probe"
+	"github.com/opencord/voltha-lib-go/v3/pkg/version"
+	"github.com/opencord/voltha-openolt-adapter/internal/pkg/config"
+	ac "github.com/opencord/voltha-openolt-adapter/internal/pkg/core"
+	ic "github.com/opencord/voltha-protos/v3/go/inter_container"
+	"github.com/opencord/voltha-protos/v3/go/voltha"
+)
+
+type adapter struct {
+	instanceID       string
+	config           *config.AdapterFlags
+	iAdapter         adapters.IAdapter
+	kafkaClient      kafka.Client
+	kvClient         kvstore.Client
+	kip              kafka.InterContainerProxy
+	coreProxy        adapterif.CoreProxy
+	adapterProxy     adapterif.AdapterProxy
+	eventProxy       adapterif.EventProxy
+	halted           bool
+	exitChannel      chan int
+	receiverChannels []<-chan *ic.InterContainerMessage
+}
+
+func newAdapter(cf *config.AdapterFlags) *adapter {
+	var a adapter
+	a.instanceID = cf.InstanceID
+	a.config = cf
+	a.halted = false
+	a.exitChannel = make(chan int, 1)
+	a.receiverChannels = make([]<-chan *ic.InterContainerMessage, 0)
+	return &a
+}
+
+func (a *adapter) start(ctx context.Context) {
+	logger.Info(ctx, "Starting Core Adapter components")
+	var err error
+
+	var p *probe.Probe
+	if value := ctx.Value(probe.ProbeContextKey); value != nil {
+		if _, ok := value.(*probe.Probe); ok {
+			p = value.(*probe.Probe)
+			p.RegisterService(
+				ctx,
+				"message-bus",
+				"kv-store",
+				"container-proxy",
+				"core-request-handler",
+				"register-with-core",
+			)
+		}
+	}
+
+	// Setup KV Client
+	logger.Debugw(ctx, "create-kv-client", log.Fields{"kvstore": a.config.KVStoreType})
+	if err = a.setKVClient(ctx); err != nil {
+		logger.Fatalw(ctx, "error-setting-kv-client", log.Fields{"error": err})
+	}
+
+	if p != nil {
+		p.UpdateStatus(ctx, "kv-store", probe.ServiceStatusRunning)
+	}
+
+	// Setup Log Config
+	cm := conf.NewConfigManager(ctx, a.kvClient, a.config.KVStoreType, a.config.KVStoreAddress, a.config.KVStoreTimeout)
+	go conf.StartLogLevelConfigProcessing(cm, ctx)
+	go conf.StartLogFeaturesConfigProcessing(cm, ctx)
+
+	// Setup Kafka Client
+	if a.kafkaClient, err = newKafkaClient(ctx, "sarama", a.config.KafkaAdapterAddress); err != nil {
+		logger.Fatalw(ctx, "Unsupported-common-client", log.Fields{"error": err})
+	}
+
+	if p != nil {
+		p.UpdateStatus(ctx, "message-bus", probe.ServiceStatusRunning)
+	}
+
+	// setup endpointManager
+
+	// Start the common InterContainer Proxy - retries indefinitely
+	if a.kip, err = a.startInterContainerProxy(ctx, -1); err != nil {
+		logger.Fatal(ctx, "error-starting-inter-container-proxy")
+	}
+
+	// Create the core proxy to handle requests to the Core
+	a.coreProxy = com.NewCoreProxy(ctx, a.kip, a.config.Topic, a.config.CoreTopic)
+
+	// Create the adaptor proxy to handle request between olt and onu
+	a.adapterProxy = com.NewAdapterProxy(ctx, a.kip, a.config.CoreTopic, cm.Backend)
+
+	// Create the event proxy to post events to KAFKA
+	a.eventProxy = com.NewEventProxy(com.MsgClient(a.kafkaClient), com.MsgTopic(kafka.Topic{Name: a.config.EventTopic}))
+
+	// Create the open OLT adapter
+	if a.iAdapter, err = a.startOpenOLT(ctx, a.kip, a.coreProxy, a.adapterProxy, a.eventProxy, a.config); err != nil {
+		logger.Fatalw(ctx, "error-starting-openolt", log.Fields{"error": err})
+	}
+
+	// Register the core request handler
+	if err = a.setupRequestHandler(ctx, a.instanceID, a.iAdapter); err != nil {
+		logger.Fatalw(ctx, "error-setting-core-request-handler", log.Fields{"error": err})
+	}
+
+	// Register this adapter to the Core - retries indefinitely
+	if err = a.registerWithCore(ctx, -1); err != nil {
+		logger.Fatal(ctx, "error-registering-with-core")
+	}
+
+	// check the readiness and liveliness and update the probe status
+	a.checkServicesReadiness(ctx)
+}
+
+/**
+This function checks the liveliness and readiness of the kakfa and kv-client services
+and update the status in the probe.
+*/
+func (a *adapter) checkServicesReadiness(ctx context.Context) {
+	// checks the kafka readiness
+	go a.checkKafkaReadiness(ctx)
+
+	// checks the kv-store readiness
+	go a.checkKvStoreReadiness(ctx)
+}
+
+/**
+This function checks the liveliness and readiness of the kv-store service
+and update the status in the probe.
+*/
+func (a *adapter) checkKvStoreReadiness(ctx context.Context) {
+	// dividing the live probe interval by 2 to get updated status every 30s
+	timeout := a.config.LiveProbeInterval / 2
+	kvStoreChannel := make(chan bool, 1)
+
+	// Default false to check the liveliness.
+	kvStoreChannel <- false
+	for {
+		timeoutTimer := time.NewTimer(timeout)
+		select {
+		case liveliness := <-kvStoreChannel:
+			if !liveliness {
+				// kv-store not reachable or down, updating the status to not ready state
+				probe.UpdateStatusFromContext(ctx, "kv-store", probe.ServiceStatusNotReady)
+				timeout = a.config.NotLiveProbeInterval
+			} else {
+				// kv-store is reachable , updating the status to running state
+				probe.UpdateStatusFromContext(ctx, "kv-store", probe.ServiceStatusRunning)
+				timeout = a.config.LiveProbeInterval / 2
+			}
+			// Check if the timer has expired or not
+			if !timeoutTimer.Stop() {
+				<-timeoutTimer.C
+			}
+		case <-timeoutTimer.C:
+			// Check the status of the kv-store. Use timeout of 2 seconds to avoid forever blocking
+			logger.Info(ctx, "kv-store liveliness-recheck")
+			timeoutCtx, cancelFunc := context.WithTimeout(ctx, 2*time.Second)
+
+			kvStoreChannel <- a.kvClient.IsConnectionUp(timeoutCtx)
+			// Cleanup cancel func resources
+			cancelFunc()
+		}
+	}
+}
+
+/**
+This function checks the liveliness and readiness of the kafka service
+and update the status in the probe.
+*/
+func (a *adapter) checkKafkaReadiness(ctx context.Context) {
+	livelinessChannel := a.kafkaClient.EnableLivenessChannel(ctx, true)
+	healthinessChannel := a.kafkaClient.EnableHealthinessChannel(ctx, true)
+	timeout := a.config.LiveProbeInterval
+	failed := false
+	for {
+		timeoutTimer := time.NewTimer(timeout)
+
+		select {
+		case healthiness := <-healthinessChannel:
+			if !healthiness {
+				// This will eventually cause K8s to restart the container, and will do
+				// so in a way that allows cleanup to continue, rather than an immediate
+				// panic and exit here.
+				probe.UpdateStatusFromContext(ctx, "message-bus", probe.ServiceStatusFailed)
+				failed = true
+			}
+			// Check if the timer has expired or not
+			if !timeoutTimer.Stop() {
+				<-timeoutTimer.C
+			}
+		case liveliness := <-livelinessChannel:
+			if failed {
+				// Failures of the message bus are permanent and can't ever be recovered from,
+				// so make sure we never inadvertently reset a failed state back to unready.
+			} else if !liveliness {
+				// kafka not reachable or down, updating the status to not ready state
+				probe.UpdateStatusFromContext(ctx, "message-bus", probe.ServiceStatusNotReady)
+				timeout = a.config.NotLiveProbeInterval
+			} else {
+				// kafka is reachable , updating the status to running state
+				probe.UpdateStatusFromContext(ctx, "message-bus", probe.ServiceStatusRunning)
+				timeout = a.config.LiveProbeInterval
+			}
+			// Check if the timer has expired or not
+			if !timeoutTimer.Stop() {
+				<-timeoutTimer.C
+			}
+		case <-timeoutTimer.C:
+			logger.Info(ctx, "kafka-proxy-liveness-recheck")
+			// send the liveness probe in a goroutine; we don't want to deadlock ourselves as
+			// the liveness probe may wait (and block) writing to our channel.
+			err := a.kafkaClient.SendLiveness(ctx)
+			if err != nil {
+				// Catch possible error case if sending liveness after Sarama has been stopped.
+				logger.Warnw(ctx, "error-kafka-send-liveness", log.Fields{"error": err})
+			}
+		}
+	}
+}
+
+func (a *adapter) stop(ctx context.Context) {
+	// Stop leadership tracking
+	a.halted = true
+
+	// send exit signal
+	a.exitChannel <- 0
+
+	// Cleanup - applies only if we had a kvClient
+	if a.kvClient != nil {
+		// Release all reservations
+		if err := a.kvClient.ReleaseAllReservations(ctx); err != nil {
+			logger.Infow(ctx, "fail-to-release-all-reservations", log.Fields{"error": err})
+		}
+		// Close the DB connection
+		a.kvClient.Close(ctx)
+	}
+
+	if a.kip != nil {
+		a.kip.Stop(ctx)
+	}
+
+	// TODO:  More cleanup
+}
+
+func newKVClient(ctx context.Context, storeType, address string, timeout time.Duration) (kvstore.Client, error) {
+
+	logger.Infow(ctx, "kv-store-type", log.Fields{"store": storeType})
+	switch storeType {
+	case "consul":
+		return kvstore.NewConsulClient(ctx, address, timeout)
+	case "etcd":
+		return kvstore.NewEtcdClient(ctx, address, timeout, log.FatalLevel)
+	}
+	return nil, errors.New("unsupported-kv-store")
+}
+
+func newKafkaClient(ctx context.Context, clientType, address string) (kafka.Client, error) {
+
+	logger.Infow(ctx, "common-client-type", log.Fields{"client": clientType})
+	switch clientType {
+	case "sarama":
+		return kafka.NewSaramaClient(
+			kafka.Address(address),
+			kafka.ProducerReturnOnErrors(true),
+			kafka.ProducerReturnOnSuccess(true),
+			kafka.ProducerMaxRetries(6),
+			kafka.ProducerRetryBackoff(time.Millisecond*30),
+			kafka.MetadatMaxRetries(15)), nil
+	}
+
+	return nil, errors.New("unsupported-client-type")
+}
+
+func (a *adapter) setKVClient(ctx context.Context) error {
+	client, err := newKVClient(ctx, a.config.KVStoreType, a.config.KVStoreAddress, a.config.KVStoreTimeout)
+	if err != nil {
+		a.kvClient = nil
+		return err
+	}
+	a.kvClient = client
+
+	return nil
+}
+
+func (a *adapter) startInterContainerProxy(ctx context.Context, retries int) (kafka.InterContainerProxy, error) {
+	logger.Infow(ctx, "starting-intercontainer-messaging-proxy", log.Fields{"address": a.config.KafkaAdapterAddress,
+		"topic": a.config.Topic})
+	var err error
+	kip := kafka.NewInterContainerProxy(
+		kafka.InterContainerAddress(a.config.KafkaAdapterAddress),
+		kafka.MsgClient(a.kafkaClient),
+		kafka.DefaultTopic(&kafka.Topic{Name: a.config.Topic}))
+	count := 0
+	for {
+		if err = kip.Start(ctx); err != nil {
+			logger.Warnw(ctx, "error-starting-messaging-proxy", log.Fields{"error": err})
+			if retries == count {
+				return nil, err
+			}
+			count = +1
+			// Take a nap before retrying
+			time.Sleep(2 * time.Second)
+		} else {
+			break
+		}
+	}
+	probe.UpdateStatusFromContext(ctx, "container-proxy", probe.ServiceStatusRunning)
+	logger.Info(ctx, "common-messaging-proxy-created")
+	return kip, nil
+}
+
+func (a *adapter) startOpenOLT(ctx context.Context, kip kafka.InterContainerProxy,
+	cp adapterif.CoreProxy, ap adapterif.AdapterProxy, ep adapterif.EventProxy,
+	cfg *config.AdapterFlags) (*ac.OpenOLT, error) {
+	logger.Info(ctx, "starting-open-olt")
+	var err error
+	sOLT := ac.NewOpenOLT(ctx, a.kip, cp, ap, ep, cfg)
+
+	if err = sOLT.Start(ctx); err != nil {
+		return nil, err
+	}
+
+	logger.Info(ctx, "open-olt-started")
+	return sOLT, nil
+}
+
+func (a *adapter) setupRequestHandler(ctx context.Context, coreInstanceID string, iadapter adapters.IAdapter) error {
+	logger.Info(ctx, "setting-request-handler")
+	requestProxy := com.NewRequestHandlerProxy(coreInstanceID, iadapter, a.coreProxy)
+	if err := a.kip.SubscribeWithRequestHandlerInterface(ctx, kafka.Topic{Name: a.config.Topic}, requestProxy); err != nil {
+		return err
+
+	}
+	probe.UpdateStatusFromContext(ctx, "core-request-handler", probe.ServiceStatusRunning)
+	logger.Info(ctx, "request-handler-setup-done")
+	return nil
+}
+
+func (a *adapter) registerWithCore(ctx context.Context, retries int) error {
+	adapterID := fmt.Sprintf("eponolt_%d", a.config.CurrentReplica)
+	logger.Infow(ctx, "registering-with-core", log.Fields{
+		"adapterID":      adapterID,
+		"currentReplica": a.config.CurrentReplica,
+		"totalReplicas":  a.config.TotalReplicas,
+	})
+	adapterDescription := &voltha.Adapter{
+		Id:      adapterID, // Unique name for the device type
+		Vendor:  "VOLTHA EPON OLT",
+		Version: version.VersionInfo.Version,
+		// TODO once we'll be ready to support multiple versions of the OpenOLT adapter
+		// the Endpoint will have to change to `openolt_<currentReplica`>
+		Endpoint:       a.config.Topic,
+		Type:           "eponolt",
+		CurrentReplica: int32(a.config.CurrentReplica),
+		TotalReplicas:  int32(a.config.TotalReplicas),
+	}
+	types := []*voltha.DeviceType{{
+		Id:                          "eponolt",
+		Adapter:                     "eponolt", // Type of the adapter that handles device type
+		AcceptsBulkFlowUpdate:       false,     // Currently openolt adapter does not support bulk flow handling
+		AcceptsAddRemoveFlowUpdates: true}}
+	deviceTypes := &voltha.DeviceTypes{Items: types}
+	count := 0
+	for {
+		if err := a.coreProxy.RegisterAdapter(log.WithSpanFromContext(context.TODO(), ctx), adapterDescription, deviceTypes); err != nil {
+			logger.Warnw(ctx, "registering-with-core-failed", log.Fields{"error": err})
+			if retries == count {
+				return err
+			}
+			count++
+			// Take a nap before retrying
+			time.Sleep(2 * time.Second)
+		} else {
+			break
+		}
+	}
+	probe.UpdateStatusFromContext(ctx, "register-with-core", probe.ServiceStatusRunning)
+	logger.Info(ctx, "registered-with-core")
+	return nil
+}
+
+func waitForExit(ctx context.Context) int {
+	signalChannel := make(chan os.Signal, 1)
+	signal.Notify(signalChannel,
+		syscall.SIGHUP,
+		syscall.SIGINT,
+		syscall.SIGTERM,
+		syscall.SIGQUIT)
+
+	exitChannel := make(chan int)
+
+	go func() {
+		s := <-signalChannel
+		switch s {
+		case syscall.SIGHUP,
+			syscall.SIGINT,
+			syscall.SIGTERM,
+			syscall.SIGQUIT:
+			logger.Infow(ctx, "closing-signal-received", log.Fields{"signal": s})
+			exitChannel <- 0
+		default:
+			logger.Infow(ctx, "unexpected-signal-received", log.Fields{"signal": s})
+			exitChannel <- 1
+		}
+	}()
+
+	code := <-exitChannel
+	return code
+}
+
+func printBanner() {
+  fmt.Println(`  _____ ____   ____  __    _  ____  _   _______   `)
+  fmt.Println(` | ____|  _ \ / __ \|  \  | |/ __ \| | |__   __|  `)
+  fmt.Println(` | |___| |_| | |  | |   \ | | |  | | |    | |     `)
+  fmt.Println(` |  ___|  __/| |  | | |\ \| | |  | | |    | |     `)
+  fmt.Println(` | |___| |   | |__| | | \   | |__| | |____| |     `)
+  fmt.Println(` |_____|_|    \____/|_|  \__|\____/|______|_|     `) 
+}
+
+
+
+func printVersion() {
+	fmt.Println("VOLTHA OpenOLT Adapter")
+	fmt.Println(version.VersionInfo.String("  "))
+}
+
+func main() {
+	ctx := context.Background()
+	start := time.Now()
+
+	cf := config.NewAdapterFlags()
+	cf.ParseCommandArguments()
+
+	// Setup logging
+
+	logLevel, err := log.StringToLogLevel(cf.LogLevel)
+	if err != nil {
+		logger.Fatalf(ctx, "Cannot setup logging, %s", err)
+	}
+
+	// Setup default logger - applies for packages that do not have specific logger set
+	if _, err := log.SetDefaultLogger(log.JSON, logLevel, log.Fields{"instanceId": cf.InstanceID}); err != nil {
+		logger.With(log.Fields{"error": err}).Fatal(ctx, "Cannot setup logging")
+	}
+
+	// Update all loggers (provisionned via init) with a common field
+	if err := log.UpdateAllLoggers(log.Fields{"instanceId": cf.InstanceID}); err != nil {
+		logger.With(log.Fields{"error": err}).Fatal(ctx, "Cannot setup logging")
+	}
+
+	log.SetAllLogLevel(logLevel)
+
+	realMain()
+
+	defer func() {
+		err := log.CleanUp()
+		if err != nil {
+			logger.Errorw(context.Background(), "unable-to-flush-any-buffered-log-entries", log.Fields{"error": err})
+		}
+	}()
+
+	// Print version / build information and exit
+	if cf.DisplayVersionOnly {
+		printVersion()
+		return
+	}
+
+	// Print banner if specified
+	if cf.Banner {
+		printBanner()
+	}
+
+	logger.Infow(ctx, "config", log.Fields{"config": *cf})
+
+	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+
+	ad := newAdapter(cf)
+
+	p := &probe.Probe{}
+	go p.ListenAndServe(ctx, ad.config.ProbeAddress)
+
+	probeCtx := context.WithValue(ctx, probe.ProbeContextKey, p)
+
+	closer, err := log.GetGlobalLFM().InitTracingAndLogCorrelation(cf.TraceEnabled, cf.TraceAgentAddress, cf.LogCorrelationEnabled)
+	if err != nil {
+		logger.Warnw(ctx, "unable-to-initialize-tracing-and-log-correlation-module", log.Fields{"error": err})
+	} else {
+		defer log.TerminateTracing(closer)
+	}
+
+	go ad.start(probeCtx)
+
+	code := waitForExit(ctx)
+	logger.Infow(ctx, "received-a-closing-signal", log.Fields{"code": code})
+
+	// Cleanup before leaving
+	ad.stop(ctx)
+
+	elapsed := time.Since(start)
+	logger.Infow(ctx, "run-time", log.Fields{"instanceId": ad.config.InstanceID, "time": elapsed / time.Second})
+}