def run_subprocess(command, logger, cwd=None, additional_env=None, additional_args=None, return_output=False): if additional_args is None: additional_args = {} if 'max_sleep_time' not in additional_args: additional_args['max_sleep_time'] = 299 args_to_pass = copy.deepcopy(additional_args) if additional_env: passed_env = args_to_pass.setdefault('env', {}) passed_env.update(os.environ) passed_env.update(additional_env) logger.info( "Running: command={cmd}, cwd={cwd}, additional_args={args}".format( cmd=obfuscate_passwords(command), cwd=cwd, args=obfuscate_passwords(args_to_pass))) general_executor_params = copy.deepcopy(args_to_pass) general_executor_params['cwd'] = cwd general_executor_params['log_stdout'] = return_output general_executor_params['log_stderr'] = True general_executor_params['stderr_to_stdout'] = False script_path = command.pop(0) general_executor_params['args'] = command return process_execution( general_executor, script_path, process=general_executor_params)
def _check_response(json, response, is_recoverable): if not is_recoverable: logger.debug('Check response (nonrecoverable) in json: {} by {}' .format(shorted_text(obfuscate_passwords(json)), repr(response))) else: logger.debug('Check response (recoverable) in json: {} by {}' .format(shorted_text(obfuscate_passwords(json)), repr(response))) if not response: return if not isinstance(response, list) and not is_recoverable: raise WrongTemplateDataException( "Response (nonrecoverable) had to be list. " "Type {} not supported. ".format( type(response))) if not isinstance(response, list) and is_recoverable: raise WrongTemplateDataException( "Response (recoverable) had to be list. " "Type {} not supported. ".format( type(response))) if isinstance(response[0], list): for item in response: _check_response(json, item, is_recoverable) else: pattern = response.pop(-1) for key in response: try: json = json[key] except (TypeError, IndexError, KeyError) as e: logger.debug(repr(e)) raise ExpectationException( 'No key or index "{}" in json {}'.format(key, json)) if ( re.match(pattern, "{0}".format(json)) and not is_recoverable ): raise NonRecoverableResponseException( "Giving up... \n" "Response value: " "{} matches regexp:{} from nonrecoverable_response. ".format( json, pattern)) if ( not re.match(pattern, "{0}".format(json)) and is_recoverable ): raise RecoverableResponseException( "Trying one more time...\n" "Response value:{} does not match regexp: {} " "from response_expectation".format( json, pattern))
def execute_as_workflow(*args, **kwargs): # get current context ctx = kwargs.get('ctx', CloudifyContext) if ctx.type != context.DEPLOYMENT: raise NonRecoverableError( "Called with wrong context: {ctx_type}".format(ctx_type=ctx.type)) # register logger file logger_file = kwargs.get('logger_file') if logger_file: fh = logging.FileHandler(logger_file) fh.setLevel(logging.DEBUG) fh.setFormatter( logging.Formatter(fmt="%(asctime)s %(levelname)s %(message)s", datefmt="%Y-%m-%d %H:%M:%S")) ctx.logger.addHandler(fh) # check inputs if len(args): inputs = args[0] else: inputs = kwargs.get('inputs', {}) auth = kwargs.get('auth') params = kwargs.get('params', {}) node_props = kwargs.get('properties', {}) template_file = kwargs.get('template_file') save_path = kwargs.get('save_path') prerender = kwargs.get('prerender', False) remove_calls = kwargs.get('remove_calls', False) if not params: params = {} ctx.logger.debug("Execute as workflow params: {} template: {}".format( repr(obfuscate_passwords(params)), repr(template_file))) params['__inputs__'] = inputs # place for save responses runtime_properties = {} _execute(params=params, template_file=template_file, ctx=ctx, instance_props=runtime_properties, node_props=node_props, save_path=save_path, prerender=prerender, remove_calls=remove_calls, auth=auth, resource_callback=workflow_get_resource, retry_count=kwargs.get('retry_count', 1), retry_sleep=kwargs.get('retry_sleep', 15)) ctx.logger.debug("Final response: {0}".format( repr(obfuscate_passwords(runtime_properties))))
def _update_runtime_properties(ctx, instance_id, properties_updates): manager = get_rest_client() resulted_state = manager.node_instances.get(instance_id) ctx.logger.debug('State before update: {}'.format( repr(obfuscate_passwords(resulted_state)))) ctx.logger.info("Update node: {}".format(instance_id)) runtime_properties = resulted_state.runtime_properties or {} runtime_properties.update(properties_updates) manager.node_instances.update(node_instance_id=instance_id, runtime_properties=runtime_properties, version=resulted_state.version + 1) resulted_state = manager.node_instances.get(instance_id) ctx.logger.debug('State after update: {}'.format( repr(obfuscate_passwords(resulted_state))))
def test_obfuscate_other_secrets(self): call = { 'Token': 'HIDE ME', 'number': -2, 'SECRET': 'HIDE ME', 'Authentication Header': { u'Bearer Token': 'HIDE ME', u'Bearer-Token': 'HIDE ME TOO', }, 'message': 'Hello world!', } self.assertNotIn(u'HIDE ME', u'{0}'.format(filters.obfuscate_passwords(call))) self.assertIn(u'Hello world!', u'{0}'.format(filters.obfuscate_passwords(call)))
def _cleanup_instances(ctx, instance_ids): manager = get_rest_client() for instance_id in instance_ids: resulted_state = manager.node_instances.get(instance_id) ctx.logger.debug('State before update: {}'.format( repr(obfuscate_passwords(resulted_state)))) ctx.logger.info("Cleanup node: {}".format(instance_id)) manager.node_instances.update(node_instance_id=instance_id, runtime_properties={}, state='uninitialized', version=resulted_state.version + 1) resulted_state = manager.node_instances.get(instance_id) ctx.logger.debug('State after update: {}'.format( repr(obfuscate_passwords(resulted_state))))
def execute(*argc, **kwargs): # get current context ctx = kwargs.get('ctx', CloudifyContext) auth = kwargs.get('auth') params = kwargs.get('params', {}) template_file = kwargs.get('template_file') save_path = kwargs.get('save_path') prerender = kwargs.get('prerender', False) remove_calls = kwargs.get('remove_calls', False) if not params: params = {} ctx.logger.debug("Execute params: {} template: {}".format( repr(obfuscate_passwords(params)), repr(template_file))) runtime_properties = ctx.instance.runtime_properties.copy() runtime_properties.update(params) _execute(params=runtime_properties, template_file=template_file, ctx=ctx, instance_props=ctx.instance.runtime_properties, node_props=ctx.node.properties, save_path=save_path, prerender=prerender, remove_calls=remove_calls, auth=auth, resource_callback=ctx.get_resource, retry_count=kwargs.get('retry_count', 1), retry_sleep=kwargs.get('retry_sleep', 15))
def _run_operation(ctx, sequence, operation, **kwargs): ctx.logger.debug("Run {}({})".format(operation, repr(obfuscate_passwords(kwargs)))) include_node_types = kwargs.get('include_node_types', []) exclude_node_types = kwargs.get('exclude_node_types', []) include_instances = kwargs.get('include_instances', []) for node in ctx.nodes: # check by node type if not _check_type(node, include_node_types, exclude_node_types): continue # check for skipped actions skip_actions = node.properties.get("skip_actions", []) if skip_actions: if operation in skip_actions: continue if operation in node.operations: for instance in node.instances: # check for skip instances if include_instances: if instance.id not in include_instances: continue # add to run operation sequence.add( instance.send_event('Starting to {}'.format(operation)), instance.execute_operation(operation, kwargs=kwargs), instance.send_event('Done {}'.format(operation)))
def test_obfuscate_passwords_dont_copy(self): call = { 'host': 'localhost', 'port': -2, 'response_translation': { "object": 'object_id' } } self.assertIs(filters.obfuscate_passwords(call), call)
def get_token(self): token = self._get_token() if not token: raise HelmKuberentesAuthenticationError( 'Cannot generate token use {variant} for data:' ' {auth_data} '.format( variant=self.__class__.__name__, auth_data=obfuscate_passwords(self.authentication_data)) ) return token
def test_obfuscate_json_string(self): call = """{ "Token": "HIDE ME", "number": -2, "SECRET": "HIDE ME", "Authentication Header": { "Bearer Token": "HIDE ME", "Bearer-Token": "HIDE ME TOO", }, "message": "Hello world!", "src_registry_password": { "value": "some_value" }, "client_secret": {"secret": "b4611ohg_k0vtazwr8jn8h88rcg2a98odapqzev-"}, "array_password": ["first_val", "second_val"], "true_false_token": true, "null_password": null, "set_token": ("firstToken","secondToken","thirdToken"), "number_secret": 123.456, "weird_password": "******", "array2_password": [123.123], "array3_password": [false], "array4_password": [true, false], "array5_password": [123.123,456.335,654.23], "dict2_password": {"x":true}, }""" obfuscated_call = """{ "Token": "xxxxxxxxxxxxxxxx", "number": -2, "SECRET": "xxxxxxxxxxxxxxxx", "Authentication Header": { "Bearer Token": "xxxxxxxxxxxxxxxx", "Bearer-Token": "xxxxxxxxxxxxxxxx", }, "message": "Hello world!", "src_registry_password": { "value": "some_value" }, "client_secret": {"secret": "xxxxxxxxxxxxxxxx"}, "array_password": ["first_val", "second_val"], "true_false_token": true, "null_password": null, "set_token": ("firstToken","secondToken","thirdToken"), "number_secret": 123.456, "weird_password": "******", "array2_password": [123.123], "array3_password": [false], "array4_password": [true, false], "array5_password": [123.123,456.335,654.23], "dict2_password": {"x":true}, }""" self.assertEqual(filters.obfuscate_passwords(call), obfuscated_call)
def test_obfuscate_passwords_deep(self): call = { 'host': 'localhost', 'port': -2, 'PAssword': '******', 'deeper': { u'password': '******' }, 'list': [{ 'url': 'https://example.com/', 'auth': { 'username': '******', 'PASSWORD': '******', } }, { 'password': '******' }], 'what': { 'should': { 'you': { 'do': { 'to': { 'go': { 'this': { 'deep': { 'password': '******' } } } } } } } }, } self.assertNotIn('HIDE ME', u'{0}'.format(filters.obfuscate_passwords(call)))
def bunch_execute(templates=None, **kwargs): # get current context ctx = kwargs.get('ctx', CloudifyContext) auth = kwargs.get('auth') for template in templates or []: params = template.get('params', {}) template_file = template.get('template_file') prerender = template.get('prerender') save_to = template.get('save_to') params_attributes = template.get('params_attributes') remove_calls = template.get('remove_calls') ctx.logger.info('Processing: {template_file}'.format( template_file=repr(template_file))) runtime_properties = {} if params: runtime_properties.update(params) if params_attributes: runtime_properties.update( _get_params_attributes(ctx, ctx.instance.runtime_properties, params_attributes)) ctx.logger.debug('Params: {params}'.format( params=repr(obfuscate_passwords(runtime_properties)))) runtime_properties["ctx"] = ctx _execute(params=runtime_properties, template_file=template_file, ctx=ctx, instance_props=ctx.instance.runtime_properties, node_props=ctx.node.properties, save_path=save_to, prerender=prerender, remove_calls=remove_calls, auth=auth, resource_callback=ctx.get_resource, retry_count=kwargs.get('retry_count', 1), retry_sleep=kwargs.get('retry_sleep', 15)) else: ctx.logger.debug('No calls.')
def _scaledown_group_to_settings(ctx, list_scale_groups, scale_compute): scale_settings = {} for scalable_entity_name in list_scale_groups: delta = list_scale_groups[scalable_entity_name]['count'] instances_remove = list_scale_groups[scalable_entity_name]['values'] ctx.logger.info('Scale down {} by delta: {}' .format(repr(scalable_entity_name), repr(delta))) if delta == 0: ctx.logger.info('delta parameter is 0, so no scaling will ' 'take place.') continue scaling_group = ctx.deployment.scaling_groups.get(scalable_entity_name) if scaling_group: curr_num_instances = ( scaling_group['properties']['current_instances'] ) planned_num_instances = curr_num_instances - delta scale_id = scalable_entity_name else: node = ctx.get_node(scalable_entity_name) if not node: raise ValueError("No scalable entity named {0} was found" .format(scalable_entity_name)) host_node = node.host_node scaled_node = host_node if (scale_compute and host_node) else node curr_num_instances = scaled_node.number_of_instances planned_num_instances = curr_num_instances - delta scale_id = scaled_node.id scale_settings[scale_id] = { 'instances': planned_num_instances, 'removed_ids_include_hint': instances_remove, } ctx.logger.info( 'Scale settings: {}'.format(repr(obfuscate_passwords(scale_settings)))) return scale_settings
def test_obfuscate_passwords(self): call = { 'host': 'localhost', 'auth': { 'user': '******', 'password': '******' }, 'port': -1, 'response_translation': { "object": ["object_id"] } } obfuscated_call = { 'host': 'localhost', 'auth': { 'user': '******', 'password': '******' }, 'port': -1, 'response_translation': { "object": ["object_id"] } } self.assertEqual(filters.obfuscate_passwords(call), obfuscated_call)
def _get_token(self): self.logger.debug('Checking Kubernetes authentication options.') for variant in self.VARIANTS: try: candidate = variant(self.logger, self.authentication_data) \ .get_token() self.logger.debug( 'Authentication option {variant} will be used'.format( variant=variant.__name__) ) return candidate except HelmKuberentesAuthenticationError: self.logger.debug( 'Authentication option {variant} cannot be used'.format( variant=variant.__name__) ) self.logger.debug( 'Cannot generate Bearer token - no suitable authentication ' 'variant found for {props} properties'.format( props=obfuscate_passwords(self.authentication_data)) ) return None
def scaledownlist(ctx, scale_compute=False, ignore_failure=False, force_db_cleanup=False, scale_transaction_field=u'', scale_node_name=None, scale_node_field=u'', scale_node_field_value=u'', all_results=False, node_sequence=None, **_): if not scale_node_field: raise ValueError('You should provide `scale_node_field` for correct' 'downscale.') if isinstance(scale_node_field_value, text_type): scale_node_field_value = [scale_node_field_value] ctx.logger.debug("Filter by values list: {}.".format( repr(obfuscate_passwords(scale_node_field_value)))) if not scale_node_name: scale_node_name = None ctx.logger.debug("Will be searched by all instances.") if isinstance(scale_node_name, text_type): scale_node_name = [scale_node_name] if isinstance(scale_node_field, text_type): scale_node_field = [scale_node_field] instances, instance_ids = _get_transaction_instances( ctx=ctx, scale_transaction_field=scale_transaction_field, scale_node_names=scale_node_name, scale_node_field_path=scale_node_field, scale_node_field_values=scale_node_field_value, all_results=all_results) if not instance_ids: ctx.logger.info("Empty list for instances for remove.") return # we have list of instances_id(string) as part of scale dictionary scale_settings = _scaledown_group_to_settings( ctx, _get_scale_list(ctx, instances, text_type), scale_compute) try: _run_scale_settings(ctx, scale_settings, {}, instances_remove_ids=instance_ids, ignore_failure=ignore_failure, node_sequence=node_sequence) except Exception as e: ctx.logger.info('Scale down based on transaction failed: {}'.format( repr(e))) # check list for forced remove removed = [] for node in ctx.nodes: for instance in node.instances: if instance.id in instance_ids: removed.append(instance) _uninstall_instances(ctx=ctx, graph=ctx.graph_mode(), removed=removed, related=[], ignore_failure=ignore_failure, node_sequence=node_sequence) # remove from DB if force_db_cleanup: ctx.logger.warn('Ignoring force_db_cleanup. Deprecated feature.')
def process(params, template, request_props, prerender=False, resource_callback=False): logger.info( 'Template:\n{}'.format(shorted_text(obfuscate_passwords(template)))) if prerender: rendered_call = render_template(template, params) template_yaml = yaml.safe_load(rendered_call) else: template_yaml = yaml.safe_load(template) result_properties = {} calls = [] if not template_yaml or not template_yaml.get('rest_calls'): logger.debug('Empty call list') return {} for call in template_yaml['rest_calls']: call_with_request_props = request_props.copy() logger.debug( 'Call: {}'.format(shorted_text(obfuscate_passwords(call)))) # enrich params with items stored in runtime props by prev calls params.update(result_properties) if not prerender: call = "{0}".format(call) # Remove quotation marks before and after jinja blocks call = re.sub(r'\'\{\%', '{%', call) call = re.sub(r'\%\}\'', '%}', call) rendered_call = render_template(call, params) call = ast.literal_eval(rendered_call) calls.append(call) logger.debug('Rendered call: {}'.format( shorted_text(obfuscate_passwords(call)))) call_with_request_props.update(call) # client/server side certification check file_to_remove = [] for field in ['verify', 'cert']: if isinstance(call_with_request_props.get(field), string_types): if not os.path.isfile(call_with_request_props.get(field)): fd, destination = tempfile.mkstemp() os.write(fd, call_with_request_props.get(field).encode()) os.close(fd) # replace to path to content call_with_request_props[field] = destination file_to_remove.append(destination) # run requests try: response = _send_request(call_with_request_props, resource_callback=resource_callback) finally: for path in file_to_remove: try: os.remove(path) except Exception as e: logger.debug( 'Cant remove temporary file {path}: {error}' .format(path=path, error=repr(e)) ) _process_response(response, call, result_properties) result_properties = {'result_properties': result_properties, 'calls': calls} return result_properties
def _execute(ctx, properties, runtime_properties, get_resource, host_ip, log_stamp, kwargs): # register logger file logger_file = kwargs.get('logger_file') if logger_file: fh = logging.FileHandler(logger_file) fh.setLevel(logging.DEBUG) fh.setFormatter( logging.Formatter(fmt="%(asctime)s %(levelname)s %(message)s", datefmt="%Y-%m-%d %H:%M:%S")) ctx.logger.addHandler(fh) # get current calls calls = kwargs.get('calls', []) if not calls: ctx.logger.info("No calls") return # credentials terminal_auth = properties.get('terminal_auth', {}) terminal_auth.update(kwargs.get('terminal_auth', {})) ip_list = terminal_auth.get('ip') # if node contained in some other node, try to overwrite ip if not ip_list and host_ip: ip_list = [host_ip] ctx.logger.info("Used host from container: {ip_list}".format( ip_list=repr(ip_list))) if isinstance(ip_list, bytes): ip_list = ip_list.decode('UTF-8') if isinstance(ip_list, text_type): ip_list = [ip_list] user = terminal_auth.get('user') password = terminal_auth.get('password') key_content = terminal_auth.get('key_content') port = terminal_auth.get('port', 22) if not ip_list or not user: raise cfy_exc.NonRecoverableError( "please check your credentials, ip or user not set") # additional settings global_promt_check = terminal_auth.get('promt_check') global_warning_examples = terminal_auth.get('warnings', []) global_error_examples = terminal_auth.get('errors', []) global_critical_examples = terminal_auth.get('criticals', []) global_responses = terminal_auth.get('responses', []) exit_command = terminal_auth.get('exit_command', 'exit') smart_device = terminal_auth.get('smart_device') # save logs to debug file log_file_name = None if terminal_auth.get('store_logs'): log_file_name = "/tmp/terminal-{log_stamp}.log".format( log_stamp=log_stamp) ctx.logger.info("Communication logs will be saved to %s" % log_file_name) if smart_device: ctx.logger.info("Used ssh shell extension.") connection = terminal_connection.SmartConnection( logger=ctx.logger, log_file_name=log_file_name) else: ctx.logger.info("Used raw stream connection.") connection = terminal_connection.RawConnection( logger=ctx.logger, log_file_name=log_file_name) for ip in ip_list: try: prompt = connection.connect(ip, user, password, key_content, port, prompt_check=global_promt_check, responses=global_responses) ctx.logger.info("Will be used: " + ip) break except Exception as ex: ctx.logger.info( "Can't connect to:{} with exception:{} and type:{}".format( repr(ip), str(ex), str(type(ex)))) else: raise cfy_exc.OperationRetry(message="Let's try one more time?") ctx.logger.info( "Device prompt: {prompt}".format(prompt=shorted_text(prompt))) for call in calls: responses = call.get('responses', global_responses) promt_check = call.get('promt_check', global_promt_check) error_examples = call.get('errors', global_error_examples) warning_examples = call.get('warnings', global_warning_examples) critical_examples = call.get('criticals', global_critical_examples) # use action if exist operation = call.get('action', "") # use template if have if not operation and 'template' in call: template_name = call.get('template') template_params = call.get('params') template = get_resource(template_name) if not template: ctx.logger.info("Empty template.") continue if not template_params: template_params = {} # save context for reuse in template template_params['ctx'] = ctx operation = render_template(template.decode('utf-8'), template_params) # incase of template_text if not operation and 'template_text' in call: template_params = call.get('params') template = call.get('template_text') if not template: ctx.logger.info("Empty template_text.") continue if not template_params: template_params = {} # save context for reuse in template template_params['ctx'] = ctx operation = render_template(template, template_params) if not operation: continue if responses: ctx.logger.info("We have predefined responses: {responses}".format( responses=shorted_text(responses))) ctx.logger.debug("Template: \n{operation}".format( operation=shorted_text(obfuscate_passwords(operation)))) result = "" for op_line in operation.split("\n"): # skip empty lines if not op_line.strip(): continue ctx.logger.info("Executing template...") ctx.logger.debug("Execute: {opline}".format( opline=shorted_text(obfuscate_passwords(op_line)))) result_part = rerun(ctx=ctx, func=connection.run, args=[], kwargs={ "command": op_line, "prompt_check": promt_check, "error_examples": error_examples, "warning_examples": warning_examples, "critical_examples": critical_examples, "responses": responses }, retry_count=call.get('retry_count', 10), retry_sleep=call.get('retry_sleep', 15)) if result_part.strip(): ctx.logger.info(shorted_text(result_part)) result += (result_part + "\n") # save results to runtime properties save_to = call.get('save_to') if save_to: ctx.logger.info( "For save: {result}".format(result=shorted_text(result))) runtime_properties[save_to] = result.strip() while not connection.is_closed() and exit_command: ctx.logger.info("Execute close") result = rerun(ctx=ctx, func=connection.run, args=[], kwargs={ "command": exit_command, "prompt_check": promt_check, "warning_examples": global_warning_examples, "error_examples": global_error_examples, "critical_examples": global_error_examples }) ctx.logger.info( "Result of close: {result}".format(result=shorted_text(result))) time.sleep(1) connection.close()
def _get_scale_list(ctx, scalable_entity_properties, property_type): # scalable_entity_properties - dictionary with such structure: # { # node_name: [{runtime_properties}] # } # property_type - kind of values inside list of node names(types). scalable_entity_dict = {} scaling_groups = ctx.deployment.scaling_groups groups = _deployments_get_groups(ctx) ctx.logger.debug("Scale entities: {}".format( repr(scalable_entity_properties))) if not isinstance(scalable_entity_properties, dict): raise ValueError( "You use wrong value for 'scalable_entity_properties': {}".format( repr(scalable_entity_properties))) for node_name in scalable_entity_properties: # get node counts node_amount = len(scalable_entity_properties[node_name]) if not isinstance(scalable_entity_properties[node_name], list): raise ValueError( "You use wrong value for 'scalable_entity_properties' item: {}" .format(repr(scalable_entity_properties[node_name]))) for el in scalable_entity_properties[node_name]: if not isinstance(el, property_type): raise ValueError( "You use wrong value for runtime properties item: {}". format(repr(scalable_entity_properties[node_name]))) # get parent group for scalegroup in groups: # check that we really have such scalling group if scalegroup not in scaling_groups: continue # check node in nodes group if node_name in groups[scalegroup]['members']: # not selected if scalegroup not in scalable_entity_dict: scalable_entity_dict[scalegroup] = { 'count': 0, 'values': [] } # already have have such group, scale by max value if scalable_entity_dict[scalegroup]['count'] < node_amount: scalable_entity_dict[scalegroup]['count'] = node_amount # save instance id's for scale down workflow # ignored for scale up scalable_entity_dict[scalegroup]['values'] += ( scalable_entity_properties[node_name]) break else: # no such group if node_name not in scalable_entity_dict: scalable_entity_dict[node_name] = {'count': 0, 'values': []} scalable_entity_dict[node_name]['count'] = node_amount scalable_entity_dict[node_name]['values'] += ( scalable_entity_properties[node_name]) ctx.logger.info("Scale rules: {}".format( repr(obfuscate_passwords(scalable_entity_dict)))) return scalable_entity_dict
def _run_scale_settings(ctx, scale_settings, scalable_entity_properties, scale_transaction_field=None, scale_transaction_value=None, ignore_failure=False, ignore_rollback_failure=True, instances_remove_ids=None, node_sequence=None): modification = ctx.deployment.start_modification(scale_settings) ctx.refresh_node_instances() graph = ctx.graph_mode() try: ctx.logger.info('Deployment modification started. ' '[modification_id={0}]'.format(modification.id)) if len(set(modification.added.node_instances)): ctx.logger.info('Added: {}'.format( repr([ node_instance._node_instance.id for node_instance in modification.added.node_instances if node_instance.modification == 'added' ]))) added_and_related = set(modification.added.node_instances) added = set(i for i in added_and_related if i.modification == 'added') related = added_and_related - added try: for node_instance in added: properties_updates = scalable_entity_properties.get( node_instance._node_instance.node_id, {}) # save properties updates properties = {} if properties_updates: # pop one dict for runtime properties properties.update(properties_updates.pop()) # save transaction list if scale_transaction_field: # save original set of instances in scale up. if scale_transaction_value: properties.update({ scale_transaction_field: scale_transaction_value }) else: properties.update( {scale_transaction_field: modification.id}) # check properties to update if properties: ctx.logger.debug( "{}: Updating {} runtime properties by {}".format( node_instance._node_instance.node_id, node_instance._node_instance.id, repr(obfuscate_passwords(properties)))) _update_runtime_properties( ctx, node_instance._node_instance.id, properties) if node_sequence: subgraph_func = lifecycle.install_node_instance_subgraph _process_node_instances( ctx=ctx, graph=graph, node_instances=added, ignore_failure=ignore_failure, node_instance_subgraph_func=subgraph_func, node_sequence=node_sequence) else: lifecycle.install_node_instances(graph=graph, node_instances=added, related_nodes=related) except Exception as ex: ctx.logger.error( 'Scale out failed, scaling back in. {}'.format(repr(ex))) _wait_for_sent_tasks(ctx, graph) _uninstall_instances(ctx=ctx, graph=graph, removed=added, related=related, ignore_failure=ignore_rollback_failure, node_sequence=node_sequence) raise ex if len(set(modification.removed.node_instances)): ctx.logger.info('Removed: {}'.format( repr([ node_instance._node_instance.id for node_instance in modification.removed.node_instances if node_instance.modification == 'removed' ]))) removed_and_related = set(modification.removed.node_instances) removed = set(i for i in removed_and_related if i.modification == 'removed') ctx.logger.info('Proposed: {}'.format(repr(instances_remove_ids))) if instances_remove_ids: for instance in removed: if instance._node_instance.id not in instances_remove_ids: raise Exception( "Instance {} not in proposed list {}.".format( repr(instance._node_instance.id), repr(instances_remove_ids))) related = removed_and_related - removed _uninstall_instances(ctx=ctx, graph=graph, removed=removed, ignore_failure=ignore_failure, related=related, node_sequence=node_sequence) except Exception as ex: ctx.logger.warn('Rolling back deployment modification. ' '[modification_id={0}]: {1}'.format( modification.id, repr(ex))) _wait_for_sent_tasks(ctx, graph) modification.rollback() raise ex else: modification.finish()
def _process_response(response, call, store_props): logger.debug('Process Response: {}'.format(shorted_text(response))) logger.debug( 'Call: {}'.format(shorted_text(obfuscate_passwords(call)))) logger.debug('Store props: {}'.format(shorted_text(store_props))) logger.debug('Store headers: {}'.format(shorted_text(response.headers))) translation_version = call.get('translation_format', 'auto') # process headers if response.headers: translate_and_save(logger, response.headers, call.get('header_translation', None), store_props, translation_version) # process cookies if response.cookies: translate_and_save(logger, response.cookies.get_dict(), call.get('cookies_translation', None), store_props, translation_version) # process body response_format = call.get('response_format', 'auto').lower() if response_format == 'auto': if response.headers.get('Content-Type'): response_content_type = response.headers['Content-Type'].lower() if ( response_content_type.startswith("application/json") or response_content_type.startswith("text/json") ): response_format = 'json' elif ( response_content_type.startswith('text/xml') or response_content_type.startswith('application/xml') ): response_format = 'xml' logger.debug('Detected type is {}'.format(repr(response_format))) # for backward compatibility set json for unknown types if response_format == 'auto': response_format = 'json' logger.debug('Response format is {}'.format(repr(response_format))) if response_format == 'json' or response_format == 'xml': if response_format == 'json': json = response.json() else: # XML json = xmltodict.parse(response.text) logger.debug('XML transformed to dict: {}' .format(shorted_text(obfuscate_passwords(json)))) _check_response(json, call.get('nonrecoverable_response'), False) _check_response(json, call.get('response_expectation'), True) translate_and_save(logger, json, call.get('response_translation', None), store_props, translation_version) elif response_format == 'text': store_props['text'] = response.text elif response_format == 'raw': logger.debug('No action for raw response_format') else: raise WrongTemplateDataException( "Response_format {} is not supported. " "Only json/xml or raw response_format is supported".format( repr(response_format)))
def run_workflow(*args, **kwargs): # get current context ctx = kwargs.get('ctx', CloudifyContext) if ctx.type != context.DEPLOYMENT: raise cfy_exc.NonRecoverableError( "Called with wrong context: {ctx_type}".format(ctx_type=ctx.type)) # register logger file logger_file = kwargs.get('logger_file') if logger_file: fh = logging.FileHandler(logger_file) fh.setLevel(logging.DEBUG) fh.setFormatter( logging.Formatter(fmt="%(asctime)s %(levelname)s %(message)s", datefmt="%Y-%m-%d %H:%M:%S")) ctx.logger.addHandler(fh) # check inputs if len(args): inputs = args[0] else: inputs = kwargs.get('inputs', {}) # dump current parameters ctx.logger.debug( "Workflow run called with {inputs} and args '{args}' and kwargs:" " {kwargs}".format(inputs=repr(obfuscate_passwords(inputs)), args=repr(obfuscate_passwords(args)), kwargs=repr(obfuscate_passwords(kwargs)))) # check deployment id, strange if empty but lets check deployment_id = inputs.get('deployment_id') if not deployment_id: ctx.logger.error("Deployment id is undefined") return # get workflow name workflow_name = kwargs.get('workflow_for_run') if not workflow_name: ctx.logger.error("Workflow for run is undefined") return # get workflow params workflow_params = kwargs.get('workflow_params', {}) # get credentials for rest connection to manager, can be used for run # workflow with different user/tenant client_config = kwargs.get('client_config', {}) if client_config: client = CloudifyClient(**client_config) else: # get client from current manager client = manager.get_rest_client() # get deployment information deployment = client.deployments.get(deployment_id=deployment_id) if not deployment: ctx.logger.error("Deployment disappear.") return # get filter dictionary filter_by = kwargs.get('filter_by', []) ctx.logger.debug("Filter {filter_by}".format(filter_by=repr(filter_by))) if filter_by: # get values from deployment information, use _get_ # for support 5+ managers inputs['deployment_inputs'] = deployment.get('inputs', {}) inputs['deployment_outputs'] = deployment.get('outputs', {}) inputs['deployment_capabilities'] = deployment.get('capabilities', {}) if not _check_filter(ctx=ctx, filter_by=filter_by, inputs=inputs): ctx.logger.debug("Event skiped by filter.") return # mark that we going to run to logs ctx.logger.info("Going to {workflow_name} on {deployment_id}".format( workflow_name=workflow_name, deployment_id=deployment_id)) # send uninstall event client.executions.start(deployment_id=deployment_id, workflow_id=workflow_name, **workflow_params)
def execute_operation(ctx, operation, operation_kwargs, allow_kwargs_override, run_by_dependency_order, type_names, node_ids, node_instance_ids, node_field, node_field_value, **kwargs): """ A generic workflow for executing arbitrary operations on nodes """ if isinstance(node_field_value, text_type): node_field_value = [node_field_value] ctx.logger.debug("Filter by values list: {}.".format( repr(obfuscate_passwords(node_field_value)))) graph = ctx.graph_mode() subgraphs = {} if isinstance(node_field, text_type): node_field = [node_field] # filtering node instances filtered_node_instances = _filter_node_instances( ctx=ctx, node_ids=node_ids, node_instance_ids=node_instance_ids, type_names=type_names, operation=operation, node_field_path=node_field, node_field_value=node_field_value) if run_by_dependency_order: # if run by dependency order is set, then create stub subgraphs for the # rest of the instances. This is done to support indirect # dependencies, i.e. when instance A is dependent on instance B # which is dependent on instance C, where A and C are to be executed # with the operation on (i.e. they're in filtered_node_instances) # yet B isn't. # We add stub subgraphs rather than creating dependencies between A # and C themselves since even though it may sometimes increase the # number of dependency relationships in the execution graph, it also # ensures their number is linear to the number of relationships in # the deployment (e.g. consider if A and C are one out of N instances # of their respective nodes yet there's a single instance of B - # using subgraphs we'll have 2N relationships instead of N^2). filtered_node_instances_ids = set(inst.id for inst in filtered_node_instances) for instance in ctx.node_instances: if instance.id not in filtered_node_instances_ids: subgraphs[instance.id] = graph.subgraph(instance.id) # preparing the parameters to the execute_operation call exec_op_params = {'kwargs': operation_kwargs, 'operation': operation} if allow_kwargs_override is not None: exec_op_params['allow_kwargs_override'] = allow_kwargs_override # registering actual tasks to sequences for instance in filtered_node_instances: start_event_message = 'Starting operation {0}'.format(operation) if operation_kwargs: start_event_message += ' (Operation parameters: {0})'.format( repr(operation_kwargs)) subgraph = graph.subgraph(instance.id) sequence = subgraph.sequence() sequence.add( instance.send_event(start_event_message), instance.execute_operation(**exec_op_params), instance.send_event('Finished operation {0}'.format(operation))) subgraphs[instance.id] = subgraph # adding tasks dependencies if required if run_by_dependency_order: for instance in ctx.node_instances: for rel in instance.relationships: graph.add_dependency(subgraphs[instance.id], subgraphs[rel.target_id]) graph.execute()
def _send_request(call, resource_callback=None): logger.debug( 'Request props: {}'.format(shorted_text(obfuscate_passwords(call)))) port = call['port'] ssl = call['ssl'] if port == -1: port = 443 if ssl else 80 if not call.get('hosts', None): call['hosts'] = [call['host']] for i, host in enumerate(call['hosts']): full_url = '{}://{}:{}{}'.format('https' if ssl else 'http', host, port, call['path']) logger.debug('Full url: {}'.format(repr(full_url))) # check if payload can be used as json payload_format = call.get('payload_format', 'json') payload_data = call.get('payload', None) # check that we have some raw payload payload_raw = call.get('payload_raw', call.get('raw_payload')) if resource_callback and payload_raw: payload_data = resource_callback(payload_raw) # url params params = call.get('params', {}) # files magic files_merged = {} files = {} files_raw = call.get("files_raw", call.get("raw_files", {})) # add all raw files for name in files_raw: files_merged[name] = resource_callback(files_raw[name]) # add inline files files_merged.update(call.get("files", {})) logger.debug('Files merged: {files_merged}' .format(files_merged=shorted_text(files_merged))) # convert files strcut to correct type for name in files_merged: if isinstance(files_merged[name], list): # convert to correct struct files[name] = tuple(files_merged[name]) elif isinstance(files_merged[name], string_types): # send string as file files[name] = StringIO(files_merged[name]) else: # let's request decide about format files[name] = files_merged[name] logger.debug('Files: {files}' .format(files=shorted_text(files))) # combine payloads and params if payload_format == 'json': json_payload = payload_data data = None elif payload_format == 'urlencoded' and isinstance(payload_data, dict): json_payload = None params.update(payload_data) data = None else: json_payload = None data = payload_data # auth if 'auth' not in call: auth = None else: auth = (call['auth'].get('user'), call['auth'].get('password')) # run request try: response = requests.request(call['method'], full_url, auth=auth, headers=call.get('headers', None), verify=call.get('verify', True), cert=call.get('cert', None), proxies=call.get('proxies', None), timeout=call.get('timeout', None), json=json_payload, params=params, files=files if files else None, data=data) except requests.exceptions.ConnectionError as e: logger.debug('ConnectionError for host: {}'.format(repr(host))) if TEMPLATE_PROPERTY_RETRY_ON_CONNECTION_ERROR in call and \ call[TEMPLATE_PROPERTY_RETRY_ON_CONNECTION_ERROR]: raise RecoverableResponseException( 'ConnectionError {0} has occurred, but flag {1} is set. ' 'Retrying...' .format( repr(e), TEMPLATE_PROPERTY_RETRY_ON_CONNECTION_ERROR ) ) if i == len(call['hosts']) - 1: logger.error('No host from list available') raise logger.info('Response content: \n{}...' .format(shorted_text(response.content))) logger.info('Status code: {}'.format(repr(response.status_code))) try: response.raise_for_status() except requests.exceptions.HTTPError as e: logger.debug(repr(e)) if response.status_code in call.get('recoverable_codes', []): raise RecoverableStatusCodeCodeException( 'Response code {} defined as recoverable'.format( response.status_code)) if response.status_code not in call.get('successful_codes', []): # code is not marked as successful raise # success? logger.debug( 'Response code {} defined as successful.'.format( response.status_code)) return response