From 2bc511c69f773ad1969c7adb29f3b846cde5b1a8 Mon Sep 17 00:00:00 2001 From: raigax9 Date: Fri, 25 Aug 2017 15:47:33 -0400 Subject: [PATCH] Added orm_client codebase Initial commit for orm_client by adding the codebase. Change-Id: I24f17561e6a426e6af9dbbe04e4a66599344d265 --- orm/orm_client/db_clear/__init__.py | 0 orm/orm_client/db_clear/cli_comander.py | 49 ++ orm/orm_client/db_clear/cms_cleaner.py | 102 ++++ orm/orm_client/db_clear/config.py | 25 + orm/orm_client/db_clear/db_comander.py | 130 +++++ orm/orm_client/db_clear/initializer.py | 10 + orm/orm_client/db_clear/utils.py | 23 + orm/orm_client/db_clear/yaml_handler.py | 25 + orm/orm_client/flavorgen/README | 41 ++ .../flavorgen/flavor_dir/Large/gv.c12r64d400 | 10 + .../flavorgen/flavor_dir/Large/gv.c1r2d12e20 | 10 + .../flavorgen/flavor_dir/Large/gv.c2r2d320 | 10 + .../flavorgen/flavor_dir/Large/gv.c2r4d12e40 | 10 + .../flavorgen/flavor_dir/Large/gv.c2r4d20 | 10 + .../flavorgen/flavor_dir/Large/gv.c4r12d320 | 10 + .../flavorgen/flavor_dir/Large/gv.c4r16d320 | 10 + .../flavorgen/flavor_dir/Large/gv.c4r2d80e160 | 10 + .../flavorgen/flavor_dir/Large/gv.c4r32d320 | 10 + .../flavorgen/flavor_dir/Large/gv.c8r16d40 | 10 + .../flavor_dir/Large/gv.c8r16d80e160 | 10 + .../flavor_dir/Large/gv.c8r32d80e320 | 10 + .../flavorgen/flavor_dir/Large/gv.c8r64d320 | 10 + .../flavorgen/flavor_dir/Large/nv.c12r64d400 | 10 + .../flavorgen/flavor_dir/Large/nv.c2r2d320 | 11 + .../flavorgen/flavor_dir/Large/nv.c2r4d20 | 10 + .../flavorgen/flavor_dir/Large/nv.c4r12d320 | 10 + .../flavorgen/flavor_dir/Large/nv.c4r16d320 | 10 + .../flavorgen/flavor_dir/Large/nv.c4r32d320 | 10 + .../flavorgen/flavor_dir/Large/nv.c8r16d40 | 10 + .../flavorgen/flavor_dir/Large/nv.c8r64d320 | 10 + orm/orm_client/flavorgen/flavorator.py | 70 +++ orm/orm_client/flavorgen/make_flav.py | 36 ++ orm/orm_client/flavorgen/make_flavor.py | 37 ++ orm/orm_client/flavorgen/regionator.py | 161 +++++++ orm/orm_client/imagegen/README | 41 ++ orm/orm_client/imagegen/image_dir/test_image | 11 + orm/orm_client/imagegen/imageator.py | 84 ++++ orm/orm_client/imagegen/regionator.py | 98 ++++ orm/orm_client/ormcli/README | 69 +++ orm/orm_client/ormcli/__init__.py | 0 orm/orm_client/ormcli/cli_common.py | 53 +++ orm/orm_client/ormcli/cmscli.py | 449 ++++++++++++++++++ orm/orm_client/ormcli/config.py | 8 + orm/orm_client/ormcli/fmscli.py | 395 +++++++++++++++ orm/orm_client/ormcli/imscli.py | 366 ++++++++++++++ orm/orm_client/ormcli/orm | 7 + orm/orm_client/ormcli/ormcli.py | 37 ++ orm/orm_client/ormcli/requirements.txt | 2 + orm/orm_client/ormcli/rmscli.py | 385 +++++++++++++++ orm/orm_client/ormcli/tests/__init__.py | 0 .../ormcli/tests/data/cms-add-cust.json | 119 +++++ .../ormcli/tests/data/ims-create-image.json | 29 ++ .../ormcli/tests/test_cli_common.py | 53 +++ orm/orm_client/ormcli/tests/test_cmscli.py | 229 +++++++++ orm/orm_client/ormcli/tests/test_fmscli.py | 172 +++++++ orm/orm_client/ormcli/tests/test_imscli.py | 199 ++++++++ orm/orm_client/ormcli/tests/test_rmscli.py | 188 ++++++++ orm/orm_client/requirements.txt | 6 + orm/orm_client/setup.cfg | 6 + orm/orm_client/setup.py | 19 + orm/orm_client/test-requirements.txt | 9 + orm/orm_client/tox.ini | 19 + 62 files changed, 3973 insertions(+) create mode 100644 orm/orm_client/db_clear/__init__.py create mode 100644 orm/orm_client/db_clear/cli_comander.py create mode 100644 orm/orm_client/db_clear/cms_cleaner.py create mode 100644 orm/orm_client/db_clear/config.py create mode 100644 orm/orm_client/db_clear/db_comander.py create mode 100644 orm/orm_client/db_clear/initializer.py create mode 100644 orm/orm_client/db_clear/utils.py create mode 100644 orm/orm_client/db_clear/yaml_handler.py create mode 100644 orm/orm_client/flavorgen/README create mode 100644 orm/orm_client/flavorgen/flavor_dir/Large/gv.c12r64d400 create mode 100644 orm/orm_client/flavorgen/flavor_dir/Large/gv.c1r2d12e20 create mode 100644 orm/orm_client/flavorgen/flavor_dir/Large/gv.c2r2d320 create mode 100644 orm/orm_client/flavorgen/flavor_dir/Large/gv.c2r4d12e40 create mode 100644 orm/orm_client/flavorgen/flavor_dir/Large/gv.c2r4d20 create mode 100644 orm/orm_client/flavorgen/flavor_dir/Large/gv.c4r12d320 create mode 100644 orm/orm_client/flavorgen/flavor_dir/Large/gv.c4r16d320 create mode 100644 orm/orm_client/flavorgen/flavor_dir/Large/gv.c4r2d80e160 create mode 100644 orm/orm_client/flavorgen/flavor_dir/Large/gv.c4r32d320 create mode 100644 orm/orm_client/flavorgen/flavor_dir/Large/gv.c8r16d40 create mode 100644 orm/orm_client/flavorgen/flavor_dir/Large/gv.c8r16d80e160 create mode 100644 orm/orm_client/flavorgen/flavor_dir/Large/gv.c8r32d80e320 create mode 100644 orm/orm_client/flavorgen/flavor_dir/Large/gv.c8r64d320 create mode 100644 orm/orm_client/flavorgen/flavor_dir/Large/nv.c12r64d400 create mode 100644 orm/orm_client/flavorgen/flavor_dir/Large/nv.c2r2d320 create mode 100644 orm/orm_client/flavorgen/flavor_dir/Large/nv.c2r4d20 create mode 100644 orm/orm_client/flavorgen/flavor_dir/Large/nv.c4r12d320 create mode 100644 orm/orm_client/flavorgen/flavor_dir/Large/nv.c4r16d320 create mode 100644 orm/orm_client/flavorgen/flavor_dir/Large/nv.c4r32d320 create mode 100644 orm/orm_client/flavorgen/flavor_dir/Large/nv.c8r16d40 create mode 100644 orm/orm_client/flavorgen/flavor_dir/Large/nv.c8r64d320 create mode 100644 orm/orm_client/flavorgen/flavorator.py create mode 100644 orm/orm_client/flavorgen/make_flav.py create mode 100644 orm/orm_client/flavorgen/make_flavor.py create mode 100644 orm/orm_client/flavorgen/regionator.py create mode 100644 orm/orm_client/imagegen/README create mode 100644 orm/orm_client/imagegen/image_dir/test_image create mode 100644 orm/orm_client/imagegen/imageator.py create mode 100644 orm/orm_client/imagegen/regionator.py create mode 100644 orm/orm_client/ormcli/README create mode 100644 orm/orm_client/ormcli/__init__.py create mode 100644 orm/orm_client/ormcli/cli_common.py create mode 100644 orm/orm_client/ormcli/cmscli.py create mode 100644 orm/orm_client/ormcli/config.py create mode 100644 orm/orm_client/ormcli/fmscli.py create mode 100644 orm/orm_client/ormcli/imscli.py create mode 100644 orm/orm_client/ormcli/orm create mode 100644 orm/orm_client/ormcli/ormcli.py create mode 100644 orm/orm_client/ormcli/requirements.txt create mode 100644 orm/orm_client/ormcli/rmscli.py create mode 100644 orm/orm_client/ormcli/tests/__init__.py create mode 100644 orm/orm_client/ormcli/tests/data/cms-add-cust.json create mode 100644 orm/orm_client/ormcli/tests/data/ims-create-image.json create mode 100644 orm/orm_client/ormcli/tests/test_cli_common.py create mode 100644 orm/orm_client/ormcli/tests/test_cmscli.py create mode 100644 orm/orm_client/ormcli/tests/test_fmscli.py create mode 100644 orm/orm_client/ormcli/tests/test_imscli.py create mode 100644 orm/orm_client/ormcli/tests/test_rmscli.py create mode 100644 orm/orm_client/requirements.txt create mode 100644 orm/orm_client/setup.cfg create mode 100644 orm/orm_client/setup.py create mode 100644 orm/orm_client/test-requirements.txt create mode 100644 orm/orm_client/tox.ini diff --git a/orm/orm_client/db_clear/__init__.py b/orm/orm_client/db_clear/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/orm/orm_client/db_clear/cli_comander.py b/orm/orm_client/db_clear/cli_comander.py new file mode 100644 index 00000000..7b1dd5cd --- /dev/null +++ b/orm/orm_client/db_clear/cli_comander.py @@ -0,0 +1,49 @@ +import config as conf +import json +import logging +import os + + +log = logging.getLogger(__name__) + + +def _get_regions(customer): + regions = [] + try: + customer_json = json.loads(customer) + if 'regions' not in customer_json: + raise Exception("got bad response from orm cli ") + for region in customer_json['regions']: + regions.append(region['name']) + except Exception as exp: + raise Exception("got bad response from orm cli {}".format(exp.message)) + message = "got no regions from orm cli" + if regions: + message = "got regions from orm cli --{}--".format(regions) + log.debug(message) + return regions + + +def _build_get_customer_cli_command(resource_id): + cli_command = """get_customer %s""" % resource_id + log.debug('cli command {}'.format(cli_command)) + return cli_command + + +def _get_customer_regions(cli_command, service): + client_header = service.upper() + log.debug("get customer with cli") + os.chdir(conf.cli_dir) + cwd = os.getcwd() + customer = os.popen('./orm %s %s ' % (service.lower(), cli_command)) + log.debug("got cusmer with cli ... check if got regions") + return _get_regions(customer.read()) + + +def get_resource_regions(resource_id, service): + log.debug("---ORM CLI---") + regions = None + if service.upper() == 'CMS': + regions = _get_customer_regions( + _build_get_customer_cli_command(resource_id), service) + return regions diff --git a/orm/orm_client/db_clear/cms_cleaner.py b/orm/orm_client/db_clear/cms_cleaner.py new file mode 100644 index 00000000..d9d8ccfe --- /dev/null +++ b/orm/orm_client/db_clear/cms_cleaner.py @@ -0,0 +1,102 @@ +"""clean cms mpdule.""" +import cli_comander as cli +import db_comander as db +import initializer +import logging +import sys +import utils +import yaml_handler as yh + + +log = logging.getLogger(__name__) + + +def _validate_service(service): + allowed_services = ['CMS', 'FMS'] + if service.upper() not in allowed_services: + raise Exception("service should be one of {}".format(allowed_services)) + return service.upper() + + +def _init(): + initializer.init_log() + return + + +def read_csv_file(file): + log.debug("reading file {}".format(file)) + return utils.read_csv_file(file) + + +def resource_db_clean(resource_id, service): + log.debug("cleaning {} db for resource {}".format(service, resource_id)) + db.remove_resource_db(resource_id, service) + return + + +def check_yaml_file(resource_id): + log.debug('checking yml file if exist for resource {}'.format(resource_id)) + files = yh.check_yaml_exist(resource_id) + message = 'no yaml files found for this resource' + if files: + message = "found files please remove manualy {}".format(files) + log.debug(message) + return + + +def get_resource_regions(resource_id, service_name): + db_regions = db.get_cms_db_resource_regions(resource_id) + orm_regions = cli.get_resource_regions(resource_id, service_name) + return orm_regions, db_regions + + +def clean_rds_resource_status(resource_id): + log.debug("clean rds status db for resource {}".format(resource_id)) + db.remove_rds_resource_status(resource_id) + return + + +def _start_cleaning(): + log.info('start cleaning') + file_path = sys.argv[1] + service = _validate_service(sys.argv[2]) + resourses_to_clean = read_csv_file(file_path) + for resource_id in resourses_to_clean: + try: + log.debug( + 'check if resource {} has any regions before clean'.format( + resource_id)) + resource_regions, db_regions = get_resource_regions(resource_id, + service) + if resource_regions or db_regions: + log.error( + "got regions {} {} please clean regions from orm before" + " removing the resource {}".format(resource_regions, + db_regions, + resource_id)) + raise Exception( + "got regions {} {} please clean regions from orm before" + " removing the resource {}".format(resource_regions, + db_regions, + resource_id)) + + log.debug('cleaning {}'.format(resource_id)) + resource_db_clean(resource_id, service) + check_yaml_file(resource_id) + clean_rds_resource_status(resource_id) + + except Exception as exp: + log.error("---------------{}---------------".format(exp.message)) + if 'not found' not in exp.message: + log.exception(exp) + continue + return + + +if __name__ == '__main__': + warning_message = raw_input( + 'IMPORTANT:- please note its your responsibility to backup the db' + ' before runing this script... click enter before continue' + ) + _init() + _start_cleaning() diff --git a/orm/orm_client/db_clear/config.py b/orm/orm_client/db_clear/config.py new file mode 100644 index 00000000..29b9ecb5 --- /dev/null +++ b/orm/orm_client/db_clear/config.py @@ -0,0 +1,25 @@ +"""config module.""" + +# db configs +sql_user = 'root' +sql_password = 'stack' +sql_server = '127.0.0.1' +sql_port = '3306' + +# cms configs +customer_table_name = "customer" +customer_region_table_name = "customer_region" +cms_db_name = "orm_cms_db" + + +# cli configs +cli_dir = '../ormcli' + +# rds configs +rds_db_name = 'orm_rds' +resource_status_table_name = 'resource_status' + +# sot configs +local_repository_path = '/opt/app/orm/ORM' +file_name_format = 's_{}.yml' +relative_path_format = '/{}/hot/{}/{}' diff --git a/orm/orm_client/db_clear/db_comander.py b/orm/orm_client/db_clear/db_comander.py new file mode 100644 index 00000000..4bf8b7bb --- /dev/null +++ b/orm/orm_client/db_clear/db_comander.py @@ -0,0 +1,130 @@ +import config as conf +import logging +import sqlalchemy + + +log = logging.getLogger(__name__) + + +db_engines = {} + + +def _db_create_engine(db_name): + global db_engines + if db_name not in db_engines: + db_address = 'mysql://{}:{}@{}:{}/{}'.format(conf.sql_user, + conf.sql_password, + conf.sql_server, + conf.sql_port, + db_name) + log.debug("DB:--- db address {}".format(db_address)) + db_engines[db_name] = sqlalchemy.create_engine(db_address) + return db_engines + + +def _run_query(query, db_name): + db_engines = _db_create_engine(db_name) + connection = db_engines[db_name].connect() + try: + sqlres = connection.execute(query) + except Exception as exp: + sqlres = None + log.error("fail to delete resource {}".format(exp)) + finally: + # close the connection + connection.close() + # db_engines[db_name].dispose() + return sqlres + + +def _build_delet_resource_status_query(resource_id, table_name): + query = ''' + DELETE from %s + WHERE resource_id = '%s' + ''' % (table_name, resource_id) + return query + + +def _build_delete_resource_query(resource_id, table_name): + query = ''' + DELETE from %s + WHERE %s.uuid = '%s' + ''' % (table_name, table_name, resource_id) + return query + + +def _build_get_resource_regions_query(resource_id, table_name): + query = ''' + select region_id from %s + WHERE customer_id = '%s' and region_id != '-1' + ''' % (table_name, resource_id) + return query + + +def _build_get_resource_id_query(resource_id, table_name): + query = ''' + select * from %s + WHERE %s.uuid = '%s' + ''' % (table_name, table_name, resource_id) + return query + + +def remove_cms_resource(resource_id): + query = _build_delete_resource_query(resource_id, conf.customer_table_name) + log.debug("DB---: deleting customer, query {}".format(query)) + _run_query(query, conf.cms_db_name) + return + + +def remove_rds_resource_status(resource_id): + query = _build_delet_resource_status_query(resource_id, + conf.resource_status_table_name) + log.debug("DB---: deleting resource status, query {}".format(query)) + _run_query(query, conf.rds_db_name) + return + + +def remove_ims_resource(resource_id): + return + + +def remove_fms_resource(resource_id): + return + + +def get_cms_db_resource_regions(resource_id): + regions = None + query = _build_get_resource_id_query(resource_id, conf.customer_table_name) + result = _run_query(query, conf.cms_db_name) + if not result.rowcount > 0: + raise Exception('resource {} not found'.format(resource_id)) + resource_internal_id = result.first().__getitem__('id') + log.debug("got resource internal id {}".format(resource_internal_id)) + # from resource id get regions + query = _build_get_resource_regions_query(resource_internal_id, + conf.customer_region_table_name) + log.debug(query) + result = _run_query(query, conf.cms_db_name) + if result.rowcount > 0: + regions = result.fetchall() + return regions + + +def get_ims_db_resource_regions(resource_id): + return + + +def get_fms_db_resource_regions(resource_id): + return + + +def get_rds_db_resource_status(resource_id): + return + + +def remove_resource_db(resource_id, service): + if service == 'CMS': + log.debug( + "cleaning {} db for resource {}".format(service, resource_id)) + remove_cms_resource(resource_id) + return diff --git a/orm/orm_client/db_clear/initializer.py b/orm/orm_client/db_clear/initializer.py new file mode 100644 index 00000000..31caa4ef --- /dev/null +++ b/orm/orm_client/db_clear/initializer.py @@ -0,0 +1,10 @@ +import logging + + +log = logging.getLogger(__name__) + + +def init_log(): + logging.basicConfig(format='%(asctime)s:%(levelname)s:%(message)s', + level=logging.DEBUG) + logging.info("logger set") diff --git a/orm/orm_client/db_clear/utils.py b/orm/orm_client/db_clear/utils.py new file mode 100644 index 00000000..d5f6e46b --- /dev/null +++ b/orm/orm_client/db_clear/utils.py @@ -0,0 +1,23 @@ +import csv +import logging + + +log = logging.getLogger(__name__) + + +def _validate_file(file): + if str(file).split('.')[-1] != 'csv': + log.error('please provide csv file') + raise TypeError('please provide csv file') + + +def read_csv_file(file): + _validate_file(file) + resources = [] + with open(file, 'rb') as csvfile: + csv_dict = csv.DictReader(csvfile) + for resource in csv_dict: + resources.append(resource["uuids"]) + log.debug( + 'list of resources to clean ----{} -------'.format(resources)) + return resources diff --git a/orm/orm_client/db_clear/yaml_handler.py b/orm/orm_client/db_clear/yaml_handler.py new file mode 100644 index 00000000..36b5a657 --- /dev/null +++ b/orm/orm_client/db_clear/yaml_handler.py @@ -0,0 +1,25 @@ +import config as conf +import fnmatch +import os + + +def _get_resource_file_path(): + file_path = conf.local_repository_path + return file_path + + +def _find_file(resource_id): + file_name = conf.file_name_format.format(resource_id) + folder_to_search = _get_resource_file_path(resource_id) + matches = [] + for root, dirnames, filenames in os.walk(folder_to_search): + for filename in fnmatch.filter(filenames, file_name): + matches.append(os.path.join(root, filename)) + return matches + + +def check_yaml_exist(resource_id): + files = _find_file(resource_id) + if files: + return files + return None diff --git a/orm/orm_client/flavorgen/README b/orm/orm_client/flavorgen/README new file mode 100644 index 00000000..a4e696a6 --- /dev/null +++ b/orm/orm_client/flavorgen/README @@ -0,0 +1,41 @@ +Flavorgen Usage + +To install: + +1. Download attached file flavorgen.tgz +2. Copy flavorgen.tgz to :/opt/app/orm/ormcli +3. cd /opt/app/orm/ormcli +4. tar xvzf flavorgen.tgz + +All the predefined flavors are defined in flavor_dir. Edit if necessary. + +To generate these flavors: + +5. cd /opt/app/orm/ormcli/flavorgen +6. ./flavorator.py + +You should normally create the list of flavors once. + +Then when you want to add regions to all the flavors, use: + +7. ./regionator.py --regions region1,region2 + +The argument is a comma-separated list of regions with no internal whitespace. + + +Use of -h will produce the following help: + +./regionator.py -h +usage: regionator [-h] [--flavor_dir FLAVOR_DIR] [--host HOST] + [--cli_command CLI_COMMAND] [--regions REGIONS] + +batch add region to flavor + +optional arguments: + -h, --help show this help message and exit + --flavor_dir FLAVOR_DIR + + --host HOST + --cli_command CLI_COMMAND + + --regions REGIONS diff --git a/orm/orm_client/flavorgen/flavor_dir/Large/gv.c12r64d400 b/orm/orm_client/flavorgen/flavor_dir/Large/gv.c12r64d400 new file mode 100644 index 00000000..8753cbd2 --- /dev/null +++ b/orm/orm_client/flavorgen/flavor_dir/Large/gv.c12r64d400 @@ -0,0 +1,10 @@ +{ + "description":"A standard GV flavor with 12 VCPUs, 64 GB RAM, 400 GB Disk, 0 GB Swap and 0 GB Ephemeral", + "series": "gv", + "ram": "65536", + "ephemeral": "0", + "visibility": "public", + "vcpus": "12", + "swap": "0", + "disk": "400" +} diff --git a/orm/orm_client/flavorgen/flavor_dir/Large/gv.c1r2d12e20 b/orm/orm_client/flavorgen/flavor_dir/Large/gv.c1r2d12e20 new file mode 100644 index 00000000..42904ced --- /dev/null +++ b/orm/orm_client/flavorgen/flavor_dir/Large/gv.c1r2d12e20 @@ -0,0 +1,10 @@ +{ + "description":"A standard GV flavor with 1 VCPU, 2 GB RAM, 12 GB Disk, 0 GB Swap and 20 GB Ephemeral", + "series": "gv", + "ram": "2048", + "ephemeral": "20", + "visibility": "public", + "vcpus": "1", + "swap": "0", + "disk": "12" +} diff --git a/orm/orm_client/flavorgen/flavor_dir/Large/gv.c2r2d320 b/orm/orm_client/flavorgen/flavor_dir/Large/gv.c2r2d320 new file mode 100644 index 00000000..9bd6dc71 --- /dev/null +++ b/orm/orm_client/flavorgen/flavor_dir/Large/gv.c2r2d320 @@ -0,0 +1,10 @@ +{ + "description":"A standard GV flavor with 2 VCPUs, 2 GB RAM, 320 GB Disk, 0 GB Swap and 0 GB Ephemeral", + "series": "gv", + "ram": "2048", + "ephemeral": "0", + "visibility": "public", + "vcpus": "2", + "swap": "0", + "disk": "320" +} diff --git a/orm/orm_client/flavorgen/flavor_dir/Large/gv.c2r4d12e40 b/orm/orm_client/flavorgen/flavor_dir/Large/gv.c2r4d12e40 new file mode 100644 index 00000000..86d58a61 --- /dev/null +++ b/orm/orm_client/flavorgen/flavor_dir/Large/gv.c2r4d12e40 @@ -0,0 +1,10 @@ +{ + "description":"A standard GV flavor with 2 VCPUs, 4 GB RAM, 12 GB Disk, 0 GB Swap and 40 GB Ephemeral", + "series": "gv", + "ram": "4096", + "ephemeral": "40", + "visibility": "public", + "vcpus": "2", + "swap": "0", + "disk": "12" +} diff --git a/orm/orm_client/flavorgen/flavor_dir/Large/gv.c2r4d20 b/orm/orm_client/flavorgen/flavor_dir/Large/gv.c2r4d20 new file mode 100644 index 00000000..b75ff5f8 --- /dev/null +++ b/orm/orm_client/flavorgen/flavor_dir/Large/gv.c2r4d20 @@ -0,0 +1,10 @@ +{ + "description":"A standard GV flavor with 2 VCPUs, 4 GB RAM, 20 GB Disk, 0 GB Swap and 0 GB Ephemeral", + "series": "gv", + "ram": "4096", + "ephemeral": "0", + "visibility": "public", + "vcpus": "2", + "swap": "0", + "disk": "20" +} diff --git a/orm/orm_client/flavorgen/flavor_dir/Large/gv.c4r12d320 b/orm/orm_client/flavorgen/flavor_dir/Large/gv.c4r12d320 new file mode 100644 index 00000000..2dc568db --- /dev/null +++ b/orm/orm_client/flavorgen/flavor_dir/Large/gv.c4r12d320 @@ -0,0 +1,10 @@ +{ + "description":"A standard GV flavor with 4 VCPUs, 12 GB RAM, 320 GB Disk, 0 GB Swap and 0 GB Ephemeral", + "series": "gv", + "ram": "12288", + "ephemeral": "0", + "visibility": "public", + "vcpus": "4", + "swap": "0", + "disk": "320" +} diff --git a/orm/orm_client/flavorgen/flavor_dir/Large/gv.c4r16d320 b/orm/orm_client/flavorgen/flavor_dir/Large/gv.c4r16d320 new file mode 100644 index 00000000..cc5e7956 --- /dev/null +++ b/orm/orm_client/flavorgen/flavor_dir/Large/gv.c4r16d320 @@ -0,0 +1,10 @@ +{ + "description":"A standard GV flavor with 4 VCPUs, 16 GB RAM, 320 GB Disk, 0 GB Swap and 0 GB Ephemeral", + "series": "gv", + "ram": "16384", + "ephemeral": "0", + "visibility": "public", + "vcpus": "4", + "swap": "0", + "disk": "320" +} diff --git a/orm/orm_client/flavorgen/flavor_dir/Large/gv.c4r2d80e160 b/orm/orm_client/flavorgen/flavor_dir/Large/gv.c4r2d80e160 new file mode 100644 index 00000000..88241051 --- /dev/null +++ b/orm/orm_client/flavorgen/flavor_dir/Large/gv.c4r2d80e160 @@ -0,0 +1,10 @@ +{ + "description":"A standard GV flavor with 4 VCPUs, 2 GB RAM, 80 GB Disk, 0 GB Swap and 160 GB Ephemeral", + "series": "gv", + "ram": "2048", + "ephemeral": "160", + "visibility": "public", + "vcpus": "4", + "swap": "0", + "disk": "80" +} diff --git a/orm/orm_client/flavorgen/flavor_dir/Large/gv.c4r32d320 b/orm/orm_client/flavorgen/flavor_dir/Large/gv.c4r32d320 new file mode 100644 index 00000000..19d04e73 --- /dev/null +++ b/orm/orm_client/flavorgen/flavor_dir/Large/gv.c4r32d320 @@ -0,0 +1,10 @@ +{ + "description":"A standard GV flavor with 4 VCPUs, 32 GB RAM, 320 GB Disk, 0 GB Swap and 0 GB Ephemeral", + "series": "gv", + "ram": "32768", + "ephemeral": "0", + "visibility": "public", + "vcpus": "4", + "swap": "0", + "disk": "320" +} diff --git a/orm/orm_client/flavorgen/flavor_dir/Large/gv.c8r16d40 b/orm/orm_client/flavorgen/flavor_dir/Large/gv.c8r16d40 new file mode 100644 index 00000000..dde12350 --- /dev/null +++ b/orm/orm_client/flavorgen/flavor_dir/Large/gv.c8r16d40 @@ -0,0 +1,10 @@ +{ + "description":"A standard GV flavor with 8 VCPUs, 16 GB RAM, 40 GB Disk, 0 GB Swap and 0 GB Ephemeral", + "series": "gv", + "ram": "16384", + "ephemeral": "0", + "visibility": "public", + "vcpus": "8", + "swap": "0", + "disk": "40" +} diff --git a/orm/orm_client/flavorgen/flavor_dir/Large/gv.c8r16d80e160 b/orm/orm_client/flavorgen/flavor_dir/Large/gv.c8r16d80e160 new file mode 100644 index 00000000..f0c552cd --- /dev/null +++ b/orm/orm_client/flavorgen/flavor_dir/Large/gv.c8r16d80e160 @@ -0,0 +1,10 @@ +{ + "description":"A standard GV flavor with 8 VCPUs, 16 GB RAM, 80 GB Disk, 0 GB Swap and 160 GB Ephemeral", + "series": "gv", + "ram": "16384", + "ephemeral": "160", + "visibility": "public", + "vcpus": "8", + "swap": "0", + "disk": "80" +} diff --git a/orm/orm_client/flavorgen/flavor_dir/Large/gv.c8r32d80e320 b/orm/orm_client/flavorgen/flavor_dir/Large/gv.c8r32d80e320 new file mode 100644 index 00000000..60e04dd8 --- /dev/null +++ b/orm/orm_client/flavorgen/flavor_dir/Large/gv.c8r32d80e320 @@ -0,0 +1,10 @@ +{ + "description":"A standard GV flavor with 8 VCPUs, 32 GB RAM, 80 GB Disk, 0 GB Swap and 320 GB Ephemeral", + "series": "gv", + "ram": "32768", + "ephemeral": "320", + "visibility": "public", + "vcpus": "8", + "swap": "0", + "disk": "80" +} diff --git a/orm/orm_client/flavorgen/flavor_dir/Large/gv.c8r64d320 b/orm/orm_client/flavorgen/flavor_dir/Large/gv.c8r64d320 new file mode 100644 index 00000000..2aa928e5 --- /dev/null +++ b/orm/orm_client/flavorgen/flavor_dir/Large/gv.c8r64d320 @@ -0,0 +1,10 @@ +{ + "description":"A standard GV flavor with 8 VCPUs, 64 GB RAM, 320 GB Disk, 0 GB Swap and 0 GB Ephemeral", + "series": "gv", + "ram": "65536", + "ephemeral": "0", + "visibility": "public", + "vcpus": "8", + "swap": "0", + "disk": "320" +} diff --git a/orm/orm_client/flavorgen/flavor_dir/Large/nv.c12r64d400 b/orm/orm_client/flavorgen/flavor_dir/Large/nv.c12r64d400 new file mode 100644 index 00000000..52543007 --- /dev/null +++ b/orm/orm_client/flavorgen/flavor_dir/Large/nv.c12r64d400 @@ -0,0 +1,10 @@ +{ + "description":"A standard NV flavor with 12 VCPUs, 64 GB RAM, 400 GB Disk, 0 GB Swap and 0 GB Ephemeral", + "series": "nv", + "ram": "65536", + "ephemeral": "0", + "visibility": "public", + "vcpus": "12", + "swap": "0", + "disk": "400" +} diff --git a/orm/orm_client/flavorgen/flavor_dir/Large/nv.c2r2d320 b/orm/orm_client/flavorgen/flavor_dir/Large/nv.c2r2d320 new file mode 100644 index 00000000..2e822c62 --- /dev/null +++ b/orm/orm_client/flavorgen/flavor_dir/Large/nv.c2r2d320 @@ -0,0 +1,11 @@ +{ + "description":"A standard NV flavor with 2 VCPUs, 2 GB RAM, 320 GB Disk, 0 GB Swap and 0 GB Ephemeral", + "series": "nv", + "ram": "2048", + "ephemeral": "0", + "visibility": "public", + "vcpus": "2", + "swap": "0", + "disk": "320" +} + diff --git a/orm/orm_client/flavorgen/flavor_dir/Large/nv.c2r4d20 b/orm/orm_client/flavorgen/flavor_dir/Large/nv.c2r4d20 new file mode 100644 index 00000000..1ccd33d9 --- /dev/null +++ b/orm/orm_client/flavorgen/flavor_dir/Large/nv.c2r4d20 @@ -0,0 +1,10 @@ +{ + "description":"A standard NV flavor with 2 VCPUs, 4 GB RAM, 20 GB Disk, 0 GB Swap and 0 GB Ephemeral", + "series": "nv", + "ram": "4096", + "ephemeral": "0", + "visibility": "public", + "vcpus": "2", + "swap": "0", + "disk": "20" +} diff --git a/orm/orm_client/flavorgen/flavor_dir/Large/nv.c4r12d320 b/orm/orm_client/flavorgen/flavor_dir/Large/nv.c4r12d320 new file mode 100644 index 00000000..65860bd8 --- /dev/null +++ b/orm/orm_client/flavorgen/flavor_dir/Large/nv.c4r12d320 @@ -0,0 +1,10 @@ +{ + "description":"A standard NV flavor with 4 VCPUs, 12 GB RAM, 320 GB Disk, 0 GB Swap and 0 GB Ephemeral", + "series": "nv", + "ram": "12288", + "ephemeral": "0", + "visibility": "public", + "vcpus": "4", + "swap": "0", + "disk": "320" +} diff --git a/orm/orm_client/flavorgen/flavor_dir/Large/nv.c4r16d320 b/orm/orm_client/flavorgen/flavor_dir/Large/nv.c4r16d320 new file mode 100644 index 00000000..9cddcccd --- /dev/null +++ b/orm/orm_client/flavorgen/flavor_dir/Large/nv.c4r16d320 @@ -0,0 +1,10 @@ +{ + "description":"A standard NV flavor with 4 VCPUs, 16 GB RAM, 320 GB Disk, 0 GB Swap and 0 GB Ephemeral", + "series": "nv", + "ram": "16384", + "ephemeral": "0", + "visibility": "public", + "vcpus": "4", + "swap": "0", + "disk": "320" +} diff --git a/orm/orm_client/flavorgen/flavor_dir/Large/nv.c4r32d320 b/orm/orm_client/flavorgen/flavor_dir/Large/nv.c4r32d320 new file mode 100644 index 00000000..afe2f424 --- /dev/null +++ b/orm/orm_client/flavorgen/flavor_dir/Large/nv.c4r32d320 @@ -0,0 +1,10 @@ +{ + "description":"A standard NV flavor with 4 VCPUs, 32 GB RAM, 320 GB Disk, 0 GB Swap and 0 GB Ephemeral", + "series": "nv", + "ram": "32768", + "ephemeral": "0", + "visibility": "public", + "vcpus": "4", + "swap": "0", + "disk": "320" +} diff --git a/orm/orm_client/flavorgen/flavor_dir/Large/nv.c8r16d40 b/orm/orm_client/flavorgen/flavor_dir/Large/nv.c8r16d40 new file mode 100644 index 00000000..9cac833a --- /dev/null +++ b/orm/orm_client/flavorgen/flavor_dir/Large/nv.c8r16d40 @@ -0,0 +1,10 @@ +{ + "description":"A standard NV flavor with 8 VCPUs, 16 GB RAM, 40 GB Disk, 0 GB Swap and 0 GB Ephemeral", + "series": "nv", + "ram": "16384", + "ephemeral": "0", + "visibility": "public", + "vcpus": "8", + "swap": "0", + "disk": "40" +} diff --git a/orm/orm_client/flavorgen/flavor_dir/Large/nv.c8r64d320 b/orm/orm_client/flavorgen/flavor_dir/Large/nv.c8r64d320 new file mode 100644 index 00000000..1635d338 --- /dev/null +++ b/orm/orm_client/flavorgen/flavor_dir/Large/nv.c8r64d320 @@ -0,0 +1,10 @@ +{ + "description":"A standard NV flavor with 8 VCPUs, 64 GB RAM, 320 GB Disk, 0 GB Swap and 0 GB Ephemeral", + "series": "nv", + "ram": "65536", + "ephemeral": "0", + "visibility": "public", + "vcpus": "8", + "swap": "0", + "disk": "320" +} diff --git a/orm/orm_client/flavorgen/flavorator.py b/orm/orm_client/flavorgen/flavorator.py new file mode 100644 index 00000000..09098a48 --- /dev/null +++ b/orm/orm_client/flavorgen/flavorator.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python +import argparse +import json +import os +import subprocess +import tempfile +import time + +# Default flavor json directory +FLAVOR_DIR = './flavor_dir' + + +def read_jsonfile(file): + return json.loads(open(file).read()) + + +def calculate_name(flavor): + return "{0}.c{1}r{2}d{3}".format(flavor['series'], flavor['vcpus'], + int(flavor['ram']) / 1024, flavor['disk']) + + +def sh(harg, file_name): + # run a shell command, echoing output, painting error lines red, + # print runtime and status + # return status and output + + cmd = create_command(harg, file_name) + + print '>> Starting: ' + cmd + start = time.time() + output = '' + p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + for line in iter(p.stdout.readline, b''): + out = line.rstrip() + print(">>> " + out) + output += out + end = time.time() + span = end - start + retcode = p.wait() + print '>> Ended: %s [%s, %d:%02d]' % (cmd, retcode, span / 60, span % 60) + return retcode, output + + +def create_command(harg, file_name): + cmd = 'python ../ormcli/orm fms %s create_flavor %s' % \ + (harg, file_name) + if ';' in cmd or '&&' in cmd: + raise Exception("Violation of command injection, cmd is " + cmd) + return cmd + + +parser = argparse.ArgumentParser(prog='flavorator', + description='batch flavor creator') +args = parser.parse_args() + +for file in [os.path.join(dp, f) for dp, dn, fn in + os.walk(os.path.expanduser(FLAVOR_DIR)) for f in fn]: + try: + f = read_jsonfile(file) + except ValueError: + continue + + print f + flavor_name = calculate_name(f) + fh, file_name = tempfile.mkstemp() + os.write(fh, json.dumps({"flavor": f})) + os.close(fh) + res, output = sh('', file_name) + os.unlink(file_name) diff --git a/orm/orm_client/flavorgen/make_flav.py b/orm/orm_client/flavorgen/make_flav.py new file mode 100644 index 00000000..f085fe3b --- /dev/null +++ b/orm/orm_client/flavorgen/make_flav.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +import json +import sys + +if sys.argv[1].isdigit(): + vcpus = sys.argv[1] +else: + vcpus = "1" +if sys.argv[1].isdigit(): + ram = sys.argv[1] +else: + ram = "1" +if sys.argv[1].isdigit(): + disk = sys.argv[1] +else: + disk = "1" + + +def calculate_name(flavor): + return "{0}.c{1}r{2}d{3}".format(flavor['series'], flavor['vcpus'], + flavor['ram'], flavor['disk']) + +data = { + "series": "gv", + "vcpus": "10", + "ram": "20", + "disk": "30", + "ephemeral": "0", + "swap": "0", + "visibility": "public" +} + +flavor_name = calculate_name(data) +series = flavor_name.split('.')[0] + +open(flavor_name, "w").write(json.dumps(data, indent=4) + '\n') diff --git a/orm/orm_client/flavorgen/make_flavor.py b/orm/orm_client/flavorgen/make_flavor.py new file mode 100644 index 00000000..b469cc40 --- /dev/null +++ b/orm/orm_client/flavorgen/make_flavor.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +import json +import re +import sys + + +data = { + "swap": "0", + "visibility": "public" +} + +flavor_name = sys.argv[1] +series, geometry = flavor_name.split('.') +try: + # Try with ephemeral + match = re.search('c(.+?)r(.+?)d(.+?)e(.*)', geometry) + vcpus = match.group(1) + ram = match.group(2) + disk = match.group(3) + ephemeral = match.group(4) +except AttributeError: + # Try without ephemeral. If this doesn't work, the input is invalid + match = re.search('c(.+?)r(.+?)d(.*)', geometry) + vcpus = match.group(1) + ram = match.group(2) + disk = match.group(3) + ephemeral = 0 + +# Fill the Flavor data +data['series'] = series +data['vcpus'] = vcpus +data['ram'] = str(int(ram) * 1024) +data['disk'] = disk +data['ephemeral'] = str(ephemeral) + +# Write the Flavor JSON to the file +open(flavor_name, "w").write(json.dumps(data, indent=4) + '\n') diff --git a/orm/orm_client/flavorgen/regionator.py b/orm/orm_client/flavorgen/regionator.py new file mode 100644 index 00000000..0c66e81e --- /dev/null +++ b/orm/orm_client/flavorgen/regionator.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python +import argparse +import ast +import json +import os +import re +import subprocess +import tempfile +import time + + +# Default flavor json directory +FLAVOR_DIR = './flavor_dir' +CLI_PATH = '../ormcli/orm' +FID = None +FILE_NAME = None +FLAVOR_NAME = None +REGION_NAME = None + + +def get_flavor_type(path): + # The last directory name is the flavor type (e.g., 'medium') + return path.split('/')[-2] + + +def get_region_list(regions): + global REGION_NAME + result = [] + for region in regions: + REGION_NAME = region + res, output = sh('get_region') + if not res: + result_region = ast.literal_eval(output) + result.append({'name': result_region['name'], + 'designType': result_region['designType']}) + else: + print 'Failed to get region %s, aborting...' % (region,) + exit(1) + + return result + + +def create_command(cli_command): + if cli_command == 'add_region': + cmd = 'python %s fms add_region %s %s' % (CLI_PATH, FID, FILE_NAME,) + elif cli_command == 'get_flavor': + cmd = '%s fms get_flavor test %s' % (CLI_PATH, FLAVOR_NAME,) + elif cli_command == 'get_region': + cmd = '%s rms get_region %s' % (CLI_PATH, REGION_NAME,) + else: + raise ValueError('Received an unknown command: %s' % (cli_command,)) + + if ';' in cmd or '&&' in cmd: + raise Exception("Violation of command injection, cmd is " + cmd) + return cmd + + +def read_jsonfile(file): + return json.loads(open(file).read()) + + +def calculate_name(flavor): + flavor_name = "{0}.c{1}r{2}d{3}".format(flavor['series'], flavor['vcpus'], + int(flavor['ram']) / 1024, + flavor['disk']) + if 'ephemeral' in flavor and int(flavor['ephemeral']) != 0: + flavor_name += 'e{0}'.format(flavor['ephemeral']) + + return flavor_name + + +def sh(cli_command): + # run a shell command, echoing output, painting error lines red, + # print runtime and status + # return status and output + + cmd = create_command(cli_command) + print '>> Starting: ' + cmd + start = time.time() + output = '' + errpat = re.compile('error', re.I) + p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE) + for line in iter(p.stdout.readline, b''): + out = line.rstrip() + print(">>> " + out) + output += out + end = time.time() + span = end - start + retcode = p.wait() + print '>> Ended: %s [%s, %d:%02d]' % (cmd, retcode, span / 60, span % 60) + return retcode, output + + +parser = argparse.ArgumentParser(prog='regionator', + description='batch add region to flavor') +parser.add_argument('regions', + type=str, + default='', + help='') +parser.add_argument('series', + type=str, + default='', + nargs='?', + help='') +args = parser.parse_args() + +regions = args.regions.split(',') +series_list = args.series.split(',') +if not regions: + print "Must specify at least one region" + exit(1) + +# Get all regions from RMS +region_list = get_region_list(regions) +any_update = False + +for file in [os.path.join(dp, f) for dp, dn, fn in + os.walk(os.path.expanduser(FLAVOR_DIR)) for f in fn]: + try: + f = read_jsonfile(file) + except ValueError: + continue + + updated = False + flavor_type = get_flavor_type(file) + if not series_list or series_list == [''] or f['series'] in series_list: + data = {'regions': []} + for region in region_list: + # Take only the regions whose design type matches the flavor's + if flavor_type.lower() == region['designType'].lower(): + data['regions'].append({'name': region['name']}) + updated = True + any_update = True + + if updated: + # Create the json file + fh, file_name = tempfile.mkstemp() + FILE_NAME = file_name + os.write(fh, json.dumps(data)) + os.close(fh) + + FLAVOR_NAME = calculate_name(f) + res, output = sh('get_flavor') + if not res: + flavor = ast.literal_eval(output) + FID = flavor['flavor']['id'] + print 'fid: ' + FID + res, output = sh('add_region') + + os.unlink(FILE_NAME) + +if not any_update: + if not args.series: + exp = 'design type of any of the regions:[{}]'.format(args.regions) + else: + exp = 'combination of regions:[{}] and series:[{}]'.format( + args.regions, args.series) + + print('No flavor was updated, please make sure that the {} matches any ' + 'flavor under the flavor directory'.format(exp)) diff --git a/orm/orm_client/imagegen/README b/orm/orm_client/imagegen/README new file mode 100644 index 00000000..944b75ce --- /dev/null +++ b/orm/orm_client/imagegen/README @@ -0,0 +1,41 @@ +imagegen Usage + +To install: + +1. Download attached file imagegen.tgz +2. Copy imagegen.tgz to :/opt/app/orm/ormcli +3. cd /opt/app/orm/ormcli +4. tar xvzf imagegen.tgz + +All the predefined imagess are defined in image_dir. Edit if necessary. + +To generate these images: + +5. cd /opt/app/orm/ormcli/imagegen +6. python imageator.py + +You should normally create the list of images once. + +Then when you want to add regions to all the images, use: + +7. python regionator.py , + +The argument is a comma-separated list of regions with no internal whitespace. + + +Use of -h will produce the following help: + +./regionator.py -h +usage: regionator [-h] [--image_dir IMAGE_DIR] [--host HOST] + [--cli_command CLI_COMMAND] [--regions REGIONS] + +batch add region to image + +optional arguments: + -h, --help show this help message and exit + --image_dir IMAGE_DIR + + --host HOST + --cli_command CLI_COMMAND + + --regions REGIONS diff --git a/orm/orm_client/imagegen/image_dir/test_image b/orm/orm_client/imagegen/image_dir/test_image new file mode 100644 index 00000000..5fd26e2a --- /dev/null +++ b/orm/orm_client/imagegen/image_dir/test_image @@ -0,0 +1,11 @@ +{ + "name": "test_image", + "url": "https://mirrors.it.att.com/images/image-name", + "visibility": "public", + "disk-format": "raw", + "container-format": "bare", + "min-disk": 0, + "owner": "ME", + "enabled": true, + "protected": false +} \ No newline at end of file diff --git a/orm/orm_client/imagegen/imageator.py b/orm/orm_client/imagegen/imageator.py new file mode 100644 index 00000000..b2477f19 --- /dev/null +++ b/orm/orm_client/imagegen/imageator.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python +from os.path import isfile, join +import argparse +import json +import os +import re +import subprocess +import tempfile +import time + +# from colorama import init, Fore, Back, Style + + +# Default flavor json directory +IMAGE_DIR = './image_dir' +CLI_PATH = '../ormcli/orm' + + +def read_jsonfile(file): + return json.loads(open(file).read()) + + +def sh(cmd): + # run a shell command, echoing output, painting error lines red, + # print runtime and status + # return status and output + + print '>> Starting: ' + cmd + start = time.time() + output = '' + errpat = re.compile('error', re.I) + p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + for line in iter(p.stdout.readline, b''): + out = line.rstrip() + print(">>> " + out) + output += out + end = time.time() + span = end - start + retcode = p.wait() + print '>> Ended: %s [%s, %d:%02d]' % (cmd, retcode, span / 60, span % 60) + return retcode, output + + +parser = argparse.ArgumentParser(prog='imageator', + description='batch image/region creator') +# parser.add_argument('--image_dir', +# type=str, +# default='./image_dir', +# help='') +# parser.add_argument('--host', +# type=str, +# help='') +# parser.add_argument('--cli_command', +# type=str, +# default='/opt/app/orm/ormcli/ormcli/orm', +# help='') +args = parser.parse_args() + +summary = [] + +for file in [f for f in os.listdir(IMAGE_DIR) if + isfile(join(IMAGE_DIR, f))]: + f = read_jsonfile(join(IMAGE_DIR, file)) + + print f + image_name = f['name'] + fh, file_name = tempfile.mkstemp() + os.write(fh, json.dumps({"image": f})) + os.close(fh) + # harg = '--orm-base-url %s' % args.host if args.host else '' + res, output = sh('%s ims create_image test %s' % ( + CLI_PATH, file_name)) + os.unlink(file_name) + + summary.append("File name: {}, Image name: {}, Create image status: {}\n". + format(file, + image_name, + 'Success' if res == 0 else 'Failed')) + +print "\nImage creation summary:" +print "-----------------------" +for s in summary: + print s diff --git a/orm/orm_client/imagegen/regionator.py b/orm/orm_client/imagegen/regionator.py new file mode 100644 index 00000000..0a77587b --- /dev/null +++ b/orm/orm_client/imagegen/regionator.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python +from os.path import isfile, join +import argparse +import ast +import json +import os +import re +import subprocess +import tempfile +import time + +# from colorama import init, Fore, Back, Style + +# Default flavor json directory +IMAGE_DIR = './image_dir' +CLI_PATH = '../ormcli/orm' + + +def read_jsonfile(file): + return json.loads(open(file).read()) + + +def sh(cmd): + # run a shell command, echoing output, painting error lines red, + # print runtime and status + # return status and output + + print '>> Starting: ' + cmd + start = time.time() + output = '' + errpat = re.compile('error', re.I) + p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE) + for line in iter(p.stdout.readline, b''): + out = line.rstrip() + print(">>> " + out) + output += out + end = time.time() + span = end - start + retcode = p.wait() + print '>> Ended: %s [%s, %d:%02d]' % (cmd, retcode, span / 60, span % 60) + return retcode, output + + +parser = argparse.ArgumentParser(prog='regionator', + description='batch add region to image') +# parser.add_argument('--image_dir', +# type=str, +# default='./image_dir', +# help='') +# parser.add_argument('--host', +# type=str, +# help='') +# parser.add_argument('--cli_command', +# type=str, +# default='/opt/app/orm/ormcli/ormcli/orm', +# help='') +parser.add_argument('regions', + type=str, + default='', + help='') +args = parser.parse_args() + +regions = args.regions.split(',') +if not regions: + print "Must specify at least one region" + exit(0) +data = {'regions': [{'name': r} for r in regions]} +fh, file_name = tempfile.mkstemp() +os.write(fh, json.dumps(data)) +os.close(fh) + +# Prepare images dict with pairs {image_name:image_id} +img_dict = {} +# harg = '--orm-base-url %s' % args.host if args.host else '' +res, output = sh( + '%s ims %s list_images test ' % (CLI_PATH, '')) +if not res: + images = ast.literal_eval(output) + for img in images['images']: + img_dict[img['name']] = img['id'] + print img_dict + + for file in [f for f in os.listdir(IMAGE_DIR) if + isfile(join(IMAGE_DIR, f))]: + f = read_jsonfile(join(IMAGE_DIR, file)) + + print f + image_name = f['name'] + if image_name in img_dict: + image_id = img_dict[image_name] + print 'image_id: ' + image_id + res, output = sh('%s ims add_regions test %s %s' % ( + CLI_PATH, image_id, file_name)) + else: + print 'image_name: {} does not exist. ignore.'.format(image_name) + +os.unlink(file_name) diff --git a/orm/orm_client/ormcli/README b/orm/orm_client/ormcli/README new file mode 100644 index 00000000..86921fee --- /dev/null +++ b/orm/orm_client/ormcli/README @@ -0,0 +1,69 @@ +How to install orm cli +====================== + +Ensure you have python and pip installed + +> tar xvzf ormcli.tgz +> cd ormcli +> pip install -r requirements.txt + + +How to run orm cli +================== + +cms, fms, and rms are all services that should be installed somewhere. +To access these services, you'll need their IP addresses and port numbers, +which should be supplied with the --host and --port arguments + +For general help +================ + +> orm -h +usage: orm [-h] ... + +ORM REST CLI + +positional arguments: + + rms Endpoint Discovery Service + cms Customer Management Service + fms Flavor Management Service + +optional arguments: + -h, --help show this help message and exit + +To get help on the cms subsystem +================================ + +> orm cms -h +usage: orm cms [-h] [--version] [--requester REQUESTER] + [--tracking_id TRACKING_ID] [--host HOST] [--port PORT] + [--timeout TIMEOUT] + auth_token auth_region client [-h] ... + +positional arguments: + auth_token keystone user authorization token + auth_region keystone region authorization id + client client (application_id) + [-h] + create_customer datafile + update_customer custid datafile + add_region custid datafile + delete_region custid regionid + add_user custid regionid datafile + delete_default_user + custid userid + delete_user_from_region + custid regionid userid + get_customer custid + +optional arguments: + -h, --help show this help message and exit + --version show program's version number and exit + --requester REQUESTER + requester (user_id) + --tracking_id TRACKING_ID + tracking id + --host HOST hostname or ip of CMS server + --port PORT port number of CMS server + --timeout TIMEOUT request timeout in ms (default: 10000) diff --git a/orm/orm_client/ormcli/__init__.py b/orm/orm_client/ormcli/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/orm/orm_client/ormcli/cli_common.py b/orm/orm_client/ormcli/cli_common.py new file mode 100644 index 00000000..132f64a6 --- /dev/null +++ b/orm/orm_client/ormcli/cli_common.py @@ -0,0 +1,53 @@ +import config +import json +import requests + +OK_CODE = 200 + +ORM_CLIENT_KWARGS = {'type': str, 'help': 'client name', 'default': None, + 'nargs': '?'} + + +class MissingArgumentError(Exception): + """Should be raised when an argument was found missing by CLI logic.""" + pass + + +def get_keystone_ep(rms_url, region_name): + """Get the Keystone EP from RMS. + + :param rms_url: RMS server URL + :param region_name: The region name + :return: Keystone EP (string), None if it was not found + """ + try: + response = requests.get('%s/v2/orm/regions?regionname=%s' % ( + rms_url, region_name, ), verify=config.verify) + except requests.exceptions.ConnectionError as e: + print('Could not connect to RMS, URL: {}'.format(rms_url)) + return None + + if response.status_code != OK_CODE: + print('RMS returned status: {}, content: {}'.format( + response.status_code, response.content)) + return None + + # RMS returned 200 + lcp = response.json() + try: + for endpoint in lcp['regions'][0]['endpoints']: + if endpoint['type'] == 'identity': + return endpoint['publicURL'] + except KeyError: + print('Response from RMS came in an unsupported format. ' + 'Make sure that you are using RMS 3.5') + return None + + # Keystone EP not found in the response + print('No identity endpoint was found in the response from RMS') + return None + + +def pretty_print_json(json_to_print): + """Print a json without the u' prefix.""" + print(json.dumps(json_to_print)) diff --git a/orm/orm_client/ormcli/cmscli.py b/orm/orm_client/ormcli/cmscli.py new file mode 100644 index 00000000..e0f96590 --- /dev/null +++ b/orm/orm_client/ormcli/cmscli.py @@ -0,0 +1,449 @@ +#!/usr/bin/python +import argparse +import cli_common +import config +import os +import requests + + +class ResponseError(Exception): + pass + + +class ConnectionError(Exception): + pass + + +def add_to_parser(service_sub): + parser = \ + service_sub.add_parser('cms', + help='Customer Management Service', + formatter_class=lambda prog: + argparse.HelpFormatter(prog, + max_help_position=30, + width=120)) + parser.add_argument('--version', action='version', version='%(prog)s 1.0') + parser.add_argument('--auth-region', type=str, + help='Region used for authentication', + default=get_environment_variable('auth-region')) + parser.add_argument('--tenant-name', type=str, + help='Keystone user tenant name', + default=get_environment_variable('tenant-name')) + parser.add_argument('--username', type=str, help='Keystone user name', + default=get_environment_variable('username')) + parser.add_argument('--password', type=str, help='Keystone user password', + default=get_environment_variable('password')) + parser.add_argument('--orm-base-url', type=str, help='ORM base URL', + default=get_environment_variable('orm-base-url')) + parser.add_argument('--tracking_id', type=str, + help='"X-AIC-ORM-Tracking-Id" header') + parser.add_argument('--port', type=int, help='port number of CMS server') + parser.add_argument('--timeout', type=int, + help='request timeout in seconds (default: 10)') + parser.add_argument('-v', '--verbose', help='show details', + action="store_true") + parser.add_argument('-f', '--faceless', + help='run without authentication', + default=False, + action="store_true") + subparsers = parser.add_subparsers(dest='subcmd', + metavar=' [-h] ') + + # customer + parser_create_customer = subparsers.add_parser('create_customer', + help='[<"X-AIC-ORM-Client" ' + 'header>] ') + parser_create_customer.add_argument('client', + **cli_common.ORM_CLIENT_KWARGS) + parser_create_customer.add_argument('datafile', + type=argparse.FileType('r'), + help='') + + parser_delete_customer = subparsers.add_parser('delete_customer', + help='[<"X-AIC-ORM-Client" ' + 'header>] ') + parser_delete_customer.add_argument('client', + **cli_common.ORM_CLIENT_KWARGS) + parser_delete_customer.add_argument('custid', type=str, + help='') + + parser_update_customer = subparsers.add_parser('update_customer', + help='[<"X-AIC-ORM-Client" ' + 'header>] ') + parser_update_customer.add_argument('client', + **cli_common.ORM_CLIENT_KWARGS) + parser_update_customer.add_argument('custid', type=str, + help='') + parser_update_customer.add_argument('datafile', + type=argparse.FileType('r'), + help='') + + # region + parser_add_region = subparsers.add_parser('add_region', + help='[<"X-AIC-ORM-Client" ' + 'header>] ' + '') + parser_add_region.add_argument('client', **cli_common.ORM_CLIENT_KWARGS) + parser_add_region.add_argument('custid', type=str, help='') + parser_add_region.add_argument('datafile', type=argparse.FileType('r'), + help='') + + parser_replace_region = subparsers.add_parser('replace_region', + help='[<"X-AIC-ORM-Client" ' + 'header>] ') + parser_replace_region.add_argument('client', + **cli_common.ORM_CLIENT_KWARGS) + parser_replace_region.add_argument('custid', type=str, + help='') + parser_replace_region.add_argument('datafile', type=argparse.FileType('r'), + help='') + + parser_delete_region = subparsers.add_parser('delete_region', + help='[<"X-AIC-ORM-Client" ' + 'header>] ' + '') + parser_delete_region.add_argument('client', **cli_common.ORM_CLIENT_KWARGS) + parser_delete_region.add_argument('custid', type=str, help='') + parser_delete_region.add_argument('regionid', type=str, help='') + + # add user + parser_add_user = subparsers.add_parser('add_user', + help='[<"X-AIC-ORM-Client" ' + 'header>] ' + ' ') + parser_add_user.add_argument('client', **cli_common.ORM_CLIENT_KWARGS) + parser_add_user.add_argument('custid', type=str, help='') + parser_add_user.add_argument('regionid', type=str, help='') + parser_add_user.add_argument('datafile', type=argparse.FileType('r'), + help='') + + # replace user + parser_replace_user = subparsers.add_parser('replace_user', + help='[<"X-AIC-ORM-Client" ' + 'header>] ' + ' ') + parser_replace_user.add_argument('client', **cli_common.ORM_CLIENT_KWARGS) + parser_replace_user.add_argument('custid', type=str, help='') + parser_replace_user.add_argument('regionid', type=str, help='') + parser_replace_user.add_argument('datafile', type=argparse.FileType('r'), + help='') + + # delete user + parser_delete_user = subparsers.add_parser( + 'delete_user', + help='[<"X-AIC-ORM-Client" header>] ' + ' ') + parser_delete_user.add_argument('client', **cli_common.ORM_CLIENT_KWARGS) + parser_delete_user.add_argument('custid', type=str, + help='') + parser_delete_user.add_argument('regionid', type=str, + help='') + parser_delete_user.add_argument('userid', type=str, + help='') + + # add default user + parser_add_default_user = \ + subparsers.add_parser('add_default_user', + help='[<"X-AIC-ORM-Client" header>] ' + ' ' + '') + parser_add_default_user.add_argument('client', + **cli_common.ORM_CLIENT_KWARGS) + parser_add_default_user.add_argument('custid', type=str, + help='') + parser_add_default_user.add_argument('datafile', + type=argparse.FileType('r'), + help='') + + # replace default user + parser_replace_default_user = \ + subparsers.add_parser('replace_default_user', + help='[<"X-AIC-ORM-Client" header>] ' + ' ' + '') + parser_replace_default_user.add_argument('client', + **cli_common.ORM_CLIENT_KWARGS) + parser_replace_default_user.add_argument('custid', type=str, + help='') + parser_replace_default_user.add_argument('datafile', + type=argparse.FileType('r'), + help='') + + # change enable + parser_enable_customer = subparsers.add_parser('enabled', + help='[<"X-AIC-ORM-Client" ' + 'header>] ' + ' ' + '') + parser_enable_customer.add_argument('client', + **cli_common.ORM_CLIENT_KWARGS) + parser_enable_customer.add_argument('custid', type=str, + help='') + parser_enable_customer.add_argument('datafile', + type=argparse.FileType('r'), + help='') + + # delete default user + parser_delete_default_user = \ + subparsers.add_parser('delete_default_user', + help='[<"X-AIC-ORM-Client" header>] ') + parser_delete_default_user.add_argument('client', + **cli_common.ORM_CLIENT_KWARGS) + parser_delete_default_user.add_argument('custid', type=str, + help='') + parser_delete_default_user.add_argument('userid', type=str, + help='') + + # add metadata + h1, h2, h3 = \ + '[<"X-AIC-ORM-Client" header>]', '', '' + parser_add_metadata = subparsers.add_parser('add_metadata', + help='%s %s %s' % (h1, h2, h3)) + parser_add_metadata.add_argument('client', **cli_common.ORM_CLIENT_KWARGS) + parser_add_metadata.add_argument('custid', type=str, + help=h2) + parser_add_metadata.add_argument('datafile', type=argparse.FileType('r'), + help=h3) + + # replace metadata + h1, h2, h3 = \ + '[<"X-AIC-ORM-Client" header>]', '', '' + parser_replace_metadata = subparsers.add_parser('replace_metadata', + help='%s %s %s' % ( + h1, h2, h3)) + parser_replace_metadata.add_argument('client', + **cli_common.ORM_CLIENT_KWARGS) + parser_replace_metadata.add_argument('custid', type=str, + help=h2) + parser_replace_metadata.add_argument('datafile', + type=argparse.FileType('r'), + help=h3) + + # get customer + parser_get_customer = subparsers.add_parser('get_customer', + help='[<"X-AIC-ORM-Client" ' + 'header>] ') + parser_get_customer.add_argument('client', **cli_common.ORM_CLIENT_KWARGS) + parser_get_customer.add_argument('custid', type=str, help='') + + # list customers + h1 = '[<"X-AIC-ORM-Client" header>]' + h2 = '[--region ] [--user ] [--metadata ]' \ + ' [starts_with ] [contains ]' + parser_list_customer = subparsers.add_parser('list_customers', + help='%s %s' % (h1, h2)) + parser_list_customer.add_argument('client', **cli_common.ORM_CLIENT_KWARGS) + parser_list_customer.add_argument('--region', type=str, help='region name') + parser_list_customer.add_argument('--user', type=str, help='user name') + parser_list_customer.add_argument('--starts_with', type=str, + help='customer name') + parser_list_customer.add_argument('--contains', type=str, + help='* contains in customer name') + parser_list_customer.add_argument('--metadata', action='append', nargs="+", + type=str, help='') + + return parser + + +def preparm(p): + return ('' if len(p) else '?') + ('&' if len(p) else '') + + +def cmd_details(args): + if args.subcmd == 'create_customer': + return requests.post, '' + elif args.subcmd == 'delete_customer': + return requests.delete, '/%s' % args.custid + elif args.subcmd == 'update_customer': + return requests.put, '/%s' % args.custid + elif args.subcmd == 'add_region': + return requests.post, '/%s/regions' % args.custid + elif args.subcmd == 'replace_region': + return requests.put, '/%s/regions' % args.custid + elif args.subcmd == 'delete_region': + return requests.delete, '/%s/regions/%s' % (args.custid, args.regionid) + elif args.subcmd == 'add_user': + return requests.post, '/%s/regions/%s/users' % ( + args.custid, args.regionid) + elif args.subcmd == 'replace_user': + return requests.put, '/%s/regions/%s/users' % ( + args.custid, args.regionid) + elif args.subcmd == 'delete_user': + return requests.delete, '/%s/regions/%s/users/%s' % ( + args.custid, args.regionid, args.userid) + elif args.subcmd == 'add_default_user': + return requests.post, '/%s/users' % args.custid + elif args.subcmd == 'replace_default_user': + return requests.put, '/%s/users' % args.custid + elif args.subcmd == 'delete_default_user': + return requests.delete, '/%s/users/%s' % (args.custid, args.userid) + elif args.subcmd == 'add_metadata': + return requests.post, '/%s/metadata' % args.custid + elif args.subcmd == 'replace_metadata': + return requests.put, '/%s/metadata' % args.custid + elif args.subcmd == 'get_customer': + return requests.get, '/%s' % args.custid + elif args.subcmd == 'enabled': + return requests.put, '/%s/enabled' % args.custid + elif args.subcmd == 'list_customers': + param = '' + if args.region: + param += '%sregion=%s' % (preparm(param), args.region) + if args.user: + param += '%suser=%s' % (preparm(param), args.user) + if args.starts_with: + param += '%sstarts_with=%s' % (preparm(param), args.starts_with) + if args.contains: + param += '%scontains=%s' % (preparm(param), args.contains) + if args.metadata: + for meta in args.metadata: + param += '%smetadata=%s' % (preparm(param), meta[0]) + return requests.get, '/%s' % param + + +def get_token(timeout, args, host): + headers = { + 'Content-Type': 'application/json', + } + url = '%s/v2.0/tokens' + data = ''' +{ +"auth": { + "tenantName": "%s", + "passwordCredentials": { + "username": "%s", + "password": "%s" + } + } +}''' + for argument in ('tenant_name', 'username', 'password', 'auth_region'): + argument_value = getattr(args, argument, None) + if argument_value is not None: + globals()[argument] = argument_value + else: + configuration_value = getattr(config, argument) + if configuration_value: + globals()[argument] = configuration_value + else: + message = ('ERROR: {} for token generation was not supplied. ' + 'Please use its command-line ' + 'argument or environment variable.'.format(argument)) + print message + raise cli_common.MissingArgumentError(message) + + keystone_ep = cli_common.get_keystone_ep('{}:8080'.format(host), + auth_region) + if keystone_ep is None: + raise ConnectionError( + 'Failed in get_token, host: {}, region: {}'.format(host, + auth_region)) + url = url % (keystone_ep,) + data = data % (tenant_name, username, password,) + + if args.verbose: + print( + "Getting token:\ntimeout: %d\nheaders: %s\nurl: %s\n" % ( + timeout, headers, url)) + try: + resp = requests.post(url, timeout=timeout, data=data, headers=headers) + if resp.status_code != 200: + raise ResponseError( + 'Failed to get token (Reason: {})'.format( + resp.status_code)) + return resp.json()['access']['token']['id'] + + except Exception as e: + print e.message + raise ConnectionError(e.message) + + +def get_environment_variable(argument): + # The rules are: all caps, underscores instead of dashes and prefixed + environment_variable = 'AIC_ORM_{}'.format( + argument.replace('-', '_').upper()) + + return os.environ.get(environment_variable) + + +def run(args): + host = args.orm_base_url if args.orm_base_url else config.orm_base_url + port = args.port if args.port else 7080 + data = args.datafile.read() if 'datafile' in args else '{}' + timeout = args.timeout if args.timeout else 10 + + rest_cmd, cmd_url = cmd_details(args) + url = '%s:%d/v1/orm/customers' % (host, port,) + cmd_url + if args.faceless: + auth_token = auth_region = requester = client = '' + else: + try: + auth_token = get_token(timeout, args, host) + except Exception: + exit(1) + auth_region = globals()['auth_region'] + requester = globals()['username'] + client = requester + + tracking_id = args.tracking_id if args.tracking_id else None + headers = { + 'content-type': 'application/json', + 'X-Auth-Token': auth_token, + 'X-Auth-Region': auth_region, + 'X-AIC-ORM-Requester': requester, + 'X-AIC-ORM-Client': client, + 'X-AIC-ORM-Tracking-Id': tracking_id + } + + if args.verbose: + print( + "Sending API:\ntimeout: %d\ndata: %s\nheaders: %s\ncmd: %s\nurl: " + "%s\n" % ( + timeout, data, headers, rest_cmd.__name__, url)) + + try: + resp = rest_cmd(url, timeout=timeout, data=data, headers=headers, + verify=config.verify) + except Exception as e: + print e + exit(1) + + if not 200 <= resp.status_code < 300: + content = resp.json() if resp.status_code == 500 else '' + print 'API error: %s %s (Reason: %d)\n%s' % ( + rest_cmd.func_name.upper(), url, resp.status_code, content) + exit(1) + + if resp.status_code == 204: # no content + exit(0) + + rj = resp.json() + if rj == 'Not found': + print 'No output was found' + else: + cli_common.pretty_print_json(rj) diff --git a/orm/orm_client/ormcli/config.py b/orm/orm_client/ormcli/config.py new file mode 100644 index 00000000..dbbb218f --- /dev/null +++ b/orm/orm_client/ormcli/config.py @@ -0,0 +1,8 @@ +# these values are used by ormcli to retrieve auth_token which is sent, +# along with region, with each cms and fms api request +tenant_name = '' +username = '' +password = False +auth_region = '' +orm_base_url = 'http://127.0.0.1' +verify = False diff --git a/orm/orm_client/ormcli/fmscli.py b/orm/orm_client/ormcli/fmscli.py new file mode 100644 index 00000000..065147e6 --- /dev/null +++ b/orm/orm_client/ormcli/fmscli.py @@ -0,0 +1,395 @@ +#!/usr/bin/python +import argparse +import cli_common +import config +import os +import requests + + +class ResponseError(Exception): + pass + + +class ConnectionError(Exception): + pass + + +def add_to_parser(service_sub): + parser = \ + service_sub.add_parser('fms', help='Flavor Management Service', + formatter_class=lambda prog: + argparse.HelpFormatter(prog, + max_help_position=30, + width=120)) + parser.add_argument('--version', action='version', version='%(prog)s 1.0') + parser.add_argument('--auth-region', type=str, + help='Region used for authentication', + default=get_environment_variable('auth-region')) + parser.add_argument('--orm-base-url', type=str, help='ORM base URL', + default=get_environment_variable('orm-base-url')) + parser.add_argument('--tracking_id', type=str, help='tracking id') + parser.add_argument('--tenant-name', type=str, + help='Keystone user tenant name', + default=get_environment_variable('tenant-name')) + parser.add_argument('--username', type=str, help='Keystone user name', + default=get_environment_variable('username')) + parser.add_argument('--password', type=str, help='Keystone user password', + default=get_environment_variable('password')) + parser.add_argument('--port', type=int, help='port number of FMS server') + parser.add_argument('--timeout', type=int, + help='request timeout in seconds (default: 10)') + parser.add_argument('-v', '--verbose', help='show details', + action="store_true") + parser.add_argument('-f', '--faceless', + help='run without authentication', + default=False, + action="store_true") + subparsers = parser.add_subparsers(dest='subcmd', + metavar=' [-h] ') + + # flavor + h1, h2 = ('[<"X-AIC-ORM-Client" header>]', + '') + parser_create_flavor = subparsers.add_parser('create_flavor', + help='%s %s' % (h1, h2)) + parser_create_flavor.add_argument('client', **cli_common.ORM_CLIENT_KWARGS) + parser_create_flavor.add_argument('datafile', type=argparse.FileType('r'), + help=h2) + + h1, h2, h3 = ('[<"X-AIC-ORM-Client" header>]', '', + '',) + parser_add_tags = subparsers.add_parser('add_tags', + help='%s %s %s' % (h1, h2, h3)) + parser_add_tags.add_argument('client', **cli_common.ORM_CLIENT_KWARGS) + parser_add_tags.add_argument('flavorid', type=str, help=h2) + parser_add_tags.add_argument('datafile', type=argparse.FileType('r'), + help=h3) + + h1, h2, h3 = ('[<"X-AIC-ORM-Client" header>]', '', + '',) + parser_replace_tags = subparsers.add_parser('replace_tags', + help='%s %s %s' % (h1, h2, h3)) + parser_replace_tags.add_argument('client', **cli_common.ORM_CLIENT_KWARGS) + parser_replace_tags.add_argument('flavorid', type=str, help=h2) + parser_replace_tags.add_argument('datafile', type=argparse.FileType('r'), + help=h3) + + h1, h2, h3 = '[<"X-AIC-ORM-Client" header>]', '', '' + parser_delete_tag = subparsers.add_parser('delete_tag', + help='%s %s %s' % (h1, h2, h3)) + parser_delete_tag.add_argument('client', **cli_common.ORM_CLIENT_KWARGS) + parser_delete_tag.add_argument('flavorid', type=str, help=h2) + parser_delete_tag.add_argument('tagname', type=str, help=h3) + + h1, h2 = '[<"X-AIC-ORM-Client" header>]', '' + parser_delete_all_tags = subparsers.add_parser('delete_all_tags', + help='%s %s' % (h1, h2)) + parser_delete_all_tags.add_argument('client', + **cli_common.ORM_CLIENT_KWARGS) + parser_delete_all_tags.add_argument('flavorid', type=str, help=h2) + + h1, h2 = '[<"X-AIC-ORM-Client" header>]', '' + parser_get_tags = subparsers.add_parser('get_tags', + help='%s %s' % (h1, h2)) + parser_get_tags.add_argument('client', **cli_common.ORM_CLIENT_KWARGS) + parser_get_tags.add_argument('flavorid', type=str, help=h2) + + # extra specs + h1, h2 = '[<"X-AIC-ORM-Client" header>]', '' + parser_get_extra_specs = subparsers.add_parser('get_extra_specs', + help='%s %s' % (h1, h2)) + parser_get_extra_specs.add_argument('client', + **cli_common.ORM_CLIENT_KWARGS) + parser_get_extra_specs.add_argument('flavorid', type=str, help=h2) + + h1, h2, h3 = ('[<"X-AIC-ORM-Client" header>]', '', + '',) + parser_replace_extra_specs = subparsers.add_parser('replace_extra_specs', + help='%s %s %s' % (h1, + h2, + h3)) + parser_replace_extra_specs.add_argument('client', + **cli_common.ORM_CLIENT_KWARGS) + parser_replace_extra_specs.add_argument('flavorid', type=str, help=h2) + parser_replace_extra_specs.add_argument('datafile', + type=argparse.FileType('r'), + help=h3) + + h1, h2 = '[<"X-AIC-ORM-Client" header>]', '' + parser_delete_all_extra_specs = subparsers.add_parser( + 'delete_all_extra_specs', help='%s %s' % (h1, h2)) + parser_delete_all_extra_specs.add_argument('client', + **cli_common.ORM_CLIENT_KWARGS) + parser_delete_all_extra_specs.add_argument('flavorid', type=str, help=h2) + + h1, h2, h3 = ('[<"X-AIC-ORM-Client" header>]', '', + '',) + parser_delete_extra_spec = subparsers.add_parser('delete_extra_spec', + help='%s%s%s' % ( + h1, h2, h3)) + parser_delete_extra_spec.add_argument('client', + **cli_common.ORM_CLIENT_KWARGS) + parser_delete_extra_spec.add_argument('flavorid', type=str, help=h2) + parser_delete_extra_spec.add_argument('eskeyname', type=str, help=h3) + + h1, h2, h3 = ('[<"X-AIC-ORM-Client" header>]', '', + '',) + parser_add_extra_specs = subparsers.add_parser('add_extra_specs', + help='%s%s%s' % ( + h1, h2, h3)) + parser_add_extra_specs.add_argument('client', + **cli_common.ORM_CLIENT_KWARGS) + parser_add_extra_specs.add_argument('flavorid', type=str, help=h2) + parser_add_extra_specs.add_argument('datafile', + type=argparse.FileType('r'), + help=h3) + + h1, h2 = '[<"X-AIC-ORM-Client" header>]', '' + parser_delete_flavor = subparsers.add_parser('delete_flavor', + help='%s %s' % (h1, h2)) + parser_delete_flavor.add_argument('client', **cli_common.ORM_CLIENT_KWARGS) + parser_delete_flavor.add_argument('flavorid', type=str, help=h2) + + h1, h2 = '[<"X-AIC-ORM-Client" header>]', '' + parser_get_flavor = subparsers.add_parser('get_flavor', + help='%s %s' % (h1, h2)) + parser_get_flavor.add_argument('client', **cli_common.ORM_CLIENT_KWARGS) + parser_get_flavor.add_argument('flavorid', type=str, help=h2) + + h1, h2 = ('[<"X-AIC-ORM-Client" header>]', + '[--visibility ] [--region ] [--tenant ' + '] [--series {gv,nv,ns,nd,ss}] [--alias ]') + parser_list_flavor = subparsers.add_parser('list_flavors', + help='%s %s' % (h1, h2)) + parser_list_flavor.add_argument('client', **cli_common.ORM_CLIENT_KWARGS) + parser_list_flavor.add_argument('--visibility', type=str, + choices=['public', 'private']) + parser_list_flavor.add_argument('--region', type=str, help='region name') + parser_list_flavor.add_argument('--tenant', type=str, help='tenant id') + parser_list_flavor.add_argument('--starts_with', type=str, + help='flavor name starts with *') + parser_list_flavor.add_argument('--contains', type=str, + help='* contains in flavor name') + parser_list_flavor.add_argument('--series', type=str, + choices=['gv', 'nv', 'ns', 'nd', 'ss']) + parser_list_flavor.add_argument('--alias', type=str, help='flavor alias') + + # region for flavor + h1, h2, h3 = ('[<"X-AIC-ORM-Client" header>]', '', + '',) + parser_add_region = subparsers.add_parser('add_region', + help='%s %s %s' % (h1, h2, h3)) + parser_add_region.add_argument('client', **cli_common.ORM_CLIENT_KWARGS) + parser_add_region.add_argument('flavorid', type=str, help=h2) + parser_add_region.add_argument('datafile', type=argparse.FileType('r'), + help=h3) + + h1, h2, h3 = '[<"X-AIC-ORM-Client" header>]', '', '' + parser_delete_region = subparsers.add_parser('delete_region', + help='%s %s %s' % ( + h1, h2, h3)) + parser_delete_region.add_argument('client', **cli_common.ORM_CLIENT_KWARGS) + parser_delete_region.add_argument('flavorid', type=str, help=h2) + parser_delete_region.add_argument('regionid', type=str, help=h3) + + # tenant for flavor + h1, h2, h3 = ('[<"X-AIC-ORM-Client" header>]', '', + '',) + parser_add_tenant = subparsers.add_parser('add_tenant', + help='%s %s %s' % (h1, h2, h3)) + parser_add_tenant.add_argument('client', **cli_common.ORM_CLIENT_KWARGS) + parser_add_tenant.add_argument('flavorid', type=str, help=h2) + parser_add_tenant.add_argument('datafile', type=argparse.FileType('r'), + help=h3) + + h1, h2, h3 = '[<"X-AIC-ORM-Client" header>]', '', '' + parser_delete_tenant = subparsers.add_parser('delete_tenant', + help='%s %s %s' % ( + h1, h2, h3)) + parser_delete_tenant.add_argument('client', **cli_common.ORM_CLIENT_KWARGS) + parser_delete_tenant.add_argument('flavorid', type=str, help=h2) + parser_delete_tenant.add_argument('tenantid', type=str, help=h3) + + +def preparm(p): + return ('' if len(p) else '?') + ('&' if len(p) else '') + + +def cmd_details(args): + if args.subcmd == 'create_flavor': + return requests.post, '' + elif args.subcmd == 'update_flavor': + return requests.put, '/%s' % args.flavorid + elif args.subcmd == 'delete_flavor': + return requests.delete, '/%s' % args.flavorid + elif args.subcmd == 'add_region': + return requests.post, '/%s/regions' % args.flavorid + elif args.subcmd == 'add_tags': + return requests.post, '/%s/tags' % args.flavorid + elif args.subcmd == 'replace_tags': + return requests.put, '/%s/tags' % args.flavorid + elif args.subcmd == 'delete_tag': + return requests.delete, '/%s/tags/%s' % (args.flavorid, args.tagname) + elif args.subcmd == 'delete_all_tags': + return requests.delete, '/%s/tags' % args.flavorid + elif args.subcmd == 'get_tags': + return requests.get, '/%s/tags' % args.flavorid + elif args.subcmd == 'delete_region': + return requests.delete, '/%s/regions/%s' % ( + args.flavorid, args.regionid) + elif args.subcmd == 'add_tenant': + return requests.post, '/%s/tenants' % args.flavorid + elif args.subcmd == 'delete_tenant': + return requests.delete, '/%s/tenants/%s' % ( + args.flavorid, args.tenantid) + elif args.subcmd == 'get_flavor': + return requests.get, '/%s' % args.flavorid + elif args.subcmd == 'get_extra_specs': + return requests.get, '/%s/os_extra_specs' % args.flavorid + elif args.subcmd == 'delete_all_extra_specs': + return requests.delete, '/%s/os_extra_specs' % args.flavorid + elif args.subcmd == 'delete_extra_spec': + return requests.delete, '/%s/os_extra_specs/%s' % ( + args.flavorid, args.eskeyname) + elif args.subcmd == 'add_extra_specs': + return requests.post, '/%s/os_extra_specs' % args.flavorid + elif args.subcmd == 'replace_extra_specs': + return requests.put, '/%s/os_extra_specs' % args.flavorid + elif args.subcmd == 'list_flavors': + param = '' + if args.visibility: + param += '%svisibility=%s' % (preparm(param), args.visibility) + if args.region: + param += '%sregion=%s' % (preparm(param), args.region) + if args.tenant: + param += '%stenant=%s' % (preparm(param), args.tenant) + if args.series: + param += '%sseries=%s' % (preparm(param), args.series) + if args.starts_with: + param += '%sstarts_with=%s' % (preparm(param), args.starts_with) + if args.contains: + param += '%scontains=%s' % (preparm(param), args.contains) + if args.alias: + param += '%salias=%s' % (preparm(param), args.alias) + return requests.get, '/%s' % param + + +def get_token(timeout, args, host): + headers = { + 'Content-Type': 'application/json', + } + url = '%s/v2.0/tokens' + data = ''' +{ +"auth": { + "tenantName": "%s", + "passwordCredentials": { + "username": "%s", + "password": "%s" + } + } +}''' + for argument in ('tenant_name', 'username', 'password', 'auth_region'): + argument_value = getattr(args, argument, None) + if argument_value is not None: + globals()[argument] = argument_value + else: + configuration_value = getattr(config, argument) + if configuration_value: + globals()[argument] = configuration_value + else: + message = ('ERROR: {} for token generation was not supplied. ' + 'Please use its command-line ' + 'argument or environment variable.'.format(argument)) + print message + raise cli_common.MissingArgumentError(message) + + keystone_ep = cli_common.get_keystone_ep('{}:8080'.format(host), + auth_region) + if keystone_ep is None: + raise ConnectionError( + 'Failed in get_token, host: {}, region: {}'.format(host, + auth_region)) + url = url % (keystone_ep,) + data = data % (tenant_name, username, password,) + + if args.verbose: + print( + "Getting token:\ntimeout: %d\nheaders: %s\nurl: %s\n" % ( + timeout, headers, url)) + try: + resp = requests.post(url, timeout=timeout, data=data, headers=headers) + if resp.status_code != 200: + raise ResponseError( + 'Failed to get token (Reason: {})'.format( + resp.status_code)) + return resp.json()['access']['token']['id'] + + except Exception as e: + print e.message + raise ConnectionError(e.message) + + +def get_environment_variable(argument): + # The rules are: all caps, underscores instead of dashes and prefixed + environment_variable = 'AIC_ORM_{}'.format( + argument.replace('-', '_').upper()) + + return os.environ.get(environment_variable) + + +def run(args): + host = args.orm_base_url if args.orm_base_url else config.orm_base_url + port = args.port if args.port else 8082 + data = args.datafile.read() if 'datafile' in args else '{}' + timeout = args.timeout if args.timeout else 10 + + rest_cmd, cmd_url = cmd_details(args) + url = '%s:%d/v1/orm/flavors' % (host, port,) + cmd_url + if args.faceless: + auth_token = auth_region = requester = client = '' + else: + try: + auth_token = get_token(timeout, args, host) + except Exception: + exit(1) + auth_region = globals()['auth_region'] + requester = globals()['username'] + client = requester + + tracking_id = args.tracking_id if args.tracking_id else None + headers = { + 'content-type': 'application/json', + 'X-Auth-Token': auth_token, + 'X-Auth-Region': auth_region, + 'X-AIC-ORM-Requester': requester, + 'X-AIC-ORM-Client': client, + 'X-AIC-ORM-Tracking-Id': tracking_id + } + + if args.verbose: + print( + "Sending API:\ntimeout: %d\ndata: %s\nheaders: %s\ncmd: %s\nurl:" + " %s\n" % ( + timeout, data, headers, rest_cmd.__name__, url)) + try: + resp = rest_cmd(url, timeout=timeout, data=data, headers=headers, + verify=config.verify) + except Exception as e: + print e + exit(1) + + if not 200 <= resp.status_code < 300: + content = resp.content + print 'API error: %s %s (Reason: %d)\n%s' % ( + rest_cmd.func_name.upper(), url, resp.status_code, content) + exit(1) + + if resp.status_code == 204: # no content + exit(0) + + rj = resp.json() + if rj == 'Not found': + print 'No output was found' + else: + cli_common.pretty_print_json(rj) diff --git a/orm/orm_client/ormcli/imscli.py b/orm/orm_client/ormcli/imscli.py new file mode 100644 index 00000000..81de9c2c --- /dev/null +++ b/orm/orm_client/ormcli/imscli.py @@ -0,0 +1,366 @@ +#!/usr/bin/python +import argparse +import cli_common +import config +import os +import requests + + +class ResponseError(Exception): + pass + + +class ConnectionError(Exception): + pass + + +def add_to_parser(service_sub): + parser = service_sub.add_parser('ims', + help='Image Management Service', + formatter_class=lambda prog: argparse. + HelpFormatter(prog, + max_help_position=30, + width=120)) + parser.add_argument('--version', action='version', version='%(prog)s 1.0') + parser.add_argument('--auth-region', type=str, + help='Region used for authentication', + default=get_environment_variable('auth-region')) + parser.add_argument('--tenant-name', type=str, + help='Keystone user tenant name', + default=get_environment_variable('tenant-name')) + parser.add_argument('--username', type=str, help='Keystone user name', + default=get_environment_variable('username')) + parser.add_argument('--password', type=str, help='Keystone user password', + default=get_environment_variable('password')) + parser.add_argument('--orm-base-url', type=str, help='ORM base URL', + default=get_environment_variable('orm-base-url')) + parser.add_argument('--tracking_id', type=str, help='tracking id') + parser.add_argument('--port', type=int, help='port number of IMS server') + parser.add_argument('--timeout', type=int, + help='request timeout in seconds (default: 10)') + parser.add_argument('-v', '--verbose', help='show details', + action="store_true") + parser.add_argument('-f', '--faceless', + help='run without authentication', + default=False, + action="store_true") + subparsers = parser.add_subparsers(dest='subcmd', + metavar=' [-h] ') + + # image + h1, h2 = '[<"X-AIC-ORM-Client" header>]', '' + parser_create_image = subparsers.add_parser('create_image', + help='%s %s' % (h1, h2)) + parser_create_image.add_argument('client', **cli_common.ORM_CLIENT_KWARGS) + parser_create_image.add_argument('datafile', type=argparse.FileType('r'), + help=h2) + + h1, h2, h3 = '[<"X-AIC-ORM-Client" header>]', '', \ + '' + parser_update_image = subparsers.add_parser('update_image', + help='%s %s %s' % (h1, + h2, + h3)) + parser_update_image.add_argument('client', **cli_common.ORM_CLIENT_KWARGS) + parser_update_image.add_argument('imageid', type=str, help=h2) + parser_update_image.add_argument('datafile', type=argparse.FileType('r'), + help=h3) + + h1, h2 = '[<"X-AIC-ORM-Client" header>]', '' + parser_delete_image = subparsers.add_parser('delete_image', + help='%s %s' % (h1, h2)) + parser_delete_image.add_argument('client', **cli_common.ORM_CLIENT_KWARGS) + parser_delete_image.add_argument('imageid', type=str, help=h2) + + # get images + h1, h2 = '[<"X-AIC-ORM-Client" header>]', '' + parser_get_image = subparsers.add_parser('get_image', + help='%s %s' % (h1, h2)) + parser_get_image.add_argument('client', **cli_common.ORM_CLIENT_KWARGS) + parser_get_image.add_argument('imageid', type=str, help=h2) + + h1, h2 = '[<"X-AIC-ORM-Client" header>]', \ + '[--visibility ] ' \ + '[--region ] [--customer ]' + parser_list_images = subparsers.add_parser('list_images', + help='%s %s' % (h1, h2)) + parser_list_images.add_argument('client', **cli_common.ORM_CLIENT_KWARGS) + parser_list_images.add_argument('--visibility', type=str, + choices=['public', 'private']) + parser_list_images.add_argument('--region', type=str, help='region name') + parser_list_images.add_argument('--customer', type=str, help='customer id') + + # activate/deactivate image + h1, h2 = '[<"X-AIC-ORM-Client" header>]', '' + parser_enable = subparsers.add_parser('enable', + help='%s %s' % (h1, h2)) + parser_enable.add_argument('client', **cli_common.ORM_CLIENT_KWARGS) + parser_enable.add_argument('imageid', type=str, help=h2) + + parser_disable = subparsers.add_parser('disable', + help='%s %s' % (h1, h2)) + + parser_disable.add_argument('client', **cli_common.ORM_CLIENT_KWARGS) + parser_disable.add_argument('imageid', type=str, help=h2) + + # region for image + h1, h2, h3 = '[<"X-AIC-ORM-Client" header>]', '', \ + '' + parser_add_regions = subparsers.add_parser('add_regions', + help='%s %s %s' % (h1, h2, h3)) + parser_add_regions.add_argument('client', **cli_common.ORM_CLIENT_KWARGS) + parser_add_regions.add_argument('imageid', type=str, help=h2) + parser_add_regions.add_argument('datafile', + type=argparse.FileType('r'), + help=h3) + + h1, h2, h3 = '[<"X-AIC-ORM-Client" header>]', '', \ + '' + parser_update_regions = subparsers.add_parser('update_regions', + help='%s %s %s' % (h1, + h2, + h3)) + parser_update_regions.add_argument('client', + **cli_common.ORM_CLIENT_KWARGS) + parser_update_regions.add_argument('imageid', type=str, help=h2) + parser_update_regions.add_argument('datafile', + type=argparse.FileType('r'), + help=h3) + + h1, h2, h3 = '[<"X-AIC-ORM-Client" header>]', '', '' + parser_delete_region = subparsers.add_parser('delete_region', + help='%s %s %s' % (h1, + h2, + h3)) + parser_delete_region.add_argument('client', **cli_common.ORM_CLIENT_KWARGS) + parser_delete_region.add_argument('imageid', type=str, help=h2) + parser_delete_region.add_argument('regionid', type=str, help=h3) + + # customer for image + h1, h2, h3 = '[<"X-AIC-ORM-Client" header>]', '', \ + '' + parser_add_customers = subparsers.add_parser('add_customers', + help='%s %s %s' % (h1, + h2, + h3)) + parser_add_customers.add_argument('client', **cli_common.ORM_CLIENT_KWARGS) + parser_add_customers.add_argument('imageid', type=str, help=h2) + parser_add_customers.add_argument('datafile', + type=argparse.FileType('r'), + help=h3) + + h1, h2, h3 = '[<"X-AIC-ORM-Client" header>]', '', \ + '' + parser_update_customer = subparsers.add_parser('update_customers', + help='%s %s %s' % (h1, + h2, + h3)) + parser_update_customer.add_argument('client', + **cli_common.ORM_CLIENT_KWARGS) + parser_update_customer.add_argument('imageid', type=str, help=h2) + parser_update_customer.add_argument('datafile', + type=argparse.FileType('r'), + help=h3) + + h1, h2, h3 = '[<"X-AIC-ORM-Client" header>]', '', \ + '' + parser_delete_customer = subparsers.add_parser('delete_customer', + help='%s %s %s' % (h1, + h2, + h3)) + parser_delete_customer.add_argument('client', + **cli_common.ORM_CLIENT_KWARGS) + parser_delete_customer.add_argument('imageid', type=str, help=h2) + parser_delete_customer.add_argument('customerid', type=str, help=h3) + + +def get_token(timeout, args, host): + headers = { + 'Content-Type': 'application/json', + } + url = '%s/v2.0/tokens' + data = ''' +{ +"auth": { + "tenantName": "%s", + "passwordCredentials": { + "username": "%s", + "password": "%s" + } + } +}''' + for argument in ('tenant_name', 'username', 'password', 'auth_region'): + argument_value = getattr(args, argument, None) + if argument_value is not None: + globals()[argument] = argument_value + else: + configuration_value = getattr(config, argument) + if configuration_value: + globals()[argument] = configuration_value + else: + message = ('ERROR: {} for token generation was not supplied. ' + 'Please use its command-line ' + 'argument or environment variable.'.format(argument)) + print message + raise cli_common.MissingArgumentError(message) + + keystone_ep = cli_common.get_keystone_ep('{}:8080'.format(host), + auth_region) + if keystone_ep is None: + raise ConnectionError( + 'Failed in get_token, host: {}, region: {}'.format(host, + auth_region)) + url = url % (keystone_ep,) + data = data % (tenant_name, username, password,) + + if args.verbose: + print( + "Getting token:\ntimeout: %d\nheaders: %s\nurl: %s\n" % ( + timeout, headers, url)) + try: + resp = requests.post(url, timeout=timeout, data=data, headers=headers) + if resp.status_code != 200: + raise ResponseError( + 'Failed to get token (Reason: {})'.format( + resp.status_code)) + return resp.json()['access']['token']['id'] + + except Exception as e: + print e.message + raise ConnectionError(e.message) + + +def preparm(p): + return ('' if len(p) else '?') + ('&' if len(p) else '') + + +def cmd_details(args): + data = args.datafile.read() if 'datafile' in args else '{}' + if args.subcmd == 'create_image': + cmd, url = requests.post, '' + elif args.subcmd == 'update_image': + cmd, url = requests.put, '/%s' % args.imageid + elif args.subcmd == 'delete_image': + cmd, url = requests.delete, '/%s' % args.imageid + + # activate/deactivate image + elif args.subcmd in ('enable', 'disable'): + cmd, url = requests.put, '/%s/enabled' % args.imageid + data = '{"enabled": %s}' % ('true' if args.subcmd == 'enable' else + 'false') + # image regions + elif args.subcmd == 'add_regions': + cmd, url = requests.post, '/%s/regions' % args.imageid + elif args.subcmd == 'update_regions': + cmd, url = requests.put, '/%s/regions' % args.imageid + elif args.subcmd == 'delete_region': + cmd, url = requests.delete, '/%s/regions/%s' % (args.imageid, + args.regionid) + + # image customers + elif args.subcmd == 'add_customers': + cmd, url = requests.post, '/%s/customers' % args.imageid + elif args.subcmd == 'update_customers': + cmd, url = requests.put, '/%s/customers' % args.imageid + elif args.subcmd == 'delete_customer': + cmd, url = requests.delete, '/%s/customers/%s' % (args.imageid, + args.customerid) + + # list images + elif args.subcmd == 'get_image': + cmd, url = requests.get, '/%s' % args.imageid + elif args.subcmd == 'list_images': + param = '' + if args.visibility: + param += '%svisibility=%s' % (preparm(param), + args.visibility) + if args.region: + param += '%sregion=%s' % (preparm(param), + args.region) + if args.customer: + param += '%scustomer=%s' % (preparm(param), + args.customer) + cmd, url = requests.get, '/%s' % param + + return cmd, url, data + + +def cmd_data(args): + # This is a special case where api has a payload needed but the CLI is + # seperated into 2 different commands. In this case we need to set the + # payload. + if args.subcmd == 'enable': + return "{\n \"enabled\": true\n}" + elif args.subcmd == 'disable': + return "{\n \"enabled\": false\n}" + else: + return args.datafile.read() if 'datafile' in args else '{}' + + +def get_environment_variable(argument): + # The rules are: all caps, underscores instead of dashes and prefixed + environment_variable = 'AIC_ORM_{}'.format( + argument.replace('-', '_').upper()) + + return os.environ.get(environment_variable) + + +def run(args): + host = args.orm_base_url if args.orm_base_url else config.orm_base_url + port = args.port if args.port else 8084 + timeout = args.timeout if args.timeout else 10 + + rest_cmd, cmd_url, data = cmd_details(args) + url = '%s:%d/v1/orm/images' % (host, port,) + cmd_url + if args.faceless: + auth_token = auth_region = requester = client = '' + else: + try: + auth_token = get_token(timeout, args, host) + except Exception: + exit(1) + auth_region = globals()['auth_region'] + requester = globals()['username'] + client = requester + + tracking_id = args.tracking_id if args.tracking_id else None + headers = { + 'content-type': 'application/json', + 'X-Auth-Token': auth_token, + 'X-Auth-Region': auth_region, + 'X-AIC-ORM-Requester': requester, + 'X-AIC-ORM-Client': client, + 'X-AIC-ORM-Tracking-Id': tracking_id + } + + if args.verbose: + print("Sending API:\ntimeout: %d\ndata: %s\n" + "headers: %s\ncmd: %s\nurl: %s\n" % (timeout, + data, + headers, + rest_cmd.__name__, + url)) + try: + resp = rest_cmd(url, timeout=timeout, data=data, headers=headers, + verify=config.verify) + except Exception as e: + print e + exit(1) + + if not 200 <= resp.status_code < 300: + content = resp.content + print 'API error: %s %s (Reason: %d)\n%s' % ( + rest_cmd.func_name.upper(), + url, + resp.status_code, + content) + exit(1) + + if resp.status_code == 204: # no content + exit(0) + + rj = resp.json() + if rj == 'Not found': + print 'No output was found' + else: + cli_common.pretty_print_json(rj) diff --git a/orm/orm_client/ormcli/orm b/orm/orm_client/ormcli/orm new file mode 100644 index 00000000..67087452 --- /dev/null +++ b/orm/orm_client/ormcli/orm @@ -0,0 +1,7 @@ +#!/usr/bin/env python + +import sys +import ormcli + +if __name__ == "__main__": + ormcli.main(sys.argv) diff --git a/orm/orm_client/ormcli/ormcli.py b/orm/orm_client/ormcli/ormcli.py new file mode 100644 index 00000000..29bffad0 --- /dev/null +++ b/orm/orm_client/ormcli/ormcli.py @@ -0,0 +1,37 @@ +#!/usr/bin/python +import argparse +import cmscli +import fmscli +import imscli +import rmscli +import sys + + +class Cli: + def create_parser(self): + self.parser = argparse.ArgumentParser(prog='orm', + description='ORM REST CLI') + service_sub = self.parser.add_subparsers(dest='service', + metavar='') + self.submod = {'cms': cmscli, 'fms': fmscli, 'ims': imscli, + 'rms': rmscli} + for s in self.submod.values(): + s.add_to_parser(service_sub) + + def parse(self, argv=sys.argv): + sys.argv = argv + self.args = self.parser.parse_args() + + def logic(self): + self.submod[self.args.service].run(self.args) + + +def main(argv): + cli = Cli() + cli.create_parser() + cli.parse(argv) + cli.logic() + + +if __name__ == "__main__": + main(sys.argv) diff --git a/orm/orm_client/ormcli/requirements.txt b/orm/orm_client/ormcli/requirements.txt new file mode 100644 index 00000000..c4c35538 --- /dev/null +++ b/orm/orm_client/ormcli/requirements.txt @@ -0,0 +1,2 @@ +requests +argparse diff --git a/orm/orm_client/ormcli/rmscli.py b/orm/orm_client/ormcli/rmscli.py new file mode 100644 index 00000000..3ac08e29 --- /dev/null +++ b/orm/orm_client/ormcli/rmscli.py @@ -0,0 +1,385 @@ +#!/usr/bin/python +import argparse +import cli_common +import config +import os +import requests + + +class ResponseError(Exception): + pass + + +class ConnectionError(Exception): + pass + + +def add_to_parser(service_sub): + parser = service_sub.add_parser('rms', help='Resource Management Service', + formatter_class=lambda prog: argparse. + HelpFormatter(prog, + max_help_position=30, + width=120)) + parser.add_argument('--version', action='version', version='%(prog)s 1.0') + parser.add_argument('--auth-region', type=str, + help='Region used for authentication', + default=get_environment_variable('auth-region')) + parser.add_argument('--tenant-name', type=str, + help='Keystone user tenant name', + default=get_environment_variable('tenant-name')) + parser.add_argument('--username', type=str, help='Keystone user name', + default=get_environment_variable('username')) + parser.add_argument('--password', type=str, help='Keystone user password', + default=get_environment_variable('password')) + parser.add_argument('--orm-base-url', type=str, help='ORM base URL', + default=get_environment_variable('orm-base-url')) + parser.add_argument('--tracking_id', type=str, help='tracking id') + parser.add_argument('--port', type=int, help='port number of IMS server') + parser.add_argument('--timeout', type=int, + help='request timeout in seconds (default: 10)') + parser.add_argument('-v', '--verbose', help='show details', + action="store_true") + parser.add_argument('-f', '--faceless', + help='run without authentication', + default=False, + action="store_true") + subparsers = parser.add_subparsers(dest='subcmd', + metavar=' [-h] ') + + # get group + h1 = '' + parser_get_group = subparsers.add_parser('get_group', help=h1) + parser_get_group.add_argument('group_id', help=h1) + + # get all groups + parser_list_groups = subparsers.add_parser('list_groups', help="") + + # create group + h1 = '' + parser_create_group = subparsers.add_parser('create_group', help=h1) + parser_create_group.add_argument('datafile', type=argparse.FileType('r'), + help=h1) + + # update group + h1, h2 = '', '' + parser_update_group = subparsers.add_parser('update_group', + help="%s %s" % (h2, h1)) + parser_update_group.add_argument('group_id', help=h2) + parser_update_group.add_argument('datafile', type=argparse.FileType('r'), + help=h1) + + # delete group + h1 = '' + parser_delete_group = subparsers.add_parser('delete_group', + help='%s' % (h1)) + parser_delete_group.add_argument('group_id', type=str, help=h1) + + # get region + h1, h2 = '', '[--use_version ]' + parser_get_region = subparsers.add_parser('get_region', + help='%s %s' % (h1, h2)) + parser_get_region.add_argument('--use_version', type=int, + help='') + parser_get_region.add_argument('region_name_or_id', type=str, help=h1) + + # update region + h1, h2 = '', '' + parser_update_region = subparsers.add_parser('update_region', + help='%s %s' % (h1, h2)) + parser_update_region.add_argument('region_id', type=str, help=h1) + parser_update_region.add_argument('datafile', type=argparse.FileType('r'), + help=h2) + + # create region + h1 = '' + parser_create_region = subparsers.add_parser('create_region', + help='%s' % (h1)) + parser_create_region.add_argument('datafile', type=argparse.FileType('r'), + help=h2) + + # delete region + h1 = '' + parser_delete_region = subparsers.add_parser('delete_region', + help='%s' % (h1)) + parser_delete_region.add_argument('region_id', type=str, help=h1) + + # list regions + parser_list_region = subparsers.add_parser('list_regions', + help='\ +[--use_version ] [--type ] [--status ]\ +[--metadata ] [--aicversion ][--clli ]\ +[--regionname ] [--osversion ] [--valet ]\ +[--state ] [--country ] [--city ] [--street ]\ +[--zip ] [--vlcp_name ]') + parser_list_region.add_argument('--use_version', type=int, + help='') + parser_list_region.add_argument('--type', type=str, help='') + parser_list_region.add_argument('--status', type=str, help='') + parser_list_region.add_argument('--metadata', action='append', nargs="+", + type=str, help='') + parser_list_region.add_argument('--aicversion', type=str, + help='') + parser_list_region.add_argument('--clli', type=str, help='') + parser_list_region.add_argument('--regionname', type=str, + help='') + parser_list_region.add_argument('--osversion', type=str, + help='') + parser_list_region.add_argument('--valet', type=str, help='') + parser_list_region.add_argument('--state', type=str, help='') + parser_list_region.add_argument('--country', type=str, help='') + parser_list_region.add_argument('--city', type=str, help='') + parser_list_region.add_argument('--street', type=str, help='') + parser_list_region.add_argument('--zip', type=str, help='') + parser_list_region.add_argument('--vlcp_name', type=str, + help='') + + # add metadata to region + h1, h2 = '', '' + parser_add_metadata = subparsers.add_parser('add_metadata', + help='%s %s' % (h1, h2)) + parser_add_metadata.add_argument('region_id', type=str, help=h1) + parser_add_metadata.add_argument('datafile', type=argparse.FileType('r'), + help=h2) + + # update region's metadata + h1, h2 = '', '' + parser_update_metadata = subparsers.add_parser('update_metadata', + help='%s %s' % (h1, h2)) + parser_update_metadata.add_argument('region_id', type=str, help=h1) + parser_update_metadata.add_argument('datafile', + type=argparse.FileType('r'), + help=h2) + # delete metadata key from region + h1, h2 = '', '' + parser_delete_metadata = subparsers.add_parser('delete_metadata', + help='%s %s' % (h1, h2)) + parser_delete_metadata.add_argument('region_id', type=str, help=h1) + parser_delete_metadata.add_argument('metadata_key', type=str, help=h2) + + # get region's metadata + h1 = '' + parser_get_metadata = subparsers.add_parser('get_metadata', + help='%s' % (h1)) + parser_get_metadata.add_argument('region_id', type=str, help=h1) + + # update region's status + h1, h2 = '', '' + parser_update_status = subparsers.add_parser('update_status', + help='%s %s' % (h1, h2)) + parser_update_status.add_argument('region_id', type=str, help=h1) + parser_update_status.add_argument('status', type=str, help=h2) + + +def get_token(timeout, args, host): + headers = { + 'Content-Type': 'application/json', + } + url = '%s/v2.0/tokens' + data = ''' +{ +"auth": { + "tenantName": "%s", + "passwordCredentials": { + "username": "%s", + "password": "%s" + } + } +}''' + for argument in ('tenant_name', 'username', 'password', 'auth_region'): + argument_value = getattr(args, argument, None) + if argument_value is not None: + globals()[argument] = argument_value + else: + # If it does not exist in the configuration, we would like the + # exception to be raised + configuration_value = getattr(config, argument) + if configuration_value: + globals()[argument] = configuration_value + else: + message = ('ERROR: {} for token generation was not supplied. ' + 'Please use its command-line ' + 'argument or environment variable.'.format(argument)) + print message + raise cli_common.MissingArgumentError(message) + + keystone_ep = cli_common.get_keystone_ep('{}:8080'.format(host), + auth_region) + if keystone_ep is None: + raise ConnectionError( + 'Failed in get_token, host: {}, region: {}'.format(host, + auth_region)) + url = url % (keystone_ep,) + data = data % (tenant_name, username, password,) + + if args.verbose: + print( + "Getting token:\ntimeout: %d\nheaders: %s\nurl: %s\n" % ( + timeout, headers, url)) + try: + resp = requests.post(url, timeout=timeout, data=data, headers=headers) + if resp.status_code != 200: + raise ResponseError( + 'Failed to get token (Reason: {})'.format( + resp.status_code)) + return resp.json()['access']['token']['id'] + + except Exception as e: + print e.message + raise ConnectionError(e.message) + + +def preparm(p): + return ('' if len(p) else '?') + ('&' if len(p) else '') + + +def cmd_details(args): + if args.subcmd == 'get_region': + return requests.get, '/%s' % args.region_name_or_id + elif args.subcmd == 'create_region': + return requests.post, '' + elif args.subcmd == 'update_region': + return requests.put, '/%s' % args.region_id + elif args.subcmd == 'delete_region': + return requests.delete, '/%s' % args.region_id + elif args.subcmd == 'list_regions': + param = '' + if args.type: + param += '%stype=%s' % (preparm(param), args.type) + if args.status: + param += '%sstatus=%s' % (preparm(param), args.status) + if args.metadata: + for meta in args.metadata: + param += '%smetadata=%s' % (preparm(param), meta[0]) + if args.aicversion: + param += '%saicversion=%s' % (preparm(param), args.aicversion) + if args.clli: + param += '%sclli=%s' % (preparm(param), args.clli) + if args.regionname: + param += '%sregionname=%s' % (preparm(param), args.regionname) + if args.osversion: + param += '%sosversion=%s' % (preparm(param), args.osversion) + if args.valet: + param += '%svalet=%s' % (preparm(param), args.valet) + if args.state: + param += '%sstate=%s' % (preparm(param), args.state) + if args.country: + param += '%scountry=%s' % (preparm(param), args.country) + if args.city: + param += '%scity=%s' % (preparm(param), args.city) + if args.street: + param += '%sstreet=%s' % (preparm(param), args.street) + if args.zip: + param += '%szip=%s' % (preparm(param), args.zip) + if args.vlcp_name: + param += '%svlcp_name=%s' % (preparm(param), args.vlcp_name) + return requests.get, '/%s' % param + elif args.subcmd == 'add_metadata': + return requests.post, '/%s/metadata' % args.region_id + elif args.subcmd == 'update_metadata': + return requests.put, '/%s/metadata' % args.region_id + elif args.subcmd == 'delete_metadata': + return requests.delete, '/%s/metadata/%s' % (args.region_id, + args.metadata_key) + elif args.subcmd == 'get_metadata': + return requests.get, '/%s/metadata' % args.region_id + elif args.subcmd == 'update_status': + return requests.put, '/%s/status' % args.region_id + elif args.subcmd == 'get_group': + return requests.get, '/%s' % args.group_id + elif args.subcmd == 'list_groups': + return requests.get, '/' + elif args.subcmd == 'create_group': + return requests.post, '/' + elif args.subcmd == 'update_group': + return requests.put, '/%s' % args.group_id + elif args.subcmd == 'delete_group': + return requests.delete, '/%s' % args.group_id + + +def get_path(args): + path = 'v2/orm/regions' + if 'group' in args.subcmd: + path = 'v2/orm/groups' + return path + + +def get_environment_variable(argument): + # The rules are: all caps, underscores instead of dashes and prefixed + environment_variable = 'AIC_ORM_{}'.format( + argument.replace('-', '_').upper()) + + return os.environ.get(environment_variable) + + +def run(args): + url_path = get_path(args) + host = args.orm_base_url if args.orm_base_url else config.orm_base_url + port = args.port if args.port else 8080 + data = args.datafile.read() if 'datafile' in args else '{}' + timeout = args.timeout if args.timeout else 10 + rest_cmd, cmd_url = cmd_details(args) + url = '%s:%d/%s' % (host, port, url_path) + cmd_url + if args.faceless: + auth_token = auth_region = requester = client = '' + else: + try: + auth_token = get_token(timeout, args, host) + except Exception: + exit(1) + auth_region = globals()['auth_region'] + requester = globals()['username'] + client = requester + + if (args.subcmd == 'get_region' or args.subcmd == 'list_regions') \ + and "use_version" in args: + if args.use_version == 1: + url = '%s:%d/lcp' % (host, port) + cmd_url + elif args.use_version is not None and args.use_version != 2: + print 'API error: use_version argument - invalid value, ' \ + 'allowed values: 1 or 2' + exit(1) + + if args.subcmd == "update_status": + data = '{"status": "%s"}' % args.status + + tracking_id = args.tracking_id if args.tracking_id else None + headers = { + 'content-type': 'application/json', + 'X-Auth-Token': auth_token, + 'X-Auth-Region': auth_region, + 'X-AIC-ORM-Requester': requester, + 'X-AIC-ORM-Client': client, + 'X-AIC-ORM-Tracking-Id': tracking_id + } + if args.verbose: + print("Sending API:\ntimeout: %d\ndata: %s\n" + "headers: %s\ncmd: %s\nurl: %s\n" % (timeout, + data, + headers, + rest_cmd.__name__, + url)) + try: + resp = rest_cmd(url, data=data, timeout=timeout, headers=headers, + verify=config.verify) + except Exception as e: + print e + exit(1) + + if not 200 <= resp.status_code < 300: + content = resp.content + print 'API error: %s %s (Reason: %d)\n%s' % ( + rest_cmd.func_name.upper(), + url, + resp.status_code, + content) + exit(1) + + if resp.status_code == 204: # no content + exit(0) + + rj = resp.json() + + if rj == 'Not found': + print 'No output was found' + else: + cli_common.pretty_print_json(rj) diff --git a/orm/orm_client/ormcli/tests/__init__.py b/orm/orm_client/ormcli/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/orm/orm_client/ormcli/tests/data/cms-add-cust.json b/orm/orm_client/ormcli/tests/data/cms-add-cust.json new file mode 100644 index 00000000..96a5318c --- /dev/null +++ b/orm/orm_client/ormcli/tests/data/cms-add-cust.json @@ -0,0 +1,119 @@ +{ + "description": "Customer description", + "enabled": true, + "name": "myDomain", + "metadata": { + "my_server_name": "Apache1", + "ocx_cust": "123456889" + }, + "regions": [ + { + "name": "SAN1", + "type": "single", + "quotas": [ + { + "compute": [ + { + "instances": "10", + "injected-files": "10", + "keypairs": "10", + "ram": "10" + } + ], + "storage": [ + { + "gigabytes": "10", + "snapshots": "10", + "volumes": "10" + } + ], + "network": [ + { + "floatingip": "10", + "network": "10", + "port": "10", + "router": "10", + "subnet": "10" + } + ] + } + ] + }, + { + "name": "AIC_MEDIUM", + "type": "group", + "quotas": [ + { + "compute": [ + { + "instances": "10", + "injected-files": "10", + "keypairs": "10", + "ram": "10" + } + ], + "storage": [ + { + "gigabytes": "10", + "snapshots": "10", + "volumes": "10" + } + ], + "network": [ + { + "floatingip": "10", + "network": "10", + "port": "10", + "router": "10", + "subnet": "10" + } + ] + } + ] + } + ], + "users": [ + { + "id": "userId1", + "role": [ + "admin", + "other" + ] + }, + { + "id": "userId2", + "role": [ + "storage" + ] + } + ], + "defaultQuotas": [ + { + "compute": [ + { + "instances": "10", + "injected-files": "10", + "keypairs": "10", + "ram": "10" + } + ], + "storage": [ + { + "gigabytes": "10", + "snapshots": "10", + "volumes": "10" + } + ], + "network": [ + { + "floatingip": "10", + "network": "10", + "port": "10", + "router": "10", + "subnet": "10" + } + ] + } + ] + } + diff --git a/orm/orm_client/ormcli/tests/data/ims-create-image.json b/orm/orm_client/ormcli/tests/data/ims-create-image.json new file mode 100644 index 00000000..4063e271 --- /dev/null +++ b/orm/orm_client/ormcli/tests/data/ims-create-image.json @@ -0,0 +1,29 @@ +{ + "name": "abc1", + "url": "https://mirrors.it.att.com/images/image-name", + "visibility": "private", + "disk-format": "raw", + "container-format": "bare", + "min-disk": 1, + "min-ram": 1, + "tags": ["tag1", "tag2", "tag3"], + "properties": { + "property1": "value1", + "property2": "value2", + "property3": "value3" + }, + "regions": [ + { + "name": "rdm1", + "type": "single" + }, + { + "name": "rdm3", + "type": "single" + } + ], + "customers": [ + "alef", + "bet" + ] +} diff --git a/orm/orm_client/ormcli/tests/test_cli_common.py b/orm/orm_client/ormcli/tests/test_cli_common.py new file mode 100644 index 00000000..639b9764 --- /dev/null +++ b/orm/orm_client/ormcli/tests/test_cli_common.py @@ -0,0 +1,53 @@ +import mock +from ormcli import cli_common +from unittest import TestCase + + +class CmsTests(TestCase): + @mock.patch.object(cli_common.requests, 'get') + def test_get_keystone_ep_sanity(self, mock_get): + my_response = mock.MagicMock() + my_response.status_code = cli_common.OK_CODE + my_response.json.return_value = { + 'regions': [{'endpoints': [{ + 'type': 'identity', 'publicURL': 'test'}]}]} + mock_get.return_value = my_response + + self.assertEqual(cli_common.get_keystone_ep('a', 'b'), 'test') + + @mock.patch.object(cli_common.requests, 'get', + side_effect=cli_common.requests.exceptions + .ConnectionError()) + def test_get_keystone_ep_connection_failed(self, mock_get): + self.assertIsNone(cli_common.get_keystone_ep('a', 'b')) + + +@mock.patch.object(cli_common.requests, 'get') +def test_get_keystone_ep_bad_status_code(self, mock_get): + my_response = mock.MagicMock() + my_response.status_code = cli_common.OK_CODE + 1 + my_response.json.return_value = { + 'regions': [{'endpoints': [{ + 'type': 'identity', 'publicURL': 'test'}]}]} + mock_get.return_value = my_response + + self.assertIsNone(cli_common.get_keystone_ep('a', 'b')) + + +@mock.patch.object(cli_common.requests, 'get') +def test_get_keystone_ep_bad_response(self, mock_get): + my_response = mock.MagicMock() + my_response.status_code = cli_common.OK_CODE + my_response.json.return_value = { + 'regions': [{'endpoinqs': [{ + 'type': 'identity', 'publicURL': 'test'}]}]} + mock_get.return_value = my_response + + self.assertIsNone(cli_common.get_keystone_ep('a', 'b')) + + my_response.json.return_value = { + 'regions': [{'endpoints': [{ + 'type': 'identiqy', 'publicURL': 'test'}]}]} + mock_get.return_value = my_response + + self.assertIsNone(cli_common.get_keystone_ep('a', 'b')) diff --git a/orm/orm_client/ormcli/tests/test_cmscli.py b/orm/orm_client/ormcli/tests/test_cmscli.py new file mode 100644 index 00000000..04091cf5 --- /dev/null +++ b/orm/orm_client/ormcli/tests/test_cmscli.py @@ -0,0 +1,229 @@ +from cStringIO import StringIO +import json +import mock +from ormcli import cmscli +from ormcli import ormcli +import requests +import sys +from unittest import TestCase + + +TJ = {'access': {'token': {'id': 'test'}}} + + +class CmsTests(TestCase): + def setUp(self): + out, sys.stdout, err, sys.stderr = sys.stdout, StringIO(), sys.stderr, StringIO() + self.mock_response = mock.Mock() + + def respond(self, value, code, headers={}, oy=False): + # Set the response according to the parameter + if oy: + response = mock.Mock() + else: + response = self.mock_response + + response.json.return_value = value + response.status_code = code + response.headers = headers + return response + + def test_cmd_details(self): + # Set up the args parameter + args = mock.MagicMock() + args.custid = 'test_custid' + args.regionid = 'test_region' + args.userid = 'test_userid' + args.region = 'test_region' + args.user = 'test_user' + args.starts_with = 'test_startswith' + args.contains = 'test_contains' + + subcmd_to_result = { + 'create_customer': (requests.post, '',), + 'delete_customer': (requests.delete, '/%s' % args.custid,), + 'update_customer': (requests.put, '/%s' % args.custid,), + 'add_region': (requests.post, '/%s/regions' % args.custid,), + 'replace_region': (requests.put, '/%s/regions' % args.custid,), + 'delete_region': ( + requests.delete, + '/%s/regions/%s' % (args.custid, args.regionid),), + 'add_user': ( + requests.post, + '/%s/regions/%s/users' % (args.custid, args.regionid),), + 'replace_user': ( + requests.put, + '/%s/regions/%s/users' % (args.custid, args.regionid),), + 'delete_user': (requests.delete, '/%s/regions/%s/users/%s' % ( + args.custid, args.regionid, args.userid),), + 'add_default_user': (requests.post, '/%s/users' % args.custid,), + 'replace_default_user': (requests.put, '/%s/users' % args.custid,), + 'delete_default_user': ( + requests.delete, '/%s/users/%s' % (args.custid, args.userid),), + 'add_metadata': (requests.post, '/%s/metadata' % args.custid,), + 'replace_metadata': (requests.put, '/%s/metadata' % args.custid,), + 'get_customer': (requests.get, '/%s' % args.custid,), + 'list_customers': (requests.get, + '/?region=%s&user=%s&starts_with=%s' + '&contains=%s' % (args.region, + args.user, args.starts_with, + args.contains)) + } + + # Assert that each subcommand returns the expected details + for subcmd in subcmd_to_result: + args.subcmd = subcmd + self.assertEqual(subcmd_to_result[subcmd], + cmscli.cmd_details(args)) + + @mock.patch.object(cmscli.cli_common, 'get_keystone_ep', + return_value=None) + def test_get_token_keystone_ep_not_found(self, mock_get_keystone_ep): + args = mock.MagicMock() + args.username = 'test' + self.assertRaises(cmscli.ConnectionError, cmscli.get_token, + 'a', args, 'c') + + @mock.patch.object(cmscli.cli_common, 'get_keystone_ep') + @mock.patch.object(cmscli.requests, 'post') + def test_get_token_errors(self, mock_post, mock_get_keystone_ep): + # Bad status code + my_response = mock.MagicMock() + my_response.status_code = 201 + mock_post.return_value = my_response + self.assertRaises(cmscli.ConnectionError, cmscli.get_token, + 3, mock.MagicMock(), 'c') + + # Post fails + mock_post.side_effect = ValueError('test') + self.assertRaises(cmscli.ConnectionError, cmscli.get_token, + 3, mock.MagicMock(), 'c') + + @mock.patch.object(cmscli.cli_common, 'get_keystone_ep') + @mock.patch.object(cmscli.requests, 'post') + @mock.patch.object(cmscli.requests, 'get') + def test_list_customers(self, mock_get, mock_post, mock_get_keystone_ep): + mock_post.return_value = self.respond(TJ, 200) + mock_get.return_value = self.mock_response + args = ormcli.main('orm cms list_customers t'.split()) + sys.stdout.seek(0) + output = sys.stdout.read() + self.assertIn(json.dumps(TJ), output) + + @mock.patch.object(cmscli.cli_common, 'get_keystone_ep') + @mock.patch.object(cmscli.requests, 'post') + @mock.patch.object(cmscli.requests, 'get') + @mock.patch.object(cmscli, 'get_token') + def test_list_customers_a(self, mock_get_token, + mock_get, mock_post, mock_get_keystone_ep): + mock_post.return_value = self.respond(TJ, 200) + mock_get.return_value = self.mock_response + mock_get.__name__ = 'a' + args = ormcli.main('orm cms --verbose list_customers t'.split()) + sys.stdout.seek(0) + output = sys.stdout.read() + self.assertIn(json.dumps(TJ), output) + + @mock.patch.object(cmscli.cli_common, 'get_keystone_ep') + @mock.patch.object(cmscli.requests, 'post') + @mock.patch.object(cmscli.requests, 'get') + def test_list_customers_e(self, mock_get, mock_post, mock_get_keystone_ep): + mock_post.return_value = self.respond(TJ, 200) + mock_get.side_effect = Exception('e') + with self.assertRaises(SystemExit) as cm: + args = ormcli.main('orm cms list_customers t'.split()) + self.assertEqual(cm.exception.code, 1) + sys.stdout.seek(0) + output = sys.stdout.read() + self.assertIn('e', output) + + @mock.patch.object(cmscli.cli_common, 'get_keystone_ep') + @mock.patch.object(cmscli.requests, 'post') + @mock.patch.object(cmscli.requests, 'get') + @mock.patch.object(cmscli, 'get_token') + @mock.patch.object(cmscli, 'globals') + def test_list_customers_errors(self, mock_globals, mock_get_token, + mock_get, mock_post, + mock_get_keystone_ep): + mock_post.return_value = self.respond(TJ, 200) + mock_get.return_value = self.respond(TJ, 204, oy=True) + with self.assertRaises(SystemExit) as cm: + args = ormcli.main('orm cms list_customers t'.split()) + self.assertEqual(cm.exception.code, 0) + sys.stdout.seek(0) + output = sys.stdout.read() + self.assertEqual('', output) + + def test_parsing(self): + cli = ormcli.Cli() + cli.create_parser() + cli.parse( + 'orm cms --orm-base-url 12.11.10.9 --port 8832 --timeout 150 ' + 'add_user ' + 'client1 customer1 region1 ' + 'ormcli/tests/data/cms-add-cust.json'.split()) + args = cli.args + self.assertEqual(args.orm_base_url, '12.11.10.9') + self.assertEqual(args.port, 8832) + self.assertEqual(args.timeout, 150) + + @mock.patch('requests.post') + def test_timeout(self, mock_post): + mock_post.side_effect = Exception("timeout boom") + cli = ormcli.Cli() + cli.create_parser() + cli.parse( + 'orm cms --faceless add_user client1 customer1 region1 ' + 'ormcli/tests/data/cms-add-cust.json'.split()) + with self.assertRaises(SystemExit) as cm: + cli.logic() + self.assertEqual(cm.exception.code, 1) + sys.stdout.seek(0) + output = sys.stdout.read() + self.assertIn('timeout boom', output) + + @mock.patch('requests.post') + @mock.patch.object(cmscli, 'get_token') + def test_no_keystone(self, mock_get_token, mock_post): + mock_post.side_effect = Exception("timeout boom") + cli = ormcli.Cli() + cli.create_parser() + cli.parse( + 'orm cms add_user client1 customer1 region1 ' + 'ormcli/tests/data/cms-add-cust.json'.split()) + with self.assertRaises(SystemExit) as cm: + cli.logic() + + @mock.patch('requests.post') + @mock.patch.object(cmscli, 'get_token') + def test_response_code(self, mock_get_token, mock_post): + cli = ormcli.Cli() + cli.create_parser() + cli.parse( + 'orm cms create_customer client1 ' + 'ormcli/tests/data/cms-add-cust.json'.split()) + resp = self.respond({"access": {"token": {"id": 989}}}, 400) + mock_post.return_value = resp + with self.assertRaises(SystemExit) as cm: + cli.logic() + + @mock.patch('requests.post') + def test_ok(self, mock_post): + cli = ormcli.Cli() + cli.create_parser() + cli.parse( + 'orm cms create_customer client1 ' + 'ormcli/tests/data/cms-add-cust.json'.split()) + mock_post.return_value = self.respond( + {"access": {"token": {"id": 989}}}, 200) + + @mock.patch('requests.get') + @mock.patch('requests.post') + def test_list_customers(self, mock_post, mock_get): + cli = ormcli.Cli() + cli.create_parser() + cli.parse( + 'orm cms list_customers --region 2 --user bob client1'.split()) + resp = self.respond('{"Hi, mom"}', 200, {'X-Subject-Token': 989}) + mock_post.return_value = self.respond( + {"access": {"token": {"id": 989}}}, 200) diff --git a/orm/orm_client/ormcli/tests/test_fmscli.py b/orm/orm_client/ormcli/tests/test_fmscli.py new file mode 100644 index 00000000..4592084f --- /dev/null +++ b/orm/orm_client/ormcli/tests/test_fmscli.py @@ -0,0 +1,172 @@ +from cStringIO import StringIO +import json +import mock +from ormcli import fmscli +from ormcli import ormcli +import requests +import sys +from unittest import TestCase + +TJ = {'access': {'token': {'id': 'test'}}} + + +class FmsTests(TestCase): + def setUp(self): + out, sys.stdout, err, sys.stderr = sys.stdout, StringIO(), sys.stderr, StringIO() + self.mock_response = mock.Mock() + + def respond(self, value, code, headers={}, oy=False): + # Set the response according to the parameter + if oy: + response = mock.Mock() + else: + response = self.mock_response + + response.json.return_value = value + response.status_code = code + response.headers = headers + return response + + def test_cmd_details(self): + # Set up the args parameter + args = mock.MagicMock() + args.flavorid = 'test_flavorid' + args.regionid = 'test_region' + args.region = 'test_region' + args.tagname = 'test_tagname' + args.eskeyname = 'test_eskeyname' + args.visibility = 'test_visibility' + args.tenant = 'test_tenant' + args.series = 'test_series' + args.starts_with = 'test_startswith' + args.contains = 'test_contains' + args.alias = 'test_alias' + list_flavors_url = '/?visibility=%s®ion=%s&tenant=%s&series=%s' \ + '&starts_with=%s&contains=%s&alias=%s' + subcmd_to_result = { + 'create_flavor': (requests.post, '',), + 'delete_flavor': (requests.delete, '/%s' % args.flavorid,), + 'add_region': (requests.post, '/%s/regions' % args.flavorid,), + 'add_tags': (requests.post, '/%s/tags' % args.flavorid,), + 'replace_tags': (requests.put, '/%s/tags' % args.flavorid,), + 'delete_tag': ( + requests.delete, + '/%s/tags/%s' % (args.flavorid, args.tagname),), + 'delete_all_tags': (requests.delete, '/%s/tags' % args.flavorid,), + 'get_tags': (requests.get, '/%s/tags' % args.flavorid,), + 'delete_region': (requests.delete, '/%s/regions/%s' % ( + args.flavorid, args.regionid),), + 'add_tenant': (requests.post, '/%s/tenants' % args.flavorid,), + 'delete_tenant': (requests.delete, '/%s/tenants/%s' % ( + args.flavorid, args.tenantid),), + 'get_flavor': (requests.get, '/%s' % args.flavorid,), + 'get_extra_specs': ( + requests.get, '/%s/os_extra_specs' % args.flavorid,), + 'delete_all_extra_specs': ( + requests.delete, '/%s/os_extra_specs' % args.flavorid,), + 'delete_extra_spec': (requests.delete, '/%s/os_extra_specs/%s' % ( + args.flavorid, args.eskeyname),), + 'add_extra_specs': ( + requests.post, '/%s/os_extra_specs' % args.flavorid,), + 'list_flavors': (requests.get, + list_flavors_url % (args.visibility, args.region, + args.tenant, args.series, + args.starts_with, + args.contains, args.alias)) + } + + # Assert that each subcommand returns the expected details + for subcmd in subcmd_to_result: + args.subcmd = subcmd + self.assertEqual(subcmd_to_result[subcmd], + fmscli.cmd_details(args)) + + @mock.patch.object(fmscli.cli_common, 'get_keystone_ep', + return_value=None) + def test_get_token_keystone_ep_not_found(self, mock_get_keystone_ep): + args = mock.MagicMock() + args.username = 'test' + self.assertRaises(fmscli.ConnectionError, fmscli.get_token, + 'a', args, 'c') + + @mock.patch.object(fmscli.cli_common, 'get_keystone_ep') + @mock.patch.object(fmscli.requests, 'post') + def test_get_token_errors(self, mock_post, mock_get_keystone_ep): + # Bad status code + my_response = mock.MagicMock() + my_response.status_code = 201 + mock_post.return_value = my_response + self.assertRaises(fmscli.ConnectionError, fmscli.get_token, + 3, mock.MagicMock(), 'c') + + # Post fails + mock_post.side_effect = ValueError('test') + self.assertRaises(fmscli.ConnectionError, fmscli.get_token, + 3, mock.MagicMock(), 'c') + + @mock.patch.object(fmscli.cli_common, 'get_keystone_ep') + @mock.patch.object(fmscli.requests, 'post') + @mock.patch.object(fmscli.requests, 'get') + @mock.patch.object(fmscli, 'get_token') + @mock.patch.object(fmscli, 'globals') + def test_list_flavors(self, mock_globals, mock_get_token, + mock_get, mock_post, mock_get_keystone_ep): + mock_post.return_value = self.respond(TJ, 200) + mock_get.return_value = self.mock_response + args = ormcli.main('orm fms list_flavors t'.split()) + sys.stdout.seek(0) + output = sys.stdout.read() + self.assertIn(json.dumps(TJ), output) + + @mock.patch.object(fmscli.cli_common, 'get_keystone_ep') + @mock.patch.object(fmscli.requests, 'post') + @mock.patch.object(fmscli.requests, 'get') + @mock.patch.object(fmscli, 'get_token') + @mock.patch.object(fmscli, 'globals') + def test_list_flavors_a(self, mock_globals, mock_get_token, + mock_get, mock_post, mock_get_keystone_ep): + mock_post.return_value = self.respond(TJ, 200) + mock_get.return_value = self.mock_response + mock_get.__name__ = 'a' + args = ormcli.main('orm fms --verbose list_flavors t'.split()) + sys.stdout.seek(0) + output = sys.stdout.read() + self.assertIn(json.dumps(TJ), output) + + @mock.patch.object(fmscli.cli_common, 'get_keystone_ep') + @mock.patch.object(fmscli.requests, 'post') + @mock.patch.object(fmscli.requests, 'get') + def test_list_flavors_e(self, mock_get, mock_post, mock_get_keystone_ep): + mock_post.return_value = self.respond(TJ, 200) + mock_get.side_effect = Exception('e') + with self.assertRaises(SystemExit) as cm: + args = ormcli.main('orm fms list_flavors t'.split()) + self.assertEqual(cm.exception.code, 1) + sys.stdout.seek(0) + output = sys.stdout.read() + self.assertIn('e', output) + + @mock.patch.object(fmscli.cli_common, 'get_keystone_ep') + @mock.patch.object(fmscli.requests, 'post') + @mock.patch.object(fmscli.requests, 'get') + @mock.patch.object(fmscli, 'get_token') + @mock.patch.object(fmscli, 'globals') + def test_list_flavors_errors(self, mock_globals, mock_get_token, + mock_get, mock_post, + mock_get_keystone_ep): + mock_post.return_value = self.respond(TJ, 200) + mock_get.return_value = self.respond(TJ, 204, oy=True) + with self.assertRaises(SystemExit) as cm: + args = ormcli.main('orm fms list_flavors t'.split()) + self.assertEqual(cm.exception.code, 0) + sys.stdout.seek(0) + output = sys.stdout.read() + self.assertEqual('', output) + + mock_get.return_value = self.respond(TJ, 404, oy=True) + with self.assertRaises(SystemExit) as cm: + args = ormcli.main('orm fms --faceless list_flavors t'.split()) + self.assertEqual(cm.exception.code, 1) + sys.stdout.seek(0) + output = sys.stdout.read() + self.assertIn('API error:', output) diff --git a/orm/orm_client/ormcli/tests/test_imscli.py b/orm/orm_client/ormcli/tests/test_imscli.py new file mode 100644 index 00000000..8cb99223 --- /dev/null +++ b/orm/orm_client/ormcli/tests/test_imscli.py @@ -0,0 +1,199 @@ +from cStringIO import StringIO +import mock +from ormcli import imscli +from ormcli.imscli import cmd_data +from ormcli import ormcli +import sys +from unittest import TestCase + + +class ImsTests(TestCase): + def setUp(self): + out, sys.stdout, err, sys.stderr = sys.stdout, StringIO(), sys.stderr, StringIO() + self.mock_response = mock.Mock() + + def respond(self, value, code, headers={}): + self.mock_response.json.return_value = value + self.mock_response.status_code = code + self.mock_response.headers = headers + return self.mock_response + + def test_error_with_empty_args(self): + with self.assertRaises(SystemExit) as cm: + args = ormcli.main([]) + self.assertEqual(cm.exception.code, 2) + sys.stderr.seek(0) + output = sys.stderr.read() + self.assertIn('too few arguments', output) + + def test_help_command(self): + with self.assertRaises(SystemExit) as cm: + args = ormcli.main('orm --help'.split()) + self.assertEqual(cm.exception.code, 0) + sys.stdout.seek(0) + output = sys.stdout.read() + self.assertIn('usage:', output) + self.assertIn('optional arguments:', output) + self.assertIn('', output) + self.assertIn('ims', output) + self.assertIn('Image Management', output) + + def test_ims_help_command(self): + with self.assertRaises(SystemExit) as cm: + args = ormcli.main('orm ims --help'.split()) + self.assertEqual(cm.exception.code, 0) + sys.stdout.seek(0) + output = sys.stdout.read() + self.assertIn('usage:', output) + self.assertIn('timeout', output) + self.assertIn('optional arguments:', output) + self.assertIn('orm ims', output) + + @mock.patch.object(imscli, 'cli_common') + @mock.patch('requests.put') + @mock.patch('requests.post') + @mock.patch.object(imscli, 'get_token') + @mock.patch.object(imscli, 'globals') + def test_timeout(self, mock_globals, mock_get_token, + mock_post, mock_put, mock_common): + mock_post.side_effect = Exception("timeout boom") + cli = ormcli.Cli() + cli.create_parser() + cli.parse( + 'orm ims create_image client1 ' + 'ormcli/tests/data/ims-create-image.json'.split()) + with self.assertRaises(SystemExit) as cm: + cli.logic() + self.assertEqual(cm.exception.code, 1) + sys.stdout.seek(0) + output = sys.stdout.read() + self.assertIn('timeout boom', output) + + @mock.patch('requests.post') + @mock.patch.object(imscli, 'get_token') + @mock.patch.object(imscli, 'globals') + def test_no_keystone(self, mock_globals, mock_get_token, mock_post): + mock_post.side_effect = Exception("timeout boom") + cli = ormcli.Cli() + cli.create_parser() + globals()['auth_region'] = 'test' + cli.parse( + 'orm ims create_image client1 ' + 'ormcli/tests/data/ims-create-image.json'.split()) + with self.assertRaises(SystemExit) as cm: + cli.logic() + + # @mock.patch.object(imscli, 'cli_common') + # @mock.patch('requests.post') + # def test_response_code(self, mock_post, mock_common): + # cli = ormcli.Cli() + # cli.create_parser() + # cli.parse( + # 'orm ims create_image client1 ' + # 'ormcli/tests/data/ims-create-image.json'.split()) + # resp = self.respond({"access": {"token": {"id": 989}}}, 200) + # mock_post.return_value = resp + # cli.logic() + # sys.stdout.seek(0) + # output = sys.stdout.read() + # # The response json should be printed, but since we mock post() only + # # once, the response would be the same response received + # # from Keystone + # self.assertIn('{\'access\': {\'token\': {\'id\': 989}}}', output) + + # @mock.patch.object(imscli, 'cli_common') + # @mock.patch('requests.get') + # @mock.patch('requests.post') + # def test_get_image_sanity(self, mock_post, mock_get, mock_common): + # mock_post.return_value = self.respond( + # {"access": {"token": {"id": 989}}}, 201) + # cli = ormcli.Cli() + # cli.create_parser() + # cli.parse('orm ims get_image client1 test'.split()) + # mock_get.return_value = self.respond( + # {"access": {"token": {"id": 989}}}, 200) + # cli.logic() + # sys.stdout.seek(0) + # output = sys.stdout.read() + # self.assertIn('{\'access\': {\'token\': {\'id\': 989}}}', output) + + # @mock.patch.object(imscli, 'cli_common') + # @mock.patch('requests.get') + # @mock.patch('requests.post') + # def test_list_images(self, mock_post, mock_get, mock_common): + # mock_post.return_value = self.respond( + # {"access": {"token": {"id": 989}}}, 201) + # cli = ormcli.Cli() + # cli.create_parser() + # cli.parse( + # 'orm ims list_images client1 --visibility public --region a ' + # '--tenant b'.split()) + # resp = self.respond({"access": {"token": {"id": 989}}}, 200) + # mock_get.return_value = self.respond( + # {"access": {"token": {"id": 989}}}, 200) + # cli.logic() + # sys.stdout.seek(0) + # output = sys.stdout.read() + # self.assertIn('{\'access\': {\'token\': {\'id\': 989}}}', output) + + @mock.patch.object(imscli, 'cli_common') + @mock.patch('requests.get') + @mock.patch('requests.post') + @mock.patch.object(imscli, 'get_token') + @mock.patch.object(imscli, 'globals') + def test_list_images_bad_request(self, mock_get_token, mock_globals, + mock_post, mock_get, mock_common): + mock_post.return_value = self.respond( + {"access": {"token": {"id": 989}}}, 201) + cli = ormcli.Cli() + cli.create_parser() + cli.parse( + 'orm ims list_images client1 --visibility public --region a ' + '--customer b'.split()) + resp = self.respond({"access": {"token": {"id": 989}}}, 200) + with self.assertRaises(SystemExit) as cm: + cli.logic() + self.assertEqual(cm.exception.code, 1) + sys.stdout.seek(0) + output = sys.stdout.read() + self.assertIn('API error', output) + + def test_cmd_data_enable(self): + my_args = mock.MagicMock() + my_args.subcmd = 'enable' + cm_data = cmd_data(my_args) + self.assertEqual("{\n \"enabled\": true\n}", cm_data) + + def test_cmd_data_disable(self): + my_args = mock.MagicMock() + my_args.subcmd = 'disable' + cm_data = cmd_data(my_args) + self.assertEqual("{\n \"enabled\": false\n}", cm_data) + + def test_cmd_data_no_data_file(self): + my_args = mock.MagicMock() + my_args.subcmd = 'xyz' + + my_args.datafile.read.return_value = "123" + cm_data = cmd_data(my_args) + self.assertEqual("{}", cm_data) + + def test_cmd_data_from_data_file(self): + my_args = MyDataFile() + cm_data = cmd_data(my_args) + + self.assertEqual("123", cm_data) + + +class MyDataFile(object): + def __init__(self): + self.subcmd = '1' + self.datafile = FakeDataFIle() + + def __iter__(self): + return iter(['datafile']) + + +class FakeDataFIle(object): + def read(self): + return '123' diff --git a/orm/orm_client/ormcli/tests/test_rmscli.py b/orm/orm_client/ormcli/tests/test_rmscli.py new file mode 100644 index 00000000..95a336d3 --- /dev/null +++ b/orm/orm_client/ormcli/tests/test_rmscli.py @@ -0,0 +1,188 @@ +from cStringIO import StringIO +import mock +from ormcli import ormcli +from ormcli import rmscli +import requests +import sys +from unittest import TestCase + + +class RmsTests(TestCase): + def setUp(self): + out, sys.stdout, err, sys.stderr = sys.stdout, StringIO(), sys.stderr, StringIO() + self.mock_response = mock.Mock() + + def respond(self, value, code, headers={}): + self.mock_response.json.return_value = value + self.mock_response.status_code = code + self.mock_response.headers = headers + return self.mock_response + + def test_cmd_details(self): + args = mock.MagicMock() + args.get_group = 'test_get_group' + args.list_groups = 'test_list_groups' + args.create_group = 'test_create_group' + args.update_group = 'test_update_group' + args.region_name_or_id = 'test_region_name_or_id' + args.type = '1' + args.status = '2' + args.metadata = '3' + args.aicversion = '4' + args.clli = '5' + args.regionname = '6' + args.osversion = '7' + args.valet = '8' + args.state = '9' + args.country = '10' + args.city = '11' + args.street = '12' + args.zip = '13' + args.vlcp_name = '14' + + AAAAAAA = '/?type=%s&status=%s&metadata=%s&aicversion=%s&clli=%s'\ + '®ionname=%s&osversion=%s&valet=%s&state=%s&country=%s'\ + '&city=%s&street=%s&zip=%s&vlcp_name=%s' % (args.type, + args.status, + args.metadata, + args.aicversion, + args.clli, + args.regionname, + args.osversion, + args.valet, + args.state, + args.country, + args.city, + args.street, + args.zip, + args.vlcp_name,) + + subcmd_to_result = {'get_region': (requests.get, + '/%s' % args.region_name_or_id), + 'get_group': (requests.get, '/%s' % args.group_id), + 'list_groups': (requests.get, '/'), + 'create_group': (requests.post, '/'), + 'update_group': ( + requests.put, '/%s' % args.group_id), + 'list_regions': (requests.get, AAAAAAA) + } + + for subcmd in subcmd_to_result: + args.subcmd = subcmd + self.assertEqual(subcmd_to_result[subcmd], + rmscli.cmd_details(args)) + + def test_parsing(self): + cli = ormcli.Cli() + cli.create_parser() + cli.parse( + 'orm rms --orm-base-url 12.11.10.9 --port 8832 --timeout 150 ' + 'list_regions --type big '.split()) + args = cli.args + self.assertEqual(args.orm_base_url, '12.11.10.9') + self.assertEqual(args.port, 8832) + self.assertEqual(args.type, 'big') + self.assertEqual(args.timeout, 150) + + @mock.patch('requests.get') + def test_timeout(self, mock_get): + cli = ormcli.Cli() + cli.create_parser() + cli.parse( + 'orm rms --faceless --orm-base-url 12.11.10.9 --port 8832' + ' --timeout 1 get_region x'.split()) + mock_get.side_effect = Exception("timeout boom") + with self.assertRaises(SystemExit) as cm: + cli.logic() + self.assertEqual(cm.exception.code, 1) + sys.stdout.seek(0) + output = sys.stdout.read() + self.assertIn('timeout boom', output) + + @mock.patch('requests.get') + def test_one_zone(self, mock_get): + cli = ormcli.Cli() + cli.create_parser() + cli.parse( + 'orm rms --faceless --orm-base-url 12.11.10.9 --port 8832' + ' --timeout 150 get_region zoneone'.split()) + resp = self.respond( + { + "clli": "n/a", + "name": "SNA 1", + "enabled": 1, + "state": "functional", + "aic_version": "aic3.0", + "endpoints": [ + { + "type": "horizon", + "publicurl": "http://horizon1.com" + }, + { + "type": "identity", + "publicurl": "http://identity1.com" + }, + { + "type": "ord", + "publicurl": "http://ord1.com" + } + ], + "id": "SNA1", + "metadata": [] + }, 200, + {'X-Subject-Token': 989}) + mock_get.return_value = resp + cli.logic() + sys.stdout.seek(0) + output = sys.stdout.read() + self.assertIn('"aic_version": "aic3.0"', output) + + # def test_error_with_wrong_port(self): + # args = self.parser.parse_args('--port 1111'.split()) + # with self.assertRaises(SystemExit) as cm: + # rmscli.rmscli_logic(args) + # self.assertEqual(cm.exception.code, 1) + # sys.stdout.seek(0) + # output = sys.stdout.read() + # self.assertIn('Connection refused', output) + + # def test_help_command(self): + # with self.assertRaises(SystemExit) as cm: + # args = self.parser.parse_args(['--help']) + # self.assertEqual(cm.exception.code, 0) + # sys.stdout.seek(0) + # output = sys.stdout.read() + # self.assertIn('usage:', output) + # self.assertIn('timeout', output) + # self.assertIn('optional arguments:', output) + # self.assertIn('--host', output) + + # @mock.patch('requests.get') + # def test_timeout(self, mock_get): + # args = self.parser.parse_args('--host 1.1.1.1 --timeout + # 1000'.split()) + # mock_get.side_effect = Exception("HTTPConnectionPool( + # host='1.1.1.1', port=8080): Max retries exceeded with url: /lcp ( + # Caused by ConnectTimeoutError( + # , 'Connection to 1.1.1.1 timed out. (connect + # timeout=1.0)'))") + # with self.assertRaises(SystemExit) as cm: + # rmscli.rmscli_logic(args) + # self.assertEqual(cm.exception.code, 1) + # sys.stdout.seek(0) + # output = sys.stdout.read() + # self.assertIn('ConnectTimeoutError', output) + + # learn how to mock 'real' request.get + + # @mock.patch('rmscli.rmscli.rmscli.requests.get', autospec=True) + # def test_bad_status(self, mock_get): + # args = self.parser.parse_args([]) + # mock_get.return_value = Response({},500) + # with self.assertRaises(SystemExit) as cm: + # rmscli.rmscli_logic(args) + # self.assertEqual(cm.exception.code, 1) + # sys.stdout.seek(0) + # output = sys.stdout.read() + # self.assertIn('GET', output) diff --git a/orm/orm_client/requirements.txt b/orm/orm_client/requirements.txt new file mode 100644 index 00000000..86147add --- /dev/null +++ b/orm/orm_client/requirements.txt @@ -0,0 +1,6 @@ +# The order of packages is significant, because pip processes them in the order +# of appearance. Changing the order has an impact on the overall integration +# process, which may cause wedges in the gate later. + +pecan==1.0.2 +requests==2.2.1 diff --git a/orm/orm_client/setup.cfg b/orm/orm_client/setup.cfg new file mode 100644 index 00000000..e7e6e160 --- /dev/null +++ b/orm/orm_client/setup.cfg @@ -0,0 +1,6 @@ +[nosetests] +match=^test +where=ormcli +nocapture=1 +cover-package=ormcli +cover-erase=1 diff --git a/orm/orm_client/setup.py b/orm/orm_client/setup.py new file mode 100644 index 00000000..f691bab3 --- /dev/null +++ b/orm/orm_client/setup.py @@ -0,0 +1,19 @@ +try: + from setuptools import setup, find_packages +except ImportError: + from ez_setup import use_setuptools + use_setuptools() + from setuptools import setup, find_packages + +setup( + name='ormcli', + version='0.1', + description='', + author='', + author_email='', + install_requires=[], + test_suite='ormcli', + zip_safe=False, + include_package_data=True, + packages=find_packages(exclude=['ez_setup']) +) diff --git a/orm/orm_client/test-requirements.txt b/orm/orm_client/test-requirements.txt new file mode 100644 index 00000000..4a784a4a --- /dev/null +++ b/orm/orm_client/test-requirements.txt @@ -0,0 +1,9 @@ +# The order of packages is significant, because pip processes them in the order +# of appearance. Changing the order has an impact on the overall integration +# process, which may cause wedges in the gate later. + +# Hacking already pins down pep8, pyflakes and flake8 +mock +coverage +testfixtures +pytest-pep8 diff --git a/orm/orm_client/tox.ini b/orm/orm_client/tox.ini new file mode 100644 index 00000000..b06444cb --- /dev/null +++ b/orm/orm_client/tox.ini @@ -0,0 +1,19 @@ +[tox] +envlist=py27,cover + +[testenv] +deps= -r{toxinidir}/requirements.txt + -r{toxinidir}/test-requirements.txt +commands= + py.test --pep8 -m pep8 + +[testenv:cover] +commands= + coverage run setup.py test + coverage report --omit=.tox/*,ormcli/tests/*,setup.py + coverage html --omit=.tox/*,ormcli/tests/*,setup.py +#commands={envpython} setup.py test -v {posargs} + +[testenv:pep8] +commands = + py.test --pep8 -m pep8