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/osm_ro/tests/db_helpers.py b/osm_ro/tests/db_helpers.py
new file mode 100644
index 0000000..bedf9a5
--- /dev/null
+++ b/osm_ro/tests/db_helpers.py
@@ -0,0 +1,187 @@
+# -*- coding: utf-8 -*-
+##
+# Copyright 2018 University of Bristol - High Performance Networks Research
+# Group
+# All Rights Reserved.
+#
+# Contributors: Anderson Bravalheri, Dimitrios Gkounis, Abubakar Siddique
+# Muqaddas, Navdeep Uniyal, Reza Nejabati and Dimitra Simeonidou
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: <highperformance-networks@bristol.ac.uk>
+#
+# Neither the name of the University of Bristol nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# This work has been performed in the context of DCMS UK 5G Testbeds
+# & Trials Programme and in the framework of the Metro-Haul project -
+# funded by the European Commission under Grant number 761727 through the
+# Horizon 2020 and 5G-PPP programmes.
+##
+import hashlib
+import shlex
+import unittest
+from contextlib import contextmanager
+from functools import wraps
+from hashlib import md5
+from os import environ, pathsep
+from subprocess import STDOUT, check_output
+from uuid import UUID
+
+from MySQLdb import connect
+
+from ..nfvo_db import nfvo_db
+
+HOST = environ.get('TEST_DB_HOST', 'localhost')
+USER = environ.get('TEST_DB_USER', 'mano')
+PASSWORD = environ.get('TEST_DB_PASSWORD', 'manopw')
+DATABASE = environ.get('TEST_DB_DATABASE', 'mano_db')
+
+
+def uuid(seed):
+    """Generates strings with a UUID format in a repeatable way"""
+    return str(UUID(md5(str(seed)).hexdigest()))
+
+
+def sha1(text):
+    """Generates SHA1 hash code from a text string"""
+    return hashlib.sha1(text).hexdigest()
+
+
+def run(*args, **kwargs):
+    """Run a command inside a subprocess, raising an exception when it fails
+
+    Arguments:
+        *args: you can pass any number of arquments as separated words in the
+            shell, or just a single string with the entire command
+        **kwargs: proxied to subprocess.check_output (by default
+            ``stderr=STDOUT`` and ``universal_newlines=True``
+    """
+    if len(args) == 1 and isinstance(args[0], str):
+        args = shlex.split(args[0])
+
+    opts = dict(stderr=STDOUT, universal_newlines=True)
+    opts.update(kwargs)
+    return check_output(args, **opts)
+
+
+# In order to not mess around, enforce user to explicit set the
+# test database in a env variable
+@unittest.skipUnless(
+    environ.get('TEST_DB_HOST'),
+    'Test database not available. Please set TEST_DB_HOST env var')
+class TestCaseWithDatabase(unittest.TestCase):
+    """Connect to the database and provide methods to facilitate isolating the
+    database stored inside it between tests.
+
+    In order to avoid connecting, reconnecting, creating tables and destroying
+    tables all the time, this class manage the database using class-level
+    fixtures. This reduce the cost of performing these actions but not
+    guarantees isolation in the DB state between the tests.
+    To enforce isolation, please call the ``setup_tables`` and
+    ``empty_database`` directly, or write one single test per class.
+    """
+
+    host = HOST
+    user = USER
+    password = PASSWORD
+    database = DATABASE
+
+    @classmethod
+    def setup_tables(cls):
+        """Make sure the database is set up and in the right version, with all the
+        required tables.
+        """
+        dbutils = environ.get('DBUTILS')
+
+        if dbutils:
+            environ["PATH"] += pathsep + dbutils
+
+        return run('init_mano_db.sh',
+                   '-u', cls.user,
+                   '-p', cls.password,
+                   '-h', cls.host,
+                   '-d', cls.database)
+
+    @classmethod
+    def empty_database(cls):
+        """Clear the database, so one test does not interfere with the other"""
+        # Create a custom connection not attached to the database, so we can
+        # destroy and recreate the database itself
+        connection = connect(cls.host, cls.user, cls.password)
+        cursor = connection.cursor()
+        cursor.execute(
+            "DROP DATABASE {};".format(
+                connection.escape_string(cls.database)))
+        cursor.execute(
+            "CREATE DATABASE {};".format(
+                connection.escape_string(cls.database)))
+        cursor.close()
+        connection.close()
+
+
+class TestCaseWithDatabasePerTest(TestCaseWithDatabase):
+    """Ensure a connection to the database before and
+    drop tables after each test runs
+    """
+
+    def setUp(self):
+        self.setup_tables()
+        self.addCleanup(self.empty_database)
+
+        self.maxDiff = None
+
+        self.db = nfvo_db(self.host, self.user, self.password, self.database)
+        self.db.connect()
+
+    def populate(self, seeds=None, **kwargs):
+        """Seed the database with initial values"""
+        if not seeds:
+            seeds = []
+        if not isinstance(seeds, (list, tuple)):
+            seeds = [seeds]
+        if kwargs:
+            seeds.append(kwargs)
+        self.db.new_rows(seeds)
+
+    def count(self, table):
+        """Count number of rows in a table"""
+        return self.db.get_rows(
+            SELECT='COUNT(*) as count', FROM=table)[0]['count']
+
+    @contextmanager
+    def disable_foreign_keys(self):
+        """Do the test without checking foreign keys"""
+        try:
+            cursor = self.db.con.cursor()
+            cursor.execute('SET FOREIGN_KEY_CHECKS=0;')
+            yield
+        finally:
+            cursor.execute('SET FOREIGN_KEY_CHECKS=1;')
+
+
+def disable_foreign_keys(test):
+    """Do the test without checking foreign keys.
+    To be used together in subclasses of TestCaseWithDatabasePerTest
+    """
+    @wraps(test)
+    def _no_check(self, *args, **kwargs):
+        with self.disable_foreign_keys():
+            result = test(self, *args, **kwargs)
+
+        return result
+
+    return _no_check