+# -*- 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