Soft Patch Panel
 help / color / mirror / Atom feed
* [spp] [PATCH 0/9] Change SPP controller to use spp-ctl
@ 2018-10-18 11:25 ogawa.yasufumi
  2018-10-18 11:25 ` [spp] [PATCH 1/9] controller: add spp-ctl client for SPP controller ogawa.yasufumi
                   ` (8 more replies)
  0 siblings, 9 replies; 10+ messages in thread
From: ogawa.yasufumi @ 2018-10-18 11:25 UTC (permalink / raw)
  To: spp, ferruh.yigit, ogawa.yasufumi

From: Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp>

SPP controller manages primary and secondary processes, and create TCP
sessions while launching. Other process is not allowed to manage SPP
processes directly without using management port. It is difficult to
manage from other process via management port for supporting detailed
operations.

It is better to manage SPP by spp-ctl and to change SPP controller as a
client because it is allowed to maange it from multiple controllers via
well-defined REST APIs. This update is to change SPP controller to a
client of spp-ctl.

For requesting spp-ctl, add SppCtlClient class to send and receive
messages as a client. SppCtlClient communicates with spp-ctl via port
7777 and using REST APIS. The previous TCP connections between from SPP
controller to each of SPP processes are obsolated for the update.

This series of patches also include refactors of 'shell.py' to improve
maintainability. Each of SPP commands, for instance pri or sec, are
moved to deligator classes SppPrimary or SppSecondary contained in
'commands' directory.

Yasufumi Ogawa (9):
  controller: add spp-ctl client for SPP controller
  controller: change controller to use spp-ctl
  spp-ctl: add IP address binding
  controller: add IP address and port binding
  controller: move pri command to SppPrimary
  controller: move sec command to SppSecondary
  controller: move topo command to SppTopo
  controller: move topo_resize command to SppTopo
  controller: move bye command to SppBye

 src/controller/commands/__init__.py   |   0
 src/controller/commands/bye.py        |  48 ++++
 src/controller/commands/pri.py        | 123 +++++++++
 src/controller/commands/sec.py        | 194 ++++++++++++++
 src/controller/{ => commands}/topo.py | 264 ++++++++++++------
 src/controller/conn_thread.py         | 251 ------------------
 src/controller/shell.py               | 485 ++++++++--------------------------
 src/controller/spp.py                 |  96 +------
 src/controller/spp_common.py          |  43 ---
 src/controller/spp_ctl_client.py      |  58 ++++
 src/spp-ctl/spp_ctl.py                |  15 +-
 src/spp-ctl/spp_webapi.py             |   5 +-
 12 files changed, 726 insertions(+), 856 deletions(-)
 create mode 100644 src/controller/commands/__init__.py
 create mode 100644 src/controller/commands/bye.py
 create mode 100644 src/controller/commands/pri.py
 create mode 100644 src/controller/commands/sec.py
 rename src/controller/{ => commands}/topo.py (53%)
 delete mode 100644 src/controller/conn_thread.py
 create mode 100644 src/controller/spp_ctl_client.py

-- 
2.13.1

^ permalink raw reply	[flat|nested] 10+ messages in thread

* [spp] [PATCH 1/9] controller: add spp-ctl client for SPP controller
  2018-10-18 11:25 [spp] [PATCH 0/9] Change SPP controller to use spp-ctl ogawa.yasufumi
@ 2018-10-18 11:25 ` ogawa.yasufumi
  2018-10-18 11:25 ` [spp] [PATCH 2/9] controller: change controller to use spp-ctl ogawa.yasufumi
                   ` (7 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: ogawa.yasufumi @ 2018-10-18 11:25 UTC (permalink / raw)
  To: spp, ferruh.yigit, ogawa.yasufumi

From: Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp>

Add SppCtlClient class which is a utility to create requests and
responses for spp-ctl.

Signed-off-by: Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp>
---
 src/controller/spp_ctl_client.py | 58 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 58 insertions(+)
 create mode 100644 src/controller/spp_ctl_client.py

diff --git a/src/controller/spp_ctl_client.py b/src/controller/spp_ctl_client.py
new file mode 100644
index 0000000..6de1ae4
--- /dev/null
+++ b/src/controller/spp_ctl_client.py
@@ -0,0 +1,58 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2018 Nippon Telegraph and Telephone Corporation
+
+import requests
+
+
+class SppCtlClient(object):
+
+    def __init__(self):
+        api_ver = 'v1'
+        ip_addr = '127.0.0.1'
+        port = 7777
+        self.base_url = 'http://%s:%d/%s' % (ip_addr, port, api_ver)
+
+    def request_handler(func):
+        """Request handler for spp-ctl.
+
+        Decorator for handling a http request of 'requests' library.
+        It receives one of the methods 'get', 'put', 'post' or 'delete'
+        as 'func' argment.
+        """
+
+        def wrapper(self, *args, **kwargs):
+            try:
+                res = func(self, *args, **kwargs)
+
+                # TODO(yasufum) revise print message to more appropriate
+                # for spp.py.
+                if res.status_code == 400:
+                    print('Syntax or lexical error, or SPP returns' +
+                          'error for the request.')
+                elif res.status_code == 404:
+                    print('URL is not supported, or no SPP process' +
+                          'of client-id in a URL.')
+                elif res.status_code == 500:
+                    print('System error occured in spp-ctl.')
+
+                return res
+            except requests.exceptions.ConnectionError:
+                print('Error: Failed to connect to spp-ctl.')
+                return None
+        return wrapper
+
+    @request_handler
+    def get(self, req):
+        url = '%s/%s' % (self.base_url, req)
+        return requests.get(url)
+
+    @request_handler
+    def put(self, req, params):
+        url = '%s/%s' % (self.base_url, req)
+        return requests.put(url, json=params)
+
+    @request_handler
+    def delete(self, req):
+        url = '%s/%s' % (self.base_url, req)
+        return requests.delete(url)
-- 
2.13.1

^ permalink raw reply	[flat|nested] 10+ messages in thread

* [spp] [PATCH 2/9] controller: change controller to use spp-ctl
  2018-10-18 11:25 [spp] [PATCH 0/9] Change SPP controller to use spp-ctl ogawa.yasufumi
  2018-10-18 11:25 ` [spp] [PATCH 1/9] controller: add spp-ctl client for SPP controller ogawa.yasufumi
@ 2018-10-18 11:25 ` ogawa.yasufumi
  2018-10-18 11:25 ` [spp] [PATCH 3/9] spp-ctl: add IP address binding ogawa.yasufumi
                   ` (6 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: ogawa.yasufumi @ 2018-10-18 11:25 UTC (permalink / raw)
  To: spp, ferruh.yigit, ogawa.yasufumi

From: Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp>

SPP controller manages primary and secondary processes, and create TCP
sessions while launching. Other process is not allowed to manage SPP
processes directly without using management port. It is difficult to
manage from other process via management port for supporting detailed
operations.

It is better to manage SPP by spp-ctl and to change SPP controller as a
client because it is allowed to maange it from multiple controllers via
well-defined REST APIs. This update is to change SPP controller to a
client of spp-ctl.

* Remove connection thread classes and queues because SPP controller is
  no need to has sessions.

* Discard management port support because spp-ctl plays the role.

* Use SppCtlClient for requesting to spp-ctl.

* Change parsers for responses from primary and secondary processes to
  spp-ctl.

* Remove arguments IP address and ports of primary and secondary
  processes.

* Add SppCtlClient class for requests and responses.

Signed-off-by: Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp>
---
 src/controller/conn_thread.py | 251 ------------------------------------
 src/controller/shell.py       | 291 +++++++++++++++++++++++++-----------------
 src/controller/spp.py         |  89 -------------
 src/controller/spp_common.py  |  40 ------
 4 files changed, 173 insertions(+), 498 deletions(-)
 delete mode 100644 src/controller/conn_thread.py

diff --git a/src/controller/conn_thread.py b/src/controller/conn_thread.py
deleted file mode 100644
index ff0697e..0000000
--- a/src/controller/conn_thread.py
+++ /dev/null
@@ -1,251 +0,0 @@
-#!/usr/bin/env python
-# SPDX-License-Identifier: BSD-3-Clause
-# Copyright(c) 2015-2016 Intel Corporation
-
-from __future__ import absolute_import
-
-from queue import Queue
-import select
-import socket
-from . import spp_common
-from .spp_common import logger
-import threading
-import traceback
-
-
-class ConnectionThread(threading.Thread):
-    """Manage connection between controller and secondary"""
-
-    def __init__(self, client_id, conn):
-        super(ConnectionThread, self).__init__()
-        self.daemon = True
-
-        self.client_id = client_id
-        self.conn = conn
-        self.stop_event = threading.Event()
-        self.conn_opened = False
-
-    def stop(self):
-        self.stop_event.set()
-
-    def run(self):
-        cmd_str = ''
-
-        # infinite loop so that function do not terminate and thread do not
-        # end.
-        while True:
-            try:
-                _, _, _ = select.select(
-                    [self.conn, ], [self.conn, ], [], 5)
-            except select.error:
-                break
-
-            # Sending message to connected secondary
-            try:
-                cmd_str = spp_common.MAIN2SEC[self.client_id].get(True)
-                self.conn.send(cmd_str)  # send only takes string
-            except KeyError:
-                break
-            except Exception as excep:
-                print(excep, ",Error while sending msg in connectionthread()!")
-                break
-
-            # Receiving from secondary
-            try:
-                data = self.conn.recv(spp_common.SOCK_BUF_SIZE)
-                if data:
-                    msg = "%s" % data.decode('utf-8')
-                    spp_common.SEC2MAIN[self.client_id].put(msg)
-                else:
-                    spp_common.SEC2MAIN[self.client_id].put(
-                        "closing:" + str(self.conn))
-                    break
-            except Exception as excep:
-                print(
-                    excep, ",Error while receiving msg in connectionthread()!")
-                break
-
-        spp_common.SECONDARY_LIST.remove(self.client_id)
-        self.conn.close()
-
-
-class AcceptThread(threading.Thread):
-    """Manage connection"""
-
-    def __init__(self, host, port):
-        super(AcceptThread, self).__init__()
-        self.daemon = True
-
-        # Creating secondary socket object
-        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-
-        # Binding secondary socket to a address. bind() takes tuple of host
-        # and port.
-        self.sock.bind((host, port))
-
-        # Listening secondary at the address
-        self.sock.listen(spp_common.MAX_SECONDARY)
-
-        self.stop_event = threading.Event()
-        self.sock_opened = False
-
-    def getclientid(self, conn):
-        """Get client_id from client"""
-
-        try:
-            conn.send(b'_get_client_id')
-        except KeyError:
-            return -1
-
-        data = conn.recv(spp_common.SOCK_BUF_SIZE)
-        if data is None:
-            return -1
-
-        if logger is not None:
-            logger.debug("data: %s" % data)
-        client_id = int(data.decode('utf-8').strip('\0'))
-
-        if client_id < 0 or client_id > spp_common.MAX_SECONDARY:
-            logger.debug("Failed to get client_id: %d" % client_id)
-            return -1
-
-        found = 0
-        for i in spp_common.SECONDARY_LIST:
-            if client_id == i:
-                found = 1
-                break
-
-        if found == 0:
-            return client_id
-
-        # client_id in use, find a free one
-        free_client_id = -1
-        for i in range(spp_common.MAX_SECONDARY):
-            found = -1
-            for j in spp_common.SECONDARY_LIST:
-                if i == j:
-                    found = i
-                    break
-            if found == -1:
-                free_client_id = i
-                break
-
-        if logger is not None:
-            logger.debug("Found free_client_id: %d" % free_client_id)
-
-        if free_client_id < 0:
-            return -1
-
-        msg = "_set_client_id %u" % free_client_id
-        conn.send(msg.encode('utf-8'))
-        data = conn.recv(spp_common.SOCK_BUF_SIZE)
-
-        return free_client_id
-
-    def stop(self):
-        if self.sock_opened is True:
-            try:
-                self.sock.shutdown(socket.SHUT_RDWR)
-            except socket.error as excep:
-                print(excep, ", Error while closing sock in AcceptThread!")
-                traceback.print_exc()
-        self.sock.close()
-        self.stop_event.set()
-
-    def run(self):
-        try:
-            while True:
-                # Accepting incoming connections
-                conn, _ = self.sock.accept()
-
-                client_id = self.getclientid(conn)
-                if client_id < 0:
-                    break
-
-                # Creating new thread.
-                # Calling secondarythread function for this function and
-                # passing conn as argument.
-                spp_common.SECONDARY_LIST.append(client_id)
-                spp_common.MAIN2SEC[client_id] = Queue()
-                spp_common.SEC2MAIN[client_id] = Queue()
-                connection_thread = ConnectionThread(client_id, conn)
-                connection_thread.daemon = True
-                connection_thread.start()
-
-                spp_common.SECONDARY_COUNT += 1
-        except Exception as excep:
-            print(excep, ", Error in AcceptThread!")
-            traceback.print_exc()
-            self.sock_opened = False
-            self.sock.close()
-
-
-class PrimaryThread(threading.Thread):
-
-    def __init__(self, host, port):
-        super(PrimaryThread, self).__init__()
-        self.daemon = True
-
-        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-        # Binding primary socket to a address. bind() takes tuple of host
-        # and port.
-        self.sock.bind((host, port))
-
-        # Listening primary at the address
-        self.sock.listen(1)  # 5 denotes the number of clients can queue
-
-        self.stop_event = threading.Event()
-        self.sock_opened = False
-
-    def stop(self):
-        if self.sock_opened is True:
-            self.sock.shutdown(socket.SHUT_RDWR)
-        self.sock.close()
-        self.stop_event.set()
-
-    def run(self):
-        cmd_str = ''
-
-        while True:
-            # waiting for connection
-            spp_common.PRIMARY = False
-            conn, addr = self.sock.accept()
-            spp_common.PRIMARY = True
-
-            while conn:
-                try:
-                    _, _, _ = select.select([conn, ], [conn, ], [], 5)
-                except select.error:
-                    break
-
-                self.sock_opened = True
-                # Sending message to connected primary
-                try:
-                    cmd_str = spp_common.MAIN2PRIMARY.get(True)
-                    conn.send(cmd_str)  # send only takes string
-                except KeyError:
-                    break
-                except Exception as excep:
-                    print(
-                        excep,
-                        ", Error while sending msg in primarythread()!")
-                    break
-
-                # Receiving from primary
-                try:
-                    data = conn.recv(spp_common.SOCK_BUF_SIZE)
-                    if data:
-                        spp_common.PRIMARY2MAIN.put(
-                                    data.decode('utf-8').strip('\0'))
-                    else:
-                        spp_common.PRIMARY2MAIN.put('{"status": "closed"}')
-                        conn.close()
-                        self.sock_opened = False
-                        break
-                except Exception as excep:
-                    print(
-                        excep,
-                        ", Error while receiving msg in primarythread()!")
-                    break
diff --git a/src/controller/shell.py b/src/controller/shell.py
index 3e0ca00..1363fc5 100644
--- a/src/controller/shell.py
+++ b/src/controller/shell.py
@@ -5,13 +5,12 @@
 from __future__ import absolute_import
 
 import cmd
-import json
 import os
-from queue import Empty
 import re
 import readline
 from .shell_lib import common
 from . import spp_common
+from . import spp_ctl_client
 from .spp_common import logger
 import subprocess
 from . import topo
@@ -20,17 +19,13 @@ from . import topo
 class Shell(cmd.Cmd, object):
     """SPP command prompt."""
 
+    # TODO(yasufum) move hist_file to $HOME as default
     hist_file = '.spp_history'
 
     intro = 'Welcome to the spp.   Type help or ? to list commands.\n'
     prompt = 'spp > '
     recorded_file = None
 
-    CMD_OK = "OK"
-    CMD_NG = "NG"
-    CMD_NOTREADY = "NOTREADY"
-    CMD_ERROR = "ERROR"
-
     PORT_TYPES = ['phy', 'ring', 'vhost', 'pcap', 'nullpmd']
 
     PRI_CMDS = ['status', 'exit', 'clear']
@@ -40,6 +35,8 @@ class Shell(cmd.Cmd, object):
 
     HIST_EXCEPT = ['bye', 'exit', 'history', 'redo']
 
+    rest_common_error_codes = [400, 404, 500]
+
     PLUGIN_DIR = 'command'
     subgraphs = {}
     topo_size = '60%'
@@ -50,6 +47,10 @@ class Shell(cmd.Cmd, object):
     else:
         readline.write_history_file(hist_file)
 
+    def __init__(self):
+        cmd.Cmd.__init__(self)
+        self.spp_ctl_cli = spp_ctl_client.SppCtlClient()
+
     def default(self, line):
         """Define defualt behaviour.
 
@@ -78,17 +79,16 @@ class Shell(cmd.Cmd, object):
 
         try:
             for line in open(self.hist_file):
-                l = line.strip()
-                if not (l.split(' ')[0] in self.HIST_EXCEPT):
-                    entries.append(l)
+                line_s = line.strip()
+                if not (line_s.split(' ')[0] in self.HIST_EXCEPT):
+                    entries.append(line_s)
             f = open(self.hist_file, "w+")
             contents = '\n'.join(entries)
             contents += '\n'
             f.write(contents)
             f.close()
         except IOError:
-            print('Error: Cannot open history file "%s"' %
-                    self.hist_file)
+            print('Error: Cannot open history file "%s"' % self.hist_file)
 
     def close_all_secondary(self):
         """Terminate all secondary processes."""
@@ -98,37 +98,39 @@ class Shell(cmd.Cmd, object):
             tmp_list.append(i)
         for i in tmp_list:
             self.command_secondary(i, 'exit')
-        spp_common.SECONDARY_COUNT = 0
-
-    def get_status(self):
-        """Return the status of SPP processes.
-
-        Show the number of each of SPP processes running on.
-
-        spp > status
-        Soft Patch Panel Status :
-        primary: 1
-        secondary count: 2
-        """
-
-        secondary = []
-        for i in spp_common.SECONDARY_LIST:
-            secondary.append("%d" % i)
-        stat = {
-            # PRIMARY is 1 if it is running
-            "primary": "%d" % spp_common.PRIMARY,
-            "secondary": secondary
-            }
-        return stat
 
     def print_status(self):
         """Display information about connected clients."""
 
-        print("Soft Patch Panel Status :")
-        print("primary: %d" % spp_common.PRIMARY)  # it is 1 if PRIMA == True
-        print("secondary count: %d" % len(spp_common.SECONDARY_LIST))
-        for i in spp_common.SECONDARY_LIST:
-            print("Connected secondary id: %d" % i)
+        res = self.spp_ctl_cli.get('processes')
+        if res is not None:
+            if res.status_code == 200:
+                proc_objs = res.json()
+                pri_obj = None
+                sec_obj = {}
+                sec_obj['nfv'] = []
+
+                for proc_obj in proc_objs:
+                    if proc_obj['type'] == 'primary':
+                        pri_obj = proc_obj
+                    elif proc_obj['type'] == 'nfv':
+                        sec_obj['nfv'].append(proc_obj)
+
+                print('- primary:')
+                if pri_obj is not None:
+                    print('  - status: running')
+                else:
+                    print('  - status: not running')
+
+                print('- secondary:')
+                print('  - processes:')
+                for obj in sec_obj['nfv']:
+                    print('    %d: %s:%s' % (
+                        obj['client-id'], obj['type'], obj['client-id']))
+            elif res.status_code in self.rest_common_error_codes:
+                pass
+            else:
+                print('Error: unknown response.')
 
     def print_pri_status(self, json_obj):
         """Parse SPP primary's status and print.
@@ -170,7 +172,7 @@ class Shell(cmd.Cmd, object):
                ...
         """
 
-        if json_obj.has_key('phy_ports'):
+        if 'phy_ports' in json_obj:
             print('Physical Ports:')
             print('  ID          rx          tx     tx_drop  mac_addr')
             for pports in json_obj['phy_ports']:
@@ -178,7 +180,7 @@ class Shell(cmd.Cmd, object):
                     pports['id'], pports['rx'],  pports['tx'],
                     pports['tx_drop'], pports['eth']))
 
-        if json_obj.has_key('ring_ports'):
+        if 'ring_ports' in json_obj:
             print('Ring Ports:')
             print('  ID          rx          tx     rx_drop     rx_drop')
             for rports in json_obj['ring_ports']:
@@ -186,7 +188,7 @@ class Shell(cmd.Cmd, object):
                     rports['id'], rports['rx'],  rports['tx'],
                     rports['rx_drop'], rports['tx_drop']))
 
-    def print_sec_status(self, msg):
+    def print_sec_status(self, json_obj):
         """Parse and print message from SPP secondary.
 
         Print status received from secondary.
@@ -203,55 +205,135 @@ class Shell(cmd.Cmd, object):
           {"client-id":1,...,"patches":[{"src":"phy:0"...},...]}'\x00..
         """
 
-        msg = msg.replace("\x00", "")  # Clean received msg
-
-        try:
-            sec_attr = json.loads(msg)
-            print('- status: %s' % sec_attr['status'])
-            print('- ports:')
-            for port in sec_attr['ports']:
-                dst = None
-                for patch in sec_attr['patches']:
-                    if patch['src'] == port:
-                        dst = patch['dst']
-
-                if dst is None:
-                    print('  - %s' % port)
-                else:
-                    print('  - %s -> %s' % (port, dst))
-        except ValueError as err:
-            print('Invalid format: {0}.'.format(err))
-            print("'%s'" % msg)
+        sec_attr = json_obj
+        print('- status: %s' % sec_attr['status'])
+        print('- ports:')
+        for port in sec_attr['ports']:
+            dst = None
+            for patch in sec_attr['patches']:
+                if patch['src'] == port:
+                    dst = patch['dst']
+
+            if dst is None:
+                print('  - %s' % port)
+            else:
+                print('  - %s -> %s' % (port, dst))
 
     def command_primary(self, command):
         """Send command to primary process"""
 
-        if spp_common.PRIMARY:
-            spp_common.MAIN2PRIMARY.put(command.encode('utf-8'))
-            recv = spp_common.PRIMARY2MAIN.get(True)
-            json_obj = json.loads(recv)
-            self.print_pri_status(json_obj)
-            return self.CMD_OK, recv
+        if command == 'status':
+            res = self.spp_ctl_cli.get('primary/status')
+            if res is not None:
+                if res.status_code == 200:
+                    self.print_pri_status(res.json())
+                elif res.status_code in self.rest_common_error_codes:
+                    pass
+                else:
+                    print('Error: unknown response.')
+
+        elif command == 'clear':
+            res = self.spp_ctl_cli.delete('primary/status')
+            if res is not None:
+                if res.status_code == 204:
+                    print('Clear port statistics.')
+                elif res.status_code in self.rest_common_error_codes:
+                    pass
+                else:
+                    print('Error: unknown response.')
+
+        elif command == 'exit':
+            print('"pri; exit" is deprecated.')
+
         else:
-            recv = "primary not started"
-            print(recv)
-            return self.CMD_NOTREADY, recv
+            print('Invalid pri command!')
 
     def command_secondary(self, sec_id, command):
-        """Send command to secondary process with sec_id"""
+        """Send command to secondary process."""
+
+        cmd = command.split(' ')[0]
+        params = command.split(' ')[1:]
+
+        if cmd == 'status':
+            res = self.spp_ctl_cli.get('nfvs/%d' % sec_id)
+            if res is not None:
+                if res.status_code == 200:
+                    self.print_sec_status(res.json())
+                elif res.status_code in self.rest_common_error_codes:
+                    pass
+                else:
+                    print('Error: unknown response.')
+
+        elif cmd == 'add':
+            req_params = {'action': 'add', 'port': params[0]}
+            res = self.spp_ctl_cli.put('nfvs/%d/ports' % sec_id, req_params)
+            if res is not None:
+                if res.status_code == 204:
+                    print('Add %s.' % params[0])
+                elif res.status_code in self.rest_common_error_codes:
+                    pass
+                else:
+                    print('Error: unknown response.')
+
+        elif cmd == 'del':
+            req_params = {'action': 'del', 'port': params[0]}
+            res = self.spp_ctl_cli.put('nfvs/%d/ports' % sec_id, req_params)
+            if res is not None:
+                if res.status_code == 204:
+                    print('Delete %s.' % params[0])
+                elif res.status_code in self.rest_common_error_codes:
+                    pass
+                else:
+                    print('Error: unknown response.')
 
-        if sec_id in spp_common.SECONDARY_LIST:
-            spp_common.MAIN2SEC[sec_id].put(command.encode('utf-8'))
-            recv = spp_common.SEC2MAIN[sec_id].get(True)
-            if command == 'status':
-                self.print_sec_status(recv)
+        elif cmd == 'forward' or cmd == 'stop':
+            if cmd == 'forward':
+                req_params = {'action': 'start'}
+            elif cmd == 'stop':
+                req_params = {'action': 'stop'}
+            else:
+                print('Unknown command. "forward" or "stop"?')
+
+            res = self.spp_ctl_cli.put('nfvs/%d/forward' % sec_id, req_params)
+            if res is not None:
+                if res.status_code == 204:
+                    if cmd == 'forward':
+                        print('Start forwarding.')
+                    else:
+                        print('Stop forwarding.')
+                elif res.status_code in self.rest_common_error_codes:
+                    pass
+                else:
+                    print('Error: unknown response.')
+
+        elif cmd == 'patch':
+            if params[0] == 'reset':
+                res = self.spp_ctl_cli.delete('nfvs/%d/patches' % sec_id)
+                if res is not None:
+                    if res.status_code == 204:
+                        print('Clear all of patches.')
+                    elif res.status_code in self.rest_common_error_codes:
+                        pass
+                    else:
+                        print('Error: unknown response.')
             else:
-                print(recv)
-            return self.CMD_OK, recv
+                req_params = {'src': params[0], 'dst': params[1]}
+                res = self.spp_ctl_cli.put(
+                        'nfvs/%d/patches' % sec_id, req_params)
+                if res is not None:
+                    if res.status_code == 204:
+                        print('Patch ports (%s -> %s).' % (
+                            params[0], params[1]))
+                    elif res.status_code in self.rest_common_error_codes:
+                        pass
+                    else:
+                        print('Error: unknown response.')
+
+        elif cmd == 'exit':
+            print('do nothing.')
+
         else:
-            message = "secondary id %d not exist" % sec_id
-            print(message)
-            return self.CMD_NOTREADY, message
+            print('Invalid command "%s".' % cmd)
 
     def is_patched_ids_valid(self, id1, id2, delim=':'):
         """Check if port IDs are valid
@@ -275,6 +357,9 @@ class Shell(cmd.Cmd, object):
     def check_sec_cmds(self, cmds):
         """Validate secondary commands before sending"""
 
+        # TODO(yasufum) change to return True or False, or None
+        # instead of 0 or 1
+
         level1 = ['status', 'exit', 'forward', 'stop']
         level2 = ['add', 'patch', 'del']
         patch_args = ['reset']
@@ -313,21 +398,6 @@ class Shell(cmd.Cmd, object):
         res = re.sub(r'\s?;\s?', ";", tmparg)
         return res
 
-    def response(self, result, message):
-        """Enqueue message from other than CLI"""
-
-        try:
-            rcmd = spp_common.RCMD_EXECUTE_QUEUE.get(False)
-        except Empty:
-            return
-
-        if (rcmd == spp_common.REMOTE_COMMAND):
-            param = result + '\n' + message
-            spp_common.RCMD_RESULT_QUEUE.put(param.encode('utf-8'))
-        else:
-            if logger is not None:
-                logger.debug("unknown remote command = %s" % rcmd)
-
     def precmd(self, line):
         """Called before running a command
 
@@ -357,8 +427,6 @@ class Shell(cmd.Cmd, object):
         """
 
         self.print_status()
-        stat = self.get_status()
-        self.response(self.CMD_OK, json.dumps(stat))
 
     def do_pri(self, command):
         """Send a command to primary process.
@@ -376,12 +444,10 @@ class Shell(cmd.Cmd, object):
             logger.info("Receive pri command: '%s'" % command)
 
         if command and (command in self.PRI_CMDS):
-            result, message = self.command_primary(command)
-            self.response(result, message)
+            self.command_primary(command)
         else:
             message = "Invalid pri command: '%s'" % command
             print(message)
-            self.response(self.CMD_ERROR, message)
 
     def complete_pri(self, text, line, begidx, endidx):
         """Completion for primary process commands"""
@@ -416,22 +482,18 @@ class Shell(cmd.Cmd, object):
         tmparg = self.clean_cmd(arg)
         cmds = tmparg.split(';')
         if len(cmds) < 2:
-            message = "error"
+            message = "'sec' requires an ID and ';' before command."
             print(message)
-            self.response(self.CMD_ERROR, message)
         elif str.isdigit(cmds[0]):
             sec_id = int(cmds[0])
             if self.check_sec_cmds(cmds[1]):
-                result, message = self.command_secondary(sec_id, cmds[1])
-                self.response(result, message)
+                self.command_secondary(sec_id, cmds[1])
             else:
                 message = "invalid cmd"
                 print(message)
-                self.response(self.CMD_ERROR, message)
         else:
             print(cmds[0])
             print("first %s" % cmds[1])
-            self.response(self.CMD_ERROR, "invalid format")
 
     def complete_sec(self, text, line, begidx, endidx):
         """Completion for secondary process commands"""
@@ -493,7 +555,6 @@ class Shell(cmd.Cmd, object):
             print("Record file is required!")
         else:
             self.recorded_file = open(fname, 'w')
-            self.response(self.CMD_OK, "record")
 
     def complete_record(self, text, line, begidx, endidx):
         return common.compl_common(text, line)
@@ -519,11 +580,9 @@ class Shell(cmd.Cmd, object):
                             lines.append("# %s" % line)
                         lines.append(line)
                     self.cmdqueue.extend(lines)
-                    self.response(self.CMD_OK, "playback")
             except IOError:
                 message = "Error: File does not exist."
                 print(message)
-                self.response(self.CMD_NG, message)
 
     def complete_playback(self, text, line, begidx, endidx):
         return common.compl_common(text, line)
@@ -661,8 +720,7 @@ class Shell(cmd.Cmd, object):
             cmd_options = ' '.join(cmd_ary)
             eval('self.do_%s(cmd_options)' % cmd)
         except IOError:
-            print('Error: Cannot open history file "%s"' %
-                    self.hist_file)
+            print('Error: Cannot open history file "%s"' % self.hist_file)
 
     def do_history(self, arg):
         """Show command history.
@@ -694,13 +752,12 @@ class Shell(cmd.Cmd, object):
 
             cnt = 1
             for line in f:
-                l = line.strip()
-                print(hist_format % (cnt, l))
+                line_s = line.strip()
+                print(hist_format % (cnt, line_s))
                 cnt += 1
             f.close()
         except IOError:
-            print('Error: Cannot open history file "%s"' %
-                    self.hist_file)
+            print('Error: Cannot open history file "%s"' % self.hist_file)
 
     def complete_cat(self, text, line, begidx, endidx):
         return common.compl_common(text, line)
@@ -877,7 +934,6 @@ class Shell(cmd.Cmd, object):
         if len(spp_common.SECONDARY_LIST) == 0:
             message = "secondary not exist"
             print(message)
-            self.response(self.CMD_NOTREADY, message)
         else:
             tp = topo.Topo(
                 spp_common.SECONDARY_LIST,
@@ -898,7 +954,6 @@ class Shell(cmd.Cmd, object):
             else:
                 print("Usage: topo dst [ftype]")
                 return False
-            self.response(self.CMD_OK, json.dumps(res_ary))
 
     def complete_topo(self, text, line, begidx, endidx):
         """Complete topo command
diff --git a/src/controller/spp.py b/src/controller/spp.py
index 80b1fab..373bb93 100644
--- a/src/controller/spp.py
+++ b/src/controller/spp.py
@@ -3,110 +3,21 @@
 # Copyright(c) 2015-2016 Intel Corporation
 
 from __future__ import absolute_import
-# from __future__ import print_function
 
 import argparse
-from .conn_thread import AcceptThread
-from .conn_thread import PrimaryThread
 from .shell import Shell
-import socket
-import socketserver
-from . import spp_common
-from .spp_common import logger
 import sys
-import threading
-import traceback
-
-
-class CmdRequestHandler(socketserver.BaseRequestHandler):
-    """Request handler for getting message from remote entities"""
-
-    CMD = None  # contains a instance of Shell class
-
-    def handle(self):
-        self.data = self.request.recv(spp_common.SOCK_BUF_SIZE).strip()
-        cur_thread = threading.currentThread()
-        print(cur_thread.getName())
-        print(self.client_address[0])
-        print(self.data)
-        if CmdRequestHandler.CMD is not None:
-            spp_common.RCMD_EXECUTE_QUEUE.put(spp_common.REMOTE_COMMAND)
-            CmdRequestHandler.CMD.onecmd(self.data)
-            ret = spp_common.RCMD_RESULT_QUEUE.get()
-            if (ret is not None):
-                if logger is not None:
-                    logger.debug("ret:%s" % ret)
-                self.request.send(ret)
-            else:
-                if logger is not None:
-                    logger.debug("ret is none")
-                self.request.send("")
-        else:
-            if logger is not None:
-                logger.debug("CMD is None")
-            self.request.send("")
 
 
 def main(argv):
-    """main"""
 
     parser = argparse.ArgumentParser(description="SPP Controller")
-
-    parser.add_argument(
-        "-p", "--pri-port",
-        type=int, default=5555,
-        help="primary port number")
-    parser.add_argument(
-        "-s", "--sec-port",
-        type=int, default=6666,
-        help="secondary port number")
-    parser.add_argument(
-        "-m", "--mng-port",
-        type=int, default=7777,
-        help="management port number")
-    parser.add_argument(
-        "-ip", "--ipaddr",
-        type=str, default='',  # 'localhost' or '127.0.0.1' or '' are all same
-        help="IP address")
     args = parser.parse_args()
 
-    host = args.ipaddr
-    primary_port = args.pri_port
-    secondary_port = args.sec_port
-    management_port = args.mng_port
-
-    print("primary port : %d" % primary_port)
-    print('secondary port : %d' % secondary_port)
-    print('management port : %d' % management_port)
-
-    primary_thread = PrimaryThread(host, primary_port)
-    primary_thread.start()
-
-    accept_thread = AcceptThread(host, secondary_port)
-    accept_thread.start()
-
     shell = Shell()
-
-    # Run request handler as a TCP server thread
-    socketserver.ThreadingTCPServer.allow_reuse_address = True
-    CmdRequestHandler.CMD = shell
-    command_server = socketserver.ThreadingTCPServer(
-        (host, management_port), CmdRequestHandler)
-
-    t = threading.Thread(target=command_server.serve_forever)
-    t.setDaemon(True)
-    t.start()
-
     shell.cmdloop()
     shell = None
 
-    try:
-        primary_thread.stop()
-        accept_thread.stop()
-    except socket.error as excep:
-        print(excep, ", Error while terminating threads in main()!")
-        traceback.print_exc()
-
 
 if __name__ == "__main__":
 
diff --git a/src/controller/spp_common.py b/src/controller/spp_common.py
index 0cac2d9..0986918 100644
--- a/src/controller/spp_common.py
+++ b/src/controller/spp_common.py
@@ -4,7 +4,6 @@
 
 import logging
 import os
-from queue import Queue
 
 # Setup logger object
 logger = logging.getLogger(__name__)
@@ -20,49 +19,10 @@ handler.setFormatter(formatter)
 logger.setLevel(logging.DEBUG)
 logger.addHandler(handler)
 
-PRIMARY = ''
 SECONDARY_LIST = []
 
-# Initialize primary comm channel
-MAIN2PRIMARY = Queue()
-PRIMARY2MAIN = Queue()
-
 # Maximum num of sock queues for secondaries
 MAX_SECONDARY = 16
 
-# Should be as same as MSG_SIZE in src/shared/common.h
-SOCK_BUF_SIZE = 2048
-
-PRIMARY = ''
-SECONDARY_COUNT = 0
-
-REMOTE_COMMAND = "RCMD"
-RCMD_EXECUTE_QUEUE = Queue()
-RCMD_RESULT_QUEUE = Queue()
-
 delim_node = '_'
 delim_label = ':'
-
-
-class GrowingList(list):
-    """Growing List
-
-    Custom list type for appending index over the range which is
-    similar to ruby's Array. Empty index is filled with 'None'.
-    It is used to contain queues for secondaries with any sec ID.
-
-    >>> gl = GrowingList()
-    >>> gl.[3] = 0
-    >>> gl
-    [None, None, None, 0]
-    """
-
-    def __setitem__(self, index, value):
-        if index >= len(self):
-            self.extend([None]*(index + 1 - len(self)))
-        list.__setitem__(self, index, value)
-
-
-# init secondary comm channel list
-MAIN2SEC = GrowingList()
-SEC2MAIN = GrowingList()
-- 
2.13.1

^ permalink raw reply	[flat|nested] 10+ messages in thread

* [spp] [PATCH 3/9] spp-ctl: add IP address binding
  2018-10-18 11:25 [spp] [PATCH 0/9] Change SPP controller to use spp-ctl ogawa.yasufumi
  2018-10-18 11:25 ` [spp] [PATCH 1/9] controller: add spp-ctl client for SPP controller ogawa.yasufumi
  2018-10-18 11:25 ` [spp] [PATCH 2/9] controller: change controller to use spp-ctl ogawa.yasufumi
@ 2018-10-18 11:25 ` ogawa.yasufumi
  2018-10-18 11:25 ` [spp] [PATCH 4/9] controller: add IP address and port binding ogawa.yasufumi
                   ` (5 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: ogawa.yasufumi @ 2018-10-18 11:25 UTC (permalink / raw)
  To: spp, ferruh.yigit, ogawa.yasufumi

From: Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp>

Spp-ctl accepts requests only from localhost. This update is to add
supporting IP address binding with '-b' or '--bind-addr' option.

  $ python3 src/spp-ctl/spp-ctl -b 192.168.1.10

Signed-off-by: Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp>
---
 src/spp-ctl/spp_ctl.py    | 15 +++++++++------
 src/spp-ctl/spp_webapi.py |  5 +++--
 2 files changed, 12 insertions(+), 8 deletions(-)

diff --git a/src/spp-ctl/spp_ctl.py b/src/spp-ctl/spp_ctl.py
index e168747..80a78b6 100644
--- a/src/spp-ctl/spp_ctl.py
+++ b/src/spp-ctl/spp_ctl.py
@@ -23,8 +23,8 @@ MSG_SIZE = 4096
 
 class Controller(object):
 
-    def __init__(self, pri_port, sec_port, api_port):
-        self.web_server = spp_webapi.WebServer(self, api_port)
+    def __init__(self, host, pri_port, sec_port, api_port):
+        self.web_server = spp_webapi.WebServer(self, host, api_port)
         self.procs = {}
         self.init_connection(pri_port, sec_port)
 
@@ -144,15 +144,18 @@ class Controller(object):
 
 def main():
     parser = argparse.ArgumentParser(description="SPP Controller")
+    parser.add_argument("-b", '--bind-addr', type=str, default='localhost',
+                        help="bind address, default=localhost")
     parser.add_argument("-p", dest='pri_port', type=int, default=5555,
-                        action='store', help="primary port")
+                        action='store', help="primary port, default=5555")
     parser.add_argument("-s", dest='sec_port', type=int, default=6666,
-                        action='store', help="secondary port")
+                        action='store', help="secondary port, default=6666")
     parser.add_argument("-a", dest='api_port', type=int, default=7777,
-                        action='store', help="web api port")
+                        action='store', help="web api port, default=7777")
     args = parser.parse_args()
 
     logging.basicConfig(level=logging.DEBUG)
 
-    controller = Controller(args.pri_port, args.sec_port, args.api_port)
+    controller = Controller(args.bind_addr, args.pri_port, args.sec_port,
+                            args.api_port)
     controller.start()
diff --git a/src/spp-ctl/spp_webapi.py b/src/spp-ctl/spp_webapi.py
index 6768aed..b2fbe3b 100644
--- a/src/spp-ctl/spp_webapi.py
+++ b/src/spp-ctl/spp_webapi.py
@@ -118,8 +118,9 @@ class WebServer(BaseHandler):
        /primary    V1PrimaryHandler
     """
 
-    def __init__(self, controller, api_port):
+    def __init__(self, controller, host, api_port):
         super(WebServer, self).__init__(controller)
+        self.host = host
         self.api_port = api_port
 
         self.mount("/v1", V1Handler(controller))
@@ -129,7 +130,7 @@ class WebServer(BaseHandler):
         self.add_hook("after_request", self.log_response)
 
     def start(self):
-        self.run(server='eventlet', host='localhost', port=self.api_port,
+        self.run(server='eventlet', host=self.host, port=self.api_port,
                  quiet=True)
 
 
-- 
2.13.1

^ permalink raw reply	[flat|nested] 10+ messages in thread

* [spp] [PATCH 4/9] controller: add IP address and port binding
  2018-10-18 11:25 [spp] [PATCH 0/9] Change SPP controller to use spp-ctl ogawa.yasufumi
                   ` (2 preceding siblings ...)
  2018-10-18 11:25 ` [spp] [PATCH 3/9] spp-ctl: add IP address binding ogawa.yasufumi
@ 2018-10-18 11:25 ` ogawa.yasufumi
  2018-10-18 11:25 ` [spp] [PATCH 5/9] controller: move pri command to SppPrimary ogawa.yasufumi
                   ` (4 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: ogawa.yasufumi @ 2018-10-18 11:25 UTC (permalink / raw)
  To: spp, ferruh.yigit, ogawa.yasufumi

From: Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp>

Add IP address and port support for connecting spp-ctl. IP address
option is '-b' or '--bind-addr', and port is '-a' or '--api-port'.

  $ python src/spp.py -b 192.168.1.10 -a 7777

Signed-off-by: Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp>
---
 src/controller/shell.py          | 5 ++---
 src/controller/spp.py            | 7 ++++++-
 src/controller/spp_ctl_client.py | 4 +---
 3 files changed, 9 insertions(+), 7 deletions(-)

diff --git a/src/controller/shell.py b/src/controller/shell.py
index 1363fc5..f1c9ccd 100644
--- a/src/controller/shell.py
+++ b/src/controller/shell.py
@@ -10,7 +10,6 @@ import re
 import readline
 from .shell_lib import common
 from . import spp_common
-from . import spp_ctl_client
 from .spp_common import logger
 import subprocess
 from . import topo
@@ -47,9 +46,9 @@ class Shell(cmd.Cmd, object):
     else:
         readline.write_history_file(hist_file)
 
-    def __init__(self):
+    def __init__(self, spp_ctl_cli):
         cmd.Cmd.__init__(self)
-        self.spp_ctl_cli = spp_ctl_client.SppCtlClient()
+        self.spp_ctl_cli = spp_ctl_cli
 
     def default(self, line):
         """Define defualt behaviour.
diff --git a/src/controller/spp.py b/src/controller/spp.py
index 373bb93..6b0d99c 100644
--- a/src/controller/spp.py
+++ b/src/controller/spp.py
@@ -6,15 +6,20 @@ from __future__ import absolute_import
 
 import argparse
 from .shell import Shell
+from . import spp_ctl_client
 import sys
 
 
 def main(argv):
 
     parser = argparse.ArgumentParser(description="SPP Controller")
+    parser.add_argument('-b', '--bind-addr', type=str, default='127.0.0.1',
+                        help='bind address, default=127.0.0.1')
+    parser.add_argument('-a', '--api-port', type=int, default=7777,
+                        help='bind address, default=7777')
     args = parser.parse_args()
 
-    shell = Shell()
+    shell = Shell(spp_ctl_client.SppCtlClient(args.bind_addr, args.api_port))
     shell.cmdloop()
     shell = None
 
diff --git a/src/controller/spp_ctl_client.py b/src/controller/spp_ctl_client.py
index 6de1ae4..a1d6d93 100644
--- a/src/controller/spp_ctl_client.py
+++ b/src/controller/spp_ctl_client.py
@@ -7,10 +7,8 @@ import requests
 
 class SppCtlClient(object):
 
-    def __init__(self):
+    def __init__(self, ip_addr='localhost', port=7777):
         api_ver = 'v1'
-        ip_addr = '127.0.0.1'
-        port = 7777
         self.base_url = 'http://%s:%d/%s' % (ip_addr, port, api_ver)
 
     def request_handler(func):
-- 
2.13.1

^ permalink raw reply	[flat|nested] 10+ messages in thread

* [spp] [PATCH 5/9] controller: move pri command to SppPrimary
  2018-10-18 11:25 [spp] [PATCH 0/9] Change SPP controller to use spp-ctl ogawa.yasufumi
                   ` (3 preceding siblings ...)
  2018-10-18 11:25 ` [spp] [PATCH 4/9] controller: add IP address and port binding ogawa.yasufumi
@ 2018-10-18 11:25 ` ogawa.yasufumi
  2018-10-18 11:25 ` [spp] [PATCH 6/9] controller: move sec command to SppSecondary ogawa.yasufumi
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: ogawa.yasufumi @ 2018-10-18 11:25 UTC (permalink / raw)
  To: spp, ferruh.yigit, ogawa.yasufumi

From: Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp>

'spp.py' includes many methods and vals for supporting several commands.
Some of them are global and others are command's local. It would be hard
to maintain if more commands are added to 'spp.py'.

SppPrimary defines 'pri' command and its completion as in a separated
module. It is intended to be used from Shell, which is derived from
'cmd.Cmd'.

Signed-off-by: Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp>
---
 src/controller/commands/__init__.py |   0
 src/controller/commands/pri.py      | 123 ++++++++++++++++++++++++++++++++++++
 src/controller/shell.py             | 108 ++-----------------------------
 3 files changed, 129 insertions(+), 102 deletions(-)
 create mode 100644 src/controller/commands/__init__.py
 create mode 100644 src/controller/commands/pri.py

diff --git a/src/controller/commands/__init__.py b/src/controller/commands/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/controller/commands/pri.py b/src/controller/commands/pri.py
new file mode 100644
index 0000000..b51138d
--- /dev/null
+++ b/src/controller/commands/pri.py
@@ -0,0 +1,123 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2018 Nippon Telegraph and Telephone Corporation
+
+
+class SppPrimary(object):
+    """Exec SPP primary command.
+
+    SppPrimary class is intended to be used in Shell class as a delegator
+    for running 'pri' command.
+
+    'self.command()' is called from do_pri() and 'self.complete()' is called
+    from complete_pri() of both of which is defined in Shell.
+    """
+
+    # All of primary commands used for validation and completion.
+    PRI_CMDS = ['status', 'exit', 'clear']
+
+    def __init__(self, spp_ctl_cli):
+        self.spp_ctl_cli = spp_ctl_cli
+
+    def run(self, cmd):
+        """Called from do_pri() to Send command to primary process."""
+
+        if not (cmd in self.PRI_CMDS):
+            print("Invalid pri command: '%s'" % cmd)
+            return None
+
+        if cmd == 'status':
+            res = self.spp_ctl_cli.get('primary/status')
+            if res is not None:
+                if res.status_code == 200:
+                    self.print_status(res.json())
+                elif res.status_code in self.rest_common_error_codes:
+                    # Print default error message
+                    pass
+                else:
+                    print('Error: unknown response.')
+
+        elif cmd == 'clear':
+            res = self.spp_ctl_cli.delete('primary/status')
+            if res is not None:
+                if res.status_code == 204:
+                    print('Clear port statistics.')
+                elif res.status_code in self.rest_common_error_codes:
+                    pass
+                else:
+                    print('Error: unknown response.')
+
+        elif cmd == 'exit':
+            print('"pri; exit" is deprecated.')
+
+        else:
+            print('Invalid pri command!')
+
+    def print_status(self, json_obj):
+        """Parse SPP primary's status and print.
+
+        Primary returns the status as JSON format, but it is just a little
+        long.
+
+            {
+                "phy_ports": [
+                    {
+                        "eth": "56:48:4f:12:34:00",
+                        "id": 0,
+                        "rx": 78932932,
+                        "tx": 78932931,
+                        "tx_drop": 1,
+                    }
+                    ...
+                ],
+                "ring_ports": [
+                    {
+                        "id": 0,
+                        "rx": 89283,
+                        "rx_drop": 0,
+                        "tx": 89283,
+                        "tx_drop": 0
+                    },
+                    ...
+                ]
+            }
+
+        It is formatted to be simple and more understandable.
+
+            Physical Ports:
+              ID          rx          tx     tx_drop  mac_addr
+               0    78932932    78932931           1  56:48:4f:53:54:00
+            Ring Ports:
+              ID          rx          tx     rx_drop     rx_drop
+               0       89283       89283           0           0
+               ...
+        """
+
+        if 'phy_ports' in json_obj:
+            print('Physical Ports:')
+            print('  ID          rx          tx     tx_drop  mac_addr')
+            for pports in json_obj['phy_ports']:
+                print('  %2d  %10d  %10d  %10d  %s' % (
+                    pports['id'], pports['rx'],  pports['tx'],
+                    pports['tx_drop'], pports['eth']))
+
+        if 'ring_ports' in json_obj:
+            print('Ring Ports:')
+            print('  ID          rx          tx     rx_drop     rx_drop')
+            for rports in json_obj['ring_ports']:
+                print('  %2d  %10d  %10d  %10d  %10d' % (
+                    rports['id'], rports['rx'],  rports['tx'],
+                    rports['rx_drop'], rports['tx_drop']))
+
+    def complete(self, text, line, begidx, endidx):
+        """Completion for primary process commands.
+
+        Called from complete_pri() to complete primary command.
+        """
+
+        if not text:
+            completions = self.PRI_CMDS[:]
+        else:
+            completions = [p for p in self.PRI_CMDS
+                           if p.startswith(text)
+                           ]
+        return completions
diff --git a/src/controller/shell.py b/src/controller/shell.py
index f1c9ccd..9834df0 100644
--- a/src/controller/shell.py
+++ b/src/controller/shell.py
@@ -1,10 +1,10 @@
-#!/usr/bin/env python
 # SPDX-License-Identifier: BSD-3-Clause
 # Copyright(c) 2015-2016 Intel Corporation
 
 from __future__ import absolute_import
 
 import cmd
+from .commands import pri
 import os
 import re
 import readline
@@ -27,7 +27,6 @@ class Shell(cmd.Cmd, object):
 
     PORT_TYPES = ['phy', 'ring', 'vhost', 'pcap', 'nullpmd']
 
-    PRI_CMDS = ['status', 'exit', 'clear']
     SEC_CMDS = ['status', 'exit', 'forward', 'stop', 'add', 'patch', 'del']
     SEC_SUBCMDS = ['vhost', 'ring', 'pcap', 'nullpmd']
     BYE_CMDS = ['sec', 'all']
@@ -49,6 +48,7 @@ class Shell(cmd.Cmd, object):
     def __init__(self, spp_ctl_cli):
         cmd.Cmd.__init__(self)
         self.spp_ctl_cli = spp_ctl_cli
+        self.spp_primary = pri.SppPrimary(self.spp_ctl_cli)
 
     def default(self, line):
         """Define defualt behaviour.
@@ -131,62 +131,6 @@ class Shell(cmd.Cmd, object):
             else:
                 print('Error: unknown response.')
 
-    def print_pri_status(self, json_obj):
-        """Parse SPP primary's status and print.
-
-        Primary returns the status as JSON format, but it is just a little
-        long.
-
-            {
-                "phy_ports": [
-                    {
-                        "eth": "56:48:4f:12:34:00",
-                        "id": 0,
-                        "rx": 78932932,
-                        "tx": 78932931,
-                        "tx_drop": 1,
-                    }
-                    ...
-                ],
-                "ring_ports": [
-                    {
-                        "id": 0,
-                        "rx": 89283,
-                        "rx_drop": 0,
-                        "tx": 89283,
-                        "tx_drop": 0
-                    },
-                    ...
-                ]
-            }
-
-        It is formatted to be simple and more understandable.
-
-            Physical Ports:
-              ID          rx          tx     tx_drop  mac_addr
-               0    78932932    78932931           1  56:48:4f:53:54:00
-            Ring Ports:
-              ID          rx          tx     rx_drop     rx_drop
-               0       89283       89283           0           0
-               ...
-        """
-
-        if 'phy_ports' in json_obj:
-            print('Physical Ports:')
-            print('  ID          rx          tx     tx_drop  mac_addr')
-            for pports in json_obj['phy_ports']:
-                print('  %2d  %10d  %10d  %10d  %s' % (
-                    pports['id'], pports['rx'],  pports['tx'],
-                    pports['tx_drop'], pports['eth']))
-
-        if 'ring_ports' in json_obj:
-            print('Ring Ports:')
-            print('  ID          rx          tx     rx_drop     rx_drop')
-            for rports in json_obj['ring_ports']:
-                print('  %2d  %10d  %10d  %10d  %10d' % (
-                    rports['id'], rports['rx'],  rports['tx'],
-                    rports['rx_drop'], rports['tx_drop']))
-
     def print_sec_status(self, json_obj):
         """Parse and print message from SPP secondary.
 
@@ -218,35 +162,6 @@ class Shell(cmd.Cmd, object):
             else:
                 print('  - %s -> %s' % (port, dst))
 
-    def command_primary(self, command):
-        """Send command to primary process"""
-
-        if command == 'status':
-            res = self.spp_ctl_cli.get('primary/status')
-            if res is not None:
-                if res.status_code == 200:
-                    self.print_pri_status(res.json())
-                elif res.status_code in self.rest_common_error_codes:
-                    pass
-                else:
-                    print('Error: unknown response.')
-
-        elif command == 'clear':
-            res = self.spp_ctl_cli.delete('primary/status')
-            if res is not None:
-                if res.status_code == 204:
-                    print('Clear port statistics.')
-                elif res.status_code in self.rest_common_error_codes:
-                    pass
-                else:
-                    print('Error: unknown response.')
-
-        elif command == 'exit':
-            print('"pri; exit" is deprecated.')
-
-        else:
-            print('Invalid pri command!')
-
     def command_secondary(self, sec_id, command):
         """Send command to secondary process."""
 
@@ -442,23 +357,12 @@ class Shell(cmd.Cmd, object):
         if logger is not None:
             logger.info("Receive pri command: '%s'" % command)
 
-        if command and (command in self.PRI_CMDS):
-            self.command_primary(command)
-        else:
-            message = "Invalid pri command: '%s'" % command
-            print(message)
+        self.spp_primary.run(command)
 
     def complete_pri(self, text, line, begidx, endidx):
-        """Completion for primary process commands"""
+        """Completion for primary process commands."""
 
-        if not text:
-            completions = self.PRI_CMDS[:]
-        else:
-            completions = [p
-                           for p in self.PRI_CMDS
-                           if p.startswith(text)
-                           ]
-        return completions
+        return self.spp_primary.complete(text, line, begidx, endidx)
 
     def do_sec(self, arg):
         """Send a command to secondary process specified with ID.
@@ -666,7 +570,7 @@ class Shell(cmd.Cmd, object):
             print('Closing secondary ...')
             self.close_all_secondary()
             print('Closing primary ...')
-            self.command_primary('exit')
+            self.spp_primary.run('exit')
         elif cmds[0] == '':
             print('Thank you for using Soft Patch Panel')
             self.close()
-- 
2.13.1

^ permalink raw reply	[flat|nested] 10+ messages in thread

* [spp] [PATCH 6/9] controller: move sec command to SppSecondary
  2018-10-18 11:25 [spp] [PATCH 0/9] Change SPP controller to use spp-ctl ogawa.yasufumi
                   ` (4 preceding siblings ...)
  2018-10-18 11:25 ` [spp] [PATCH 5/9] controller: move pri command to SppPrimary ogawa.yasufumi
@ 2018-10-18 11:25 ` ogawa.yasufumi
  2018-10-18 11:25 ` [spp] [PATCH 7/9] controller: move topo command to SppTopo ogawa.yasufumi
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: ogawa.yasufumi @ 2018-10-18 11:25 UTC (permalink / raw)
  To: spp, ferruh.yigit, ogawa.yasufumi

From: Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp>

SppSecondary defines 'sec' command and its completion as in a separated
module. It is intended to be used from Shell, which is derived from
'cmd.Cmd'.

Signed-off-by: Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp>
---
 src/controller/commands/sec.py   | 194 ++++++++++++++++++++++++++++++++++++++
 src/controller/shell.py          | 197 +++++----------------------------------
 src/controller/spp_ctl_client.py |   2 +
 3 files changed, 221 insertions(+), 172 deletions(-)
 create mode 100644 src/controller/commands/sec.py

diff --git a/src/controller/commands/sec.py b/src/controller/commands/sec.py
new file mode 100644
index 0000000..77cfe62
--- /dev/null
+++ b/src/controller/commands/sec.py
@@ -0,0 +1,194 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2018 Nippon Telegraph and Telephone Corporation
+
+
+class SppSecondary(object):
+    """Exec SPP secondary command.
+
+    SppSecondaryclass is intended to be used in Shell class as a delegator
+    for running 'sec' command.
+
+    'self.command()' is called from do_sec() and 'self.complete()' is called
+    from complete_sec() of both of which is defined in Shell.
+    """
+
+    # All of commands and sub-commands used for validation and completion.
+    SEC_CMDS = ['status', 'exit', 'forward', 'stop', 'add', 'patch', 'del']
+    SEC_SUBCMDS = ['vhost', 'ring', 'pcap', 'nullpmd']
+
+    def __init__(self, spp_ctl_cli):
+        self.spp_ctl_cli = spp_ctl_cli
+
+    def run(self, sec_id, cmdline):
+        """Called from do_sec() to Send command to secondary process."""
+
+        cmd = cmdline.split(' ')[0]
+        params = cmdline.split(' ')[1:]
+
+        if cmd == 'status':
+            res = self.spp_ctl_cli.get('nfvs/%d' % sec_id)
+            if res is not None:
+                error_codes = self.spp_ctl_cli.rest_common_error_codes
+                if res.status_code == 200:
+                    self.print_sec_status(res.json())
+                elif res.status_code in error_codes:
+                    pass
+                else:
+                    print('Error: unknown response.')
+
+        elif cmd == 'add':
+            req_params = {'action': 'add', 'port': params[0]}
+            res = self.spp_ctl_cli.put('nfvs/%d/ports' % sec_id, req_params)
+            if res is not None:
+                error_codes = self.spp_ctl_cli.rest_common_error_codes
+                if res.status_code == 204:
+                    print('Add %s.' % params[0])
+                elif res.status_code in error_codes:
+                    pass
+                else:
+                    print('Error: unknown response.')
+
+        elif cmd == 'del':
+            req_params = {'action': 'del', 'port': params[0]}
+            res = self.spp_ctl_cli.put('nfvs/%d/ports' % sec_id, req_params)
+            if res is not None:
+                error_codes = self.spp_ctl_cli.rest_common_error_codes
+                if res.status_code == 204:
+                    print('Delete %s.' % params[0])
+                elif res.status_code in error_codes:
+                    pass
+                else:
+                    print('Error: unknown response.')
+
+        elif cmd == 'forward' or cmd == 'stop':
+            if cmd == 'forward':
+                req_params = {'action': 'start'}
+            elif cmd == 'stop':
+                req_params = {'action': 'stop'}
+            else:
+                print('Unknown command. "forward" or "stop"?')
+
+            res = self.spp_ctl_cli.put('nfvs/%d/forward' % sec_id, req_params)
+            if res is not None:
+                error_codes = self.spp_ctl_cli.rest_common_error_codes
+                if res.status_code == 204:
+                    if cmd == 'forward':
+                        print('Start forwarding.')
+                    else:
+                        print('Stop forwarding.')
+                elif res.status_code in error_codes:
+                    pass
+                else:
+                    print('Error: unknown response.')
+
+        elif cmd == 'patch':
+            if params[0] == 'reset':
+                res = self.spp_ctl_cli.delete('nfvs/%d/patches' % sec_id)
+                if res is not None:
+                    error_codes = self.spp_ctl_cli.rest_common_error_codes
+                    if res.status_code == 204:
+                        print('Clear all of patches.')
+                    elif res.status_code in error_codes:
+                        pass
+                    else:
+                        print('Error: unknown response.')
+            else:
+                req_params = {'src': params[0], 'dst': params[1]}
+                res = self.spp_ctl_cli.put(
+                        'nfvs/%d/patches' % sec_id, req_params)
+                if res is not None:
+                    error_codes = self.spp_ctl_cli.rest_common_error_codes
+                    if res.status_code == 204:
+                        print('Patch ports (%s -> %s).' % (
+                            params[0], params[1]))
+                    elif res.status_code in error_codes:
+                        pass
+                    else:
+                        print('Error: unknown response.')
+
+        elif cmd == 'exit':
+            print('do nothing.')
+
+        else:
+            print('Invalid command "%s".' % cmd)
+
+    def print_sec_status(self, json_obj):
+        """Parse and print message from SPP secondary.
+
+        Print status received from secondary.
+
+          spp > sec 1;status
+          - status: idling
+          - ports:
+            - phy:0 -> ring:0
+            - phy:1
+
+        The format of the received message is JSON and ended with
+        series of null character "\x00".
+
+          {"client-id":1,...,"patches":[{"src":"phy:0"...},...]}'\x00..
+        """
+
+        sec_attr = json_obj
+        print('- status: %s' % sec_attr['status'])
+        print('- ports:')
+        for port in sec_attr['ports']:
+            dst = None
+            for patch in sec_attr['patches']:
+                if patch['src'] == port:
+                    dst = patch['dst']
+
+            if dst is None:
+                print('  - %s' % port)
+            else:
+                print('  - %s -> %s' % (port, dst))
+
+    def complete(self, sec_ids, text, line, begidx, endidx):
+        """Completion for secondary process commands.
+
+        Called from complete_sec() to complete secondary command.
+        """
+
+        try:
+            cleaned_line = line
+
+            if len(cleaned_line.split()) == 1:
+                completions = [str(i)+";" for i in sec_ids]
+            elif len(cleaned_line.split()) == 2:
+                if not (";" in cleaned_line):
+                    tmplist = [str(i) for i in sec_ids]
+                    completions = [p+";"
+                                   for p in tmplist
+                                   if p.startswith(text)
+                                   ]
+                elif cleaned_line[-1] == ";":
+                    completions = self.SEC_CMDS[:]
+                else:
+                    seccmd = cleaned_line.split(";")[1]
+                    if cleaned_line[-1] != " ":
+                        completions = [p
+                                       for p in self.SEC_CMDS
+                                       if p.startswith(seccmd)
+                                       ]
+                    elif ("add" in seccmd) or ("del" in seccmd):
+                        completions = self.SEC_SUBCMDS[:]
+                    else:
+                        completions = []
+            elif len(cleaned_line.split()) == 3:
+                subcmd = cleaned_line.split()[-1]
+                if ("add" == subcmd) or ("del" == subcmd):
+                    completions = self.SEC_SUBCMDS[:]
+                else:
+                    if cleaned_line[-1] == " ":
+                        completions = []
+                    else:
+                        completions = [p
+                                       for p in self.SEC_SUBCMDS
+                                       if p.startswith(subcmd)
+                                       ]
+            else:
+                completions = []
+            return completions
+        except Exception as e:
+            print(len(cleaned_line.split()))
+            print(e)
diff --git a/src/controller/shell.py b/src/controller/shell.py
index 9834df0..ca4775b 100644
--- a/src/controller/shell.py
+++ b/src/controller/shell.py
@@ -5,6 +5,7 @@ from __future__ import absolute_import
 
 import cmd
 from .commands import pri
+from .commands import sec
 import os
 import re
 import readline
@@ -27,14 +28,10 @@ class Shell(cmd.Cmd, object):
 
     PORT_TYPES = ['phy', 'ring', 'vhost', 'pcap', 'nullpmd']
 
-    SEC_CMDS = ['status', 'exit', 'forward', 'stop', 'add', 'patch', 'del']
-    SEC_SUBCMDS = ['vhost', 'ring', 'pcap', 'nullpmd']
     BYE_CMDS = ['sec', 'all']
 
     HIST_EXCEPT = ['bye', 'exit', 'history', 'redo']
 
-    rest_common_error_codes = [400, 404, 500]
-
     PLUGIN_DIR = 'command'
     subgraphs = {}
     topo_size = '60%'
@@ -49,6 +46,7 @@ class Shell(cmd.Cmd, object):
         cmd.Cmd.__init__(self)
         self.spp_ctl_cli = spp_ctl_cli
         self.spp_primary = pri.SppPrimary(self.spp_ctl_cli)
+        self.spp_secondary = sec.SppSecondary(self.spp_ctl_cli)
 
     def default(self, line):
         """Define defualt behaviour.
@@ -71,6 +69,20 @@ class Shell(cmd.Cmd, object):
         """
         pass
 
+    def get_sec_ids(self, ptype):
+        """Return a list of IDs of running secondary processes.
+
+        'ptype' is 'nfv' or 'vf'.
+        """
+
+        ids = []
+        res = self.spp_ctl_cli.get('processes')
+        if res.status_code == 200:
+            for ent in res.json():
+                if ent['type'] == ptype:
+                    ids.append(ent['client-id'])
+        return ids
+
     def clean_hist_file(self):
         """Remove useless entries in history file."""
 
@@ -126,129 +138,11 @@ class Shell(cmd.Cmd, object):
                 for obj in sec_obj['nfv']:
                     print('    %d: %s:%s' % (
                         obj['client-id'], obj['type'], obj['client-id']))
-            elif res.status_code in self.rest_common_error_codes:
+            elif res.status_code in self.spp_ctl_cli.rest_common_error_codes:
                 pass
             else:
                 print('Error: unknown response.')
 
-    def print_sec_status(self, json_obj):
-        """Parse and print message from SPP secondary.
-
-        Print status received from secondary.
-
-          spp > sec 1;status
-          - status: idling
-          - ports:
-            - phy:0 -> ring:0
-            - phy:1
-
-        The format of the received message is JSON and ended with
-        series of null character "\x00".
-
-          {"client-id":1,...,"patches":[{"src":"phy:0"...},...]}'\x00..
-        """
-
-        sec_attr = json_obj
-        print('- status: %s' % sec_attr['status'])
-        print('- ports:')
-        for port in sec_attr['ports']:
-            dst = None
-            for patch in sec_attr['patches']:
-                if patch['src'] == port:
-                    dst = patch['dst']
-
-            if dst is None:
-                print('  - %s' % port)
-            else:
-                print('  - %s -> %s' % (port, dst))
-
-    def command_secondary(self, sec_id, command):
-        """Send command to secondary process."""
-
-        cmd = command.split(' ')[0]
-        params = command.split(' ')[1:]
-
-        if cmd == 'status':
-            res = self.spp_ctl_cli.get('nfvs/%d' % sec_id)
-            if res is not None:
-                if res.status_code == 200:
-                    self.print_sec_status(res.json())
-                elif res.status_code in self.rest_common_error_codes:
-                    pass
-                else:
-                    print('Error: unknown response.')
-
-        elif cmd == 'add':
-            req_params = {'action': 'add', 'port': params[0]}
-            res = self.spp_ctl_cli.put('nfvs/%d/ports' % sec_id, req_params)
-            if res is not None:
-                if res.status_code == 204:
-                    print('Add %s.' % params[0])
-                elif res.status_code in self.rest_common_error_codes:
-                    pass
-                else:
-                    print('Error: unknown response.')
-
-        elif cmd == 'del':
-            req_params = {'action': 'del', 'port': params[0]}
-            res = self.spp_ctl_cli.put('nfvs/%d/ports' % sec_id, req_params)
-            if res is not None:
-                if res.status_code == 204:
-                    print('Delete %s.' % params[0])
-                elif res.status_code in self.rest_common_error_codes:
-                    pass
-                else:
-                    print('Error: unknown response.')
-
-        elif cmd == 'forward' or cmd == 'stop':
-            if cmd == 'forward':
-                req_params = {'action': 'start'}
-            elif cmd == 'stop':
-                req_params = {'action': 'stop'}
-            else:
-                print('Unknown command. "forward" or "stop"?')
-
-            res = self.spp_ctl_cli.put('nfvs/%d/forward' % sec_id, req_params)
-            if res is not None:
-                if res.status_code == 204:
-                    if cmd == 'forward':
-                        print('Start forwarding.')
-                    else:
-                        print('Stop forwarding.')
-                elif res.status_code in self.rest_common_error_codes:
-                    pass
-                else:
-                    print('Error: unknown response.')
-
-        elif cmd == 'patch':
-            if params[0] == 'reset':
-                res = self.spp_ctl_cli.delete('nfvs/%d/patches' % sec_id)
-                if res is not None:
-                    if res.status_code == 204:
-                        print('Clear all of patches.')
-                    elif res.status_code in self.rest_common_error_codes:
-                        pass
-                    else:
-                        print('Error: unknown response.')
-            else:
-                req_params = {'src': params[0], 'dst': params[1]}
-                res = self.spp_ctl_cli.put(
-                        'nfvs/%d/patches' % sec_id, req_params)
-                if res is not None:
-                    if res.status_code == 204:
-                        print('Patch ports (%s -> %s).' % (
-                            params[0], params[1]))
-                    elif res.status_code in self.rest_common_error_codes:
-                        pass
-                    else:
-                        print('Error: unknown response.')
-
-        elif cmd == 'exit':
-            print('do nothing.')
-
-        else:
-            print('Invalid command "%s".' % cmd)
-
     def is_patched_ids_valid(self, id1, id2, delim=':'):
         """Check if port IDs are valid
 
@@ -364,7 +258,7 @@ class Shell(cmd.Cmd, object):
 
         return self.spp_primary.complete(text, line, begidx, endidx)
 
-    def do_sec(self, arg):
+    def do_sec(self, cmd):
         """Send a command to secondary process specified with ID.
 
         SPP secondary process is specified with secondary ID and takes
@@ -382,18 +276,16 @@ class Shell(cmd.Cmd, object):
         """
 
         # remove unwanted spaces to avoid invalid command error
-        tmparg = self.clean_cmd(arg)
+        tmparg = self.clean_cmd(cmd)
         cmds = tmparg.split(';')
         if len(cmds) < 2:
-            message = "'sec' requires an ID and ';' before command."
-            print(message)
+            print("Required an ID and ';' before command.")
         elif str.isdigit(cmds[0]):
             sec_id = int(cmds[0])
             if self.check_sec_cmds(cmds[1]):
-                self.command_secondary(sec_id, cmds[1])
+                self.spp_secondary.run(sec_id, cmds[1])
             else:
-                message = "invalid cmd"
-                print(message)
+                print("Invalid sec command")
         else:
             print(cmds[0])
             print("first %s" % cmds[1])
@@ -401,48 +293,9 @@ class Shell(cmd.Cmd, object):
     def complete_sec(self, text, line, begidx, endidx):
         """Completion for secondary process commands"""
 
-        try:
-            cleaned_line = self.clean_cmd(line)
-            if len(cleaned_line.split()) == 1:
-                completions = [str(i)+";" for i in spp_common.SECONDARY_LIST]
-            elif len(cleaned_line.split()) == 2:
-                if not (";" in cleaned_line):
-                    tmplist = [str(i) for i in spp_common.SECONDARY_LIST]
-                    completions = [p+";"
-                                   for p in tmplist
-                                   if p.startswith(text)
-                                   ]
-                elif cleaned_line[-1] == ";":
-                    completions = self.SEC_CMDS[:]
-                else:
-                    seccmd = cleaned_line.split(";")[1]
-                    if cleaned_line[-1] != " ":
-                        completions = [p
-                                       for p in self.SEC_CMDS
-                                       if p.startswith(seccmd)
-                                       ]
-                    elif ("add" in seccmd) or ("del" in seccmd):
-                        completions = self.SEC_SUBCMDS[:]
-                    else:
-                        completions = []
-            elif len(cleaned_line.split()) == 3:
-                subcmd = cleaned_line.split()[-1]
-                if ("add" == subcmd) or ("del" == subcmd):
-                    completions = self.SEC_SUBCMDS[:]
-                else:
-                    if cleaned_line[-1] == " ":
-                        completions = []
-                    else:
-                        completions = [p
-                                       for p in self.SEC_SUBCMDS
-                                       if p.startswith(subcmd)
-                                       ]
-            else:
-                completions = []
-            return completions
-        except Exception as e:
-            print(len(cleaned_line.split()))
-            print(e)
+        line = self.clean_cmd(line)
+        return self.spp_secondary.complete(
+                self.get_sec_ids('nfv'), text, line, begidx, endidx)
 
     def do_record(self, fname):
         """Save commands as a recipe file.
diff --git a/src/controller/spp_ctl_client.py b/src/controller/spp_ctl_client.py
index a1d6d93..8a88fa4 100644
--- a/src/controller/spp_ctl_client.py
+++ b/src/controller/spp_ctl_client.py
@@ -7,6 +7,8 @@ import requests
 
 class SppCtlClient(object):
 
+    rest_common_error_codes = [400, 404, 500]
+
     def __init__(self, ip_addr='localhost', port=7777):
         api_ver = 'v1'
         self.base_url = 'http://%s:%d/%s' % (ip_addr, port, api_ver)
-- 
2.13.1

^ permalink raw reply	[flat|nested] 10+ messages in thread

* [spp] [PATCH 7/9] controller: move topo command to SppTopo
  2018-10-18 11:25 [spp] [PATCH 0/9] Change SPP controller to use spp-ctl ogawa.yasufumi
                   ` (5 preceding siblings ...)
  2018-10-18 11:25 ` [spp] [PATCH 6/9] controller: move sec command to SppSecondary ogawa.yasufumi
@ 2018-10-18 11:25 ` ogawa.yasufumi
  2018-10-18 11:25 ` [spp] [PATCH 8/9] controller: move topo_resize " ogawa.yasufumi
  2018-10-18 11:25 ` [spp] [PATCH 9/9] controller: move bye command to SppBye ogawa.yasufumi
  8 siblings, 0 replies; 10+ messages in thread
From: ogawa.yasufumi @ 2018-10-18 11:25 UTC (permalink / raw)
  To: spp, ferruh.yigit, ogawa.yasufumi

From: Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp>

SppTopo defines 'topo' command and its completion as in a separated
module. It is intended to be used from Shell, which is derived from
'cmd.Cmd'.

Signed-off-by: Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp>
---
 src/controller/{ => commands}/topo.py | 247 +++++++++++++++++++++++-----------
 src/controller/shell.py               | 104 ++++----------
 src/controller/spp_common.py          |   3 -
 3 files changed, 190 insertions(+), 164 deletions(-)
 rename src/controller/{ => commands}/topo.py (56%)

diff --git a/src/controller/topo.py b/src/controller/commands/topo.py
similarity index 56%
rename from src/controller/topo.py
rename to src/controller/commands/topo.py
index e608961..fc22a98 100644
--- a/src/controller/topo.py
+++ b/src/controller/commands/topo.py
@@ -4,15 +4,14 @@
 
 import os
 import re
-from . import spp_common
-from .spp_common import logger
+import socket
 import subprocess
 import traceback
 import uuid
 import yaml
 
 
-class Topo(object):
+class SppTopo(object):
     """Setup and output network topology for topo command
 
     Topo command supports four types of output.
@@ -22,37 +21,64 @@ class Topo(object):
     * text (dot, json, yaml)
     """
 
-    def __init__(self, sec_ids, m2s_queues, s2m_queues, sub_graphs):
-        logger.info("Topo initialized with sec IDs %s" % sec_ids)
+    delim_node = '_'
+
+    def __init__(self, spp_ctl_cli, sec_ids, subgraphs):
+        self.spp_ctl_cli = spp_ctl_cli
         self.sec_ids = sec_ids
-        self.m2s_queues = m2s_queues
-        self.s2m_queues = s2m_queues
-        self.sub_graphs = sub_graphs
+        self.subgraphs = subgraphs
+
+    def run(self, args, topo_size):
+        args_ary = args.split()
+        if len(args_ary) == 0:
+            print("Usage: topo dst [ftype]")
+            return False
+        elif (args_ary[0] == "term") or (args_ary[0] == "http"):
+            self.show(args_ary[0], topo_size)
+        elif len(args_ary) == 1:
+            ftype = args_ary[0].split(".")[-1]
+            self.output(args_ary[0], ftype)
+        elif len(args_ary) == 2:
+            self.output(args_ary[0], args_ary[1])
+        else:
+            print("Usage: topo dst [ftype]")
 
     def show(self, dtype, size):
         res_ary = []
+        error_codes = self.spp_ctl_cli.rest_common_error_codes
+
         for sec_id in self.sec_ids:
-            msg = "status"
-            self.m2s_queues[sec_id].put(msg.encode('utf-8'))
-            res = self.format_sec_status(
-                sec_id, self.s2m_queues[sec_id].get(True))
-            res_ary.append(res)
+            res = self.spp_ctl_cli.get('nfvs/%d' % sec_id)
+            if res.status_code == 200:
+                res_ary.append(res.json())
+            elif res.status_code in error_codes:
+                # Print default error message
+                pass
+            else:
+                # Ignore unknown response because no problem for drawing graph
+                pass
+
         if dtype == "http":
             self.to_http(res_ary)
         elif dtype == "term":
             self.to_term(res_ary, size)
         else:
             print("Invalid file type")
-            return res_ary
 
     def output(self, fname, ftype="dot"):
         res_ary = []
+        error_codes = self.spp_ctl_cli.rest_common_error_codes
+
         for sec_id in self.sec_ids:
-            msg = "status"
-            self.m2s_queues[sec_id].put(msg.encode('utf-8'))
-            res = self.format_sec_status(
-                sec_id, self.s2m_queues[sec_id].get(True))
-            res_ary.append(res)
+            res = self.spp_ctl_cli.get('nfvs/%d' % sec_id)
+            if res.status_code == 200:
+                res_ary.append(res.json())
+            elif res.status_code in error_codes:
+                # Print default error message
+                pass
+            else:
+                # Ignore unknown response because no problem for drawing graph
+                pass
 
         if ftype == "dot":
             self.to_dot(res_ary, fname)
@@ -69,8 +95,6 @@ class Topo(object):
         return res_ary
 
     def to_dot(self, sec_list, output_fname):
-        # Label given if outport is "none"
-        NO_PORT = None
 
         # Graphviz params
         # TODO(yasufum) consider to move gviz params to config file.
@@ -89,7 +113,7 @@ class Topo(object):
 
         node_attrs = 'node[shape="rectangle", style="filled"];'
 
-        node_template = '%s' + spp_common.delim_node + '%s'
+        node_template = '%s' + self.delim_node + '%s'
 
         phys = []
         rings = []
@@ -100,38 +124,45 @@ class Topo(object):
         for sec in sec_list:
             if sec is None:
                 continue
-            for port in sec["ports"]:
-                if port["iface"]["type"] == "phy":
-                    phys.append(port)
-                elif port["iface"]["type"] == "ring":
-                    rings.append(port)
-                elif port["iface"]["type"] == "vhost":
-                    vhosts.append(port)
-                else:
-                    raise ValueError(
-                        "Invaid interface type: %s" % port["iface"]["type"])
-
-                if port['out'] != NO_PORT:
-                    out_type, out_id = port['out'].split(':')
-                    if sec['status'] == 'running':
-                        l_style = LINE_STYLE["running"]
+            for port in sec['ports']:
+                if self._is_valid_port(port):
+                    r_type = port.split(':')[0]
+                    # TODO(yasufum) change decision of r_type smarter
+                    if r_type == 'phy':
+                        phys.append(port)
+                    elif r_type == 'ring':
+                        rings.append(port)
+                    elif r_type == 'vhost':
+                        vhosts.append(port)
+                    # TODO(yasufum) add drawing pcap and nullpmd
+                    elif r_type == 'pcap':
+                        pass
+                    elif r_type == 'nullpmd':
+                        pass
                     else:
-                        l_style = LINE_STYLE["idling"]
-                    attrs = '[label="%s", color="%s", style="%s"]' % (
-                        "sec%d" % sec["sec_id"],
-                        SEC_COLORS[sec["sec_id"]],
-                        l_style
-                    )
-                    link_style = node_template + ' %s ' + node_template + '%s;'
-                    tmp = link_style % (
-                        port["iface"]["type"],
-                        port["iface"]["id"],
-                        LINK_TYPE,
-                        out_type,
-                        out_id,
-                        attrs
-                    )
-                    links.append(tmp)
+                        raise ValueError(
+                            "Invaid interface type: %s" % r_type)
+
+            for patch in sec['patches']:
+                if sec['status'] == 'running':
+                    l_style = LINE_STYLE["running"]
+                else:
+                    l_style = LINE_STYLE["idling"]
+                attrs = '[label="%s", color="%s", style="%s"]' % (
+                    "sec%d" % sec["client-id"],
+                    SEC_COLORS[sec["client-id"]],
+                    l_style
+                )
+                link_style = node_template + ' %s ' + node_template + '%s;'
+
+                if self._is_valid_port(patch['src']):
+                    src_type, src_id = patch['src'].split(':')
+                if self._is_valid_port(patch['dst']):
+                    dst_type, dst_id = patch['dst'].split(':')
+
+                tmp = link_style % (src_type, src_id, LINK_TYPE,
+                                    dst_type, dst_id, attrs)
+                links.append(tmp)
 
         output = ["%s spp{" % GRAPH_TYPE]
         output.append("newrank=true;")
@@ -139,70 +170,65 @@ class Topo(object):
 
         phy_nodes = []
         for node in phys:
+            r_type, r_id = node.split(':')
             phy_nodes.append(
-                node_template % (node['iface']['type'], node['iface']['id']))
+                node_template % (r_type, r_id))
         phy_nodes = list(set(phy_nodes))
         for node in phy_nodes:
-            label = re.sub(
-                r'%s' % spp_common.delim_node, spp_common.delim_label, node)
+            label = re.sub(r'%s' % self.delim_node, ':', node)
             output.append(
                 '%s[label="%s", fillcolor="%s"];' % (
                     node, label, PORT_COLORS["phy"]))
 
         ring_nodes = []
-        for p in rings:
-            ring_nodes.append(
-                node_template % (p['iface']['type'], p['iface']['id']))
+        for node in rings:
+            r_type, r_id = node.split(':')
+            ring_nodes.append(node_template % (r_type, r_id))
         ring_nodes = list(set(ring_nodes))
         for node in ring_nodes:
-            label = re.sub(
-                r'%s' % spp_common.delim_node, spp_common.delim_label, node)
+            label = re.sub(r'%s' % self.delim_node, ':', node)
             output.append(
                 '%s[label="%s", fillcolor="%s"];' % (
                     node, label, PORT_COLORS["ring"]))
 
         vhost_nodes = []
-        for p in vhosts:
-            vhost_nodes.append(
-                node_template % (p["iface"]["type"], p["iface"]["id"]))
+        for node in vhosts:
+            r_type, r_id = node.split(':')
+            vhost_nodes.append(node_template % (r_type, r_id))
         vhost_nodes = list(set(vhost_nodes))
         for node in vhost_nodes:
-            label = re.sub(
-                r'%s' % spp_common.delim_node, spp_common.delim_label, node)
+            label = re.sub(r'%s' % self.delim_node, ':', node)
             output.append(
                 '%s[label="%s", fillcolor="%s"];' % (
                     node, label, PORT_COLORS["vhost"]))
 
-        # rank
+        # Align the same type of nodes with rank attribute
         output.append(
             '{rank=same; %s}' % ("; ".join(ring_nodes)))
         output.append(
             '{rank=same; %s}' % ("; ".join(vhost_nodes)))
 
+        # Decide the bottom, phy or vhost
         rank_style = '{rank=max; %s}' % node_template
         if len(phys) > 0:
-            output.append(
-                rank_style % (
-                    phys[0]["iface"]["type"], phys[0]["iface"]["id"]))
+            r_type, r_id = phys[0].split(':')
         elif len(vhosts) > 0:
-            output.append(
-                rank_style % (
-                    vhosts[0]["iface"]["type"], vhosts[0]["iface"]["id"]))
+            r_type, r_id = vhosts[0].split(':')
+        output.append(rank_style % (r_type, r_id))
 
+        # TODO(yasufum) check if it is needed, or is not needed for vhost_nodes
         if len(phy_nodes) > 0:
             output.append(
                 '{rank=same; %s}' % ("; ".join(phy_nodes)))
 
         # Add subgraph
         ssgs = []
-        if len(self.sub_graphs) > 0:
+        if len(self.subgraphs) > 0:
             cnt = 1
-            for label, val in self.sub_graphs.items():
+            for label, val in self.subgraphs.items():
                 cluster_id = "cluster%d" % cnt
                 ssg_label = label
-                ssg_ports = re.sub(
-                    r'%s' % spp_common.delim_label,
-                    spp_common.delim_node, val)
+                ssg_ports = re.sub(r'%s' % ':', self.delim_node, val)
                 ssg = 'subgraph %s {label="%s" %s}' % (
                     cluster_id, ssg_label, ssg_ports)
                 ssgs.append(ssg)
@@ -258,16 +284,19 @@ class Topo(object):
         msg = open(tmpfile).read()
         subprocess.call("rm -f %s" % tmpfile, shell=True)
         ws_url = "ws://localhost:8989/spp_ws"
-        ws = websocket.create_connection(ws_url)
-        ws.send(msg)
-        ws.close()
+        try:
+            ws = websocket.create_connection(ws_url)
+            ws.send(msg)
+            ws.close()
+        except socket.error:
+            print('Error: Connection refused! Is running websocket server?')
 
     def to_term(self, sec_list, size):
         tmpfile = "%s.jpg" % uuid.uuid4().hex
         self.to_img(sec_list, tmpfile)
         from distutils import spawn
 
-        # TODO(yasufum) Add check for using only supported terminal
+        # TODO(yasufum) add check for using only supported terminal
         if spawn.find_executable("img2sixel") is not None:
             img_cmd = "img2sixel"
         else:
@@ -362,3 +391,57 @@ class Topo(object):
         except Exception:
             traceback.print_exc()
             return None
+
+    def complete(self, text, line, begidx, endidx):
+        """Complete topo command
+
+        If no token given, return 'term' and 'http'.
+        On the other hand, complete 'term' or 'http' if token starts
+        from it, or complete file name if is one of supported formats.
+        """
+
+        terms = ['term', 'http']
+        # Supported formats
+        img_exts = ['jpg', 'png', 'bmp']
+        txt_exts = ['dot', 'yml', 'js']
+
+        # Number of given tokens is expected as two. First one is
+        # 'topo'. If it is three or more, this methods returns nothing.
+        tokens = re.sub(r"\s+", " ", line).split(' ')
+        if (len(tokens) == 2):
+            if (text == ''):
+                completions = terms
+            else:
+                completions = []
+                # Check if 2nd token is a part of terms.
+                for t in terms:
+                    if t.startswith(tokens[1]):
+                        completions.append(t)
+                # Not a part of terms, so check for completion for
+                # output file name.
+                if len(completions) == 0:
+                    if tokens[1].endswith('.'):
+                        completions = img_exts + txt_exts
+                    elif ('.' in tokens[1]):
+                        fname = tokens[1].split('.')[0]
+                        token = tokens[1].split('.')[-1]
+                        for ext in img_exts:
+                            if ext.startswith(token):
+                                completions.append(fname + '.' + ext)
+                        for ext in txt_exts:
+                            if ext.startswith(token):
+                                completions.append(fname + '.' + ext)
+            return completions
+        else:  # do nothing for three or more tokens
+            pass
+
+    def _is_valid_port(self, port):
+        """Check if port's format is valid.
+
+        Return True if the format is 'r_type:r_id', for example, 'phy:0'.
+        """
+
+        if (':' in port) and (len(port.split(':')) == 2):
+            return True
+        else:
+            return False
diff --git a/src/controller/shell.py b/src/controller/shell.py
index ca4775b..0f95447 100644
--- a/src/controller/shell.py
+++ b/src/controller/shell.py
@@ -6,6 +6,7 @@ from __future__ import absolute_import
 import cmd
 from .commands import pri
 from .commands import sec
+from .commands import topo
 import os
 import re
 import readline
@@ -13,7 +14,6 @@ from .shell_lib import common
 from . import spp_common
 from .spp_common import logger
 import subprocess
-from . import topo
 
 
 class Shell(cmd.Cmd, object):
@@ -33,7 +33,6 @@ class Shell(cmd.Cmd, object):
     HIST_EXCEPT = ['bye', 'exit', 'history', 'redo']
 
     PLUGIN_DIR = 'command'
-    subgraphs = {}
     topo_size = '60%'
 
     # setup history file
@@ -47,6 +46,9 @@ class Shell(cmd.Cmd, object):
         self.spp_ctl_cli = spp_ctl_cli
         self.spp_primary = pri.SppPrimary(self.spp_ctl_cli)
         self.spp_secondary = sec.SppSecondary(self.spp_ctl_cli)
+        self.spp_topo = topo.SppTopo(self.spp_ctl_cli,
+                                     self.get_sec_ids('nfv'),
+                                     {})
 
     def default(self, line):
         """Define defualt behaviour.
@@ -77,10 +79,11 @@ class Shell(cmd.Cmd, object):
 
         ids = []
         res = self.spp_ctl_cli.get('processes')
-        if res.status_code == 200:
-            for ent in res.json():
-                if ent['type'] == ptype:
-                    ids.append(ent['client-id'])
+        if res is not None:
+            if res.status_code == 200:
+                for ent in res.json():
+                    if ent['type'] == ptype:
+                        ids.append(ent['client-id'])
         return ids
 
     def clean_hist_file(self):
@@ -589,13 +592,18 @@ class Shell(cmd.Cmd, object):
         label: vm1	subgraph: "vhost:1;vhost:2"
         """
 
+        #logger.info("Topo initialized with sec IDs %s" % sec_ids)
+
+        # delimiter of node in dot file
+        delim_node = '_'
+
         args_cleaned = re.sub(r"\s+", ' ', args).strip()
         # Show subgraphs if given no argments
         if (args_cleaned == ''):
-            if len(self.subgraphs) == 0:
+            if len(self.spp_topo.subgraphs) == 0:
                 print("No subgraph.")
             else:
-                for label, subg in self.subgraphs.items():
+                for label, subg in self.spp_topo.subgraphs.items():
                     print('label: %s\tsubgraph: "%s"' % (label, subg))
         else:  # add or del
             tokens = args_cleaned.split(' ')
@@ -605,13 +613,11 @@ class Shell(cmd.Cmd, object):
                     label = tokens[1]
                     subg = tokens[2]
                     if ',' in subg:
-                        subg = re.sub(
-                            r'%s' % spp_common.delim_node,
-                            spp_common.delim_label, subg)
+                        subg = re.sub(r'%s' % delim_node, ':', subg)
                         subg = re.sub(r",", ";", subg)
 
                     # TODO(yasufum) add validation for subgraph
-                    self.subgraphs[label] = subg
+                    self.spp_topo.subgraphs[label] = subg
                     print("Add subgraph '%s'" % label)
                 else:
                     print("Invalid syntax '%s'!" % args_cleaned)
@@ -619,7 +625,7 @@ class Shell(cmd.Cmd, object):
             elif ((tokens[0] == 'del') or
                     (tokens[0] == 'delete') or
                     (tokens[0] == 'remove')):
-                del(self.subgraphs[tokens[1]])
+                del(self.spp_topo.subgraphs[tokens[1]])
                 print("Delete subgraph '%s'" % tokens[1])
 
             else:
@@ -633,11 +639,11 @@ class Shell(cmd.Cmd, object):
             if len(tokens) == 1:
                 return terms
             elif len(tokens) == 2 and tokens[1] == 'del':
-                return self.subgraphs.keys()
+                return self.spp_topo.subgraphs.keys()
         elif text != '':
             completions = []
             if len(tokens) == 3 and tokens[1] == 'del':
-                for t in self.subgraphs.keys():
+                for t in self.spp_topo.subgraphs.keys():
                     if t.startswith(tokens[2]):
                         completions.append(t)
             elif len(tokens) == 2:
@@ -679,80 +685,20 @@ class Shell(cmd.Cmd, object):
         * terminal (but very few terminals supporting to display images)
         * browser (websocket server is required)
         * image file (jpg, png, bmp)
-        * text (dot, json, yaml)
+        * text (dot, js or json, yml or yaml)
 
         spp > topo term  # terminal
         spp > topo http  # browser
         spp > topo network_conf.jpg  # image
         spp > topo network_conf.dot  # text
+        spp > topo network_conf.js# text
         """
 
-        if len(spp_common.SECONDARY_LIST) == 0:
-            message = "secondary not exist"
-            print(message)
-        else:
-            tp = topo.Topo(
-                spp_common.SECONDARY_LIST,
-                spp_common.MAIN2SEC,
-                spp_common.SEC2MAIN,
-                self.subgraphs)
-            args_ary = args.split()
-            if len(args_ary) == 0:
-                print("Usage: topo dst [ftype]")
-                return False
-            elif (args_ary[0] == "term") or (args_ary[0] == "http"):
-                res_ary = tp.show(args_ary[0], self.topo_size)
-            elif len(args_ary) == 1:
-                ftype = args_ary[0].split(".")[-1]
-                res_ary = tp.output(args_ary[0], ftype)
-            elif len(args_ary) == 2:
-                res_ary = tp.output(args_ary[0], args_ary[1])
-            else:
-                print("Usage: topo dst [ftype]")
-                return False
+        self.spp_topo.run(args, self.topo_size)
 
     def complete_topo(self, text, line, begidx, endidx):
-        """Complete topo command
 
-        If no token given, return 'term' and 'http'.
-        On the other hand, complete 'term' or 'http' if token starts
-        from it, or complete file name if is one of supported formats.
-        """
-
-        terms = ['term', 'http']
-        # Supported formats
-        img_exts = ['jpg', 'png', 'bmp']
-        txt_exts = ['dot', 'yml', 'js']
-
-        # Number of given tokens is expected as two. First one is
-        # 'topo'. If it is three or more, this methods returns nothing.
-        tokens = re.sub(r"\s+", " ", line).split(' ')
-        if (len(tokens) == 2):
-            if (text == ''):
-                completions = terms
-            else:
-                completions = []
-                # Check if 2nd token is a part of terms.
-                for t in terms:
-                    if t.startswith(tokens[1]):
-                        completions.append(t)
-                # Not a part of terms, so check for completion for
-                # output file name.
-                if len(completions) == 0:
-                    if tokens[1].endswith('.'):
-                        completions = img_exts + txt_exts
-                    elif ('.' in tokens[1]):
-                        fname = tokens[1].split('.')[0]
-                        token = tokens[1].split('.')[-1]
-                        for ext in img_exts:
-                            if ext.startswith(token):
-                                completions.append(fname + '.' + ext)
-                        for ext in txt_exts:
-                            if ext.startswith(token):
-                                completions.append(fname + '.' + ext)
-            return completions
-        else:  # do nothing for three or more tokens
-            pass
+        return self.spp_topo.complete(text, line, begidx, endidx)
 
     def do_load_cmd(self, args):
         """Load command plugin.
diff --git a/src/controller/spp_common.py b/src/controller/spp_common.py
index 0986918..809bee5 100644
--- a/src/controller/spp_common.py
+++ b/src/controller/spp_common.py
@@ -23,6 +23,3 @@ SECONDARY_LIST = []
 
 # Maximum num of sock queues for secondaries
 MAX_SECONDARY = 16
-
-delim_node = '_'
-delim_label = ':'
-- 
2.13.1

^ permalink raw reply	[flat|nested] 10+ messages in thread

* [spp] [PATCH 8/9] controller: move topo_resize command to SppTopo
  2018-10-18 11:25 [spp] [PATCH 0/9] Change SPP controller to use spp-ctl ogawa.yasufumi
                   ` (6 preceding siblings ...)
  2018-10-18 11:25 ` [spp] [PATCH 7/9] controller: move topo command to SppTopo ogawa.yasufumi
@ 2018-10-18 11:25 ` ogawa.yasufumi
  2018-10-18 11:25 ` [spp] [PATCH 9/9] controller: move bye command to SppBye ogawa.yasufumi
  8 siblings, 0 replies; 10+ messages in thread
From: ogawa.yasufumi @ 2018-10-18 11:25 UTC (permalink / raw)
  To: spp, ferruh.yigit, ogawa.yasufumi

From: Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp>

Add graph size to SppTopo and change 'topo_resize' command to change
this attiribute.

Signed-off-by: Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp>
---
 src/controller/commands/topo.py | 23 +++++++++++++++++++----
 src/controller/shell.py         | 18 +++---------------
 2 files changed, 22 insertions(+), 19 deletions(-)

diff --git a/src/controller/commands/topo.py b/src/controller/commands/topo.py
index fc22a98..53cc8b5 100644
--- a/src/controller/commands/topo.py
+++ b/src/controller/commands/topo.py
@@ -1,4 +1,3 @@
-#!/usr/bin/env python
 # SPDX-License-Identifier: BSD-3-Clause
 # Copyright(c) 2017-2018 Nippon Telegraph and Telephone Corporation
 
@@ -23,18 +22,19 @@ class SppTopo(object):
 
     delim_node = '_'
 
-    def __init__(self, spp_ctl_cli, sec_ids, subgraphs):
+    def __init__(self, spp_ctl_cli, sec_ids, subgraphs, size):
         self.spp_ctl_cli = spp_ctl_cli
         self.sec_ids = sec_ids
         self.subgraphs = subgraphs
+        self.graph_size = size
 
-    def run(self, args, topo_size):
+    def run(self, args):
         args_ary = args.split()
         if len(args_ary) == 0:
             print("Usage: topo dst [ftype]")
             return False
         elif (args_ary[0] == "term") or (args_ary[0] == "http"):
-            self.show(args_ary[0], topo_size)
+            self.show(args_ary[0], self.graph_size)
         elif len(args_ary) == 1:
             ftype = args_ary[0].split(".")[-1]
             self.output(args_ary[0], ftype)
@@ -320,6 +320,21 @@ class SppTopo(object):
             topo_doc += "commands/experimental.html"
             print("See '%s' for required packages." % topo_doc)
 
+    def resize_graph(self, args):
+        if args == '':
+            print(self.graph_size)
+        else:
+            if '%' in args:
+                self.graph_size = args
+                print(self.graph_size)
+            elif '.' in args:
+                ii = float(args) * 100
+                self.graph_size = str(ii) + '%'
+                print(self.graph_size)
+            else:  # TODO(yasufum) add check for no number
+                self.graph_size = str(float(args) * 100) + '%'
+                print(self.graph_size)
+
     def format_sec_status(self, sec_id, stat):
         """Return formatted secondary status as a hash
 
diff --git a/src/controller/shell.py b/src/controller/shell.py
index 0f95447..bdc41fe 100644
--- a/src/controller/shell.py
+++ b/src/controller/shell.py
@@ -48,7 +48,7 @@ class Shell(cmd.Cmd, object):
         self.spp_secondary = sec.SppSecondary(self.spp_ctl_cli)
         self.spp_topo = topo.SppTopo(self.spp_ctl_cli,
                                      self.get_sec_ids('nfv'),
-                                     {})
+                                     {}, self.topo_size)
 
     def default(self, line):
         """Define defualt behaviour.
@@ -664,19 +664,7 @@ class Shell(cmd.Cmd, object):
 
         """
 
-        if args == '':
-            print(self.topo_size)
-        else:
-            if '%' in args:
-                self.topo_size = args
-                print(self.topo_size)
-            elif '.' in args:
-                ii = float(args) * 100
-                self.topo_size = str(ii) + '%'
-                print(self.topo_size)
-            else:  # TODO(yasufum) add check for no number
-                self.topo_size = str(float(args) * 100) + '%'
-                print(self.topo_size)
+        self.spp_topo.resize_graph(args)
 
     def do_topo(self, args):
         """Output network topology.
@@ -694,7 +682,7 @@ class Shell(cmd.Cmd, object):
         spp > topo network_conf.js# text
         """
 
-        self.spp_topo.run(args, self.topo_size)
+        self.spp_topo.run(args)
 
     def complete_topo(self, text, line, begidx, endidx):
 
-- 
2.13.1

^ permalink raw reply	[flat|nested] 10+ messages in thread

* [spp] [PATCH 9/9] controller: move bye command to SppBye
  2018-10-18 11:25 [spp] [PATCH 0/9] Change SPP controller to use spp-ctl ogawa.yasufumi
                   ` (7 preceding siblings ...)
  2018-10-18 11:25 ` [spp] [PATCH 8/9] controller: move topo_resize " ogawa.yasufumi
@ 2018-10-18 11:25 ` ogawa.yasufumi
  8 siblings, 0 replies; 10+ messages in thread
From: ogawa.yasufumi @ 2018-10-18 11:25 UTC (permalink / raw)
  To: spp, ferruh.yigit, ogawa.yasufumi

From: Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp>

SppBye defines 'bye' command and its completion as in a separated
module. It is intended to be used from Shell, which is derived from
'cmd.Cmd'.

Signed-off-by: Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp>
---
 src/controller/commands/bye.py | 48 ++++++++++++++++++++++++++++++++++++++++++
 src/controller/shell.py        | 44 ++++++++++----------------------------
 2 files changed, 59 insertions(+), 33 deletions(-)
 create mode 100644 src/controller/commands/bye.py

diff --git a/src/controller/commands/bye.py b/src/controller/commands/bye.py
new file mode 100644
index 0000000..e5ca3bb
--- /dev/null
+++ b/src/controller/commands/bye.py
@@ -0,0 +1,48 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2018 Nippon Telegraph and Telephone Corporation
+
+
+class SppBye(object):
+    """Exec SPP bye command.
+
+    SppBye class is intended to be used in Shell class as a delegator
+    for running 'bye' command.
+
+    'self.command()' is called from do_bye() and 'self.complete()' is called
+    from complete_bye() of both of which is defined in Shell.
+    """
+
+    BYE_CMDS = ['sec', 'all']
+
+    def __init__(self, spp_ctl_cli, spp_primary, spp_secondary):
+        self.spp_ctl_cli = spp_ctl_cli
+        self.spp_primary = spp_primary
+        self.spp_secondary = spp_secondary
+
+    def run(self, args, sec_ids):
+
+        cmds = args.split(' ')
+        if cmds[0] == 'sec':
+            self.close_all_secondary(sec_ids)
+        elif cmds[0] == 'all':
+            print('Closing secondary ...')
+            self.close_all_secondary(sec_ids)
+            print('Closing primary ...')
+            self.spp_primary.run('exit')
+
+    def complete(self, text, line, begidx, endidx):
+
+        if not text:
+            completions = self.BYE_CMDS[:]
+        else:
+            completions = [p
+                           for p in self.BYE_CMDS
+                           if p.startswith(text)
+                           ]
+        return completions
+
+    def close_all_secondary(self, sec_ids):
+        """Terminate all secondary processes."""
+
+        for i in sec_ids:
+            self.spp_secondary.run(i, 'exit')
diff --git a/src/controller/shell.py b/src/controller/shell.py
index bdc41fe..ec5f481 100644
--- a/src/controller/shell.py
+++ b/src/controller/shell.py
@@ -4,6 +4,7 @@
 from __future__ import absolute_import
 
 import cmd
+from .commands import bye
 from .commands import pri
 from .commands import sec
 from .commands import topo
@@ -11,7 +12,6 @@ import os
 import re
 import readline
 from .shell_lib import common
-from . import spp_common
 from .spp_common import logger
 import subprocess
 
@@ -28,8 +28,6 @@ class Shell(cmd.Cmd, object):
 
     PORT_TYPES = ['phy', 'ring', 'vhost', 'pcap', 'nullpmd']
 
-    BYE_CMDS = ['sec', 'all']
-
     HIST_EXCEPT = ['bye', 'exit', 'history', 'redo']
 
     PLUGIN_DIR = 'command'
@@ -49,6 +47,8 @@ class Shell(cmd.Cmd, object):
         self.spp_topo = topo.SppTopo(self.spp_ctl_cli,
                                      self.get_sec_ids('nfv'),
                                      {}, self.topo_size)
+        self.spp_bye = bye.SppBye(self.spp_ctl_cli, self.spp_primary,
+                                  self.spp_secondary)
 
     def default(self, line):
         """Define defualt behaviour.
@@ -104,15 +104,6 @@ class Shell(cmd.Cmd, object):
         except IOError:
             print('Error: Cannot open history file "%s"' % self.hist_file)
 
-    def close_all_secondary(self):
-        """Terminate all secondary processes."""
-
-        tmp_list = []
-        for i in spp_common.SECONDARY_LIST:
-            tmp_list.append(i)
-        for i in tmp_list:
-            self.command_secondary(i, 'exit')
-
     def print_status(self):
         """Display information about connected clients."""
 
@@ -403,7 +394,7 @@ class Shell(cmd.Cmd, object):
     def complete_mkdir(self, text, line, begidx, endidx):
         return common.compl_common(text, line)
 
-    def do_bye(self, arg):
+    def do_bye(self, args):
         """Terminate SPP processes and controller.
 
         There are three usages for terminating processes.
@@ -419,30 +410,17 @@ class Shell(cmd.Cmd, object):
         spp > bye
         """
 
-        cmds = arg.split(' ')
-        if cmds[0] == 'sec':
-            self.close_all_secondary()
-        elif cmds[0] == 'all':
-            print('Closing secondary ...')
-            self.close_all_secondary()
-            print('Closing primary ...')
-            self.spp_primary.run('exit')
-        elif cmds[0] == '':
-            print('Thank you for using Soft Patch Panel')
-            self.close()
+        cmds = args.split(' ')
+        if cmds[0] == '':
+            self.do_exit('')
             return True
+        else:  # 'all' or 'sec'
+            self.spp_bye.run(args, self.get_sec_ids('nfv'))
 
     def complete_bye(self, text, line, begidx, endidx):
         """Completion for bye commands"""
 
-        if not text:
-            completions = self.BYE_CMDS[:]
-        else:
-            completions = [p
-                           for p in self.BYE_CMDS
-                           if p.startswith(text)
-                           ]
-        return completions
+        return self.spp_bye.complete(text, line, begidx, endidx)
 
     def do_cat(self, arg):
         """View contents of a file.
@@ -592,7 +570,7 @@ class Shell(cmd.Cmd, object):
         label: vm1	subgraph: "vhost:1;vhost:2"
         """
 
-        #logger.info("Topo initialized with sec IDs %s" % sec_ids)
+        # logger.info("Topo initialized with sec IDs %s" % sec_ids)
 
         # delimiter of node in dot file
         delim_node = '_'
-- 
2.13.1

^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2018-10-18 11:25 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-10-18 11:25 [spp] [PATCH 0/9] Change SPP controller to use spp-ctl ogawa.yasufumi
2018-10-18 11:25 ` [spp] [PATCH 1/9] controller: add spp-ctl client for SPP controller ogawa.yasufumi
2018-10-18 11:25 ` [spp] [PATCH 2/9] controller: change controller to use spp-ctl ogawa.yasufumi
2018-10-18 11:25 ` [spp] [PATCH 3/9] spp-ctl: add IP address binding ogawa.yasufumi
2018-10-18 11:25 ` [spp] [PATCH 4/9] controller: add IP address and port binding ogawa.yasufumi
2018-10-18 11:25 ` [spp] [PATCH 5/9] controller: move pri command to SppPrimary ogawa.yasufumi
2018-10-18 11:25 ` [spp] [PATCH 6/9] controller: move sec command to SppSecondary ogawa.yasufumi
2018-10-18 11:25 ` [spp] [PATCH 7/9] controller: move topo command to SppTopo ogawa.yasufumi
2018-10-18 11:25 ` [spp] [PATCH 8/9] controller: move topo_resize " ogawa.yasufumi
2018-10-18 11:25 ` [spp] [PATCH 9/9] controller: move bye command to SppBye ogawa.yasufumi

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).