Implement feature 5949

Enable dynamic connectivity setup in multi-site Network Services

The code required to implement the feature is contained in `osm_ro/wim`
as much as possible.

* `wim/engine.py` works together with `nfvo.py` to implement the
  feature
* `wim/persistence.py` is equivalent to `nfvo_db.py` and try to
  encapsulate most of the SQL-specific code, implementing a persistence
  layer
* `wim/http_handler.py` extends `httpserver.py` adding WIM-related HTTP
  routes
* `wim/wim_thread.py` is similar to `vim_thread.py` and controls the
  execution of WIM-related tasks
* `wim/actions.py` and `wim/wan_link_actions.py` implement the action
  handling specific code, calling instances of the `wim/wimconn.py`
  subclasses

WIM connectors are still a work in progress

Individual change details (newer to older)

- Add errors for inconsistent state

- Delay re-scheduled tasks

- Move lock to inside the persistence object

- Better errors for connector failures

- Try to cache the wan_link information before it is deleted from the database

- Integrate WanLinkDelete to NFVO

- Add WanLinkDelete implementation draft with some tests

- Add basic wim network creation

- Add minimal documentation for actions

- Add checks to the create action

- Improve documentation, rearrange insert_pending and remove unused functions on WimThread

- Integrate Action classes in refresh_tasks

- Add Action classes to avoid intricate conditions

- Adding Proposed License

- Move grouping of actions to persistence

- Change WimThread to use SQL to do the heavy lifting

- Simplify WimThread reload_actions

- Add tests for derive_wan_links

- Implement find_common_wim(s)

- Add tests for create_wim_account

- Add migration scripts for version 33

- Changes to WIM and VIM threads for vim_wim_actions

- Implement wim_account management according to the discussion

- Add WimHandler integration inside httpserver

- Add quick instructions to run the tests

- Add WIM functional tests using real database

- Add DB WIM port mapping

- RO WIM-related console scripts

- Add WIM integration to NFVO

- Improve database support focusing on tests

- RO NBI WIM-related commands in HTTP server

- Adding WIM tables to MANO DB

- Add wim http handler initial implementation

- Move http utility functions to separated files

    This separation allows the code to be reused more easily and avoids
    circular dependencies.

    (The httpserver can import other modules implementing http routes,
    and those modules can then use the utility functions without having
    to import back httpserver)

- Add a HTTP handler class and custom route decorator

    These tools can be used to create independent groups of bottle
    routes/callbacks in a OOP fashion

- Extract http error codes and related logic to separated file

Change-Id: Icd5fc9fa345852b8cf571e48f427dc10bdbd24c5
Signed-off-by: Anderson Bravalheri <a.bravalheri@bristol.ac.uk>
diff --git a/test/test_RO.py b/test/test_RO.py
index 42bc9cb..df77a9b 100755
--- a/test/test_RO.py
+++ b/test/test_RO.py
@@ -1683,6 +1683,72 @@
     return executed, failed
 
 
+def test_wim(args):
+    global test_config
+    sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + "/osm_ro")
+    import openmanoclient
+    executed = 0
+    failed = 0
+    test_config["client"] = openmanoclient.openmanoclient(
+        endpoint_url=args.endpoint_url,
+        tenant_name=args.tenant_name,
+        datacenter_name=args.datacenter,
+        debug=args.debug, logger=test_config["logger_name"])
+    clsmembers = inspect.getmembers(sys.modules[__name__], inspect.isclass)
+    # If only want to obtain a tests list print it and exit
+    if args.list_tests:
+        tests_names = []
+        for cls in clsmembers:
+            if cls[0].startswith('test_WIM'):
+                tests_names.append(cls[0])
+
+        msg = "The 'wim' set tests are:\n\t" + ', '.join(sorted(tests_names)) +\
+              "\nNOTE: The test test_VIM_tenant_operations will fail in case the used datacenter is type OpenStack " \
+              "unless RO has access to the admin endpoint. Therefore this test is excluded by default"
+        print(msg)
+        logger.info(msg)
+        sys.exit(0)
+
+    # Create the list of tests to be run
+    code_based_tests = []
+    if args.tests:
+        for test in args.tests:
+            for t in test.split(','):
+                matches_code_based_tests = [item for item in clsmembers if item[0] == t]
+                if len(matches_code_based_tests) > 0:
+                    code_based_tests.append(matches_code_based_tests[0][1])
+                else:
+                    logger.critical("Test '{}' is not among the possible ones".format(t))
+                    sys.exit(1)
+    if not code_based_tests:
+        # include all tests
+        for cls in clsmembers:
+            # We exclude 'test_VIM_tenant_operations' unless it is specifically requested by the user
+            if cls[0].startswith('test_VIM') and cls[0] != 'test_VIM_tenant_operations':
+                code_based_tests.append(cls[1])
+
+    logger.debug("tests to be executed: {}".format(code_based_tests))
+
+    # TextTestRunner stream is set to /dev/null in order to avoid the method to directly print the result of tests.
+    # This is handled in the tests using logging.
+    stream = open('/dev/null', 'w')
+
+    # Run code based tests
+    basic_tests_suite = unittest.TestSuite()
+    for test in code_based_tests:
+        basic_tests_suite.addTest(unittest.makeSuite(test))
+    result = unittest.TextTestRunner(stream=stream, failfast=failfast).run(basic_tests_suite)
+    executed += result.testsRun
+    failed += len(result.failures) + len(result.errors)
+    if failfast and failed:
+        sys.exit(1)
+    if len(result.failures) > 0:
+        logger.debug("failures : {}".format(result.failures))
+    if len(result.errors) > 0:
+        logger.debug("errors : {}".format(result.errors))
+    return executed, failed
+
+
 def test_deploy(args):
     global test_config
     sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + "/osm_ro")
@@ -1822,6 +1888,19 @@
     vimconn_parser.add_argument('-u', '--url', dest='endpoint_url', default='http://localhost:9090/openmano',
                                help="Set the openmano server url. By default 'http://localhost:9090/openmano'")
 
+    # WIM test set
+    # -------------------
+    vimconn_parser = subparsers.add_parser('wim', parents=[parent_parser], help="test wim")
+    vimconn_parser.set_defaults(func=test_wim)
+
+    # Mandatory arguments
+    mandatory_arguments = vimconn_parser.add_argument_group('mandatory arguments')
+    mandatory_arguments.add_argument('-d', '--datacenter', required=True, help='Set the datacenter to test')
+
+    # Optional arguments
+    vimconn_parser.add_argument('-u', '--url', dest='endpoint_url', default='http://localhost:9090/openmano',
+                                help="Set the openmano server url. By default 'http://localhost:9090/openmano'")
+
     argcomplete.autocomplete(parser)
     args = parser.parse_args()
     # print str(args)
diff --git a/test/test_openmanoclient.py b/test/test_openmanoclient.py
index 5e8876b..64902f5 100755
--- a/test/test_openmanoclient.py
+++ b/test/test_openmanoclient.py
@@ -230,7 +230,80 @@
         to_delete_list.insert(0,{"item": "datacenter-detach", "function": client.detach_datacenter, "params":{"name": test_datacenter} })
 
         client["datacenter_name"] = test_datacenter
-        
+
+        # WIMs
+        print("  {}. TEST create_wim".format(test_number))
+        test_number += 1
+        long_name = _get_random_name(60)
+
+        wim = client.create_wim(name=long_name, wim_url="http://fakeurl/fake")
+        if verbose: print(wim)
+
+        print("  {}. TEST list_wims".format(test_number))
+        test_number += 1
+        wims = client.list_wims(all_tenants=True)
+        if verbose: print(wims)
+
+        print("  {}. TEST list_tenans filter by name".format(test_number))
+        test_number += 1
+        wims_ = client.list_wims(all_tenants=True, name=long_name)
+        if not wims_["wims"]:
+            raise Exception("Text error, no TENANT found with name")
+        if verbose: print(wims_)
+
+        print("  {}. TEST get_wim by UUID".format(test_number))
+        test_number += 1
+        wim = client.get_wim(uuid=wims_["wims"][0]["uuid"], all_tenants=True)
+        if verbose: print(wim)
+
+        print("  {}. TEST delete_wim by name".format(test_number))
+        test_number += 1
+        wim = client.delete_wim(name=long_name)
+        if verbose: print(wim)
+
+        print("  {}. TEST create_wim for remaining tests".format(test_number))
+        test_number += 1
+        test_wim = "test-wim " + \
+                          ''.join(random.choice('abcdefghijklmnopqrstuvwxyz') for _ in range(40))
+        wim = client.create_wim(name=test_wim, vim_url="http://127.0.0.1:9080/odl")
+        if verbose: print(wim)
+        to_delete_list.insert(0,
+                              {
+                                    "item": "wim", "function": client.delete_wim,
+                                    "params":
+                                        {
+                                                "name": test_wim
+                                        }
+                                })
+
+        test_wim_tenant = "test-wimtenant " + \
+                           ''.join(random.choice('abcdefghijklmnopqrstuvwxyz') for _ in range(40))
+
+        # print("  {}. TEST datacenter new tenenat".format(test_number))
+        # test_number += 1
+        # test_vim_tenant = "test-vimtenant " + \
+        #                   ''.join(random.choice('abcdefghijklmnopqrstuvwxyz') for _ in range(40))
+        # vim_tenant = client.vim_action("create", "tenants", datacenter_name=test_datacenter, all_tenants=True,
+        #                                name=test_vim_tenant)
+        # if verbose: print(vim_tenant)
+        # client["datacenter_name"] = test_datacenter
+        # to_delete_list.insert(0, {"item": "vim_tenant",
+        #                           "function": client.vim_action,
+        #                           "params": {
+        #                               "action": "delete",
+        #                               "item": "tenants",
+        #                               "datacenter_name": test_datacenter,
+        #                               "all_tenants": True,
+        #                               "uuid": vim_tenant["tenant"]["id"]
+        #                           }
+        #                           })
+
+        print("  {}. TEST wim attach".format(test_number))
+        test_number += 1
+        wim = client.attach_wim(name=test_wim, wim_tenant_name=test_wim_tenant)
+        if verbose: print(wim)
+        to_delete_list.insert(0, {"item": "wim-detach", "function": client.detach_wim,
+                                  "params": {"name": test_wim}})
     
     #VIM_ACTIONS
     print("  {}. TEST create_VIM_tenant".format(test_number))