def create_stack(self, cnxt, stack_name, template, params, files, args): """ The create_stack method creates a new stack using the template provided. Note that at this stage the template has already been fetched from the heat-api process if using a template-url. :param cnxt: RPC context. :param stack_name: Name of the stack you want to create. :param template: Template of stack you want to create. :param params: Stack Input Params :param files: Files referenced from the template :param args: Request parameters/args passed from API """ logger.info(_('template is %s') % template) def _stack_create(stack): # Create the stack, and create the periodic task if successful stack.create() if stack.action == stack.CREATE and stack.status == stack.COMPLETE: # Schedule a periodic watcher task for this stack self._start_watch_task(stack.id, cnxt) else: logger.warning( _("Stack create failed, status %s") % stack.status) if db_api.stack_get_by_name(cnxt, stack_name): raise exception.StackExists(stack_name=stack_name) tenant_limit = cfg.CONF.max_stacks_per_tenant if db_api.stack_count_all_by_tenant(cnxt) >= tenant_limit: message = _("You have reached the maximum stacks per tenant, %d." " Please delete some stacks.") % tenant_limit raise exception.RequestLimitExceeded(message=message) tmpl = parser.Template(template, files=files) if len(tmpl[tpl.RESOURCES]) > cfg.CONF.max_resources_per_stack: raise exception.RequestLimitExceeded( message=exception.StackResourceLimitExceeded.msg_fmt) # Extract the common query parameters common_params = api.extract_args(args) env = environment.Environment(params) stack = parser.Stack(cnxt, stack_name, tmpl, env, **common_params) self._validate_deferred_auth_context(cnxt, stack) stack.validate() stack_id = stack.store() self._start_in_thread(stack_id, _stack_create, stack) return dict(stack.identifier())
def _validate_new_stack(self, cnxt, stack_name, parsed_template): if db_api.stack_get_by_name(cnxt, stack_name): raise exception.StackExists(stack_name=stack_name) tenant_limit = cfg.CONF.max_stacks_per_tenant if db_api.stack_count_all(cnxt) >= tenant_limit: message = _("You have reached the maximum stacks per tenant, %d." " Please delete some stacks.") % tenant_limit raise exception.RequestLimitExceeded(message=message) num_resources = len(parsed_template[parsed_template.RESOURCES]) if num_resources > cfg.CONF.max_resources_per_stack: message = exception.StackResourceLimitExceeded.msg_fmt raise exception.RequestLimitExceeded(message=message)
def parse(tmpl_str): ''' Takes a string and returns a dict containing the parsed structure. This includes determination of whether the string is using the JSON or YAML format. ''' if len(tmpl_str) > cfg.CONF.max_template_size: msg = _('Template exceeds maximum allowed size.') raise exception.RequestLimitExceeded(message=msg) try: tpl = json.loads(tmpl_str) except ValueError: try: tpl = yaml.load(tmpl_str, Loader=yaml_loader) except yaml.YAMLError as yea: raise ValueError(yea) else: if tpl is None: tpl = {} if not isinstance(tpl, dict): raise ValueError( _('The template is not a JSON object ' 'or YAML mapping.')) # Looking for supported version keys in the loaded template if not ('HeatTemplateFormatVersion' in tpl or 'heat_template_version' in tpl or 'AWSTemplateFormatVersion' in tpl): raise ValueError(_("Template format version not found.")) return tpl
def create_with_template(self, child_template, user_params, timeout_mins=None, adopt_data=None): ''' Handle the creation of the nested stack from a given JSON template. ''' if self.recursion_depth >= cfg.CONF.max_nested_stack_depth: msg = _("Recursion depth exceeds %d.") % \ cfg.CONF.max_nested_stack_depth raise exception.RequestLimitExceeded(message=msg) template = parser.Template(child_template) if ((len(template[template.RESOURCES]) + self.stack.root_stack.total_resources() > cfg.CONF.max_resources_per_stack)): raise exception.RequestLimitExceeded( message=exception.StackResourceLimitExceeded.msg_fmt) self._outputs_to_attribs(child_template) # Note we disable rollback for nested stacks, since they # should be rolled back by the parent stack on failure nested = parser.Stack(self.context, self.physical_resource_name(), template, environment.Environment(user_params), timeout_mins=timeout_mins, disable_rollback=True, parent_resource=self, owner_id=self.stack.id, adopt_stack_data=adopt_data) nested.validate() self._nested = nested nested_id = self._nested.store() self.resource_id_set(nested_id) action = self._nested.CREATE if adopt_data: action = self._nested.ADOPT stack_creator = scheduler.TaskRunner(self._nested.stack_task, action=action) stack_creator.start(timeout=self._nested.timeout_secs()) return stack_creator
def update_stack(self, cnxt, stack_identity, template, params, files, args): """ The update_stack method updates an existing stack based on the provided template and parameters. Note that at this stage the template has already been fetched from the heat-api process if using a template-url. :param cnxt: RPC context. :param stack_identity: Name of the stack you want to create. :param template: Template of stack you want to create. :param params: Stack Input Params :param files: Files referenced from the template :param args: Request parameters/args passed from API """ LOG.info(_('template is %s') % template) # Get the database representation of the existing stack db_stack = self._get_stack(cnxt, stack_identity) current_stack = parser.Stack.load(cnxt, stack=db_stack) if current_stack.action == current_stack.SUSPEND: msg = _('Updating a stack when it is suspended') raise exception.NotSupported(feature=msg) if current_stack.status == current_stack.IN_PROGRESS: msg = _('Updating a stack when another action is in progress') raise exception.NotSupported(feature=msg) # Now parse the template and any parameters for the updated # stack definition. tmpl = parser.Template(template, files=files) if len(tmpl[tmpl.RESOURCES]) > cfg.CONF.max_resources_per_stack: raise exception.RequestLimitExceeded( message=exception.StackResourceLimitExceeded.msg_fmt) stack_name = current_stack.name common_params = api.extract_args(args) common_params.setdefault(rpc_api.PARAM_TIMEOUT, current_stack.timeout_mins) env = environment.Environment(params) updated_stack = parser.Stack(cnxt, stack_name, tmpl, env, **common_params) updated_stack.parameters.set_stack_id(current_stack.identifier()) self._validate_deferred_auth_context(cnxt, updated_stack) updated_stack.validate() self.thread_group_mgr.start_with_lock(cnxt, current_stack, self.engine_id, current_stack.update, updated_stack) return dict(current_stack.identifier())
def _validate_nested_resources(self, templ): total_resources = (len(templ[templ.RESOURCES]) + self.stack.root_stack.total_resources()) if self.nested(): # It's an update and these resources will be deleted total_resources -= len(self.nested().resources) if (total_resources > cfg.CONF.max_resources_per_stack): message = exception.StackResourceLimitExceeded.msg_fmt raise exception.RequestLimitExceeded(message=message)
def from_json(self, datastring): try: if len(datastring) > cfg.CONF.max_json_body_size: msg = _('JSON body size (%(len)s bytes) exceeds maximum ' 'allowed size (%(limit)s bytes).' ) % {'len': len(datastring), 'limit': cfg.CONF.max_json_body_size} raise exception.RequestLimitExceeded(message=msg) return jsonutils.loads(datastring) except ValueError as ex: raise webob.exc.HTTPBadRequest(str(ex))
def test_preview_validates_nested_resources(self): stack_resource = MyImplementedStackResource('test', ws_res_snippet, self.parent_stack) stack_resource.child_template = mock.Mock(return_value={}) stack_resource.child_params = mock.Mock() exc = exception.RequestLimitExceeded(message='Validation Failed') validation_mock = mock.Mock(side_effect=exc) stack_resource._validate_nested_resources = validation_mock self.assertRaises(exception.RequestLimitExceeded, stack_resource.preview)
def validate_template_limit(contain_str): """Validate limit for the template. Check if the contain exceeds allowed size range. """ if len(contain_str) > cfg.CONF.max_template_size: msg = _("Template size (%(actual_len)s bytes) exceeds maximum " "allowed size (%(limit)s bytes)." ) % {'actual_len': len(contain_str), 'limit': cfg.CONF.max_template_size} raise exception.RequestLimitExceeded(message=msg)
def test_preview_dict_validates_nested_resources(self): parent_t = self.parent_stack.t resource_defns = parent_t.resource_definitions(self.parent_stack) stk_resource = MyImplementedStackResource( 'test', resource_defns[self.ws_resname], self.parent_stack) stk_resource.child_params = mock.Mock(return_value={}) stk_resource.child_template = mock.Mock( return_value=self.simple_template) exc = exception.RequestLimitExceeded(message='Validation Failed') validation_mock = mock.Mock(side_effect=exc) stk_resource._validate_nested_resources = validation_mock self.assertRaises(exception.RequestLimitExceeded, stk_resource.preview)
def parse(tmpl_str): """Takes a string and returns a dict containing the parsed structure. This includes determination of whether the string is using the JSON or YAML format. """ if len(tmpl_str) > cfg.CONF.max_template_size: msg = (_('Template exceeds maximum allowed size (%s bytes)') % cfg.CONF.max_template_size) raise exception.RequestLimitExceeded(message=msg) tpl = simple_parse(tmpl_str) # Looking for supported version keys in the loaded template if not ('HeatTemplateFormatVersion' in tpl or 'heat_template_version' in tpl or 'AWSTemplateFormatVersion' in tpl): raise ValueError(_("Template format version not found.")) return tpl
def update_stack(self, cnxt, stack_identity, template, params, files, args): """ The update_stack method updates an existing stack based on the provided template and parameters. Note that at this stage the template has already been fetched from the heat-api process if using a template-url. arg1 -> RPC context. arg2 -> Name of the stack you want to create. arg3 -> Template of stack you want to create. arg4 -> Stack Input Params arg4 -> Request parameters/args passed from API """ logger.info('template is %s' % template) # Get the database representation of the existing stack db_stack = self._get_stack(cnxt, stack_identity) current_stack = parser.Stack.load(cnxt, stack=db_stack) if current_stack.action == current_stack.SUSPEND: msg = _('Updating a stack when it is suspended') raise exception.NotSupported(feature=msg) if current_stack.status == current_stack.IN_PROGRESS: msg = _('Updating a stack when another action is in progress') raise exception.NotSupported(feature=msg) # Now parse the template and any parameters for the updated # stack definition. tmpl = parser.Template(template, files=files) if len(tmpl[tpl.RESOURCES]) > cfg.CONF.max_resources_per_stack: raise exception.RequestLimitExceeded( message=exception.StackResourceLimitExceeded.msg_fmt) stack_name = current_stack.name common_params = api.extract_args(args) env = environment.Environment(params) updated_stack = parser.Stack(cnxt, stack_name, tmpl, env, **common_params) self._validate_deferred_auth_context(cnxt, updated_stack) updated_stack.validate() self._start_in_thread(db_stack.id, current_stack.update, updated_stack) return dict(current_stack.identifier())
def _parse_nested_stack(self, stack_name, child_template, child_params=None, timeout_mins=None, adopt_data=None): if self.stack.nested_depth >= cfg.CONF.max_nested_stack_depth: msg = _("Recursion depth exceeds %d." ) % cfg.CONF.max_nested_stack_depth raise exception.RequestLimitExceeded(message=msg) parsed_template = self._parse_child_template(child_template) self._validate_nested_resources(parsed_template) # Don't overwrite the attributes_schema for subclasses that # define their own attributes_schema. if not hasattr(type(self), 'attributes_schema'): self.attributes = None self._outputs_to_attribs(parsed_template) if timeout_mins is None: timeout_mins = self.stack.timeout_mins stack_user_project_id = self.stack.stack_user_project_id new_nested_depth = self.stack.nested_depth + 1 if child_params is None: child_params = self.child_params() child_env = environment.get_child_environment(self.stack.env, child_params) # Note we disable rollback for nested stacks, since they # should be rolled back by the parent stack on failure nested = parser.Stack(self.context, stack_name, parsed_template, env=child_env, timeout_mins=timeout_mins, disable_rollback=True, parent_resource=self, owner_id=self.stack.id, user_creds_id=self.stack.user_creds_id, stack_user_project_id=stack_user_project_id, adopt_stack_data=adopt_data, nested_depth=new_nested_depth) return nested
def _validate_nested_resources(self, templ): if cfg.CONF.max_resources_per_stack == -1: return total_resources = (len(templ[templ.RESOURCES]) + self.stack.total_resources(self.root_stack_id)) identity = self.nested_identifier() if identity is not None: existing = self.rpc_client().list_stack_resources(self.context, identity) # Don't double-count existing resources during an update total_resources -= len(existing) if (total_resources > cfg.CONF.max_resources_per_stack): message = exception.StackResourceLimitExceeded.msg_fmt raise exception.RequestLimitExceeded(message=message)
def update_with_template(self, child_template, user_params, timeout_mins=None): """Update the nested stack with the new template.""" template = parser.Template(child_template, files=self.stack.t.files) # Note that there is no call to self._outputs_to_attribs here. # If we have a use case for updating attributes of the resource based # on updated templates we should make sure it's optional because not # all subclasses want that behavior, since they may offer custom # attributes. nested_stack = self.nested() if nested_stack is None: raise exception.Error( _('Cannot update %s, stack not created') % self.name) res_diff = (len(template[template.RESOURCES]) - len(nested_stack.resources)) new_size = nested_stack.root_stack.total_resources() + res_diff if new_size > cfg.CONF.max_resources_per_stack: raise exception.RequestLimitExceeded( message=exception.StackResourceLimitExceeded.msg_fmt) if timeout_mins is None: timeout_mins = self.stack.timeout_mins # Note we disable rollback for nested stacks, since they # should be rolled back by the parent stack on failure stack = parser.Stack(self.context, self.physical_resource_name(), template, self._nested_environment(user_params), timeout_mins=timeout_mins, disable_rollback=True, parent_resource=self, owner_id=self.stack.id) stack.parameters.set_stack_id(nested_stack.identifier()) stack.validate() if not hasattr(type(self), 'attributes_schema'): self.attributes = None self._outputs_to_attribs(child_template) updater = scheduler.TaskRunner(nested_stack.update_task, stack) updater.start() return updater
def update_with_template(self, child_template, user_params, timeout_mins=None): """Update the nested stack with the new template.""" if isinstance(child_template, parser.Template): template = child_template template.files = self.stack.t.files else: template = parser.Template(child_template, files=self.stack.t.files) nested_stack = self.nested() if nested_stack is None: raise exception.Error(_('Cannot update %s, stack not created') % self.name) res_diff = ( len(template[template.RESOURCES]) - len(nested_stack.resources)) new_size = nested_stack.root_stack.total_resources() + res_diff if new_size > cfg.CONF.max_resources_per_stack: raise exception.RequestLimitExceeded( message=exception.StackResourceLimitExceeded.msg_fmt) if timeout_mins is None: timeout_mins = self.stack.timeout_mins # Note we disable rollback for nested stacks, since they # should be rolled back by the parent stack on failure stack = parser.Stack(self.context, self.physical_resource_name(), template, self._nested_environment(user_params), timeout_mins=timeout_mins, disable_rollback=True, parent_resource=self, owner_id=self.stack.id) stack.parameters.set_stack_id(nested_stack.identifier()) stack.validate() # Don't overwrite the attributes_schema on update for subclasses that # define their own attributes_schema. if not hasattr(type(self), 'attributes_schema'): self.attributes = None self._outputs_to_attribs(template) updater = scheduler.TaskRunner(nested_stack.update_task, stack) updater.start() return updater
def parse(tmpl_str): ''' Takes a string and returns a dict containing the parsed structure. This includes determination of whether the string is using the JSON or YAML format. ''' if len(tmpl_str) > cfg.CONF.max_template_size: msg = _('Template exceeds maximum allowed size.') raise exception.RequestLimitExceeded(message=msg) if tmpl_str.startswith('{'): tpl = json.loads(tmpl_str) else: try: tpl = yaml.load(tmpl_str, Loader=yaml_loader) except yaml.YAMLError as yea: raise ValueError(yea) else: if tpl is None: tpl = {} if u'heat_template_version' not in tpl: default_for_missing(tpl, u'HeatTemplateFormatVersion', HEAT_VERSIONS) return tpl
def _child_nested_depth(self): if self.stack.nested_depth >= cfg.CONF.max_nested_stack_depth: msg = _("Recursion depth exceeds %d." ) % cfg.CONF.max_nested_stack_depth raise exception.RequestLimitExceeded(message=msg) return self.stack.nested_depth + 1
def _validate_nested_resources(self, template): total_resources = (len(template[template.RESOURCES]) + self.stack.root_stack.total_resources()) if (total_resources > cfg.CONF.max_resources_per_stack): message = exception.StackResourceLimitExceeded.msg_fmt raise exception.RequestLimitExceeded(message=message)
def test_map_remote_error_request_limit_exceeded(self): ex = common_exception.RequestLimitExceeded(message="testing") expected = aws_exception.HeatRequestLimitExceeded self.assertIsInstance(aws_exception.map_remote_error(ex), expected)