[VOL-4819] REST Interface for device and ports

Change-Id: I10e136ad5cfe32878fbbca5f897491de721c2fba
diff --git a/internal/pkg/controller/auditdevice.go b/internal/pkg/controller/auditdevice.go
index 33be2e5..2ba8772 100644
--- a/internal/pkg/controller/auditdevice.go
+++ b/internal/pkg/controller/auditdevice.go
@@ -174,7 +174,7 @@
 
 		// Error is ignored as it only drops duplicate ports
 		logger.Infow(ctx, "Calling AddPort", log.Fields{"No": mp.PortNo, "Name": mp.Name})
-		if err := ad.device.AddPort(cntx, mp.PortNo, mp.Name); err != nil {
+		if err := ad.device.AddPort(cntx, mp); err != nil {
 			logger.Warnw(ctx, "AddPort Failed", log.Fields{"No": mp.PortNo, "Name": mp.Name, "Reason": err})
 		}
 		if mp.State == uint32(ofp.OfpPortState_OFPPS_LIVE) {
diff --git a/internal/pkg/controller/changeevent.go b/internal/pkg/controller/changeevent.go
index 46b1be5..9a14d1a 100644
--- a/internal/pkg/controller/changeevent.go
+++ b/internal/pkg/controller/changeevent.go
@@ -74,7 +74,7 @@
 		state := status.PortStatus.Desc.State
 		logger.Infow(ctx, "Process Port Change Event", log.Fields{"Port No": portNo, "Port Name": portName, "State": state, "Reason": status.PortStatus.Reason})
 		if status.PortStatus.Reason == ofp.OfpPortReason_OFPPR_ADD {
-			_ = cet.device.AddPort(ctx, portNo, portName)
+			_ = cet.device.AddPort(ctx, status.PortStatus.Desc)
 			if state == uint32(ofp.OfpPortState_OFPPS_LIVE) {
 				cet.device.ProcessPortState(ctx, portNo, state)
 			}
diff --git a/internal/pkg/controller/controller.go b/internal/pkg/controller/controller.go
index eb48c28..ead1182 100644
--- a/internal/pkg/controller/controller.go
+++ b/internal/pkg/controller/controller.go
@@ -91,7 +91,7 @@
 // AddDevice to add device
 func (v *VoltController) AddDevice(cntx context.Context, config *intf.VPClientCfg) intf.IVPClient {
 
-	d := NewDevice(cntx, config.DeviceID, config.SerialNum, config.VolthaClient, config.SouthBoundID)
+	d := NewDevice(cntx, config.DeviceID, config.SerialNum, config.VolthaClient, config.SouthBoundID, config.MfrDesc, config.HwDesc, config.SwDesc)
 	v.devices[config.DeviceID] = d
 	v.app.AddDevice(cntx, d.ID, d.SerialNum, config.SouthBoundID)
 
diff --git a/internal/pkg/controller/device.go b/internal/pkg/controller/device.go
index d60855a..9fbe172 100644
--- a/internal/pkg/controller/device.go
+++ b/internal/pkg/controller/device.go
@@ -19,8 +19,10 @@
 	"context"
 	"encoding/json"
 	"errors"
+	"fmt"
 	infraerror "voltha-go-controller/internal/pkg/errorcodes"
 	"strconv"
+	"strings"
 	"sync"
 	"time"
 
@@ -53,18 +55,26 @@
 // DevicePort structure
 type DevicePort struct {
 	tasks.Tasks
-	Name    string
-	ID      uint32
-	State   PortState
-	Version string
+	Name      string
+	ID        uint32
+	State     PortState
+	Version   string
+	HwAddr    string
+	CurrSpeed uint32
+	MaxSpeed  uint32
 }
 
 // NewDevicePort is the constructor for DevicePort
-func NewDevicePort(id uint32, name string) *DevicePort {
+func NewDevicePort(mp *ofp.OfpPort) *DevicePort {
 	var port DevicePort
 
-	port.ID = id
-	port.Name = name
+	port.ID = mp.PortNo
+	port.Name = mp.Name
+
+	//port.HwAddr = strings.Trim(strings.Join(strings.Fields(fmt.Sprint("%02x", mp.HwAddr)), ":"), "[]")
+	port.HwAddr = strings.Trim(strings.ReplaceAll(fmt.Sprintf("%02x", mp.HwAddr), " ", ":"), "[]")
+	port.CurrSpeed = mp.CurrSpeed
+	port.MaxSpeed = mp.MaxSpeed
 	port.State = PortStateDown
 	return &port
 }
@@ -127,10 +137,14 @@
 	flowQueue             map[uint32]*UniIDFlowQueue // key is hash ID generated and value is UniIDFlowQueue.
 	deviceAuditInProgress bool
 	SouthBoundID          string
+	MfrDesc               string
+	HwDesc                string
+	SwDesc                string
+	TimeStamp             time.Time
 }
 
 // NewDevice is the constructor for Device
-func NewDevice(cntx context.Context, id string, slno string, vclientHldr *holder.VolthaServiceClientHolder, southBoundID string) *Device {
+func NewDevice(cntx context.Context, id string, slno string, vclientHldr *holder.VolthaServiceClientHolder, southBoundID, mfr, hwDesc, swDesc string) *Device {
 	var device Device
 	device.ID = id
 	device.SerialNum = slno
@@ -143,6 +157,10 @@
 	device.flowQueue = make(map[uint32]*UniIDFlowQueue)
 	//Get the flowhash from db and update the flowhash variable in the device.
 	device.SouthBoundID = southBoundID
+	device.MfrDesc = mfr
+	device.HwDesc = hwDesc
+	device.SwDesc = swDesc
+	device.TimeStamp = time.Now()
 	flowHash, err := db.GetFlowHash(cntx, id)
 	if err != nil {
 		device.flowHash = DefaultMaxFlowQueues
@@ -446,10 +464,11 @@
 
 // AddPort to add the port as requested by the device/VOLTHA
 // Inform the application if the port is successfully added
-func (d *Device) AddPort(cntx context.Context, id uint32, name string) error {
+func (d *Device) AddPort(cntx context.Context, mp *ofp.OfpPort) error {
 	d.portLock.Lock()
 	defer d.portLock.Unlock()
-
+	id := mp.PortNo
+	name := mp.Name
 	if _, ok := d.PortsByID[id]; ok {
 		return errors.New("Duplicate port")
 	}
@@ -457,7 +476,7 @@
 		return errors.New("Duplicate port")
 	}
 
-	p := NewDevicePort(id, name)
+	p := NewDevicePort(mp)
 	d.PortsByID[id] = p
 	d.PortsByName[name] = p
 	d.WritePortToDb(cntx, p)
@@ -631,6 +650,7 @@
 
 	logger.Warnw(ctx, "Device State change Ind: UP", log.Fields{"Device": d.ID})
 	d.State = DeviceStateUP
+	d.TimeStamp = time.Now()
 	GetController().DeviceUpInd(d.ID)
 
 	logger.Warnw(ctx, "Device State change Ind: UP, trigger Audit Tasks", log.Fields{"Device": d.ID})
@@ -667,6 +687,7 @@
 func (d *Device) DeviceUpInd() {
 	logger.Warnw(ctx, "Device State change Ind: UP", log.Fields{"Device": d.ID})
 	d.State = DeviceStateUP
+	d.TimeStamp = time.Now()
 	GetController().DeviceUpInd(d.ID)
 
 	logger.Warnw(ctx, "Device State change Ind: UP, trigger Audit Tasks", log.Fields{"Device": d.ID})
@@ -684,6 +705,7 @@
 func (d *Device) DeviceDownInd() {
 	logger.Warnw(ctx, "Device State change Ind: Down", log.Fields{"Device": d.ID})
 	d.State = DeviceStateDOWN
+	d.TimeStamp = time.Now()
 	GetController().DeviceDownInd(d.ID)
 }
 
@@ -698,6 +720,7 @@
 	}
 
 	d.State = DeviceStateREBOOTED
+	d.TimeStamp = time.Now()
 	GetController().SetRebootInProgressForDevice(d.ID)
 	GetController().DeviceRebootInd(cntx, d.ID, d.SerialNum, d.SouthBoundID)
 	d.ReSetAllPortStates(cntx)
@@ -707,6 +730,7 @@
 func (d *Device) DeviceDisabledInd(cntx context.Context) {
 	logger.Warnw(ctx, "Device State change Ind: Disabled", log.Fields{"Device": d.ID})
 	d.State = DeviceStateDISABLED
+	d.TimeStamp = time.Now()
 	GetController().DeviceDisableInd(cntx, d.ID)
 }
 
diff --git a/internal/pkg/intf/vpagent.go b/internal/pkg/intf/vpagent.go
index 094c58b..3dfbcb2 100644
--- a/internal/pkg/intf/vpagent.go
+++ b/internal/pkg/intf/vpagent.go
@@ -17,6 +17,7 @@
 
 import (
 	"context"
+	"time"
 
 	"voltha-go-controller/internal/pkg/holder"
 
@@ -28,6 +29,10 @@
 	DeviceID         string
 	SerialNum        string
 	SouthBoundID     string
+	MfrDesc          string
+	HwDesc           string
+	SwDesc           string
+	TimeStamp        time.Time
 	VolthaClient     *holder.VolthaServiceClientHolder
 	PacketOutChannel chan *ofp.PacketOut
 }
diff --git a/internal/pkg/vpagent/refresh.go b/internal/pkg/vpagent/refresh.go
index 2761408..71e33ad 100644
--- a/internal/pkg/vpagent/refresh.go
+++ b/internal/pkg/vpagent/refresh.go
@@ -105,15 +105,25 @@
 	vpa.mapLock.Lock()
 	defer vpa.mapLock.Unlock()
 	var serialNum = "Unknown"
+	var mfrDesc = "Unknown"
+	var hwDesc = "Unknown"
+	var swDesc = "Unknown"
 	if device.Desc != nil {
 		serialNum = device.Desc.SerialNum
+		mfrDesc = device.Desc.MfrDesc
+		hwDesc = device.Desc.HwDesc
+		swDesc = device.Desc.SwDesc
 	}
 	vpc := vpa.clientMap[device.Id]
 	if vpc == nil {
 		vpa.VPClientAgent.AddNewDevice(&intf.VPClientCfg{
 			DeviceID:         device.Id,
 			SerialNum:        serialNum,
+			MfrDesc:          mfrDesc,
+			HwDesc:           hwDesc,
+			SwDesc:           swDesc,
 			SouthBoundID:     device.RootDeviceId,
+			TimeStamp:        time.Now(),
 			VolthaClient:     vpa.volthaClient,
 			PacketOutChannel: vpa.packetOutChannel,
 		})
diff --git a/voltha-go-controller/nbi/rest.go b/voltha-go-controller/nbi/rest.go
index e768cb4..d1db3cc 100644
--- a/voltha-go-controller/nbi/rest.go
+++ b/voltha-go-controller/nbi/rest.go
@@ -34,6 +34,8 @@
 	IgmpProxyPath   string = "/igmp-proxy/"
 	MulticastPath   string = "/multicast/"
 	FlowsPath       string = "/flows/"
+	DevicesPath       string = "/devices"
+	PortsPath       string = "/devices/ports"
 	FlowsPerDeviceIDPath string = "/flows/{deviceId}"
 	FlowPerDeviceIDFlowIDPath string = "/flows/{deviceId}/{flowId}"
 	PendingFlowsPath          string = "/flows/pending/"
@@ -63,6 +65,8 @@
         mu.HandleFunc(ServicePortStagCtagTpIDPath, (&onos_nbi.ServiceAdapter{}).ServeHTTPWithPortName)
         mu.HandleFunc(AllocationsPath, (&onos_nbi.DhcpRelayHandle{}).ServeHTTP)
         mu.HandleFunc(AllocationsDeviceIDPath, (&onos_nbi.DhcpRelayHandle{}).ServeHTTP)
+        mu.HandleFunc(DevicesPath, (&onos_nbi.DeviceHandle{}).ServeHTTP)
+        mu.HandleFunc(PortsPath, (&onos_nbi.DevicePortHandle{}).ServeHTTP)
 
 	err := http.ListenAndServe(":8181", mu)
 	logger.Infow(ctx, "Rest Server Started", log.Fields{"Error": err})
diff --git a/voltha-go-controller/onos_nbi/deviceportadapter.go b/voltha-go-controller/onos_nbi/deviceportadapter.go
new file mode 100644
index 0000000..cd8538b
--- /dev/null
+++ b/voltha-go-controller/onos_nbi/deviceportadapter.go
@@ -0,0 +1,121 @@
+/*
+* 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 onos_nbi
+
+import (
+	"encoding/json"
+	"net/http"
+
+	app "voltha-go-controller/internal/pkg/application"
+	"voltha-go-controller/log"
+)
+
+// DeviceHandle Handle DeviceIDList Requests
+type DeviceHandle struct {
+}
+
+// DevicePortHandle Handle Ports Requests
+type DevicePortHandle struct {
+}
+
+// ServeHTTP to serve HTTP requests
+func (dh *DeviceHandle) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	logger.Infow(ctx, "Received-northbound-request", log.Fields{"Method": r.Method, "URL": r.URL})
+	switch r.Method {
+	case "GET":
+		dh.GetDeviceList(w, r)
+	default:
+		logger.Warnw(ctx, "Unsupported Method", log.Fields{"Method": r.Method})
+	}
+}
+
+// GetDeviceList to get device id list
+func (dh *DeviceHandle) GetDeviceList(w http.ResponseWriter, r *http.Request) {
+
+	va := app.GetApplication()
+	var deviceListResp DeviceEntry
+	deviceListResp.Devices = []Device{}
+
+	getDeviceList := func(key, value interface{}) bool {
+		voltDevice := value.(*app.VoltDevice)
+		device := convertVoltDeviceToDevice(voltDevice)
+		deviceListResp.Devices = append(deviceListResp.Devices, device)
+		return true
+	}
+	va.DevicesDisc.Range(getDeviceList)
+
+	deviceListJSON, err := json.Marshal(deviceListResp)
+	if err != nil {
+		logger.Errorw(ctx, "Error occurred while marshaling device list response", log.Fields{"Error": err})
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+
+	w.Header().Add("Content-Type", "application/json")
+	_, err = w.Write(deviceListJSON)
+	if err != nil {
+		logger.Errorw(ctx, "error in sending deviceList response", log.Fields{"Error": err})
+		w.WriteHeader(http.StatusInternalServerError)
+	}
+}
+
+// ServeHTTP to serve HTTP requests
+func (dh *DevicePortHandle) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	logger.Infow(ctx, "Received-northbound-request", log.Fields{"Method": r.Method, "URL": r.URL})
+	switch r.Method {
+	case "GET":
+		dh.GetPortList(w, r)
+	default:
+		logger.Warnw(ctx, "Unsupported Method", log.Fields{"Method": r.Method})
+	}
+}
+
+// GetPortList to get device id list
+func (dh *DevicePortHandle) GetPortList(w http.ResponseWriter, r *http.Request) {
+
+	va := app.GetApplication()
+	var portListResp PortEntry
+	portListResp.Ports = []Port{}
+
+	getPortList := func(key, value interface{}) bool {
+		voltPort := value.(*app.VoltPort)
+		port := convertVoltPortToPort(voltPort)
+		portListResp.Ports = append(portListResp.Ports, port)
+		return true
+	}
+
+	getDeviceList := func(key, value interface{}) bool {
+		voltDevice := value.(*app.VoltDevice)
+		voltDevice.Ports.Range(getPortList)
+		return true
+	}
+	va.DevicesDisc.Range(getDeviceList)
+
+	portListJSON, err := json.Marshal(portListResp)
+	if err != nil {
+		logger.Errorw(ctx, "Error occurred while marshaling port list response", log.Fields{"Error": err})
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+
+	w.Header().Add("Content-Type", "application/json")
+	_, err = w.Write(portListJSON)
+	if err != nil {
+		logger.Errorw(ctx, "error in sending portList response", log.Fields{"Error": err})
+		w.WriteHeader(http.StatusInternalServerError)
+	}
+}
+
diff --git a/voltha-go-controller/onos_nbi/models.go b/voltha-go-controller/onos_nbi/models.go
index 101f160..9f83404 100644
--- a/voltha-go-controller/onos_nbi/models.go
+++ b/voltha-go-controller/onos_nbi/models.go
@@ -1,4 +1,5 @@
 /*
+
 * 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.
@@ -19,6 +20,7 @@
 	"strconv"
 	"voltha-go-controller/internal/pkg/of"
 	app "voltha-go-controller/internal/pkg/application"
+	"voltha-go-controller/internal/pkg/controller"
 )
 
 const (
@@ -397,6 +399,7 @@
 
 func ConvertFlowToFlowEntry (subFlow *of.VoltSubFlow) FlowEntry {
 	var flowEntry FlowEntry
+	flowEntry.Flows = []Flow{}
 	flow := ConvertVoltSubFlowToOnosFlow(subFlow)
 	flowEntry.Flows = append(flowEntry.Flows, flow)
 	return flowEntry
@@ -404,6 +407,7 @@
 
 func ConvertFlowsToFlowEntry (subFlows []*of.VoltSubFlow) FlowEntry {
 	var flowEntry FlowEntry
+	flowEntry.Flows = []Flow{}
 	for _, subFlow := range subFlows {
 		flow := ConvertVoltSubFlowToOnosFlow(subFlow)
 		flowEntry.Flows = append(flowEntry.Flows, flow)
@@ -574,3 +578,97 @@
         return subs
 }
 
+type DeviceEntry struct {
+	Devices []Device `json:"devices"`
+}
+
+type Device struct {
+	ID                      string `json:"id"`
+	Type                    string `json:"type"`
+	Available               bool   `json:"available"`
+	Role                    string `json:"role"`
+	Mfr                     string `json:"mfr"`
+	Hw                      string `json:"hw"`
+	Sw                      string `json:"sw"`
+	Serial                  string `json:"serial"`
+	Driver                  string `json:"driver"`
+	ChassisID               string `json:"chassisId"`
+	LastUpdate              string `json:"lastUpdate"`
+	HumanReadableLastUpdate string `json:"humanReadableLastUpdate"`
+	Annotations             DeviceAnnotations `json:"annotations"`
+}
+type DeviceAnnotations struct {
+	ChannelID         string `json:"channelId"`
+	ManagementAddress string `json:"managementAddress"`
+	Protocol          string `json:"protocol"`
+}
+
+func convertVoltDeviceToDevice(voltDevice *app.VoltDevice) Device {
+	var device Device
+
+	d, err := controller.GetController().GetDevice(voltDevice.Name)
+	if err != nil {
+		device.ID = voltDevice.Name
+		return device
+	}
+	device.ID = d.ID
+	if d.State == controller.DeviceStateUP {
+		device.Available = true
+	} else {
+		device.Available = false
+	}
+	device.Serial = d.SerialNum
+	device.Mfr    = d.MfrDesc
+	device.Hw     = d.HwDesc
+	device.Sw     = d.SwDesc
+	device.LastUpdate = d.TimeStamp.String()
+	device.HumanReadableLastUpdate = d.TimeStamp.String()
+	return device
+}
+type PortEntry struct {
+	Ports []Port `json:"ports"`
+}
+
+type Port struct {
+	Element     string `json:"element"`
+	Port        string `json:"port"`
+	IsEnabled   bool   `json:"isEnabled"`
+	Type        string `json:"type"`
+	PortSpeed   int    `json:"portSpeed"`
+	Annotations PortAnnotations `json:"annotations"`
+}
+type PortAnnotations struct {
+	AdminState string `json:"adminState"`
+	PortMac    string `json:"portMac"`
+	PortName   string `json:"portName"`
+}
+
+func convertVoltPortToPort(voltPort *app.VoltPort) Port {
+	var port Port
+	port.Port = strconv.Itoa(int(voltPort.ID))
+	port.Element = voltPort.Device
+	if voltPort.State == app.PortStateUp {
+		port.IsEnabled = true
+	} else {
+		port.IsEnabled = false
+	}
+	if voltPort.Type == app.VoltPortTypeNni {
+		port.Type = "fiber"
+	} else {
+		port.Type = "copper"
+	}
+	port.Annotations.AdminState = "enabled"
+	port.Annotations.PortName = voltPort.Name
+
+	device, err := controller.GetController().GetDevice(voltPort.Device)
+	if err != nil {
+		return port
+	}
+
+	devicePort := device.GetPortByName(voltPort.Name)
+	if devicePort != nil {
+		port.PortSpeed = int(devicePort.MaxSpeed)
+		port.Annotations.PortMac = devicePort.HwAddr
+	}
+	return port
+}