diff --git a/scripts/sylk-pushserver-db b/scripts/sylk-pushserver-db new file mode 100755 index 0000000..3a53ddb --- /dev/null +++ b/scripts/sylk-pushserver-db @@ -0,0 +1,160 @@ +#!/usr/bin/env python3 +import argparse +import sys +import os + +from application import log +from application.process import process + +CASSANDRA_MODULES_AVAILABLE = False +try: + from cassandra.cqlengine import columns, connection +except ImportError: + pass +else: + try: + from cassandra.cqlengine.models import Model + except ImportError: + pass + else: + CASSANDRA_MODULES_AVAILABLE = True + from cassandra import InvalidRequest + from cassandra.cqlengine.query import LWTException + from cassandra.cluster import Cluster, Session, NoHostAvailable, ExecutionProfile, EXEC_PROFILE_DEFAULT + from cassandra.io import asyncioreactor + from cassandra.policies import DCAwareRoundRobinPolicy + from cassandra.cqlengine.management import sync_table, create_keyspace_simple + +parser = argparse.ArgumentParser(add_help=False) +parser.add_argument('-h', '--help', + action='help', + default=argparse.SUPPRESS, + help='Show this help message and exit.') + +parser.add_argument("--config_dir", + default=None, + metavar='PATH', + help="Specify a config directory that contains " + "general.ini, applications.ini and " + "the credentials directory, " + "Default it uses '/etc/sylk-pushserver'") + +options = parser.parse_args() + + +class colors: + if sys.stdout.isatty(): + TGREEN = '\033[32m' + ENDC = '\033[m' + BOLD = '\033[1m' + else: + TGREEN = '' + ENDC = '' + BOLD = '' + + +class parse_level: + def format(record): + if record.levelname != 'INFO': + return f'{record.levelname:<8s} ' + else: + return f"{' ......':<8s} " + + +def ask(question): + try: + while ( res:=input(colors.TGREEN + f"{'>>':>8s} {question} (Enter y/n) " + colors.ENDC).lower() ) not in {"y", "n"}: pass + except KeyboardInterrupt: + sys.exit(1) + if res == "y": + return True + return False + +def bold(string): + return colors.BOLD + string + colors.ENDC + +log.Formatter.prefix_format = parse_level + +if options.config_dir is not None: + if not os.path.exists(f'{options.config_dir}'): + log.info(f'Specified config directory {options.config_dir} does not exist') + sys.exit(1) + config_dir = options.config_dir + +process.configuration.local_directory = config_dir +from pushserver.resources.storage.configuration import CassandraConfig, ServerConfig +from pushserver.models.cassandra import PushTokens, OpenSips + +log.info(f"\n{' Sylk Pushserver - Cassandra database create/maintenance ':*^80s}\n") +log.warn('Please note, this script can potentially destroy the data in the Cassandra database.') +log.warn('Make sure you have a backup if you already have data in the Cassandra cluster') +if not ask("Would you like to continue?"): + sys.exit() + +os.environ['CQLENG_ALLOW_SCHEMA_MANAGEMENT'] = '1' + +configuration = CassandraConfig.__cfgtype__(CassandraConfig.__cfgfile__) +if configuration.files: + log.info('Reading storage configuration from {}'.format(', '.join(configuration.files))) + +if CassandraConfig.table: + PushTokens.__table_name__ = CassandraConfig.table + +if CASSANDRA_MODULES_AVAILABLE: + if CassandraConfig.cluster_contact_points: + profile = ExecutionProfile( + load_balancing_policy=DCAwareRoundRobinPolicy(), + request_timeout=60 + ) + cluster = Cluster(CassandraConfig.cluster_contact_points, protocol_version=4, execution_profiles={EXEC_PROFILE_DEFAULT: profile}) + try: + session = cluster.connect() + except NoHostAvailable as e: + log.warning("Can't connect to Cassandra cluster") + sys.exit() + else: + connection.set_session(session) + if CassandraConfig.keyspace in cluster.metadata.keyspaces: + log.info(f"Keyspace {bold(CassandraConfig.keyspace)} is already on the server.") + else: + log.warning(f"Keyspace {bold(CassandraConfig.keyspace)} is {bold('not')} defined on the server") + if ask("Would you like to create the keyspace with SimpleStrategy?"): + create_keyspace_simple(CassandraConfig.keyspace, 1) + else: + sys.exit(1) + + keyspace = cluster.metadata.keyspaces[CassandraConfig.keyspace] + replication_strategy = keyspace.replication_strategy + log.info(f'Server has keyspace {bold(keyspace.name)} with replication strategy: {keyspace.replication_strategy.name}') + +# if replication_strategy.name == 'NetworkTopologyStrategy': +# for dc in replication_strategy.dc_replication_factors_info: +# log.info(f'DC: {dc}') + + PushTokens.__keyspace__ = CassandraConfig.keyspace + OpenSips.__keyspace__ = CassandraConfig.keyspace + if CassandraConfig.table in cluster.metadata.keyspaces[CassandraConfig.keyspace].tables: + log.info(f'Table {bold(CassandraConfig.table)} is in the keyspace {bold(CassandraConfig.keyspace)}') + + if ask("Would you like to update the schema with the model?"): + sync_table(PushTokens) + else: + log.info(f'Table {bold(CassandraConfig.table)} is not the keyspace {bold(CassandraConfig.keyspace)}') + + if ask ("Would you like to create the table from the model?"): + sync_table(PushTokens) + + if 'mobile_devices' in cluster.metadata.keyspaces[CassandraConfig.keyspace].tables: + log.info(f"The {bold('mobile_devices')} table is in keyspace {bold(CassandraConfig.keyspace)}") + if ask("Would you like to update the schema with the model?"): + sync_table(OpenSips) + else: + log.warn(f"The {bold('mobile_devices')} table is {bold('not')} in keyspace {bold(CassandraConfig.keyspace)}") + if ask("Would you like to create 'mobile-devices' table from the model?"): + sync_table(OpenSips) + else: + log.warning("Cassandra cluster contact points are not set, please adjust 'general.ini'") + sys.exit() +else: + log.warning('The python Cassandra drivers are not installed, please make sure they are installed') + sys.exit() diff --git a/setup.py b/setup.py index 0895dce..7bf4bc3 100755 --- a/setup.py +++ b/setup.py @@ -1,54 +1,54 @@ #!/usr/bin/python3 import glob import os from setuptools import setup import pushserver.__info__ as package_info long_description = """ Sylk Pushserver was designed to act as a central dispatcher for mobile push notifications inside RTC provider infrastructures. Both the provider and the mobile application customer, in the case of a shared infrastructure, can easily audit problems related to the processing of push notifications. """ def find_packages(root): return [directory.replace(os.path.sep, '.') for directory, sub_dirs, files in os.walk(root) if '__init__.py' in files] def requirements(): install_requires = [] with open('requirements.txt') as f: for line in f: install_requires.append(line.strip()) return install_requires setup(name=package_info.__project__, version=package_info.__version__, description=package_info.__summary__, long_description=long_description, author=package_info.__author__, license=package_info.__license__, platforms=['Platform Independent'], author_email=package_info.__email__, url=package_info.__webpage__, - scripts=['sylk-pushserver', 'scripts/sylk-pushclient'], + scripts=['sylk-pushserver', 'scripts/sylk-pushclient', 'scripts/sylk-pushclient-v2', 'scripts/sylk-pushserver-db'], packages=find_packages('pushserver'), # install_requires=requirements(), classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Service Providers', 'License :: GPL v3', 'Operating System :: OS Independent', 'Programming Language :: Python', ], data_files=[('/etc/sylk-pushserver', []), ('/etc/sylk-pushserver', glob.glob('config/*.sample')), ('/etc/sylk-pushserver/credentials', []), ('/etc/sylk-pushserver/applications', glob.glob('config/applications/*.py')), ('/etc/sylk-pushserver/applications/app_template', glob.glob('config/applications/app_template/*.py')), ('/var/log/sylk-pushserver', [])] )