예제 #1
0
    def get_db_host():
        if cfg.exists('database_admin_url'):
            return osdb.get_url_host(cfg.get('database_admin_url'))
        elif cfg.exists('database_url'):
            return osdb.get_url_host(cfg.get('database_url'))

        return "localhost"
예제 #2
0
 def __exclude__(self):
     if cfg.exists("dababase_user_url"):
         db_url = cfg.get("database_user_url")
     elif cfg.exists("database_url"):
         db_url = cfg.get("database_url")
     else:
         return not osdb.has_sqlalchemy()
     return not osdb.has_dialect(osdb.get_dialect(db_url))
예제 #3
0
    def get_db_engine():
        if cfg.exists('database_admin_url'):
            engine = osdb.get_url_driver(cfg.get('database_admin_url'))
        elif cfg.exists('database_url'):
            engine = osdb.get_url_driver(cfg.get('database_url'))
        else:
            engine = "mysql"

        if engine not in SUPPORTED_BACKENDS:
            logger.error("bad database engine ({}), supported: {}".format(
                         engine, " ".join(SUPPORTED_BACKENDS)))
            return None
        return engine
예제 #4
0
    def get_admin_db_url(self, db_name):
        engine = osdb.get_db_engine()
        if not engine:
            return None

        if cfg.exists('database_admin_url'):
            admin_url = cfg.get("database_admin_url")
            if engine == "postgres":
                admin_url = osdb.set_url_db(admin_url, 'postgres')
        else:
            if engine == 'postgres':
                if getuser() != "postgres":
                    logger.error("Command must be run as 'postgres' user: "******"sudo -u postgres opensips-cli ...")
                    return None
                """
                For PG, do the initial setup using 'postgres' as role + DB
                """
                admin_url = "postgres://postgres@localhost/postgres"
            else:
                admin_url = "{}://root@localhost".format(engine)

        if osdb.get_url_pswd(admin_url) is None:
            pswd = getpass("Password for admin {} user ({}): ".format(
                osdb.get_url_driver(admin_url, capitalize=True),
                osdb.get_url_user(admin_url)))
            logger.debug("read password: '******'", pswd)
            admin_url = osdb.set_url_password(admin_url, pswd)

        logger.debug("admin DB URL: '{}'".format(admin_url))
        return admin_url
예제 #5
0
    def update_instance(self, instance):

        # first of all, let's handle logging
        self.current_instance = instance
        self.update_logger()

        # Update the intro and prompt
        self.intro = cfg.get('prompt_intro')
        self.prompt = '(%s): ' % cfg.get('prompt_name')

        # initialize communcation handler
        self.handler = comm.initialize()

        # remove all loaded modules
        self.modules = {}

        if not self.execute:
            print(self.intro)
            # add the built-in modules and commands list
            for mod in ['clear', 'help', 'history', 'exit', 'quit']:
                self.modules[mod] = (self, None)

        if not cfg.exists('skip_modules'):
            skip_modules = []
        else:
            skip_modules = cfg.get('skip_modules')

        available_modules = {
            key[20:]: sys.modules[key]
            for key in sys.modules.keys()
            if key.startswith("opensipscli.modules.")
            and key[20:] not in skip_modules
        }
        for name, module in available_modules.items():
            m = importlib.import_module("opensipscli.modules.{}".format(name))
            if not hasattr(m, "Module"):
                logger.debug(
                    "Skipping module '{}' - does not extend Module".format(
                        name))
                continue
            if not hasattr(m, name):
                logger.debug(
                    "Skipping module '{}' - module implementation not found".
                    format(name))
                continue
            mod = getattr(module, name)
            if not hasattr(mod, '__exclude__') or not hasattr(
                    mod, '__get_methods__'):
                logger.debug(
                    "Skipping module '{}' - module does not implement Module".
                    format(name))
                continue
            if mod.__exclude__(mod):
                logger.debug(
                    "Skipping module '{}' - excluded on purpose".format(name))
                continue
            logger.debug("Loaded module '{}'".format(name))
            imod = mod()
            self.modules[name] = (imod, mod.__get_methods__(imod))
예제 #6
0
 def __exclude__(self):
     """
     method exlusion list
     """
     if cfg.exists("database_url"):
         db_url = cfg.get("database_url")
         return not osdb.has_dialect(osdb.get_dialect(db_url))
     else:
         return not osdb.has_sqlalchemy()
예제 #7
0
    def create_tables(self,
                      db_name,
                      db_url,
                      admin_db,
                      tables=[],
                      create_std=True):
        """
        create database tables
        """
        db_url = osdb.set_url_db(db_url, db_name)

        # 2) prepare new object store database instance
        #    use it to connect to the created database
        db = self.get_db(db_url, db_name)
        if db is None:
            return -1

        if not db.exists():
            logger.warning("database '{}' does not exist!".format(db_name))
            return -1

        schema_path = self.get_schema_path(db.dialect)
        if schema_path is None:
            return -1

        if create_std:
            standard_file_path = os.path.join(schema_path,
                                              "standard-create.sql")
            if not os.path.isfile(standard_file_path):
                logger.error(
                    "cannot find stardard OpenSIPS DB file: '{}'!".format(
                        standard_file_path))
                return -1
            table_files = {'standard': standard_file_path}
        else:
            table_files = {}

        # check to see what tables we shall deploy
        if tables:
            pass
        elif cfg.exists("database_modules"):
            # we know exactly what modules we want to instsall
            tables_line = cfg.get("database_modules").strip().lower()
            if tables_line == "all":
                logger.debug("Creating all tables")
                tables = [ f.replace('-create.sql', '') \
                            for f in os.listdir(schema_path) \
                            if os.path.isfile(os.path.join(schema_path, f)) and \
                                f.endswith('-create.sql') ]
            else:
                logger.debug("Creating custom tables")
                tables = tables_line.split(" ")
        else:
            logger.debug("Creating standard tables")
            tables = STANDARD_DB_MODULES

        # check for corresponding SQL schemas files in system path
        logger.debug("checking tables: {}".format(" ".join(tables)))

        for table in tables:
            if table == "standard":
                # already checked for it
                continue
            table_file_path = os.path.join(schema_path,
                                           "{}-create.sql".format(table))
            if not os.path.isfile(table_file_path):
                logger.warn("cannot find SQL file for module {}: {}".format(
                    table, table_file_path))
            else:
                table_files[table] = table_file_path

        username = osdb.get_url_user(db_url)
        admin_db.connect(db_name)

        # create tables from SQL schemas
        for module, table_file in table_files.items():
            logger.info("Running {}...".format(os.path.basename(table_file)))
            try:
                db.create_module(table_file)
                if db.dialect == "postgres":
                    self.pg_grant_table_access(table_file, username, admin_db)
            except osdbModuleAlreadyExistsError:
                logger.error("{} table(s) are already created!".format(module))
            except osdbError as ex:
                logger.error("cannot import: {}".format(ex))

        # terminate active database connection
        db.destroy()
        return 0
예제 #8
0
    def _do_create(self, db, db_name=None, do_all_tables=False):
        if db_name is None:
            db_name = db.db_name

        # check to see if the database has already been created
        if db.exists(db_name):
            logger.error("database '{}' already exists!".format(db_name))
            return -2

        db_schema = db.db_url.split(":")[0]
        schema_path = self.get_schema_path(db_schema)
        if schema_path is None:
            return -1

        standard_file_path = os.path.join(schema_path, "standard-create.sql")
        if not os.path.isfile(standard_file_path):
            logger.error("cannot find stardard OpenSIPS DB file: '{}'!".
                    format(standard_file_path))
            return -1
        tables_files = [ standard_file_path ]

        # all good now - check to see what tables we shall deploy
        if cfg.read_param(None,
                "Create [a]ll tables or just the [c]urrently configured ones?",
                default="a").lower() == "a":
            print("Creating all tables ...")
            tables = [ f.replace('-create.sql', '') \
                        for f in os.listdir(schema_path) \
                        if os.path.isfile(os.path.join(schema_path, f)) and \
                            f.endswith('-create.sql') ]
        else:
            print("Creating the currently configured set of tables ...")
            if cfg.exists("database_modules"):
                tables = cfg.get("database_modules").split(" ")
            else:
                tables = STANDARD_DB_MODULES

        logger.debug("deploying tables {}".format(" ".join(tables)))
        for table in tables:
            if table == "standard":
                # already checked for it
                continue
            table_file_path = os.path.join(schema_path,
                    "{}-create.sql".format(table))
            if not os.path.isfile(table_file_path):
                logger.warn("cannot find file to create {}: {}".
                        format(table, table_file_path))
            else:
                tables_files.append(table_file_path)

        db.create(db_name)
        db.use(db_name)

        for table_file in tables_files:
            print("Running {}...".format(os.path.basename(table_file)))
            try:
                db.create_module(table_file)
            except osdbError as ex:
                logger.error("cannot import: {}".format(ex))

        print("The '{}' database has been successfully created!".format(db_name))
예제 #9
0
    def do_rootCA(self, params):
        global cfg
        logger.info("Preparing to generate CA cert + key...")

        # TODO
        # separate cli.cfg files for TLS are fully deprecated, this if block is
        # only kept for backwards-compatibility.  Remove starting from v3.2! <3
        if cfg.exists('tls_ca_config'):
            tls_cfg = cfg.get('tls_ca_config')
            cfg = OpenSIPSCLIConfig()
            cfg.parse(tls_cfg)

        ca_dir = cfg.read_param("tls_ca_dir", "Output directory",
                                "/etc/opensips/tls/rootCA/")
        cert_file = cfg.read_param("tls_ca_cert_file", "Output cert file",
                                   "cacert.pem")
        key_file = cfg.read_param("tls_ca_key_file", "Output key file",
                                  "private/cakey.pem")
        c_f = join(ca_dir, cert_file)
        k_f = join(ca_dir, key_file)

        if (exists(c_f) or exists(k_f)) and not cfg.read_param(
                "tls_ca_overwrite",
                "CA certificate or key already exists, overwrite?", "yes",
                True):
            return

        # create a self-signed cert
        cert = crypto.X509()

        cert.get_subject().CN = cfg.read_param("tls_ca_common_name",
                                               "Website address (CN)",
                                               "opensips.org")
        cert.get_subject().C = cfg.read_param("tls_ca_country", "Country (C)",
                                              "RO")
        cert.get_subject().ST = cfg.read_param("tls_ca_state", "State (ST)",
                                               "Bucharest")
        cert.get_subject().L = cfg.read_param("tls_ca_locality",
                                              "Locality (L)", "Bucharest")
        cert.get_subject().O = cfg.read_param("tls_ca_organisation",
                                              "Organization (O)", "OpenSIPS")
        cert.get_subject().OU = cfg.read_param("tls_ca_organisational_unit",
                                               "Organisational Unit (OU)",
                                               "Project")
        cert.set_serial_number(randrange(100000))
        cert.gmtime_adj_notBefore(0)
        notafter = int(
            cfg.read_param("tls_ca_notafter", "Certificate validity (seconds)",
                           315360000))
        cert.gmtime_adj_notAfter(notafter)
        cert.set_issuer(cert.get_subject())

        # create a key pair
        key = crypto.PKey()
        key_size = int(
            cfg.read_param("tls_ca_key_size", "RSA key size (bits)", 4096))
        key.generate_key(crypto.TYPE_RSA, key_size)

        cert.set_pubkey(key)
        md = cfg.read_param("tls_ca_md", "Digest Algorithm", "SHA1")
        cert.sign(key, md)

        try:
            if not exists(dirname(c_f)):
                makedirs(dirname(c_f))
            open(c_f, "wt").write(
                crypto.dump_certificate(crypto.FILETYPE_PEM,
                                        cert).decode('utf-8'))
        except Exception as e:
            logger.exception(e)
            logger.error("Failed to write to %s", c_f)
            return

        try:
            if not exists(dirname(k_f)):
                makedirs(dirname(k_f))
            open(k_f, "wt").write(
                crypto.dump_privatekey(crypto.FILETYPE_PEM,
                                       key).decode('utf-8'))
        except Exception as e:
            logger.exception(e)
            logger.error("Failed to write to %s", k_f)
            return

        logger.info("CA certificate created in " + c_f)
        logger.info("CA private key created in " + k_f)
예제 #10
0
    def do_trap(self, params):

        self.pids = []
        self.gdb_outputs = {}
        self.process_info = ""

        if cfg.exists("trap_file"):
            trap_file = cfg.get("trap_file")
        else:
            trap_file = TRAP_FILE_NAME

        logger.info("Trapping {} in {}".format(PROCESS_NAME, trap_file))
        if params and len(params) > 0:
            self.pids = params
        else:
            thread = Thread(target=self.get_pids)
            thread.start()
            thread.join(timeout=1)
            if len(self.pids) == 0:
                logger.warning("could not get OpenSIPS pids through MI!")
                try:
                    ps_pids = subprocess.check_output(["pidof",PROCESS_NAME])
                    self.pids = ps_pids.split()
                except:
                    logger.warning("could not find any OpenSIPS running!")
                    self.pids = []

        if len(self.pids) < 1:
            logger.error("could not find OpenSIPS' pids")
            return -1

        logger.debug("Dumping PIDs: {}".format(", ".join(self.pids)))

        # get process line of first pid
        process = os.readlink("/proc/{}/exe".format(self.pids[0]))

        threads = []
        for pid in self.pids:
            thread = Thread(target=self.get_gdb_output, args=(process, pid))
            thread.start()
            threads.append(thread)

        for thread in threads:
            thread.join()

        if len(self.gdb_outputs) == 0:
            logger.error("could not get output of gdb")
            return -1

        with open(trap_file, "w") as tf:
            tf.write(self.process_info)
            for pid in self.pids:
                if pid not in self.gdb_outputs:
                    logger.warning("No output from pid {}".format(pid))
                    continue
                try:
                    procinfo = subprocess.check_output(
                        ["ps", "--no-headers", "-ww", "-fp", pid]).decode()[:-1]
                except:
                    procinfo = "UNKNOWN"

                tf.write("\n\n---start {} ({})\n{}".
                        format(pid, procinfo, self.gdb_outputs[pid]))

        print("Trap file: {}".format(trap_file))
예제 #11
0
    def do_create(self, params=None):

        db_url = cfg.read_param("database_url",
                                "Please provide us the URL of the database")
        if db_url is None:
            print()
            logger.error("no URL specified: aborting!")
            return -1

        if params and len(params) > 0:
            db_name = params[0]
        else:
            db_name = cfg.read_param("database_name",
                                     "Please provide the database to create",
                                     DEFAULT_DB_NAME)

        db = osdb(db_url, db_name)

        # check to see if the database has already been created
        if db.exists():
            logger.warn("database '{}' already exists!".format(db_name))
            return -2
        db_schema = db_url.split(":")[0]
        schema_path = self.get_schema_path(db_schema)
        if schema_path is None:
            return -1

        standard_file_path = os.path.join(schema_path, "standard-create.sql")
        if not os.path.isfile(standard_file_path):
            logger.error("cannot find stardard OpenSIPS DB file: '{}'!".format(
                standard_file_path))
            return -1
        tables_files = [standard_file_path]

        # all good now - check to see what tables we shall deploy
        if cfg.exists("database_modules"):
            tables = cfg.get("database_modules").split(" ")
        else:
            tables = STANDARD_DB_MODULES

        logger.debug("deploying tables {}".format(" ".join(tables)))
        for table in tables:
            if table == "standard":
                # already checked for it
                continue
            table_file_path = os.path.join(schema_path,
                                           "{}-create.sql".format(table))
            if not os.path.isfile(table_file_path):
                logger.warn("cannot find file to create {}: {}".format(
                    table, table_file_path))
            else:
                tables_files.append(table_file_path)

        db.create()
        db.use()

        for table_file in tables_files:
            try:
                db.create_module(table_file)
            except osdbError as ex:
                logger.error("cannot import: {}".format(ex))

        db.destroy()
        logger.info("The database has been successfully created.")
        return 0
예제 #12
0
    def do_userCERT(self, params):
        global cfg
        logger.info("Preparing to generate user cert + key + CA list...")

        # TODO
        # separate cli.cfg files for TLS are fully deprecated, this if block is
        # only kept for backwards-compatibility.  Remove starting from v3.2! <3
        if cfg.exists('tls_user_config'):
            tls_cfg = cfg.get('tls_user_config')
            cfg = OpenSIPSCLIConfig()
            cfg.parse(tls_cfg)

        user_dir = cfg.read_param("tls_user_dir", "Output directory", "/etc/opensips/tls/user/")
        cert_file = cfg.read_param("tls_user_cert_file", "Output cert file", "user-cert.pem")
        key_file = cfg.read_param("tls_user_key_file", "Output key file", "user-privkey.pem")
        calist_file = cfg.read_param("tls_user_calist_file", "Output CA list file", "user-calist.pem")

        c_f = join(user_dir, cert_file)
        k_f = join(user_dir, key_file)
        ca_f = join(user_dir, calist_file)

        if (exists(c_f) or exists(k_f) or exists(ca_f)) and not cfg.read_param("tls_user_overwrite",
                "User certificate, key or CA list file already exists, overwrite?", "yes", True):
            return

        cacert = cfg.read_param("tls_user_cacert", "CA cert file", "/etc/opensips/tls/rootCA/cacert.pem")
        cakey = cfg.read_param("tls_user_cakey", "CA key file", "/etc/opensips/tls/rootCA/private/cakey.pem")

        try:
            ca_cert = crypto.load_certificate(crypto.FILETYPE_PEM, open(cacert, 'rt').read())
        except Exception as e:
            logger.exception(e)
            logger.error("Failed to load %s", cacert)
            return

        try:
            ca_key = crypto.load_privatekey(crypto.FILETYPE_PEM, open(cakey, 'rt').read())
        except Exception as e:
            logger.exception(e)
            logger.error("Failed to load %s", cakey)
            return

        # create a self-signed cert
        cert = crypto.X509()

        cert.set_version(2)
        cert.get_subject().CN = cfg.read_param("tls_user_common_name", "Website address (CN)", "www.opensips.org")
        cert.get_subject().C = cfg.read_param("tls_user_country", "Country (C)", "RO")
        cert.get_subject().ST = cfg.read_param("tls_user_state", "State (ST)", "Bucharest")
        cert.get_subject().L = cfg.read_param("tls_user_locality", "Locality (L)", "Bucharest")
        cert.get_subject().O = cfg.read_param("tls_user_organisation", "Organization (O)", "OpenSIPS")
        cert.get_subject().OU = cfg.read_param("tls_user_organisational_unit", "Organisational Unit (OU)", "Project")

        cert.set_serial_number(randrange(100000))
        cert.gmtime_adj_notBefore(0)
        notafter = int(cfg.read_param("tls_user_notafter", "Certificate validity (seconds)", 315360000))
        cert.gmtime_adj_notAfter(notafter)
        cert.set_issuer(ca_cert.get_subject())

        extensions = [
            crypto.X509Extension(b'basicConstraints', False, b'CA:FALSE'),
            crypto.X509Extension(b'extendedKeyUsage', False, b'clientAuth,serverAuth')
        ]

        cert.add_extensions(extensions)

        # create a key pair
        key = crypto.PKey()
        key_size = int(cfg.read_param("tls_user_key_size", "RSA key size (bits)", 4096))
        key.generate_key(crypto.TYPE_RSA, key_size)

        cert.set_pubkey(key)
        md = cfg.read_param("tls_user_md", "Digest Algorithm", "SHA256")
        cert.sign(ca_key, md)

        try:
            if not exists(dirname(c_f)):
                makedirs(dirname(c_f))
            open(c_f, "wt").write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert).decode('utf-8'))
        except Exception as e:
            logger.exception(e)
            logger.error("Failed to write to %s", c_f)
            return

        try:
            if not exists(dirname(k_f)):
                makedirs(dirname(k_f))
            open(k_f, "wt").write(crypto.dump_privatekey(crypto.FILETYPE_PEM, key).decode('utf-8'))
        except Exception as e:
            logger.exception(e)
            logger.error("Failed to write to %s", k_f)
            return

        try:
            if not exists(dirname(ca_f)):
                makedirs(dirname(ca_f))
            open(ca_f, "wt").write(crypto.dump_certificate(crypto.FILETYPE_PEM, ca_cert).decode('utf-8'))
        except Exception as e:
            logger.exception(e)
            logger.error("Failed to write to %s", ca_f)
            return

        logger.info("user certificate created in " + c_f)
        logger.info("user private key created in " + k_f)
        logger.info("user CA list (chain of trust) created in " + ca_f)
예제 #13
0
    def do_trace(self, params):

        filters = []

        if params is None:
            caller_f = input("Caller filter: ")
            if caller_f != "":
                filters.append(caller_f)
            callee_f = input("Callee filter: ")
            if callee_f != "":
                filters.append(callee_f)
            ip_f = input("Source IP filter: ")
            if ip_f != "":
                filters.append(ip_f)
            if len(filters) == 0:
                ans = cfg.read_param(None, "No filter specified! "\
                        "Continue without a filter?", False, True)
                if not ans:
                    return False
                filters = None
        else:
            filters = params

        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        if cfg.exists("trace_listen_ip"):
            trace_ip = cfg.get("trace_listen_ip")
        else:
            trace_ip = "127.0.0.1"
        if cfg.exists("trace_listen_port"):
            trace_port = cfg.get("trace_listen_port")
        else:
            trace_port = 0
        s.bind((trace_ip, int(trace_port)))
        if trace_port == 0:
            trace_port = s.getsockname()[1]
        s.listen(1)
        conn = None
        trace_name = "opensips-cli.{}".format(random.randint(0, 65536))
        trace_socket = "hep:{}:{};transport=tcp;version=3".format(
                trace_ip, trace_port)
        args = {
            'id': trace_name,
            'uri': trace_socket,
        }
        if filters:
            args['filters'] = filters

        logger.debug("filters are {}".format(filters))
        trace_started = comm.execute('trace_start', args)
        if not trace_started:
            return False

        try:
            conn, addr = s.accept()
            logger.debug("New TCP connection from {}:{}".
                    format(addr[0], addr[1]))
            remaining = b''
            while True:
                data = conn.recv(TRACE_BUFFER_SIZE)
                if not data:
                    break
                remaining = self.__print_hep(remaining + data)
                if remaining is None:
                    break
        except KeyboardInterrupt:
            comm.execute('trace_stop', {'id' : trace_name }, True)
            if conn is not None:
                conn.close()
예제 #14
0
    def create_tables(self, db_name=None, do_all_tables=False, db_url=None):
        """
        create database tables
        """
        if db_url is None:
            db_url = cfg.read_param(
                "database_url",
                "Please provide the URL connecting to the database")
            if db_url is None:
                logger.error("no URL specified: aborting!")
                return -1

        # 2) prepare new object store database instance
#    use it to connect to the created database
        db = self.get_db(db_url, db_name)
        if db is None:
            return -1

        # connect to the database
        db.connect(db_name)

        # check to see if the database has already been created
        #if db.exists(db_name):
        #    logger.error("database '{}' already exists!".format(db_name))
        #    return -2

        db_schema = db.db_url.split(":")[0]
        schema_path = self.get_schema_path(db_schema)
        if schema_path is None:
            return -1

        standard_file_path = os.path.join(schema_path, "standard-create.sql")
        if not os.path.isfile(standard_file_path):
            logger.error("cannot find stardard OpenSIPS DB file: '{}'!".format(
                standard_file_path))
            return -1
        tables_files = [standard_file_path]

        # check to see what tables we shall deploy
        if cfg.read_param(
                None,
                "Create [a]ll tables or just the [c]urrently configured ones?",
                default="a").lower() == "a":
            print("Creating all tables ...")
            tables = [ f.replace('-create.sql', '') \
                        for f in os.listdir(schema_path) \
                        if os.path.isfile(os.path.join(schema_path, f)) and \
                            f.endswith('-create.sql') ]
        else:
            print("Creating the currently configured set of tables ...")
            if cfg.exists("database_modules"):
                tables = cfg.get("database_modules").split(" ")
            else:
                tables = STANDARD_DB_MODULES

        # check for corresponding SQL schemas files in system path
        logger.debug("deploying tables {}".format(" ".join(tables)))
        for table in tables:
            if table == "standard":
                # already checked for it
                continue
            table_file_path = os.path.join(schema_path,
                                           "{}-create.sql".format(table))
            if not os.path.isfile(table_file_path):
                logger.warn("cannot find file to create {}: {}".format(
                    table, table_file_path))
            else:
                tables_files.append(table_file_path)

        # create tables from SQL schemas
        for table_file in tables_files:
            print("Running {}...".format(os.path.basename(table_file)))
            try:
                db.create_module(table_file)
            except osdbError as ex:
                logger.error("cannot import: {}".format(ex))
        logger.info("database tables have been successfully created.")

        # terminate active database connection
        db.destroy()

        return 0