From 3dbb84755b31229eb1de7bc8b56c637ca9dbcfd2 Mon Sep 17 00:00:00 2001
From: Nikolay Stanchev <ns17@it-innovation.soton.ac.uk>
Date: Fri, 22 Jun 2018 16:09:49 +0100
Subject: [PATCH] Implements database connectivity using PostgreSQL for the
 CLMC service

---
 src/service/clmcservice/__init__.py      |  6 ++++
 src/service/clmcservice/initialize_db.py | 38 ++++++++++++++++++++++++
 src/service/clmcservice/models.py        | 28 +++++++++++++++++
 src/service/development.ini              | 13 ++++++--
 src/service/production.ini               | 11 +++++--
 src/service/setup.py                     |  6 ++++
 6 files changed, 97 insertions(+), 5 deletions(-)
 create mode 100644 src/service/clmcservice/initialize_db.py
 create mode 100644 src/service/clmcservice/models.py

diff --git a/src/service/clmcservice/__init__.py b/src/service/clmcservice/__init__.py
index 3f29bc4..69ee81b 100644
--- a/src/service/clmcservice/__init__.py
+++ b/src/service/clmcservice/__init__.py
@@ -23,6 +23,8 @@
 """
 
 from pyramid.config import Configurator
+from sqlalchemy import engine_from_config
+from clmcservice.models import DBSession, Base
 from clmcservice.aggregationapi.utilities import validate_conf_file, RUNNING_FLAG, MALFORMED_FLAG, CONF_FILE_ATTRIBUTE, CONF_OBJECT, AGGREGATOR_CONFIG_SECTION
 
 
@@ -31,6 +33,10 @@ def main(global_config, **settings):
     This function returns a Pyramid WSGI application.
     """
 
+    engine = engine_from_config(settings, 'sqlalchemy.')  # initialise a database engine by using the 'sqlalchemy' setting in the configuration .ini file
+    DBSession.configure(bind=engine)  # bind the engine to a DB session
+    Base.metadata.bind = engine  # bind the engine to the Base class metadata
+
     # validate and use (if valid) the configuration file
     conf_file_path = settings[CONF_FILE_ATTRIBUTE]
     conf = validate_conf_file(conf_file_path)  # if None returned here, service is in unconfigured state
diff --git a/src/service/clmcservice/initialize_db.py b/src/service/clmcservice/initialize_db.py
new file mode 100644
index 0000000..a0b1a09
--- /dev/null
+++ b/src/service/clmcservice/initialize_db.py
@@ -0,0 +1,38 @@
+import os
+import sys
+from sqlalchemy import engine_from_config
+from pyramid.paster import get_appsettings, setup_logging
+from clmcservice.models import DBSession, Base
+
+
+def usage(argv):
+    """
+    A method to be called when the script has been used in an incorrect way.
+
+    :param argv: cmd arguments
+    """
+
+    cmd = os.path.basename(argv[0])
+    print('usage: %s <config_uri>\n'
+          '(example: "%s development.ini")' % (cmd, cmd))
+    sys.exit(1)
+
+
+def main(argv=sys.argv):
+    """
+    Main method of the script - initialises the database by creating all tables declared in the models.py module
+
+    :param argv: command line arguments - expects a configuration .ini file from which it retrieves the URL with which to connect to postgresql
+    """
+
+    if len(argv) != 2:
+        usage(argv)  # in case of wrong usage
+
+    config_uri = argv[1]
+    setup_logging(config_uri)
+
+    settings = get_appsettings(config_uri)  # get application specific settings
+    engine = engine_from_config(settings, 'sqlalchemy.')  # create the db engine from the sqlalchemy setting configured in the .ini file
+
+    DBSession.configure(bind=engine)
+    Base.metadata.create_all(engine)  # creates all model tables
diff --git a/src/service/clmcservice/models.py b/src/service/clmcservice/models.py
new file mode 100644
index 0000000..840fc08
--- /dev/null
+++ b/src/service/clmcservice/models.py
@@ -0,0 +1,28 @@
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.orm import scoped_session, sessionmaker
+from zope.sqlalchemy import ZopeTransactionExtension
+from sqlalchemy import Column, String, Integer, UniqueConstraint
+
+
+DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))  # initialise a ORM session, ought to be reused across the different modules
+Base = declarative_base()  # initialise a declarative Base instance to use for the web app models
+
+
+class ServiceFunctionEndpoint(Base):
+    """
+    This class defines the main model of the WHOAMI API, declaring the global tags for a specific service function on a specific endpoint.
+    """
+
+    __tablename__ = 'sfendpoints'  # table name in the PostgreSQL database
+
+    __table_args__ = (UniqueConstraint('sf_i', 'sf_endpoint', 'sr'),)  # defines a unique constraint across 3 columns - sf_i, sf_endpoint, sr
+
+    uid = Column(Integer, primary_key=True, autoincrement=True)  # a primary key integer field (auto incremented)
+
+    location = Column(String)  # cluster label
+    sfc = Column(String)  # service function chain label
+    sfc_i = Column(String)  # service function chain instance identifier
+    sf = Column(String)  # service function label
+    sf_i = Column(String)   # service function identifier (potentially FQDN)
+    sf_endpoint = Column(String)  # service function endpoint (potentially IP address)
+    sr = Column(String)  # service router ID - service router that connects the VM to FLAME
diff --git a/src/service/development.ini b/src/service/development.ini
index 390e2a5..18e7f24 100644
--- a/src/service/development.ini
+++ b/src/service/development.ini
@@ -14,9 +14,11 @@ pyramid.default_locale_name = en
 pyramid.includes = pyramid_debugtoolbar pyramid_exclog
 exclog.ignore =
 
-## Configuration file path
+# Configuration file path
 configuration_file_path = /etc/flame/clmc/service.conf
 
+# PostgreSQL connection url
+sqlalchemy.url = postgresql://clmc:clmc_service@localhost:5432/whoamidb
 
 # By default, the toolbar only appears for clients from IP addresses
 # '127.0.0.1' and '::1'.
@@ -36,7 +38,7 @@ listen = localhost:9080
 ###
 
 [loggers]
-keys = root, exc_logger, clmcservice
+keys = root, exc_logger, service_logger, sqlalchemy.engine.base.Engine
 
 [handlers]
 keys = console, filelog, exc_handler
@@ -48,7 +50,12 @@ keys = generic, exc_formatter
 level = INFO
 handlers = console
 
-[logger_clmcservice]
+[logger_sqlalchemy.engine.base.Engine]
+level = INFO
+handlers =
+qualname = sqlalchemy.engine.base.Engine
+
+[logger_service_logger]
 level = INFO
 handlers = filelog
 qualname = service_logger
diff --git a/src/service/production.ini b/src/service/production.ini
index 3e8876b..8ebe666 100644
--- a/src/service/production.ini
+++ b/src/service/production.ini
@@ -14,9 +14,11 @@ pyramid.default_locale_name = en
 pyramid.includes = pyramid_exclog
 exclog.ignore =
 
-## Configuration file path
+# Configuration file path
 configuration_file_path = /etc/flame/clmc/service.conf
 
+# PostgreSQL connection url
+sqlalchemy.url = postgresql://clmc:clmc_service@localhost:5432/whoamidb
 
 ###
 # wsgi server configuration
@@ -32,7 +34,7 @@ listen = *:9080
 ###
 
 [loggers]
-keys = root, exc_logger, service_logger
+keys = root, exc_logger, service_logger, sqlalchemy.engine.base.Engine
 
 [handlers]
 keys = console, filelog, exc_handler
@@ -44,6 +46,11 @@ keys = generic, exc_formatter
 level = INFO
 handlers = console
 
+[logger_sqlalchemy.engine.base.Engine]
+level = INFO
+handlers =
+qualname = sqlalchemy.engine.base.Engine
+
 [logger_service_logger]
 level = INFO
 handlers = filelog
diff --git a/src/service/setup.py b/src/service/setup.py
index 4b80091..7bbc332 100644
--- a/src/service/setup.py
+++ b/src/service/setup.py
@@ -46,6 +46,9 @@ requires = [
     'pyramid_debugtoolbar',
     'pyramid_exclog',
     'waitress',
+    'sqlalchemy',
+    'zope.sqlalchemy',
+    'psycopg2',
     'influxdb',
     'pytest',
 ]
@@ -80,5 +83,8 @@ setup(
         'paste.app_factory': [
             'main = clmcservice:main',
         ],
+        'console_scripts': [
+            'initialize_clmcservice_db = clmcservice.initialize_db:main',
+        ]
     },
 )
\ No newline at end of file
-- 
GitLab