diff --git a/rally-jobs/extra/resource_group_with_outputs.yaml.template b/rally-jobs/extra/resource_group_with_outputs.yaml.template new file mode 100644 index 00000000..f47d03cc --- /dev/null +++ b/rally-jobs/extra/resource_group_with_outputs.yaml.template @@ -0,0 +1,37 @@ +heat_template_version: 2013-05-23 +parameters: + attr_wait_secs: + type: number + default: 0.5 + +resources: + rg: + type: OS::Heat::ResourceGroup + properties: + count: 10 + resource_def: + type: OS::Heat::TestResource + properties: + attr_wait_secs: {get_param: attr_wait_secs} + +outputs: + val1: + value: {get_attr: [rg, resource.0.output]} + val2: + value: {get_attr: [rg, resource.1.output]} + val3: + value: {get_attr: [rg, resource.2.output]} + val4: + value: {get_attr: [rg, resource.3.output]} + val5: + value: {get_attr: [rg, resource.4.output]} + val6: + value: {get_attr: [rg, resource.5.output]} + val7: + value: {get_attr: [rg, resource.6.output]} + val8: + value: {get_attr: [rg, resource.7.output]} + val9: + value: {get_attr: [rg, resource.8.output]} + val10: + value: {get_attr: [rg, resource.9.output]} \ No newline at end of file diff --git a/rally-jobs/heat.yaml b/rally-jobs/heat.yaml index 367a9a35..6b8066e0 100644 --- a/rally-jobs/heat.yaml +++ b/rally-jobs/heat.yaml @@ -295,3 +295,57 @@ sla: failure_rate: max: 0 + + HeatStacks.create_stack_and_list_output: + - + args: + template_path: "~/.rally/extra/resource_group_with_outputs.yaml.template" + runner: + type: "constant" + times: 5 + concurrency: 2 + context: + users: + tenants: 2 + users_per_tenant: 2 + + HeatStacks.create_stack_and_list_output_via_API: + - + args: + template_path: "~/.rally/extra/resource_group_with_outputs.yaml.template" + runner: + type: "constant" + times: 5 + concurrency: 2 + context: + users: + tenants: 2 + users_per_tenant: 2 + + HeatStacks.create_stack_and_show_output: + - + args: + template_path: "~/.rally/extra/resource_group_with_outputs.yaml.template" + output_key: "val1" + runner: + type: "constant" + times: 5 + concurrency: 2 + context: + users: + tenants: 2 + users_per_tenant: 2 + + HeatStacks.create_stack_and_show_output_via_API: + - + args: + template_path: "~/.rally/extra/resource_group_with_outputs.yaml.template" + output_key: "val1" + runner: + type: "constant" + times: 5 + concurrency: 2 + context: + users: + tenants: 2 + users_per_tenant: 2 diff --git a/rally/plugins/openstack/scenarios/heat/stacks.py b/rally/plugins/openstack/scenarios/heat/stacks.py index 49a46b29..1d0d13c7 100644 --- a/rally/plugins/openstack/scenarios/heat/stacks.py +++ b/rally/plugins/openstack/scenarios/heat/stacks.py @@ -239,3 +239,91 @@ class HeatStacks(utils.HeatScenario): snapshot = self._snapshot_stack(stack) self._restore_stack(stack, snapshot["id"]) self._delete_stack(stack) + + @types.convert(template_path={"type": "file"}, files={"type": "file_dict"}) + @validation.required_services(consts.Service.HEAT) + @validation.required_openstack(users=True) + @scenario.configure(context={"cleanup": ["heat"]}) + def create_stack_and_show_output_via_API(self, template_path, output_key, + parameters=None, files=None, + environment=None): + """Create stack and show output by using old algorithm. + + Measure performance of the following commands: + heat stack-create + heat output-show + :param template_path: path to stack template file + :param output_key: the stack output key that corresponds to + the scaling webhook + :param parameters: parameters to use in heat template + :param files: files used in template + :param environment: stack environment definition + """ + stack = self._create_stack( + template_path, parameters, files, environment) + self._stack_show_output_via_API(stack, output_key) + + @types.convert(template_path={"type": "file"}, files={"type": "file_dict"}) + @validation.required_services(consts.Service.HEAT) + @validation.required_openstack(users=True) + @scenario.configure(context={"cleanup": ["heat"]}) + def create_stack_and_show_output(self, template_path, output_key, + parameters=None, files=None, + environment=None): + """Create stack and show output by using new algorithm. + + Measure performance of the following commands: + heat stack-create + heat output-show + :param template_path: path to stack template file + :param output_key: the stack output key that corresponds to + the scaling webhook + :param parameters: parameters to use in heat template + :param files: files used in template + :param environment: stack environment definition + """ + stack = self._create_stack( + template_path, parameters, files, environment) + self._stack_show_output(stack, output_key) + + @types.convert(template_path={"type": "file"}, files={"type": "file_dict"}) + @validation.required_services(consts.Service.HEAT) + @validation.required_openstack(users=True) + @scenario.configure(context={"cleanup": ["heat"]}) + def create_stack_and_list_output_via_API(self, template_path, + parameters=None, files=None, + environment=None): + """Create stack and list outputs by using old algorithm. + + Measure performance of the following commands: + heat stack-create + heat output-list + :param template_path: path to stack template file + :param parameters: parameters to use in heat template + :param files: files used in template + :param environment: stack environment definition + """ + stack = self._create_stack( + template_path, parameters, files, environment) + self._stack_list_output_via_API(stack) + + @types.convert(template_path={"type": "file"}, files={"type": "file_dict"}) + @validation.required_services(consts.Service.HEAT) + @validation.required_openstack(users=True) + @scenario.configure(context={"cleanup": ["heat"]}) + def create_stack_and_list_output(self, template_path, + parameters=None, files=None, + environment=None): + """Create stack and list outputs by using new algorithm. + + Measure performance of the following commands: + heat stack-create + heat output-list + :param template_path: path to stack template file + :param parameters: parameters to use in heat template + :param files: files used in template + :param environment: stack environment definition + """ + stack = self._create_stack( + template_path, parameters, files, environment) + self._stack_list_output(stack) diff --git a/rally/plugins/openstack/scenarios/heat/utils.py b/rally/plugins/openstack/scenarios/heat/utils.py index 0cce2afd..faf881b5 100644 --- a/rally/plugins/openstack/scenarios/heat/utils.py +++ b/rally/plugins/openstack/scenarios/heat/utils.py @@ -291,6 +291,60 @@ class HeatScenario(scenario.OpenStackScenario): check_interval=CONF.benchmark.heat_stack_restore_poll_interval ) + @atomic.action_timer("heat.show_output") + def _stack_show_output(self, stack, output_key): + """Execute output_show for specified "output_key". + + This method uses new output API call. + :param stack: stack with output_key output. + :param output_key: The name of the output. + """ + output = self.clients("heat").stacks.output_show(stack.id, output_key) + return output + + @atomic.action_timer("heat.show_output_via_API") + def _stack_show_output_via_API(self, stack, output_key): + """Execute output_show for specified "output_key". + + This method uses old way for getting output value. + It gets whole stack object and then finds necessary "output_key". + :param stack: stack with output_key output. + :param output_key: The name of the output. + """ + # this code copy-pasted and adopted for rally from old client version + # https://github.com/openstack/python-heatclient/blob/0.8.0/heatclient/ + # v1/shell.py#L682-L699 + stack = self.clients("heat").stacks.get(stack_id=stack.id) + for output in stack.to_dict().get("outputs", []): + if output["output_key"] == output_key: + return output + + @atomic.action_timer("heat.list_output") + def _stack_list_output(self, stack): + """Execute output_list for specified "stack". + + This method uses new output API call. + :param stack: stack to call output-list. + """ + output_list = self.clients("heat").stacks.output_list(stack.id) + return output_list + + @atomic.action_timer("heat.list_output_via_API") + def _stack_list_output_via_API(self, stack): + """Execute output_list for specified "stack". + + This method uses old way for getting output value. + It gets whole stack object and then prints all outputs + belongs this stack. + :param stack: stack to call output-list. + """ + # this code copy-pasted and adopted for rally from old client version + # https://github.com/openstack/python-heatclient/blob/0.8.0/heatclient/ + # v1/shell.py#L649-L663 + stack = self.clients("heat").stacks.get(stack_id=stack.id) + output_list = stack.to_dict()["outputs"] + return output_list + def _count_instances(self, stack): """Count instances in a Heat stack. diff --git a/tests/unit/plugins/openstack/scenarios/heat/test_stacks.py b/tests/unit/plugins/openstack/scenarios/heat/test_stacks.py index 02e24f12..440237db 100644 --- a/tests/unit/plugins/openstack/scenarios/heat/test_stacks.py +++ b/tests/unit/plugins/openstack/scenarios/heat/test_stacks.py @@ -29,6 +29,7 @@ class HeatStacksTestCase(test.ScenarioTestCase): self.default_parameters = {"dummy_param": "dummy_key"} self.default_files = ["dummy_file.yaml"] self.default_environment = {"env": "dummy_env"} + self.default_output_key = "dummy_output_key" @mock.patch(HEAT_STACKS + ".generate_random_name") @mock.patch(HEAT_STACKS + "._list_stacks") @@ -216,3 +217,73 @@ class HeatStacksTestCase(test.ScenarioTestCase): mock__create_stack.return_value, "dummy_id") mock__delete_stack.assert_called_once_with( mock__create_stack.return_value) + + @mock.patch(HEAT_STACKS + "._stack_show_output_via_API") + @mock.patch(HEAT_STACKS + "._create_stack") + def test_create_and_show_output_via_API(self, mock__create_stack, + mock__stack_show_output_via_api): + heat_scenario = stacks.HeatStacks(self.context) + heat_scenario.create_stack_and_show_output_via_API( + template_path=self.default_template, + output_key=self.default_output_key, + parameters=self.default_parameters, + files=self.default_files, + environment=self.default_environment + ) + mock__create_stack.assert_called_once_with( + self.default_template, self.default_parameters, + self.default_files, self.default_environment) + mock__stack_show_output_via_api.assert_called_once_with( + mock__create_stack.return_value, self.default_output_key) + + @mock.patch(HEAT_STACKS + "._stack_show_output") + @mock.patch(HEAT_STACKS + "._create_stack") + def test_create_and_show_output(self, mock__create_stack, + mock__stack_show_output): + heat_scenario = stacks.HeatStacks(self.context) + heat_scenario.create_stack_and_show_output( + template_path=self.default_template, + output_key=self.default_output_key, + parameters=self.default_parameters, + files=self.default_files, + environment=self.default_environment + ) + mock__create_stack.assert_called_once_with( + self.default_template, self.default_parameters, + self.default_files, self.default_environment) + mock__stack_show_output.assert_called_once_with( + mock__create_stack.return_value, self.default_output_key) + + @mock.patch(HEAT_STACKS + "._stack_list_output_via_API") + @mock.patch(HEAT_STACKS + "._create_stack") + def test_create_and_list_output_via_API(self, mock__create_stack, + mock__stack_list_output_via_api): + heat_scenario = stacks.HeatStacks(self.context) + heat_scenario.create_stack_and_list_output_via_API( + template_path=self.default_template, + parameters=self.default_parameters, + files=self.default_files, + environment=self.default_environment + ) + mock__create_stack.assert_called_once_with( + self.default_template, self.default_parameters, + self.default_files, self.default_environment) + mock__stack_list_output_via_api.assert_called_once_with( + mock__create_stack.return_value) + + @mock.patch(HEAT_STACKS + "._stack_list_output") + @mock.patch(HEAT_STACKS + "._create_stack") + def test_create_and_list_output(self, mock__create_stack, + mock__stack_list_output): + heat_scenario = stacks.HeatStacks(self.context) + heat_scenario.create_stack_and_list_output( + template_path=self.default_template, + parameters=self.default_parameters, + files=self.default_files, + environment=self.default_environment + ) + mock__create_stack.assert_called_once_with( + self.default_template, self.default_parameters, + self.default_files, self.default_environment) + mock__stack_list_output.assert_called_once_with( + mock__create_stack.return_value) diff --git a/tests/unit/plugins/openstack/scenarios/heat/test_utils.py b/tests/unit/plugins/openstack/scenarios/heat/test_utils.py index 75da1d59..30ace78c 100644 --- a/tests/unit/plugins/openstack/scenarios/heat/test_utils.py +++ b/tests/unit/plugins/openstack/scenarios/heat/test_utils.py @@ -33,6 +33,7 @@ class HeatScenarioTestCase(test.ScenarioTestCase): self.dummy_parameters = {"dummy_param": "dummy_key"} self.dummy_files = ["dummy_file.yaml"] self.dummy_environment = {"dummy_env": "dummy_env_value"} + self.default_output_key = "dummy_output_key" def test_list_stacks(self): scenario = utils.HeatScenario(self.context) @@ -241,6 +242,39 @@ class HeatScenarioTestCase(test.ScenarioTestCase): self.assertRaises(exceptions.InvalidConfigException, scenario._stack_webhook, stack, "bogus") + def test_stack_show_output(self): + scenario = utils.HeatScenario(self.context) + scenario._stack_show_output(self.stack, self.default_output_key) + self.clients("heat").stacks.output_show.assert_called_once_with( + self.stack.id, self.default_output_key) + self._test_atomic_action_timer(scenario.atomic_actions(), + "heat.show_output") + + def test_stack_show_output_via_API(self): + scenario = utils.HeatScenario(self.context) + scenario._stack_show_output_via_API( + self.stack, self.default_output_key) + self.clients("heat").stacks.get.assert_called_once_with( + stack_id=self.stack.id) + self._test_atomic_action_timer(scenario.atomic_actions(), + "heat.show_output_via_API") + + def test_stack_list_output(self): + scenario = utils.HeatScenario(self.context) + scenario._stack_list_output(self.stack) + self.clients("heat").stacks.output_list.assert_called_once_with( + self.stack.id) + self._test_atomic_action_timer(scenario.atomic_actions(), + "heat.list_output") + + def test_stack_list_output_via_API(self): + scenario = utils.HeatScenario(self.context) + scenario._stack_list_output_via_API(self.stack) + self.clients("heat").stacks.get.assert_called_once_with( + stack_id=self.stack.id) + self._test_atomic_action_timer(scenario.atomic_actions(), + "heat.list_output_via_API") + class HeatScenarioNegativeTestCase(test.ScenarioTestCase): patch_benchmark_utils = False