First Commit of Voltha-Go-Controller from Radisys

Change-Id: I8e2e908e7ab09a4fe3d86849da18b6d69dcf4ab0
diff --git a/internal/pkg/application/dhcpserverhandler.go b/internal/pkg/application/dhcpserverhandler.go
new file mode 100644
index 0000000..c208910
--- /dev/null
+++ b/internal/pkg/application/dhcpserverhandler.go
@@ -0,0 +1,248 @@
+/*
+* Copyright 2022-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 application
+
+import (
+	"sync"
+	"time"
+
+	"github.com/opencord/voltha-lib-go/v7/pkg/log"
+)
+
+const (
+	dhcpTimeout uint8 = 60
+)
+
+// done channel required to gracefully stop dhcp server handler thread
+var done = make(chan bool)
+
+// dhcpServerInfo map having dhcp network as key and dhcp request response transaction as value
+var dhcpServerInfo map[dhcpServerTag]dhcpTransactionInfo
+
+// alarmsRaised is struct having array of dhcp network for which dhcp unreachable alarm raised
+var alarmsRaised alarmsRaisedInfo
+
+// mux is mutex variable used for lock unlock
+var mux sync.Mutex
+
+// StartDhcpServerHandler starts go routine periodically(every second) to verify DHCP server reachability.
+func StartDhcpServerHandler() {
+	// Intialize global dhcp map and ticker as one second
+	dhcpServerInfo = make(map[dhcpServerTag]dhcpTransactionInfo)
+	ticker := time.NewTicker(1 * time.Second)
+
+	// go routine runs checkDhcpTimeout every second and exit if done value is set.
+	go func() {
+		for {
+			select {
+			case <-done:
+				ticker.Stop()
+				return
+			case <-ticker.C:
+				mux.Lock()
+				checkDhcpTimeout()
+				mux.Unlock()
+
+			}
+		}
+	}()
+}
+
+// checkDhcpTimeout method called every second to verify dhcp timeout for each DHCP network
+func checkDhcpTimeout() {
+	// logger.Debugw(ctx, "[dhcptimeout] DHCP MAP Info", log.Fields{"Map": dhcpServerInfo})
+	for dsTag, dtInfo := range dhcpServerInfo {
+		dtInfo.decrementTimer()
+		if dtInfo.getTimer() == 0 {
+			logger.Debugw(ctx, "[dhcptimeout]Timer Expired", log.Fields{"ctag": dsTag.cTag, "stag": dsTag.sTag})
+			if dtInfo.getReceivedResponseCount() == 0 && !alarmsRaised.isexist(dsTag) {
+				alarmsRaised.add(dsTag)
+				logger.Infow(ctx, "Alarms Raised", log.Fields{"ctag": dsTag.cTag, "stag": dsTag.sTag})
+			}
+
+			// Reset helps in
+			// case 1: when 2 requests, 1 response received within timeout interval.
+			// case 2: 1 request and no response even after timeout. (Unreachable alarm raised)
+			// In both cases, reset method provides additional timeout to receive response before deleting
+			dtInfo.resetRequestResponseCount(dhcpTimeout)
+
+			// Delete dhcp entry in map and continue to process next entry if pending request set to 0
+			if dtInfo.getPendingRequestCount() == 0 {
+				delete(dhcpServerInfo, dsTag)
+				logger.Debugw(ctx, "[dhcptimeout]DhcpServerTag info removed", log.Fields{"ctag": dsTag.cTag, "stag": dsTag.sTag})
+				// logger.Debugw(ctx, "[dhcptimeout] DHCP MAP Info", log.Fields{"Map": dhcpServerInfo})
+				continue
+			}
+		}
+		// Update decremented timer value and continue loop
+		dhcpServerInfo[dsTag] = dtInfo
+	}
+}
+
+// dhcpRequestReceived called for every DHCP request received from client.
+func dhcpRequestReceived(cTag, sTag uint16, smac string) {
+	var dtInfo dhcpTransactionInfo
+	var valueExist bool
+	dsTag := newDhcpServerTag(cTag, sTag)
+
+	mux.Lock()
+	logger.Debugw(ctx, "dhcpRequestReceived", log.Fields{"ctag": cTag, "stag": sTag, "smac": smac})
+	if dtInfo, valueExist = dhcpServerInfo[dsTag]; !valueExist {
+		dtInfo = newDhcpTransactionInfo(dhcpTimeout, smac)
+		dtInfo.incrementPendingRequestCount()
+	}
+
+	// Source mac received in dhcp request is not same as dtInfo mac then
+	// Its new subscriber request, hence increment pending request count.
+	// If multiple dhcp request received with same mac are ignored.
+	if dtInfo.smac != smac {
+		dtInfo.incrementPendingRequestCount()
+	}
+
+	dhcpServerInfo[dsTag] = dtInfo
+	mux.Unlock()
+}
+
+// dhcpResponseReceived called for every DHCP response received from dhcp server.
+func dhcpResponseReceived(cTag, sTag uint16) {
+	var dtInfo dhcpTransactionInfo
+	var valueExist bool
+	dsTag := newDhcpServerTag(cTag, sTag)
+
+	mux.Lock()
+	logger.Debugw(ctx, "dhcpResponseReceived", log.Fields{"ctag": cTag, "stag": sTag})
+	if dtInfo, valueExist = dhcpServerInfo[dsTag]; !valueExist {
+		logger.Warnw(ctx, "Ignore unknown response", log.Fields{"DhcpResp": dsTag})
+		mux.Unlock()
+		return
+	}
+
+	// If already unreachable alarm raised, clear and remove from array
+	if alarmsRaised.isexist(dsTag) {
+		alarmsRaised.remove(dsTag)
+		logger.Infow(ctx, "Alarm Cleared", log.Fields{"ctag": dsTag.cTag, "stag": dsTag.sTag})
+	}
+
+	// Increments received count and decrement pending count
+	dtInfo.responseReceived()
+	logger.Debugw(ctx, "Updated dtInfo", log.Fields{"pendingReq": dtInfo.pendingRequestCount, "receivedReq": dtInfo.receivedResponseCount})
+
+	if dtInfo.getPendingRequestCount() == 0 {
+		delete(dhcpServerInfo, dsTag)
+	} else {
+		dhcpServerInfo[dsTag] = dtInfo
+	}
+	mux.Unlock()
+}
+
+// StopDhcpServerHandler stops dhcp server handler go routine
+func StopDhcpServerHandler() {
+	done <- true
+}
+
+// dhcpServerTag contains unique dhcp network information
+type dhcpServerTag struct {
+	sTag uint16
+	cTag uint16
+}
+
+func newDhcpServerTag(cTag, sTag uint16) dhcpServerTag {
+	var d dhcpServerTag
+	d.sTag = sTag
+	d.cTag = cTag
+	return d
+}
+
+// dhcpTransactionInfo contains DHCP request response transaction information.
+type dhcpTransactionInfo struct {
+	timer                 uint8
+	pendingRequestCount   uint32
+	receivedResponseCount uint32
+	previousRequestCount  uint32
+	smac                  string
+}
+
+func newDhcpTransactionInfo(timer uint8, smac string) dhcpTransactionInfo {
+	var dt dhcpTransactionInfo
+	dt.timer = timer
+	dt.smac = smac
+	return dt
+}
+
+func (dt *dhcpTransactionInfo) getTimer() uint8 {
+	return dt.timer
+}
+
+func (dt *dhcpTransactionInfo) decrementTimer() uint8 {
+	dt.timer--
+	return dt.timer
+}
+
+func (dt *dhcpTransactionInfo) getPendingRequestCount() uint32 {
+	return dt.pendingRequestCount
+}
+
+func (dt *dhcpTransactionInfo) incrementPendingRequestCount() {
+	dt.pendingRequestCount++
+}
+
+func (dt *dhcpTransactionInfo) getReceivedResponseCount() uint32 {
+	return dt.receivedResponseCount
+}
+
+func (dt *dhcpTransactionInfo) responseReceived() {
+	dt.receivedResponseCount++
+	dt.pendingRequestCount--
+}
+
+func (dt *dhcpTransactionInfo) resetRequestResponseCount(timer uint8) {
+	if dt.pendingRequestCount >= dt.previousRequestCount {
+		dt.pendingRequestCount = dt.pendingRequestCount - dt.previousRequestCount
+	}
+	dt.previousRequestCount = dt.pendingRequestCount
+	dt.receivedResponseCount = 0
+	dt.timer = timer
+}
+
+// alarmsRaisedInfo contains the all networks alarm raised information
+type alarmsRaisedInfo struct {
+	arrayInfo []dhcpServerTag
+}
+
+// add an entry into alarm raised array
+func (a *alarmsRaisedInfo) add(val dhcpServerTag) {
+	a.arrayInfo = append(a.arrayInfo, val)
+}
+
+// isexist check if entry exist in alarm raised array
+func (a *alarmsRaisedInfo) isexist(val dhcpServerTag) bool {
+	for _, srvTag := range a.arrayInfo {
+		if srvTag == val {
+			return true
+		}
+	}
+	return false
+}
+
+// remove deletes given entry from alarm raised array
+func (a *alarmsRaisedInfo) remove(val dhcpServerTag) {
+	for ind := range a.arrayInfo {
+		if a.arrayInfo[ind] == val {
+			a.arrayInfo = append(a.arrayInfo[:ind], a.arrayInfo[ind+1:]...)
+			break
+		}
+	}
+}