VOL-2463 : Enable and disable pon/NNI port
This patch has dependency on https://gerrit.opencord.org/#/c/17105/

Change-Id: I9b63fcf332204d282f4cc3a2056a73a03df6b507
diff --git a/internal/pkg/commands/devices.go b/internal/pkg/commands/devices.go
index 36c4762..0943303 100644
--- a/internal/pkg/commands/devices.go
+++ b/internal/pkg/commands/devices.go
@@ -23,6 +23,8 @@
 	"github.com/jhump/protoreflect/dynamic"
 	"github.com/opencord/voltctl/pkg/format"
 	"github.com/opencord/voltctl/pkg/model"
+	"os"
+	"strconv"
 	"strings"
 )
 
@@ -53,6 +55,8 @@
 
 type DeviceId string
 
+type PortNum uint32
+
 type DeviceDelete struct {
 	Args struct {
 		Ids []DeviceId `positional-arg-name:"DEVICE_ID" required:"yes"`
@@ -98,6 +102,20 @@
 	} `positional-args:"yes"`
 }
 
+type DevicePortEnable struct {
+	Args struct {
+		Id     DeviceId `positional-arg-name:"DEVICE_ID" required:"yes"`
+		PortId PortNum  `positional-arg-name:"PORT_NUMBER" required:"yes"`
+	} `positional-args:"yes"`
+}
+
+type DevicePortDisable struct {
+	Args struct {
+		Id     DeviceId `positional-arg-name:"DEVICE_ID" required:"yes"`
+		PortId PortNum  `positional-arg-name:"PORT_NUMBER" required:"yes"`
+	} `positional-args:"yes"`
+}
+
 type DeviceOpts struct {
 	List    DeviceList     `command:"list"`
 	Create  DeviceCreate   `command:"create"`
@@ -105,9 +123,13 @@
 	Enable  DeviceEnable   `command:"enable"`
 	Disable DeviceDisable  `command:"disable"`
 	Flows   DeviceFlowList `command:"flows"`
-	Ports   DevicePortList `command:"ports"`
-	Inspect DeviceInspect  `command:"inspect"`
-	Reboot  DeviceReboot   `command:"reboot"`
+	Port    struct {
+		List    DevicePortList    `command:"list"`
+		Enable  DevicePortEnable  `command:"enable"`
+		Disable DevicePortDisable `command:"disable"`
+	} `command:"port"`
+	Inspect DeviceInspect `command:"inspect"`
+	Reboot  DeviceReboot  `command:"reboot"`
 }
 
 var deviceOpts = DeviceOpts{}
@@ -118,6 +140,83 @@
 	}
 }
 
+func (i *PortNum) Complete(match string) []flags.Completion {
+	conn, err := NewConnection()
+	if err != nil {
+		return nil
+	}
+	defer conn.Close()
+
+	descriptor, method, err := GetMethod("device-ports")
+	if err != nil {
+		return nil
+	}
+
+	/*
+	 * The command line args when completing for PortNum will be a DeviceId
+	 * followed by one or more PortNums. So walk the argument list from the
+	 * end and find the first argument that is enable/disable as those are
+	 * the subcommands that come before the positional arguments. It would
+	 * be nice if this package gave us the list of optional arguments
+	 * already parsed.
+	 */
+	var deviceId string
+found:
+	for i := len(os.Args) - 1; i >= 0; i -= 1 {
+		switch os.Args[i] {
+		case "enable":
+			fallthrough
+		case "disable":
+			if len(os.Args) > i+1 {
+				deviceId = os.Args[i+1]
+			} else {
+				return nil
+			}
+			break found
+		default:
+		}
+	}
+
+	if len(deviceId) == 0 {
+		return nil
+	}
+
+	h := &RpcEventHandler{
+		Fields: map[string]map[string]interface{}{ParamNames[GlobalConfig.ApiVersion]["ID"]: {"id": deviceId}},
+	}
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	defer cancel()
+	err = grpcurl.InvokeRPC(ctx, descriptor, conn, method, []string{}, h, h.GetParams)
+	if err != nil {
+		return nil
+	}
+
+	if h.Status != nil && h.Status.Err() != nil {
+		return nil
+	}
+
+	d, err := dynamic.AsDynamicMessage(h.Response)
+	if err != nil {
+		return nil
+	}
+
+	items, err := d.TryGetFieldByName("items")
+	if err != nil {
+		return nil
+	}
+
+	list := make([]flags.Completion, 0)
+	for _, item := range items.([]interface{}) {
+		val := item.(*dynamic.Message)
+		pn := strconv.FormatUint(uint64(val.GetFieldByName("port_no").(uint32)), 10)
+		if strings.HasPrefix(pn, match) {
+			list = append(list, flags.Completion{Item: pn})
+		}
+	}
+
+	return list
+}
+
 func (i *DeviceId) Complete(match string) []flags.Completion {
 	conn, err := NewConnection()
 	if err != nil {
@@ -546,3 +645,63 @@
 	GenerateOutput(&result)
 	return nil
 }
+
+/*Device  Port Enable */
+func (options *DevicePortEnable) Execute(args []string) error {
+	conn, err := NewConnection()
+	if err != nil {
+		return err
+	}
+	defer conn.Close()
+
+	descriptor, method, err := GetMethod("device-port-enable")
+	if err != nil {
+		return err
+	}
+
+	h := &RpcEventHandler{
+		Fields: map[string]map[string]interface{}{ParamNames[GlobalConfig.ApiVersion]["port"]: {"device_id": options.Args.Id, "port_no": options.Args.PortId}},
+	}
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	defer cancel()
+
+	err = grpcurl.InvokeRPC(ctx, descriptor, conn, method, []string{}, h, h.GetParams)
+	if err != nil {
+		Error.Printf("Error enabling port number %v on device Id %s,err=%s\n", options.Args.PortId, options.Args.Id, ErrorToString(err))
+		return err
+	} else if h.Status != nil && h.Status.Err() != nil {
+		Error.Printf("Error enabling port number %v on device Id %s,err=%s\n", options.Args.PortId, options.Args.Id, ErrorToString(h.Status.Err()))
+		return h.Status.Err()
+	}
+
+	return nil
+}
+
+/*Device Port Disable */
+func (options *DevicePortDisable) Execute(args []string) error {
+	conn, err := NewConnection()
+	if err != nil {
+		return err
+	}
+	defer conn.Close()
+
+	descriptor, method, err := GetMethod("device-port-disable")
+	if err != nil {
+		return err
+	}
+	h := &RpcEventHandler{
+		Fields: map[string]map[string]interface{}{ParamNames[GlobalConfig.ApiVersion]["port"]: {"device_id": options.Args.Id, "port_no": options.Args.PortId}},
+	}
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	defer cancel()
+
+	err = grpcurl.InvokeRPC(ctx, descriptor, conn, method, []string{}, h, h.GetParams)
+	if err != nil {
+		Error.Printf("Error disabling port number %v on device Id %s,err=%s\n", options.Args.PortId, options.Args.Id, ErrorToString(err))
+		return err
+	} else if h.Status != nil && h.Status.Err() != nil {
+		Error.Printf("Error disabling port number %v on device Id %s,err=%s\n", options.Args.PortId, options.Args.Id, ErrorToString(h.Status.Err()))
+		return h.Status.Err()
+	}
+	return nil
+}