def test_put_blueprint_archive_from_url(self): port = 53230 blueprint_id = 'new_blueprint_id' archive_path = self.archive_mock_blueprint( archive_func=archiving.make_tarbz2file) archive_filename = os.path.basename(archive_path) archive_dir = os.path.dirname(archive_path) archive_url = 'http://localhost:{0}/{1}'.format( port, archive_filename) fs = FileServer(archive_dir, False, port) fs.start() try: self.wait_for_url(archive_url) blueprint_id = self.client.blueprints.publish_archive( archive_url, blueprint_id).id # verifying blueprint exists result = self.client.blueprints.get(blueprint_id) self.assertEqual(blueprint_id, result.id) finally: fs.stop()
def test_publish_archive_blueprint_main_file_name(self): port = 53230 blueprint_id = 'publish_archive_blueprint_main_file_name' main_file_name = 'blueprint_with_workflows.yaml' archive_path = self.archive_mock_blueprint() archive_filename = os.path.basename(archive_path) archive_dir = os.path.dirname(archive_path) fs = FileServer(archive_dir, False, port) fs.start() try: archive_url = 'http://localhost:{0}/{1}'.format( port, archive_filename) self.wait_for_url(archive_url) response = self.client.blueprints.publish_archive( archive_url, blueprint_id, main_file_name) finally: fs.stop() self.assertEqual(blueprint_id, response.id) self.assertEqual(main_file_name, response.main_file_name)
def test_publish_archive_blueprint_main_file_name(self): port = 53230 blueprint_id = 'publish_archive_blueprint_main_file_name' main_file_name = 'blueprint_with_workflows.yaml' archive_path = self.archive_mock_blueprint() archive_filename = os.path.basename(archive_path) archive_dir = os.path.dirname(archive_path) fs = FileServer(archive_dir, False, port) fs.start() try: archive_url = 'http://localhost:{0}/{1}'.format( port, archive_filename) self.wait_for_url(archive_url) response = self.client.blueprints.publish_archive(archive_url, blueprint_id, main_file_name) finally: fs.stop() self.assertEqual(blueprint_id, response.id) self.assertEqual(main_file_name, response.main_file_name)
class BaseServerTestCase(unittest.TestCase): def __init__(self, *args, **kwargs): super(BaseServerTestCase, self).__init__(*args, **kwargs) def create_client_with_tenant(self, username, password, tenant=DEFAULT_TENANT_NAME): headers = utils.create_auth_header(username=username, password=password) headers[CLOUDIFY_TENANT_HEADER] = tenant return self.create_client(headers=headers) def create_client(self, headers=None): client = CloudifyClient(host='localhost', headers=headers) mock_http_client = MockHTTPClient(self.app, headers=headers, file_server=self.file_server) client._client = mock_http_client client.blueprints.api = mock_http_client client.deployments.api = mock_http_client client.deployments.outputs.api = mock_http_client client.deployment_modifications.api = mock_http_client client.executions.api = mock_http_client client.nodes.api = mock_http_client client.node_instances.api = mock_http_client client.manager.api = mock_http_client client.evaluate.api = mock_http_client client.tokens.api = mock_http_client client.events.api = mock_http_client # only exists in v2 and above if CLIENT_API_VERSION != 'v1': client.plugins.api = mock_http_client client.snapshots.api = mock_http_client # only exists in v2.1 and above if CLIENT_API_VERSION != 'v2': client.maintenance_mode.api = mock_http_client client.deployment_updates.api = mock_http_client # only exists in v3 and above if CLIENT_API_VERSION != 'v2.1': client.tenants.api = mock_http_client client.user_groups.api = mock_http_client client.users.api = mock_http_client client.ldap.api = mock_http_client client.secrets.api = mock_http_client return client def setUp(self): self._create_temp_files_and_folders() self._init_file_server() self._mock_amqp_manager() server_module = self._set_config_path_and_get_server_module() self._create_config_and_reset_app(server_module) self._handle_flask_app_and_db(server_module) self.client = self.create_client() self.sm = get_storage_manager() self.initialize_provider_context() def _mock_amqp_manager(self): """ Mock the pyrabbit.Client for all unittests - no RabbitMQ """ self._amqp_patcher = patch('manager_rest.amqp_manager.Client') self.addCleanup(self._amqp_patcher.stop) self._amqp_patcher.start() def _create_temp_files_and_folders(self): self.tmpdir = tempfile.mkdtemp(prefix='fileserver-') fd, self.rest_service_log = tempfile.mkstemp(prefix='rest-log-') os.close(fd) self.maintenance_mode_dir = tempfile.mkdtemp(prefix='maintenance-') fd, self.tmp_conf_file = tempfile.mkstemp(prefix='conf-file-') os.close(fd) def _init_file_server(self): self.file_server = FileServer(self.tmpdir) self.file_server.start() self.addCleanup(self.cleanup) def _set_config_path_and_get_server_module(self): """Workaround for setting the rest service log path, since it's needed when 'server' module is imported. right after the import the log path is set normally like the rest of the variables (used in the reset_state) """ with open(self.tmp_conf_file, 'w') as f: json.dump( { 'rest_service_log_path': self.rest_service_log, 'rest_service_log_file_size_MB': 1, 'rest_service_log_files_backup_count': 1, 'rest_service_log_level': 'DEBUG' }, f) os.environ['MANAGER_REST_CONFIG_PATH'] = self.tmp_conf_file try: from manager_rest import server finally: del (os.environ['MANAGER_REST_CONFIG_PATH']) return server def _create_config_and_reset_app(self, server): """Create config, and reset Flask app :type server: module """ self.server_configuration = self.create_configuration() utils.copy_resources(self.server_configuration.file_server_root) server.SQL_DIALECT = 'sqlite' server.reset_app(self.server_configuration) def _handle_flask_app_and_db(self, server): """Set up Flask app context, and handle DB related tasks :type server: module """ self._set_flask_app_context(server.app) self.app = self._get_app(server.app) self._handle_default_db_config(server) self._setup_anonymous_user(server.app, server.user_datastore) def _set_flask_app_context(self, flask_app): flask_app_context = flask_app.test_request_context() flask_app_context.push() self.addCleanup(flask_app_context.pop) @staticmethod def _handle_default_db_config(server): server.db.create_all() admin_user = get_admin_user() # We're mocking the AMQPManager, as we aren't really using Rabbit here default_tenant = create_default_user_tenant_and_roles( admin_username=admin_user['username'], admin_password=admin_user['password'], amqp_manager=MagicMock()) server.app.config[constants.CURRENT_TENANT_CONFIG] = default_tenant @staticmethod def _get_app(flask_app): """Create a flask.testing FlaskClient :param flask_app: Flask app :return: Our modified version of Flask's test client """ flask_app.test_client_class = TestClient return flask_app.test_client() @staticmethod def _setup_anonymous_user(flask_app, user_datastore): """Change the anonymous user to be admin, in order to have arbitrary access to the storage manager (which otherwise requires a valid user) :param flask_app: Flask app """ admin_user = user_datastore.get_user(get_admin_user()['username']) login_manager = flask_app.extensions['security'].login_manager login_manager.anonymous_user = MagicMock(return_value=admin_user) def cleanup(self): self.quiet_delete(self.rest_service_log) self.quiet_delete(self.tmp_conf_file) self.quiet_delete_directory(self.maintenance_mode_dir) if self.file_server: self.file_server.stop() self.quiet_delete_directory(self.tmpdir) def initialize_provider_context(self): provider_context = models.ProviderContext( id=constants.PROVIDER_CONTEXT_ID, name=self.id(), context={'cloudify': {}}) self.sm.put(provider_context) def create_configuration(self): test_config = config.Config() test_config.test_mode = True test_config.postgresql_db_name = ':memory:' test_config.postgresql_host = '' test_config.postgresql_username = '' test_config.postgresql_password = '' test_config.file_server_root = self.tmpdir test_config.file_server_url = 'http://localhost:{0}'.format( self.file_server.port) test_config.rest_service_log_level = 'DEBUG' test_config.rest_service_log_path = self.rest_service_log test_config.rest_service_log_file_size_MB = 100, test_config.rest_service_log_files_backup_count = 20 test_config.maintenance_folder = self.maintenance_mode_dir test_config.security_hash_salt = 'hash_salt' test_config.security_secret_key = 'secret_key' test_config.security_encoding_alphabet = \ 'L7SMZ4XebsuIK8F6aVUBYGQtW0P12Rn' test_config.security_encoding_block_size = 24 test_config.security_encoding_min_length = 5 return test_config def _version_url(self, url): # method for versionifying URLs for requests which don't go through # the REST client; the version is taken from the REST client regardless if not url.startswith('/api/'): url = '/api/{0}{1}'.format(CLIENT_API_VERSION, url) return url def post(self, resource_path, data, query_params=None): url = self._version_url(resource_path) result = self.app.post(urllib.quote(url), content_type='application/json', data=json.dumps(data), query_string=build_query_string(query_params)) result.json = json.loads(result.data) return result def post_file(self, resource_path, file_path, query_params=None): url = self._version_url(resource_path) with open(file_path) as f: result = self.app.post( urllib.quote(url), data=f.read(), query_string=build_query_string(query_params)) result.json = json.loads(result.data) return result def put_file(self, resource_path, file_path, query_params=None): url = self._version_url(resource_path) with open(file_path) as f: result = self.app.put( urllib.quote(url), data=f.read(), query_string=build_query_string(query_params)) result.json = json.loads(result.data) return result def put(self, resource_path, data=None, query_params=None): url = self._version_url(resource_path) result = self.app.put(urllib.quote(url), content_type='application/json', data=json.dumps(data) if data else None, query_string=build_query_string(query_params)) result.json = json.loads(result.data) return result def patch(self, resource_path, data): url = self._version_url(resource_path) result = self.app.patch(urllib.quote(url), content_type='application/json', data=json.dumps(data)) result.json = json.loads(result.data) return result def get(self, resource_path, query_params=None, headers=None): url = self._version_url(resource_path) result = self.app.get(urllib.quote(url), headers=headers, query_string=build_query_string(query_params)) result.json = json.loads(result.data) return result def head(self, resource_path): url = self._version_url(resource_path) result = self.app.head(urllib.quote(url)) return result def delete(self, resource_path, query_params=None): url = self._version_url(resource_path) result = self.app.delete(urllib.quote(url), query_string=build_query_string(query_params)) result.json = json.loads(result.data) return result def _check_if_resource_on_fileserver(self, folder, container_id, resource_path): url = 'http://localhost:{0}/{1}/{2}/{3}'.format( FILE_SERVER_PORT, folder, container_id, resource_path) try: urllib2.urlopen(url) return True except urllib2.HTTPError: return False def check_if_resource_on_fileserver(self, blueprint_id, resource_path): return self._check_if_resource_on_fileserver( os.path.join(FILE_SERVER_BLUEPRINTS_FOLDER, DEFAULT_TENANT_NAME), blueprint_id, resource_path) def get_blueprint_path(self, blueprint_dir_name): return os.path.join(os.path.dirname(os.path.abspath(__file__)), blueprint_dir_name) def archive_mock_blueprint(self, archive_func=archiving.make_targzfile, blueprint_dir='mock_blueprint'): archive_path = tempfile.mkstemp()[1] source_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), blueprint_dir) archive_func(archive_path, source_dir) return archive_path def get_mock_blueprint_path(self): return os.path.join(os.path.dirname(os.path.abspath(__file__)), 'mock_blueprint', 'blueprint.yaml') def put_blueprint_args(self, blueprint_file_name=None, blueprint_id='blueprint', archive_func=archiving.make_targzfile, blueprint_dir='mock_blueprint'): resource_path = self._version_url('/blueprints/{1}'.format( CLIENT_API_VERSION, blueprint_id)) result = [ resource_path, self.archive_mock_blueprint(archive_func, blueprint_dir), ] if blueprint_file_name: data = {'application_file_name': blueprint_file_name} else: data = {} result.append(data) return result def put_deployment(self, deployment_id='deployment', blueprint_file_name=None, blueprint_id='blueprint', inputs=None, blueprint_dir='mock_blueprint', skip_plugins_validation=None): blueprint_response = self.put_blueprint(blueprint_dir, blueprint_file_name, blueprint_id) blueprint_id = blueprint_response['id'] create_deployment_kwargs = {'inputs': inputs} if skip_plugins_validation is not None: create_deployment_kwargs['skip_plugins_validation'] =\ skip_plugins_validation deployment = self.client.deployments.create(blueprint_id, deployment_id, **create_deployment_kwargs) return blueprint_id, deployment.id, blueprint_response, deployment def put_blueprint(self, blueprint_dir, blueprint_file_name, blueprint_id): blueprint_response = self.put_file(*self.put_blueprint_args( blueprint_file_name, blueprint_id, blueprint_dir=blueprint_dir)).json if 'error_code' in blueprint_response: raise RuntimeError('{}: {}'.format( blueprint_response['error_code'], blueprint_response['message'])) return blueprint_response def upload_plugin(self, package_name, package_version): temp_file_path = self.create_wheel(package_name, package_version) response = self.post_file('/plugins', temp_file_path) os.remove(temp_file_path) return response def create_wheel(self, package_name, package_version): module_src = '{0}=={1}'.format(package_name, package_version) wagon_client = Wagon(module_src) return wagon_client.create( archive_destination_dir=tempfile.gettempdir(), force=True) def wait_for_url(self, url, timeout=5): end = time.time() + timeout while end >= time.time(): try: status = urllib.urlopen(url).getcode() if status == 200: return except IOError: time.sleep(1) raise RuntimeError('Url {0} is not available (waited {1} ' 'seconds)'.format(url, timeout)) @staticmethod def quiet_delete(file_path): try: os.remove(file_path) except: pass @staticmethod def quiet_delete_directory(file_path): shutil.rmtree(file_path, ignore_errors=True) def wait_for_deployment_creation(self, client, deployment_id): env_creation_execution = None deployment_executions = client.executions.list(deployment_id) for execution in deployment_executions: if execution.workflow_id == 'create_deployment_environment': env_creation_execution = execution break if env_creation_execution: self.wait_for_execution(client, env_creation_execution) @staticmethod def wait_for_execution(client, execution, timeout=900): # Poll for execution status until execution ends deadline = time.time() + timeout while True: if time.time() > deadline: raise Exception( 'execution of operation {0} for deployment {1} timed out'. format(execution.workflow_id, execution.deployment_id)) execution = client.executions.get(execution.id) if execution.status in ExecutionState.END_STATES: break time.sleep(3) def _add_blueprint(self, blueprint_id=None): if not blueprint_id: unique_str = str(uuid.uuid4()) blueprint_id = 'blueprint-{0}'.format(unique_str) now = utils.get_formatted_timestamp() blueprint = models.Blueprint(id=blueprint_id, created_at=now, updated_at=now, description=None, plan={'name': 'my-bp'}, main_file_name='aaa') return self.sm.put(blueprint) def _add_deployment(self, blueprint, deployment_id=None): if not deployment_id: unique_str = str(uuid.uuid4()) deployment_id = 'deployment-{0}'.format(unique_str) now = utils.get_formatted_timestamp() deployment = models.Deployment(id=deployment_id, created_at=now, updated_at=now, permalink=None, description=None, workflows={}, inputs={}, policy_types={}, policy_triggers={}, groups={}, scaling_groups={}, outputs={}) deployment.blueprint = blueprint return self.sm.put(deployment) def _add_execution_with_id(self, execution_id): blueprint = self._add_blueprint() deployment = self._add_deployment(blueprint.id) return self._add_execution(deployment.id, execution_id) def _add_execution(self, deployment, execution_id=None): if not execution_id: unique_str = str(uuid.uuid4()) execution_id = 'execution-{0}'.format(unique_str) execution = models.Execution( id=execution_id, status=ExecutionState.TERMINATED, workflow_id='', created_at=utils.get_formatted_timestamp(), error='', parameters=dict(), is_system_workflow=False) execution.deployment = deployment return self.sm.put(execution) def _add_deployment_update(self, deployment, execution, deployment_update_id=None): if not deployment_update_id: unique_str = str(uuid.uuid4()) deployment_update_id = 'deployment_update-{0}'.format(unique_str) now = utils.get_formatted_timestamp() deployment_update = models.DeploymentUpdate( deployment_plan={'name': 'my-bp'}, state='staged', id=deployment_update_id, deployment_update_nodes=None, deployment_update_node_instances=None, deployment_update_deployment=None, modified_entity_ids=None, created_at=now) deployment_update.deployment = deployment if execution: deployment_update.execution = execution return self.sm.put(deployment_update) def _test_invalid_input(self, func, argument, *args): self.assertRaisesRegexp( CloudifyClientError, 'The `{0}` argument contains illegal characters'.format(argument), func, *args)
class BaseServerTestCase(unittest.TestCase): def create_client_with_tenant(self, username, password, tenant=DEFAULT_TENANT_NAME): headers = utils.create_auth_header(username=username, password=password) headers[CLOUDIFY_TENANT_HEADER] = tenant return self.create_client(headers=headers) def create_client(self, headers=None): client = CloudifyClient(host='localhost', headers=headers) mock_http_client = MockHTTPClient(self.app, headers=headers, file_server=self.file_server) client._client = mock_http_client client.blueprints.api = mock_http_client client.deployments.api = mock_http_client client.deployments.outputs.api = mock_http_client client.deployment_modifications.api = mock_http_client client.executions.api = mock_http_client client.nodes.api = mock_http_client client.node_instances.api = mock_http_client client.manager.api = mock_http_client client.evaluate.api = mock_http_client client.tokens.api = mock_http_client client.events.api = mock_http_client # only exists in v2 and above if CLIENT_API_VERSION != 'v1': client.plugins.api = mock_http_client client.snapshots.api = mock_http_client # only exists in v2.1 and above if CLIENT_API_VERSION != 'v2': client.maintenance_mode.api = mock_http_client client.deployment_updates.api = mock_http_client # only exists in v3 and above if CLIENT_API_VERSION != 'v2.1': client.tenants.api = mock_http_client client.user_groups.api = mock_http_client client.users.api = mock_http_client client.ldap.api = mock_http_client client.secrets.api = mock_http_client # only exists in v3.1 and above if CLIENT_API_VERSION != 'v3': client.deployments.capabilities.api = mock_http_client client.agents.api = mock_http_client return client def setUp(self): self._create_temp_files_and_folders() self._init_file_server() self._mock_amqp_modules() server_module = self._set_config_path_and_get_server_module() self._create_config_and_reset_app(server_module) self._mock_get_encryption_key() self._handle_flask_app_and_db(server_module) self.client = self.create_client() self.sm = get_storage_manager() self.initialize_provider_context() self._mock_verify_role() def _mock_verify_role(self): self._original_verify_role = rest_utils.verify_role self.addCleanup(self._restore_verify_role) rest_utils.verify_role = MagicMock() def _restore_verify_role(self): rest_utils.verify_role = self._original_verify_role def _mock_amqp_modules(self): """ Mock RabbitMQ related modules - AMQP manager and workflow executor - that use pika, because we don't have RabbitMQ in the unittests """ amqp_patches = [ patch('manager_rest.amqp_manager.RabbitMQClient'), patch('manager_rest.workflow_executor._execute_task', mock_execute_task), ] for amqp_patch in amqp_patches: self.addCleanup(amqp_patch.stop) amqp_patch.start() def _mock_get_encryption_key(self): """ Mock the _get_encryption_key_patcher function for all unittests """ self._get_encryption_key_patcher = patch( 'cloudify.cryptography_utils._get_encryption_key' ) self.addCleanup(self._get_encryption_key_patcher.stop) self._get_encryption_key = self._get_encryption_key_patcher.start() self._get_encryption_key.return_value = \ config.instance.security_encryption_key def _create_temp_files_and_folders(self): self.tmpdir = tempfile.mkdtemp(prefix='fileserver-') fd, self.rest_service_log = tempfile.mkstemp(prefix='rest-log-') os.close(fd) self.maintenance_mode_dir = tempfile.mkdtemp(prefix='maintenance-') fd, self.tmp_conf_file = tempfile.mkstemp(prefix='conf-file-') os.close(fd) def _init_file_server(self): self.file_server = FileServer(self.tmpdir) self.file_server.start() self.addCleanup(self.cleanup) def _set_config_path_and_get_server_module(self): """Workaround for setting the rest service log path, since it's needed when 'server' module is imported. right after the import the log path is set normally like the rest of the variables (used in the reset_state) """ with open(self.tmp_conf_file, 'w') as f: json.dump({'rest_service_log_path': self.rest_service_log, 'rest_service_log_file_size_MB': 1, 'rest_service_log_files_backup_count': 1, 'rest_service_log_level': 'DEBUG'}, f) os.environ['MANAGER_REST_CONFIG_PATH'] = self.tmp_conf_file try: from manager_rest import server finally: del(os.environ['MANAGER_REST_CONFIG_PATH']) return server def _create_config_and_reset_app(self, server): """Create config, and reset Flask app :type server: module """ self.server_configuration = self.create_configuration() utils.copy_resources(self.server_configuration.file_server_root) server.SQL_DIALECT = 'sqlite' server.reset_app(self.server_configuration) def _handle_flask_app_and_db(self, server): """Set up Flask app context, and handle DB related tasks :type server: module """ self._set_flask_app_context(server.app) self.app = self._get_app(server.app) self._handle_default_db_config(server) self._setup_anonymous_user(server.app, server.user_datastore) def _set_flask_app_context(self, flask_app): flask_app_context = flask_app.test_request_context() flask_app_context.push() self.addCleanup(flask_app_context.pop) @staticmethod def _handle_default_db_config(server): server.db.create_all() admin_user = get_admin_user() fd, temp_auth_file = tempfile.mkstemp() os.close(fd) with open(temp_auth_file, 'w') as f: yaml.dump(auth_dict, f) try: # We're mocking the AMQPManager, we aren't really using Rabbit here default_tenant = create_default_user_tenant_and_roles( admin_username=admin_user['username'], admin_password=admin_user['password'], amqp_manager=MagicMock(), authorization_file_path=temp_auth_file ) default_tenant.rabbitmq_password = encrypt( AMQPManager._generate_user_password() ) finally: os.remove(temp_auth_file) utils.set_current_tenant(default_tenant) @staticmethod def _get_app(flask_app): """Create a flask.testing FlaskClient :param flask_app: Flask app :return: Our modified version of Flask's test client """ flask_app.test_client_class = TestClient return flask_app.test_client() @staticmethod def _setup_anonymous_user(flask_app, user_datastore): """Change the anonymous user to be admin, in order to have arbitrary access to the storage manager (which otherwise requires a valid user) :param flask_app: Flask app """ admin_user = user_datastore.get_user(get_admin_user()['username']) login_manager = flask_app.extensions['security'].login_manager login_manager.anonymous_user = MagicMock(return_value=admin_user) def cleanup(self): self.quiet_delete(self.rest_service_log) self.quiet_delete(self.tmp_conf_file) self.quiet_delete_directory(self.maintenance_mode_dir) if self.file_server: self.file_server.stop() self.quiet_delete_directory(self.tmpdir) def initialize_provider_context(self): provider_context = models.ProviderContext( id=constants.PROVIDER_CONTEXT_ID, name=self.id(), context={'cloudify': {}} ) self.sm.put(provider_context) def create_configuration(self): test_config = config.Config() test_config.test_mode = True test_config.postgresql_db_name = ':memory:' test_config.postgresql_host = '' test_config.postgresql_username = '' test_config.postgresql_password = '' test_config.file_server_root = self.tmpdir test_config.file_server_url = 'http://localhost:{0}'.format( self.file_server.port) test_config.rest_service_log_level = 'DEBUG' test_config.rest_service_log_path = self.rest_service_log test_config.rest_service_log_file_size_MB = 100, test_config.rest_service_log_files_backup_count = 7 test_config.maintenance_folder = self.maintenance_mode_dir test_config.security_hash_salt = 'hash_salt' test_config.security_secret_key = 'secret_key' test_config.security_encoding_alphabet = \ 'L7SMZ4XebsuIK8F6aVUBYGQtW0P12Rn' test_config.security_encoding_block_size = 24 test_config.security_encoding_min_length = 5 test_config.authorization_permissions = auth_dict['permissions'] test_config.security_encryption_key = ( 'lF88UP5SJKluylJIkPDYrw5UMKOgv9w8TikS0Ds8m2UmM' 'SzFe0qMRa0EcTgHst6LjmF_tZbq_gi_VArepMsrmw==' ) return test_config def _version_url(self, url): # method for versionifying URLs for requests which don't go through # the REST client; the version is taken from the REST client regardless if not url.startswith('/api/'): url = '/api/{0}{1}'.format(CLIENT_API_VERSION, url) return url # this method is completely copied from the cli. once caravan sits in a # more general package, it should be removed. @staticmethod def _create_caravan(mappings, dest, name=None): tempdir = tempfile.mkdtemp() metadata = {} for wgn_path, yaml_path in mappings.iteritems(): plugin_root_dir = os.path.basename(wgn_path).split('.', 1)[0] os.mkdir(os.path.join(tempdir, plugin_root_dir)) dest_wgn_path = os.path.join(plugin_root_dir, os.path.basename(wgn_path)) dest_yaml_path = os.path.join(plugin_root_dir, os.path.basename(yaml_path)) shutil.copy(wgn_path, os.path.join(tempdir, dest_wgn_path)) shutil.copy(yaml_path, os.path.join(tempdir, dest_yaml_path)) metadata[dest_wgn_path] = dest_yaml_path with open(os.path.join(tempdir, 'METADATA'), 'w+') as f: yaml.dump(metadata, f) tar_name = name or 'palace' tar_path = os.path.join(dest, '{0}.cvn'.format(tar_name)) tarfile_ = tarfile.open(tar_path, 'w:gz') try: tarfile_.add(tempdir, arcname=tar_name) finally: tarfile_.close() return tar_path def post(self, resource_path, data, query_params=None): url = self._version_url(resource_path) result = self.app.post(urllib.quote(url), content_type='application/json', data=json.dumps(data), query_string=build_query_string(query_params)) return result def post_file(self, resource_path, file_path, query_params=None): url = self._version_url(resource_path) with open(file_path) as f: result = self.app.post(urllib.quote(url), data=f.read(), query_string=build_query_string( query_params)) return result def put_file(self, resource_path, file_path, query_params=None): url = self._version_url(resource_path) with open(file_path) as f: result = self.app.put(urllib.quote(url), data=f.read(), query_string=build_query_string( query_params)) return result def put(self, resource_path, data=None, query_params=None): url = self._version_url(resource_path) result = self.app.put(urllib.quote(url), content_type='application/json', data=json.dumps(data) if data else None, query_string=build_query_string(query_params)) return result def patch(self, resource_path, data): url = self._version_url(resource_path) result = self.app.patch(urllib.quote(url), content_type='application/json', data=json.dumps(data)) return result def get(self, resource_path, query_params=None, headers=None): url = self._version_url(resource_path) result = self.app.get(urllib.quote(url), headers=headers, query_string=build_query_string(query_params)) return result def head(self, resource_path): url = self._version_url(resource_path) result = self.app.head(urllib.quote(url)) return result def delete(self, resource_path, query_params=None): url = self._version_url(resource_path) result = self.app.delete(urllib.quote(url), query_string=build_query_string(query_params)) return result def _check_if_resource_on_fileserver(self, folder, container_id, resource_path): url = 'http://localhost:{0}/{1}/{2}/{3}'.format( FILE_SERVER_PORT, folder, container_id, resource_path) try: urllib2.urlopen(url) return True except urllib2.HTTPError: return False def check_if_resource_on_fileserver(self, blueprint_id, resource_path): return self._check_if_resource_on_fileserver( os.path.join(FILE_SERVER_BLUEPRINTS_FOLDER, DEFAULT_TENANT_NAME), blueprint_id, resource_path) def get_blueprint_path(self, blueprint_dir_name): return os.path.join(os.path.dirname( os.path.abspath(__file__)), blueprint_dir_name) def get_full_path(self, relative_file_path): return os.path.join(os.path.dirname( os.path.abspath(__file__)), relative_file_path) def upload_blueprint(self, client, visibility=VisibilityState.TENANT, blueprint_id='bp_1'): bp_path = self.get_blueprint_path('mock_blueprint/blueprint.yaml') client.blueprints.upload(path=bp_path, entity_id=blueprint_id, visibility=visibility) return blueprint_id def archive_mock_blueprint(self, archive_func=archiving.make_targzfile, blueprint_dir='mock_blueprint'): archive_path = tempfile.mkstemp()[1] source_dir = os.path.join(os.path.dirname( os.path.abspath(__file__)), blueprint_dir) archive_func(archive_path, source_dir) return archive_path def get_mock_blueprint_path(self): return os.path.join(os.path.dirname( os.path.abspath(__file__)), 'mock_blueprint', 'blueprint.yaml') def put_blueprint_args(self, blueprint_file_name=None, blueprint_id='blueprint', archive_func=archiving.make_targzfile, blueprint_dir='mock_blueprint'): resource_path = self._version_url( '/blueprints/{1}'.format(CLIENT_API_VERSION, blueprint_id)) result = [ resource_path, self.archive_mock_blueprint(archive_func, blueprint_dir), ] if blueprint_file_name: data = {'application_file_name': blueprint_file_name} else: data = {} result.append(data) return result def put_deployment(self, deployment_id='deployment', blueprint_file_name=None, blueprint_id='blueprint', inputs=None, blueprint_dir='mock_blueprint', skip_plugins_validation=None): blueprint_response = self.put_blueprint(blueprint_dir, blueprint_file_name, blueprint_id) blueprint_id = blueprint_response['id'] create_deployment_kwargs = {'inputs': inputs} if skip_plugins_validation is not None: create_deployment_kwargs['skip_plugins_validation'] =\ skip_plugins_validation deployment = self.client.deployments.create(blueprint_id, deployment_id, **create_deployment_kwargs) return blueprint_id, deployment.id, blueprint_response, deployment def put_blueprint(self, blueprint_dir, blueprint_file_name, blueprint_id): blueprint_response = self.put_file( *self.put_blueprint_args(blueprint_file_name, blueprint_id, blueprint_dir=blueprint_dir)).json if 'error_code' in blueprint_response: raise RuntimeError( '{}: {}'.format(blueprint_response['error_code'], blueprint_response['message'])) return blueprint_response def _create_wagon_and_yaml(self, package_name, package_version, package_yaml_file='mock_blueprint/plugin.yaml'): temp_file_path = self.create_wheel(package_name, package_version) yaml_path = self.get_full_path(package_yaml_file) return temp_file_path, yaml_path def upload_plugin(self, package_name, package_version, package_yaml='mock_blueprint/plugin.yaml'): wgn_path, yaml_path = self._create_wagon_and_yaml(package_name, package_version, package_yaml) zip_path = self.zip_files([wgn_path, yaml_path]) response = self.post_file('/plugins', zip_path) os.remove(wgn_path) return response def zip_files(self, files): source_folder = tempfile.mkdtemp() destination_zip = source_folder + '.zip' for path in files: shutil.copy(path, source_folder) self.zip(source_folder, destination_zip, include_folder=False) return destination_zip def zip(self, source, destination, include_folder=True): with zipfile.ZipFile(destination, 'w') as zip_file: for root, _, files in os.walk(source): for filename in files: file_path = os.path.join(root, filename) source_dir = os.path.dirname(source) if include_folder \ else source zip_file.write( file_path, os.path.relpath(file_path, source_dir)) return destination def create_wheel(self, package_name, package_version): module_src = '{0}=={1}'.format(package_name, package_version) return wagon.create( module_src, archive_destination_dir=tempfile.gettempdir(), force=True ) def upload_caravan(self, packages): mapping = dict( self._create_wagon_and_yaml( package, version_and_yaml[0], version_and_yaml[1]) for package, version_and_yaml in packages.items() ) caravan_path = self._create_caravan(mapping, tempfile.gettempdir()) response = self.post_file('/plugins', caravan_path) os.remove(caravan_path) return response def wait_for_url(self, url, timeout=5): end = time.time() + timeout while end >= time.time(): try: status = urllib.urlopen(url).getcode() if status == 200: return except IOError: time.sleep(1) raise RuntimeError('Url {0} is not available (waited {1} ' 'seconds)'.format(url, timeout)) @staticmethod def quiet_delete(file_path): try: os.remove(file_path) except Exception: pass @staticmethod def quiet_delete_directory(file_path): shutil.rmtree(file_path, ignore_errors=True) def wait_for_deployment_creation(self, client, deployment_id): env_creation_execution = None deployment_executions = client.executions.list( deployment_id=deployment_id ) for execution in deployment_executions: if execution.workflow_id == 'create_deployment_environment': env_creation_execution = execution break if env_creation_execution: self.wait_for_execution(client, env_creation_execution) @staticmethod def wait_for_execution(client, execution, timeout=900): # Poll for execution status until execution ends deadline = time.time() + timeout while True: if time.time() > deadline: raise Exception( 'execution of operation {0} for deployment {1} timed out'. format(execution.workflow_id, execution.deployment_id)) execution = client.executions.get(execution.id) if execution.status in ExecutionState.END_STATES: break time.sleep(3) def _add_blueprint(self, blueprint_id=None): if not blueprint_id: unique_str = str(uuid.uuid4()) blueprint_id = 'blueprint-{0}'.format(unique_str) now = utils.get_formatted_timestamp() blueprint = models.Blueprint(id=blueprint_id, created_at=now, updated_at=now, description=None, plan={'name': 'my-bp'}, main_file_name='aaa') return self.sm.put(blueprint) def _add_deployment(self, blueprint, deployment_id=None): if not deployment_id: unique_str = str(uuid.uuid4()) deployment_id = 'deployment-{0}'.format(unique_str) now = utils.get_formatted_timestamp() deployment = models.Deployment(id=deployment_id, created_at=now, updated_at=now, permalink=None, description=None, workflows={}, inputs={}, policy_types={}, policy_triggers={}, groups={}, scaling_groups={}, outputs={}) deployment.blueprint = blueprint return self.sm.put(deployment) def _add_execution_with_id(self, execution_id): blueprint = self._add_blueprint() deployment = self._add_deployment(blueprint.id) return self._add_execution(deployment.id, execution_id) def _add_execution(self, deployment, execution_id=None): if not execution_id: unique_str = str(uuid.uuid4()) execution_id = 'execution-{0}'.format(unique_str) execution = models.Execution( id=execution_id, status=ExecutionState.TERMINATED, workflow_id='', created_at=utils.get_formatted_timestamp(), error='', parameters=dict(), is_system_workflow=False) execution.deployment = deployment return self.sm.put(execution) def _add_deployment_update(self, deployment, execution, deployment_update_id=None): if not deployment_update_id: unique_str = str(uuid.uuid4()) deployment_update_id = 'deployment_update-{0}'.format(unique_str) now = utils.get_formatted_timestamp() deployment_update = models.DeploymentUpdate( deployment_plan={'name': 'my-bp'}, state='staged', id=deployment_update_id, deployment_update_nodes=None, deployment_update_node_instances=None, deployment_update_deployment=None, modified_entity_ids=None, created_at=now) deployment_update.deployment = deployment if execution: deployment_update.execution = execution return self.sm.put(deployment_update) def _test_invalid_input(self, func, argument, *args): self.assertRaisesRegexp( CloudifyClientError, 'The `{0}` argument contains illegal characters'.format(argument), func, *args )
class BaseServerTestCase(unittest.TestCase): def __init__(self, *args, **kwargs): super(BaseServerTestCase, self).__init__(*args, **kwargs) def create_client(self, headers=None): client = CloudifyClient(host='localhost', headers=headers) mock_http_client = MockHTTPClient(self.app, headers=headers, file_server=self.file_server) client._client = mock_http_client client.blueprints.api = mock_http_client client.deployments.api = mock_http_client client.deployments.outputs.api = mock_http_client client.deployment_modifications.api = mock_http_client client.executions.api = mock_http_client client.nodes.api = mock_http_client client.node_instances.api = mock_http_client client.manager.api = mock_http_client client.evaluate.api = mock_http_client client.tokens.api = mock_http_client client.events.api = mock_http_client # only exists in v2 and above if CLIENT_API_VERSION != 'v1': client.plugins.api = mock_http_client client.snapshots.api = mock_http_client # only exists in v2.1 and above if CLIENT_API_VERSION != 'v2': client.maintenance_mode.api = mock_http_client client.deployment_updates.api = mock_http_client return client def setUp(self): self.tmpdir = tempfile.mkdtemp(prefix='fileserver-') fd, self.rest_service_log = tempfile.mkstemp(prefix='rest-log-') os.close(fd) fd, self.sqlite_db_file = tempfile.mkstemp(prefix='sqlite-db-') os.close(fd) self.file_server = FileServer(self.tmpdir) self.maintenance_mode_dir = tempfile.mkdtemp(prefix='maintenance-') self.addCleanup(self.cleanup) self.file_server.start() # workaround for setting the rest service log path, since it's # needed when 'server' module is imported. # right after the import the log path is set normally like the rest # of the variables (used in the reset_state) fd, self.tmp_conf_file = tempfile.mkstemp(prefix='conf-file-') os.close(fd) with open(self.tmp_conf_file, 'w') as f: json.dump({'rest_service_log_path': self.rest_service_log, 'rest_service_log_file_size_MB': 1, 'rest_service_log_files_backup_count': 1, 'rest_service_log_level': 'DEBUG'}, f) os.environ['MANAGER_REST_CONFIG_PATH'] = self.tmp_conf_file try: from manager_rest import server finally: del(os.environ['MANAGER_REST_CONFIG_PATH']) self.server_configuration = self.create_configuration() server.SQL_DIALECT = 'sqlite' server.reset_app(self.server_configuration) utils.copy_resources(config.instance.file_server_root) self._flask_app_context = server.app.test_request_context() self._flask_app_context.push() self.addCleanup(self._flask_app_context.pop) self.app = self._get_app(server.app) self.client = self.create_client() server.db.create_all() default_tenant = self._init_default_tenant(server.db, server.app) self.sm = get_storage_manager() self._add_users_and_roles(server.user_datastore, default_tenant) self.initialize_provider_context() @staticmethod def _init_default_tenant(db, app): default_tenant = 'default_tenant' t = Tenant(name=default_tenant) db.session.add(t) db.session.commit() app.config['tenant_id'] = t.id return t @staticmethod def _get_app(flask_app): """Create a flask.testing FlaskClient :param flask_app: Flask app """ flask_app.test_client_class = TestClient return flask_app.test_client() def _add_users_and_roles(self, user_datastore, default_tenant): """Add users and roles for the test :param user_datastore: SQLAlchemyDataUserstore """ # Add a fictitious admin user to the user_datastore utils.add_users_and_roles_to_userstore( user_datastore, self._get_users(), self._get_roles(), default_tenant ) for user in self._get_users(): user = User.query.filter_by(username=user['username']).first() tenant = self.sm.get_tenant_by_name(DEFAULT_TENANT_NAME) if tenant not in user.tenants: user.tenants.append(tenant) self.sm.update_entity(user) @staticmethod def _get_users(): return get_admin_user() @staticmethod def _get_roles(): return get_admin_role() def cleanup(self): self.quiet_delete(self.rest_service_log) self.quiet_delete(self.sqlite_db_file) self.quiet_delete_directory(self.maintenance_mode_dir) if self.file_server: self.file_server.stop() self.quiet_delete_directory(self.tmpdir) def initialize_provider_context(self): self.sm.put_provider_context( {'name': self.id(), 'context': {'cloudify': {}}} ) def create_configuration(self): test_config = config.Config() test_config.test_mode = True test_config.postgresql_db_name = self.sqlite_db_file test_config.postgresql_host = '' test_config.postgresql_username = '' test_config.postgresql_password = '' test_config.file_server_root = self.tmpdir test_config.file_server_base_uri = 'http://localhost:{0}'.format( FILE_SERVER_PORT) test_config.file_server_blueprints_folder = \ FILE_SERVER_BLUEPRINTS_FOLDER test_config.file_server_deployments_folder = \ FILE_SERVER_DEPLOYMENTS_FOLDER test_config.file_server_uploaded_blueprints_folder = \ FILE_SERVER_UPLOADED_BLUEPRINTS_FOLDER test_config.file_server_snapshots_folder = \ FILE_SERVER_SNAPSHOTS_FOLDER test_config.file_server_resources_uri = FILE_SERVER_RESOURCES_URI test_config.rest_service_log_level = 'DEBUG' test_config.rest_service_log_path = self.rest_service_log test_config.rest_service_log_file_size_MB = 100, test_config.rest_service_log_files_backup_count = 20 test_config.maintenance_folder = self.maintenance_mode_dir return test_config def _version_url(self, url): # method for versionifying URLs for requests which don't go through # the REST client; the version is taken from the REST client regardless if CLIENT_API_VERSION not in url: url = '/api/{0}{1}'.format(CLIENT_API_VERSION, url) return url def post(self, resource_path, data, query_params=None): url = self._version_url(resource_path) result = self.app.post(urllib.quote(url), content_type='application/json', data=json.dumps(data), query_string=build_query_string(query_params)) result.json = json.loads(result.data) return result def post_file(self, resource_path, file_path, query_params=None): url = self._version_url(resource_path) with open(file_path) as f: result = self.app.post(urllib.quote(url), data=f.read(), query_string=build_query_string( query_params)) result.json = json.loads(result.data) return result def put_file(self, resource_path, file_path, query_params=None): url = self._version_url(resource_path) with open(file_path) as f: result = self.app.put(urllib.quote(url), data=f.read(), query_string=build_query_string( query_params)) result.json = json.loads(result.data) return result def put(self, resource_path, data=None, query_params=None): url = self._version_url(resource_path) result = self.app.put(urllib.quote(url), content_type='application/json', data=json.dumps(data) if data else None, query_string=build_query_string(query_params)) result.json = json.loads(result.data) return result def patch(self, resource_path, data): url = self._version_url(resource_path) result = self.app.patch(urllib.quote(url), content_type='application/json', data=json.dumps(data)) result.json = json.loads(result.data) return result def get(self, resource_path, query_params=None, headers=None): url = self._version_url(resource_path) result = self.app.get(urllib.quote(url), headers=headers, query_string=build_query_string(query_params)) result.json = json.loads(result.data) return result def head(self, resource_path): url = self._version_url(resource_path) result = self.app.head(urllib.quote(url)) return result def delete(self, resource_path, query_params=None): url = self._version_url(resource_path) result = self.app.delete(urllib.quote(url), query_string=build_query_string(query_params)) result.json = json.loads(result.data) return result def _check_if_resource_on_fileserver(self, folder, container_id, resource_path): url = 'http://localhost:{0}/{1}/{2}/{3}'.format( FILE_SERVER_PORT, folder, container_id, resource_path) try: urllib2.urlopen(url) return True except urllib2.HTTPError: return False def check_if_resource_on_fileserver(self, blueprint_id, resource_path): return self._check_if_resource_on_fileserver( FILE_SERVER_BLUEPRINTS_FOLDER, blueprint_id, resource_path) def get_blueprint_path(self, blueprint_dir_name): return os.path.join(os.path.dirname( os.path.abspath(__file__)), blueprint_dir_name) def archive_mock_blueprint(self, archive_func=archiving.make_targzfile, blueprint_dir='mock_blueprint'): archive_path = tempfile.mkstemp()[1] source_dir = os.path.join(os.path.dirname( os.path.abspath(__file__)), blueprint_dir) archive_func(archive_path, source_dir) return archive_path def get_mock_blueprint_path(self): return os.path.join(os.path.dirname( os.path.abspath(__file__)), 'mock_blueprint', 'blueprint.yaml') def put_blueprint_args(self, blueprint_file_name=None, blueprint_id='blueprint', archive_func=archiving.make_targzfile, blueprint_dir='mock_blueprint'): resource_path = self._version_url( '/blueprints/{1}'.format(CLIENT_API_VERSION, blueprint_id)) result = [ resource_path, self.archive_mock_blueprint(archive_func, blueprint_dir), ] if blueprint_file_name: data = {'application_file_name': blueprint_file_name} else: data = {} result.append(data) return result def put_deployment(self, deployment_id='deployment', blueprint_file_name=None, blueprint_id='blueprint', inputs=None, blueprint_dir='mock_blueprint'): blueprint_response = self.put_file( *self.put_blueprint_args(blueprint_file_name, blueprint_id, blueprint_dir=blueprint_dir)).json if 'error_code' in blueprint_response: raise RuntimeError( '{}: {}'.format(blueprint_response['error_code'], blueprint_response['message'])) blueprint_id = blueprint_response['id'] deployment = self.client.deployments.create(blueprint_id, deployment_id, inputs=inputs) return blueprint_id, deployment.id, blueprint_response, deployment def upload_plugin(self, package_name, package_version): temp_file_path = self.create_wheel(package_name, package_version) response = self.post_file('/plugins', temp_file_path) os.remove(temp_file_path) return response def create_wheel(self, package_name, package_version): module_src = '{0}=={1}'.format(package_name, package_version) wagon_client = Wagon(module_src) return wagon_client.create( archive_destination_dir=tempfile.gettempdir(), force=True) def wait_for_url(self, url, timeout=5): end = time.time() + timeout while end >= time.time(): try: status = urllib.urlopen(url).getcode() if status == 200: return except IOError: time.sleep(1) raise RuntimeError('Url {0} is not available (waited {1} ' 'seconds)'.format(url, timeout)) @staticmethod def quiet_delete(file_path): try: os.remove(file_path) except: pass @staticmethod def quiet_delete_directory(file_path): shutil.rmtree(file_path, ignore_errors=True) def wait_for_deployment_creation(self, client, deployment_id): env_creation_execution = None deployment_executions = client.executions.list(deployment_id) for execution in deployment_executions: if execution.workflow_id == 'create_deployment_environment': env_creation_execution = execution break if env_creation_execution: self.wait_for_execution(client, env_creation_execution) @staticmethod def wait_for_execution(client, execution, timeout=900): # Poll for execution status until execution ends deadline = time.time() + timeout while True: if time.time() > deadline: raise Exception( 'execution of operation {0} for deployment {1} timed out'. format(execution.workflow_id, execution.deployment_id)) execution = client.executions.get(execution.id) if execution.status in Execution.END_STATES: break time.sleep(3) def _add_blueprint(self, blueprint_id=None): if not blueprint_id: unique_str = str(uuid.uuid4()) blueprint_id = 'blueprint-{0}'.format(unique_str) now = utils.get_formatted_timestamp() blueprint = models.Blueprint(id=blueprint_id, created_at=now, updated_at=now, description=None, plan={'name': 'my-bp'}, main_file_name='aaa') return self.sm.put_blueprint(blueprint) def _add_deployment(self, blueprint_id, deployment_id=None): if not deployment_id: unique_str = str(uuid.uuid4()) deployment_id = 'deployment-{0}'.format(unique_str) now = utils.get_formatted_timestamp() deployment = models.Deployment(id=deployment_id, created_at=now, updated_at=now, blueprint_id=blueprint_id, permalink=None, description=None, workflows={}, inputs={}, policy_types={}, policy_triggers={}, groups={}, scaling_groups={}, outputs={}) return self.sm.put_deployment(deployment) def _add_execution_with_id(self, execution_id): blueprint = self._add_blueprint() deployment = self._add_deployment(blueprint.id) return self._add_execution(deployment.id, blueprint.id, execution_id) def _add_execution(self, deployment_id, blueprint_id, execution_id=None): if not execution_id: unique_str = str(uuid.uuid4()) execution_id = 'execution-{0}'.format(unique_str) execution = models.Execution( id=execution_id, status=models.Execution.TERMINATED, deployment_id=deployment_id, workflow_id='', blueprint_id=blueprint_id, created_at=utils.get_formatted_timestamp(), error='', parameters=dict(), is_system_workflow=False) return self.sm.put_execution(execution) def _add_deployment_update(self, blueprint_id, execution_id, deployment_update_id=None): if not deployment_update_id: unique_str = str(uuid.uuid4()) deployment_update_id = 'deployment_update-{0}'.format(unique_str) now = utils.get_formatted_timestamp() deployment_update = models.DeploymentUpdate( deployment_id=blueprint_id, deployment_plan={'name': 'my-bp'}, state='staged', id=deployment_update_id, deployment_update_nodes=None, deployment_update_node_instances=None, deployment_update_deployment=None, modified_entity_ids=None, execution_id=execution_id, created_at=now) return self.sm.put_deployment_update(deployment_update)