summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Goddard <mark@stackhpc.com>2019-02-14 11:25:09 +0000
committerMark Goddard <mark@stackhpc.com>2019-03-01 14:24:28 +0000
commitcc3725342820862724a86e27d63190d276231141 (patch)
tree243b0c66883e93b85f7eb66db84c419c48ea37d9
parentfdba8ed994bcbf1b3fc82803ccec49faf505b081 (diff)
downloadpython-ironicclient-cc3725342820862724a86e27d63190d276231141.tar.gz
Deploy templates: client support
Adds OSC support for the deploy templates API. Change-Id: I0f2f37e840449ee41f747e2a43ed6f53c927094e Depends-On: https://review.openstack.org/631845 Story: 1722275 Task: 28678
-rw-r--r--ironicclient/common/http.py2
-rw-r--r--ironicclient/common/utils.py16
-rw-r--r--ironicclient/osc/v1/baremetal_deploy_template.py345
-rwxr-xr-xironicclient/osc/v1/baremetal_node.py10
-rw-r--r--ironicclient/osc/v1/baremetal_port.py2
-rw-r--r--ironicclient/tests/functional/osc/v1/base.py57
-rw-r--r--ironicclient/tests/functional/osc/v1/test_baremetal_deploy_template_basic.py177
-rw-r--r--ironicclient/tests/unit/osc/v1/fakes.py18
-rw-r--r--ironicclient/tests/unit/osc/v1/test_baremetal_deploy_template.py450
-rw-r--r--ironicclient/tests/unit/v1/test_deploy_template.py291
-rw-r--r--ironicclient/v1/client.py3
-rw-r--r--ironicclient/v1/deploy_template.py86
-rw-r--r--ironicclient/v1/resource_fields.py19
-rw-r--r--releasenotes/notes/deploy-templates-df354ce825b00430.yaml11
-rw-r--r--setup.cfg6
15 files changed, 1484 insertions, 9 deletions
diff --git a/ironicclient/common/http.py b/ironicclient/common/http.py
index 57652bb..f930e21 100644
--- a/ironicclient/common/http.py
+++ b/ironicclient/common/http.py
@@ -43,7 +43,7 @@ from ironicclient import exc
# http://specs.openstack.org/openstack/ironic-specs/specs/kilo/api-microversions.html # noqa
# for full details.
DEFAULT_VER = '1.9'
-LAST_KNOWN_API_VERSION = 54
+LAST_KNOWN_API_VERSION = 55
LATEST_VERSION = '1.{}'.format(LAST_KNOWN_API_VERSION)
LOG = logging.getLogger(__name__)
diff --git a/ironicclient/common/utils.py b/ironicclient/common/utils.py
index c09ae84..fac5278 100644
--- a/ironicclient/common/utils.py
+++ b/ironicclient/common/utils.py
@@ -411,3 +411,19 @@ def poll(timeout, poll_interval, poll_delay_function, timeout_message):
count += 1
raise exc.StateTransitionTimeout(timeout_message)
+
+
+def handle_json_arg(json_arg, info_desc):
+ """Read a JSON argument from stdin, file or string.
+
+ :param json_arg: May be a file name containing the JSON, a JSON string, or
+ '-' indicating that the argument should be read from standard input.
+ :param info_desc: A string description of the desired information
+ :returns: A list or dictionary parsed from JSON.
+ :raises: InvalidAttribute if the argument cannot be parsed.
+ """
+ if json_arg == '-':
+ json_arg = get_from_stdin(info_desc)
+ if json_arg:
+ json_arg = handle_json_or_file_arg(json_arg)
+ return json_arg
diff --git a/ironicclient/osc/v1/baremetal_deploy_template.py b/ironicclient/osc/v1/baremetal_deploy_template.py
new file mode 100644
index 0000000..3a1a8c3
--- /dev/null
+++ b/ironicclient/osc/v1/baremetal_deploy_template.py
@@ -0,0 +1,345 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+import itertools
+import json
+import logging
+
+from osc_lib.command import command
+from osc_lib import utils as oscutils
+
+from ironicclient.common.i18n import _
+from ironicclient.common import utils
+from ironicclient import exc
+from ironicclient.v1 import resource_fields as res_fields
+
+
+_DEPLOY_STEPS_HELP = _(
+ "The deploy steps in JSON format. May be the path to a file containing "
+ "the deploy steps; OR '-', with the deploy steps being read from standard "
+ "input; OR a string. The value should be a list of deploy-step "
+ "dictionaries; each dictionary should have keys 'interface', 'step', "
+ "'args' and 'priority'.")
+
+
+class CreateBaremetalDeployTemplate(command.ShowOne):
+ """Create a new deploy template"""
+
+ log = logging.getLogger(__name__ + ".CreateBaremetalDeployTemplate")
+
+ def get_parser(self, prog_name):
+ parser = super(CreateBaremetalDeployTemplate, self).get_parser(
+ prog_name)
+
+ parser.add_argument(
+ 'name',
+ metavar='<name>',
+ help=_('Unique name for this deploy template. Must be a valid '
+ 'trait name')
+ )
+ parser.add_argument(
+ '--uuid',
+ dest='uuid',
+ metavar='<uuid>',
+ help=_('UUID of the deploy template.'))
+ parser.add_argument(
+ '--extra',
+ metavar="<key=value>",
+ action='append',
+ help=_("Record arbitrary key/value metadata. "
+ "Can be specified multiple times."))
+ parser.add_argument(
+ '--steps',
+ metavar="<steps>",
+ required=True,
+ help=_DEPLOY_STEPS_HELP
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ self.log.debug("take_action(%s)", parsed_args)
+ baremetal_client = self.app.client_manager.baremetal
+
+ steps = utils.handle_json_arg(parsed_args.steps, 'deploy steps')
+
+ field_list = ['name', 'uuid', 'extra']
+ fields = dict((k, v) for (k, v) in vars(parsed_args).items()
+ if k in field_list and v is not None)
+ fields = utils.args_array_to_dict(fields, 'extra')
+ template = baremetal_client.deploy_template.create(steps=steps,
+ **fields)
+
+ data = dict([(f, getattr(template, f, '')) for f in
+ res_fields.DEPLOY_TEMPLATE_DETAILED_RESOURCE.fields])
+
+ return self.dict2columns(data)
+
+
+class ShowBaremetalDeployTemplate(command.ShowOne):
+ """Show baremetal deploy template details."""
+
+ log = logging.getLogger(__name__ + ".ShowBaremetalDeployTemplate")
+
+ def get_parser(self, prog_name):
+ parser = super(ShowBaremetalDeployTemplate, self).get_parser(prog_name)
+ parser.add_argument(
+ "template",
+ metavar="<template>",
+ help=_("Name or UUID of the deploy template.")
+ )
+ parser.add_argument(
+ '--fields',
+ nargs='+',
+ dest='fields',
+ metavar='<field>',
+ action='append',
+ choices=res_fields.DEPLOY_TEMPLATE_DETAILED_RESOURCE.fields,
+ default=[],
+ help=_("One or more deploy template fields. Only these fields "
+ "will be fetched from the server.")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ self.log.debug("take_action(%s)", parsed_args)
+
+ baremetal_client = self.app.client_manager.baremetal
+ fields = list(itertools.chain.from_iterable(parsed_args.fields))
+ fields = fields if fields else None
+
+ template = baremetal_client.deploy_template.get(
+ parsed_args.template, fields=fields)._info
+
+ template.pop("links", None)
+ return zip(*sorted(template.items()))
+
+
+class SetBaremetalDeployTemplate(command.Command):
+ """Set baremetal deploy template properties."""
+
+ log = logging.getLogger(__name__ + ".SetBaremetalDeployTemplate")
+
+ def get_parser(self, prog_name):
+ parser = super(SetBaremetalDeployTemplate, self).get_parser(prog_name)
+
+ parser.add_argument(
+ 'template',
+ metavar='<template>',
+ help=_("Name or UUID of the deploy template")
+ )
+ parser.add_argument(
+ '--name',
+ metavar='<name>',
+ help=_('Set unique name of the deploy template. Must be a valid '
+ 'trait name.')
+ )
+ parser.add_argument(
+ '--steps',
+ metavar="<steps>",
+ help=_DEPLOY_STEPS_HELP
+ )
+ parser.add_argument(
+ "--extra",
+ metavar="<key=value>",
+ action='append',
+ help=_('Extra to set on this baremetal deploy template '
+ '(repeat option to set multiple extras).'),
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ self.log.debug("take_action(%s)", parsed_args)
+
+ baremetal_client = self.app.client_manager.baremetal
+
+ properties = []
+ if parsed_args.name:
+ name = ["name=%s" % parsed_args.name]
+ properties.extend(utils.args_array_to_patch('add', name))
+ if parsed_args.steps:
+ steps = utils.handle_json_arg(parsed_args.steps, 'deploy steps')
+ steps = ["steps=%s" % json.dumps(steps)]
+ properties.extend(utils.args_array_to_patch('add', steps))
+ if parsed_args.extra:
+ properties.extend(utils.args_array_to_patch(
+ 'add', ['extra/' + x for x in parsed_args.extra]))
+
+ if properties:
+ baremetal_client.deploy_template.update(parsed_args.template,
+ properties)
+ else:
+ self.log.warning("Please specify what to set.")
+
+
+class UnsetBaremetalDeployTemplate(command.Command):
+ """Unset baremetal deploy template properties."""
+ log = logging.getLogger(__name__ + ".UnsetBaremetalDeployTemplate")
+
+ def get_parser(self, prog_name):
+ parser = super(UnsetBaremetalDeployTemplate, self).get_parser(
+ prog_name)
+
+ parser.add_argument(
+ 'template',
+ metavar='<template>',
+ help=_("Name or UUID of the deploy template")
+ )
+ parser.add_argument(
+ "--extra",
+ metavar="<key>",
+ action='append',
+ help=_('Extra to unset on this baremetal deploy template '
+ '(repeat option to unset multiple extras).'),
+ )
+
+ return parser
+
+ def take_action(self, parsed_args):
+ self.log.debug("take_action(%s)", parsed_args)
+
+ baremetal_client = self.app.client_manager.baremetal
+
+ properties = []
+ if parsed_args.extra:
+ properties.extend(utils.args_array_to_patch('remove',
+ ['extra/' + x for x in parsed_args.extra]))
+
+ if properties:
+ baremetal_client.deploy_template.update(parsed_args.template,
+ properties)
+ else:
+ self.log.warning("Please specify what to unset.")
+
+
+class DeleteBaremetalDeployTemplate(command.Command):
+ """Delete deploy template(s)."""
+
+ log = logging.getLogger(__name__ + ".DeleteBaremetalDeployTemplate")
+
+ def get_parser(self, prog_name):
+ parser = super(DeleteBaremetalDeployTemplate, self).get_parser(
+ prog_name)
+ parser.add_argument(
+ "templates",
+ metavar="<template>",
+ nargs="+",
+ help=_("Name(s) or UUID(s) of the deploy template(s) to delete.")
+ )
+
+ return parser
+
+ def take_action(self, parsed_args):
+ self.log.debug("take_action(%s)", parsed_args)
+
+ baremetal_client = self.app.client_manager.baremetal
+
+ failures = []
+ for template in parsed_args.templates:
+ try:
+ baremetal_client.deploy_template.delete(template)
+ print(_('Deleted deploy template %s') % template)
+ except exc.ClientException as e:
+ failures.append(_("Failed to delete deploy template "
+ "%(template)s: %(error)s")
+ % {'template': template, 'error': e})
+
+ if failures:
+ raise exc.ClientException("\n".join(failures))
+
+
+class ListBaremetalDeployTemplate(command.Lister):
+ """List baremetal deploy templates."""
+
+ log = logging.getLogger(__name__ + ".ListBaremetalDeployTemplate")
+
+ def get_parser(self, prog_name):
+ parser = super(ListBaremetalDeployTemplate, self).get_parser(prog_name)
+ parser.add_argument(
+ '--limit',
+ metavar='<limit>',
+ type=int,
+ help=_('Maximum number of deploy templates to return per request, '
+ '0 for no limit. Default is the maximum number used '
+ 'by the Baremetal API Service.')
+ )
+ parser.add_argument(
+ '--marker',
+ metavar='<template>',
+ help=_('DeployTemplate UUID (for example, of the last deploy '
+ 'template in the list from a previous request). Returns '
+ 'the list of deploy templates after this UUID.')
+ )
+ parser.add_argument(
+ '--sort',
+ metavar="<key>[:<direction>]",
+ help=_('Sort output by specified deploy template fields and '
+ 'directions (asc or desc) (default: asc). Multiple fields '
+ 'and directions can be specified, separated by comma.')
+ )
+ display_group = parser.add_mutually_exclusive_group()
+ display_group.add_argument(
+ '--long',
+ dest='detail',
+ action='store_true',
+ default=False,
+ help=_("Show detailed information about deploy templates.")
+ )
+ display_group.add_argument(
+ '--fields',
+ nargs='+',
+ dest='fields',
+ metavar='<field>',
+ action='append',
+ default=[],
+ choices=res_fields.DEPLOY_TEMPLATE_DETAILED_RESOURCE.fields,
+ help=_("One or more deploy template fields. Only these fields "
+ "will be fetched from the server. Can not be used when "
+ "'--long' is specified.")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ self.log.debug("take_action(%s)", parsed_args)
+ client = self.app.client_manager.baremetal
+
+ columns = res_fields.DEPLOY_TEMPLATE_RESOURCE.fields
+ labels = res_fields.DEPLOY_TEMPLATE_RESOURCE.labels
+
+ params = {}
+ if parsed_args.limit is not None and parsed_args.limit < 0:
+ raise exc.CommandError(
+ _('Expected non-negative --limit, got %s') %
+ parsed_args.limit)
+ params['limit'] = parsed_args.limit
+ params['marker'] = parsed_args.marker
+
+ if parsed_args.detail:
+ params['detail'] = parsed_args.detail
+ columns = res_fields.DEPLOY_TEMPLATE_DETAILED_RESOURCE.fields
+ labels = res_fields.DEPLOY_TEMPLATE_DETAILED_RESOURCE.labels
+
+ elif parsed_args.fields:
+ params['detail'] = False
+ fields = itertools.chain.from_iterable(parsed_args.fields)
+ resource = res_fields.Resource(list(fields))
+ columns = resource.fields
+ labels = resource.labels
+ params['fields'] = columns
+
+ self.log.debug("params(%s)", params)
+ data = client.deploy_template.list(**params)
+
+ data = oscutils.sort_items(data, parsed_args.sort)
+
+ return (labels,
+ (oscutils.get_item_properties(s, columns) for s in data))
diff --git a/ironicclient/osc/v1/baremetal_node.py b/ironicclient/osc/v1/baremetal_node.py
index fd1e2db..718e12a 100755
--- a/ironicclient/osc/v1/baremetal_node.py
+++ b/ironicclient/osc/v1/baremetal_node.py
@@ -66,10 +66,7 @@ class ProvisionStateBaremetalNode(command.Command):
baremetal_client = self.app.client_manager.baremetal
clean_steps = getattr(parsed_args, 'clean_steps', None)
- if clean_steps == '-':
- clean_steps = utils.get_from_stdin('clean steps')
- if clean_steps:
- clean_steps = utils.handle_json_or_file_arg(clean_steps)
+ clean_steps = utils.handle_json_arg(clean_steps, 'clean steps')
config_drive = getattr(parsed_args, 'config_drive', None)
rescue_password = getattr(parsed_args, 'rescue_password', None)
@@ -1207,9 +1204,8 @@ class SetBaremetalNode(command.Command):
# also being modified.
if parsed_args.target_raid_config:
raid_config = parsed_args.target_raid_config
- if raid_config == '-':
- raid_config = utils.get_from_stdin('target_raid_config')
- raid_config = utils.handle_json_or_file_arg(raid_config)
+ raid_config = utils.handle_json_arg(raid_config,
+ 'target_raid_config')
baremetal_client.node.set_target_raid_config(parsed_args.node,
raid_config)
diff --git a/ironicclient/osc/v1/baremetal_port.py b/ironicclient/osc/v1/baremetal_port.py
index bc0468f..ceb6702 100644
--- a/ironicclient/osc/v1/baremetal_port.py
+++ b/ironicclient/osc/v1/baremetal_port.py
@@ -443,7 +443,7 @@ class ListBaremetalPort(command.Lister):
type=int,
help=_('Maximum number of ports to return per request, '
'0 for no limit. Default is the maximum number used '
- 'by the Ironic API Service.')
+ 'by the Baremetal API Service.')
)
parser.add_argument(
'--marker',
diff --git a/ironicclient/tests/functional/osc/v1/base.py b/ironicclient/tests/functional/osc/v1/base.py
index b2ce630..77308df 100644
--- a/ironicclient/tests/functional/osc/v1/base.py
+++ b/ironicclient/tests/functional/osc/v1/base.py
@@ -401,3 +401,60 @@ class TestCase(base.FunctionalTestBase):
except exceptions.CommandFailed:
if not ignore_exceptions:
raise
+
+ def deploy_template_create(self, name, params=''):
+ """Create baremetal deploy template and add cleanup.
+
+ :param String name: deploy template name
+ :param String params: additional parameters
+ :return: JSON object of created deploy template
+ """
+ opts = self.get_opts()
+ template = self.openstack('baremetal deploy template create {0} {1} '
+ '{2}'.format(opts, name, params))
+ template = json.loads(template)
+ if not template:
+ self.fail('Baremetal deploy template has not been created!')
+ self.addCleanup(self.deploy_template_delete, template['uuid'], True)
+ return template
+
+ def deploy_template_list(self, fields=None, params=''):
+ """List baremetal deploy templates.
+
+ :param List fields: List of fields to show
+ :param String params: Additional kwargs
+ :return: list of JSON deploy template objects
+ """
+ opts = self.get_opts(fields=fields)
+ output = self.openstack('baremetal deploy template list {0} {1}'
+ .format(opts, params))
+ return json.loads(output)
+
+ def deploy_template_show(self, identifier, fields=None, params=''):
+ """Show specified baremetal deploy template.
+
+ :param String identifier: Name or UUID of the deploy template
+ :param List fields: List of fields to show
+ :param List params: Additional kwargs
+ :return: JSON object of deploy template
+ """
+ opts = self.get_opts(fields)
+ output = self.openstack('baremetal deploy template show {0} {1} {2}'
+ .format(opts, identifier, params))
+ return json.loads(output)
+
+ def deploy_template_delete(self, identifier, ignore_exceptions=False):
+ """Try to delete baremetal deploy template by UUID.
+
+ :param String identifier: Name or UUID of the deploy template
+ :param Bool ignore_exceptions: Ignore exception (needed for cleanUp)
+ :return: raw values output
+ :raise: CommandFailed exception when command fails to delete a deploy
+ template
+ """
+ try:
+ return self.openstack('baremetal deploy template delete {0}'
+ .format(identifier))
+ except exceptions.CommandFailed:
+ if not ignore_exceptions:
+ raise
diff --git a/ironicclient/tests/functional/osc/v1/test_baremetal_deploy_template_basic.py b/ironicclient/tests/functional/osc/v1/test_baremetal_deploy_template_basic.py
new file mode 100644
index 0000000..1fa2932
--- /dev/null
+++ b/ironicclient/tests/functional/osc/v1/test_baremetal_deploy_template_basic.py
@@ -0,0 +1,177 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import json
+
+import ddt
+from tempest.lib.common.utils import data_utils
+from tempest.lib import exceptions
+
+from ironicclient.tests.functional.osc.v1 import base
+
+
+@ddt.ddt
+class BaremetalDeployTemplateTests(base.TestCase):
+ """Functional tests for baremetal deploy template commands."""
+
+ @staticmethod
+ def _get_random_trait():
+ return data_utils.rand_name('CUSTOM', '').replace('-', '_')
+
+ def setUp(self):
+ super(BaremetalDeployTemplateTests, self).setUp()
+ self.steps = json.dumps([{
+ 'interface': 'bios',
+ 'step': 'apply_configuration',
+ 'args': {},
+ 'priority': 10,
+ }])
+ name = self._get_random_trait()
+ self.template = self.deploy_template_create(
+ name, params="--steps '%s'" % self.steps)
+
+ def tearDown(self):
+ if self.template is not None:
+ self.deploy_template_delete(self.template['uuid'])
+ super(BaremetalDeployTemplateTests, self).tearDown()
+
+ def test_list(self):
+ """Check baremetal deploy template list command.
+
+ Test steps:
+ 1) Create baremetal deploy template in setUp.
+ 2) List baremetal deploy templates.
+ 3) Check deploy template name and UUID in deploy templates list.
+ """
+ template_list = self.deploy_template_list()
+ self.assertIn(self.template['name'],
+ [template['Name']
+ for template in template_list])
+ self.assertIn(self.template['uuid'],
+ [template['UUID']
+ for template in template_list])
+
+ def test_list_long(self):
+ """Check baremetal deploy template list --long command
+
+ Test steps:
+ 1) Create baremetal deploy template in setUp.
+ 2) List baremetal deploy templates with detail=True.
+ 3) Check deploy template fields in output.
+ """
+ template_list = self.deploy_template_list(params='--long')
+ template = [template for template in template_list
+ if template['Name'] == self.template['name']][0]
+ self.assertEqual(self.template['extra'], template['Extra'])
+ self.assertEqual(self.template['name'], template['Name'])
+ self.assertEqual(self.template['steps'], template['Steps'])
+ self.assertEqual(self.template['uuid'], template['UUID'])
+
+ def test_show(self):
+ """Check baremetal deploy template show command with UUID.
+
+ Test steps:
+ 1) Create baremetal deploy template in setUp.
+ 2) Show baremetal deploy template calling it by UUID.
+ 3) Check deploy template fields in output.
+ """
+ template = self.deploy_template_show(self.template['uuid'])
+ self.assertEqual(self.template['extra'], template['extra'])
+ self.assertEqual(self.template['name'], template['name'])
+ self.assertEqual(self.template['steps'], template['steps'])
+ self.assertEqual(self.template['uuid'], template['uuid'])
+
+ def test_delete(self):
+ """Check baremetal deploy template delete command.
+
+ Test steps:
+ 1) Create baremetal deploy template in setUp.
+ 2) Delete baremetal deploy template by UUID.
+ 3) Check that deploy template deleted successfully and not in list.
+ """
+ output = self.deploy_template_delete(self.template['uuid'])
+ self.assertIn('Deleted deploy template {0}'.format(
+ self.template['uuid']), output)
+ template_list = self.deploy_template_list()
+ self.assertNotIn(self.template['name'],
+ [template['Name'] for template in template_list])
+ self.assertNotIn(self.template['uuid'],
+ [template['UUID'] for template in template_list])
+ self.template = None
+
+ def test_set_steps(self):
+ """Check baremetal deploy template set command for steps.
+
+ Test steps:
+ 1) Create baremetal deploy template in setUp.
+ 2) Set steps for deploy template.
+ 3) Check that baremetal deploy template steps were set.
+ """
+ steps = [{
+ 'interface': 'bios',
+ 'step': 'apply_configuration',
+ 'args': {},
+ 'priority': 20,
+ }]
+ self.openstack("baremetal deploy template set --steps '{0}' {1}"
+ .format(json.dumps(steps), self.template['uuid']))
+
+ show_prop = self.deploy_template_show(self.template['uuid'],
+ fields=['steps'])
+ self.assertEqual(steps, show_prop['steps'])
+
+ def test_set_unset(self):
+ """Check baremetal deploy template set and unset commands.
+
+ Test steps:
+ 1) Create baremetal deploy template in setUp.
+ 2) Set extra data for deploy template.
+ 3) Check that baremetal deploy template extra data was set.
+ 4) Unset extra data for deploy template.
+ 5) Check that baremetal deploy template extra data was unset.
+ """
+ extra_key = 'ext'
+ extra_value = 'testdata'
+ self.openstack(
+ 'baremetal deploy template set --extra {0}={1} {2}'
+ .format(extra_key, extra_value, self.template['uuid']))
+
+ show_prop = self.deploy_template_show(self.template['uuid'],
+ fields=['extra'])
+ self.assertEqual(extra_value, show_prop['extra'][extra_key])
+
+ self.openstack('baremetal deploy template unset --extra {0} {1}'
+ .format(extra_key, self.template['uuid']))
+
+ show_prop = self.deploy_template_show(self.template['uuid'],
+ fields=['extra'])
+ self.assertNotIn(extra_key, show_prop['extra'])
+
+ @ddt.data(
+ ('--uuid', '', 'expected one argument'),
+ ('--uuid', '!@#$^*&%^', 'Expected a UUID'),
+ ('', '', 'too few arguments'),
+ ('', 'not/a/name', 'Deploy template name must be a valid trait'),
+ ('', 'foo', 'Deploy template name must be a valid trait'),
+ ('--steps', '', 'expected one argument'),
+ ('--steps', '[]', 'No deploy steps specified'))
+ @ddt.unpack
+ def test_create_negative(self, argument, value, ex_text):
+ """Check errors on invalid input parameters."""
+ base_cmd = 'baremetal deploy template create'
+ if argument != '':
+ base_cmd += ' %s' % self._get_random_trait()
+ if argument != '--steps':
+ base_cmd += " --steps '%s'" % self.steps
+ command = self.construct_cmd(base_cmd, argument, value)
+ self.assertRaisesRegex(exceptions.CommandFailed, ex_text,
+ self.openstack, command)
diff --git a/ironicclient/tests/unit/osc/v1/fakes.py b/ironicclient/tests/unit/osc/v1/fakes.py
index 1e93fc4..419b709 100644
--- a/ironicclient/tests/unit/osc/v1/fakes.py
+++ b/ironicclient/tests/unit/osc/v1/fakes.py
@@ -14,6 +14,8 @@
# under the License.
#
+import json
+
import mock
from osc_lib.tests import utils
@@ -201,6 +203,22 @@ ALLOCATION = {
'node_uuid': baremetal_uuid,
}
+baremetal_deploy_template_uuid = 'ddd-tttttt-dddd'
+baremetal_deploy_template_name = 'DeployTemplate-name'
+baremetal_deploy_template_steps = json.dumps([{
+ 'interface': 'raid',
+ 'step': 'create_configuration',
+ 'args': {},
+ 'priority': 10
+}])
+baremetal_deploy_template_extra = {'key1': 'value1', 'key2': 'value2'}
+DEPLOY_TEMPLATE = {
+ 'uuid': baremetal_deploy_template_uuid,
+ 'name': baremetal_deploy_template_name,
+ 'steps': baremetal_deploy_template_steps,
+ 'extra': baremetal_deploy_template_extra,
+}
+
class TestBaremetal(utils.TestCommand):
diff --git a/ironicclient/tests/unit/osc/v1/test_baremetal_deploy_template.py b/ironicclient/tests/unit/osc/v1/test_baremetal_deploy_template.py
new file mode 100644
index 0000000..799ce1d
--- /dev/null
+++ b/ironicclient/tests/unit/osc/v1/test_baremetal_deploy_template.py
@@ -0,0 +1,450 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+import copy
+import json
+
+import mock
+from osc_lib.tests import utils as osctestutils
+
+from ironicclient import exc
+from ironicclient.osc.v1 import baremetal_deploy_template
+from ironicclient.tests.unit.osc.v1 import fakes as baremetal_fakes
+
+
+class TestBaremetalDeployTemplate(baremetal_fakes.TestBaremetal):
+
+ def setUp(self):
+ super(TestBaremetalDeployTemplate, self).setUp()
+
+ self.baremetal_mock = self.app.client_manager.baremetal
+ self.baremetal_mock.reset_mock()
+
+
+class TestCreateBaremetalDeployTemplate(TestBaremetalDeployTemplate):
+ def setUp(self):
+ super(TestCreateBaremetalDeployTemplate, self).setUp()
+
+ self.baremetal_mock.deploy_template.create.return_value = (
+ baremetal_fakes.FakeBaremetalResource(
+ None,
+ copy.deepcopy(baremetal_fakes.DEPLOY_TEMPLATE),
+ loaded=True,
+ ))
+
+ # Get the command object to test
+ self.cmd = baremetal_deploy_template.CreateBaremetalDeployTemplate(
+ self.app, None)
+
+ def test_baremetal_deploy_template_create(self):
+ arglist = [
+ baremetal_fakes.baremetal_deploy_template_name,
+ '--steps', baremetal_fakes.baremetal_deploy_template_steps,
+ ]
+
+ verifylist = [
+ ('name', baremetal_fakes.baremetal_deploy_template_name),
+ ('steps', baremetal_fakes.baremetal_deploy_template_steps),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ args = {
+ 'name': baremetal_fakes.baremetal_deploy_template_name,
+ 'steps': json.loads(
+ baremetal_fakes.baremetal_deploy_template_steps),
+ }
+
+ self.baremetal_mock.deploy_template.create.assert_called_once_with(
+ **args)
+
+ def test_baremetal_deploy_template_create_uuid(self):
+ arglist = [
+ baremetal_fakes.baremetal_deploy_template_name,
+ '--steps', baremetal_fakes.baremetal_deploy_template_steps,
+ '--uuid', baremetal_fakes.baremetal_deploy_template_uuid,
+ ]
+
+ verifylist = [
+ ('name', baremetal_fakes.baremetal_deploy_template_name),
+ ('steps', baremetal_fakes.baremetal_deploy_template_steps),
+ ('uuid', baremetal_fakes.baremetal_deploy_template_uuid),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ args = {
+ 'name': baremetal_fakes.baremetal_deploy_template_name,
+ 'steps': json.loads(
+ baremetal_fakes.baremetal_deploy_template_steps),
+ 'uuid': baremetal_fakes.baremetal_deploy_template_uuid,
+ }
+
+ self.baremetal_mock.deploy_template.create.assert_called_once_with(
+ **args)
+
+ def test_baremetal_deploy_template_create_no_name(self):
+ arglist = [
+ '--steps', baremetal_fakes.baremetal_deploy_template_steps,
+ ]
+
+ verifylist = [
+ ('steps', baremetal_fakes.baremetal_deploy_template_steps),
+ ]
+
+ self.assertRaises(osctestutils.ParserException,
+ self.check_parser,
+ self.cmd, arglist, verifylist)
+ self.assertFalse(self.baremetal_mock.deploy_template.create.called)
+
+ def test_baremetal_deploy_template_create_no_steps(self):
+ arglist = [
+ '--name', baremetal_fakes.baremetal_deploy_template_name,
+ ]
+
+ verifylist = [
+ ('name', baremetal_fakes.baremetal_deploy_template_name),
+ ]
+
+ self.assertRaises(osctestutils.ParserException,
+ self.check_parser,
+ self.cmd, arglist, verifylist)
+ self.assertFalse(self.baremetal_mock.deploy_template.create.called)
+
+
+class TestShowBaremetalDeployTemplate(TestBaremetalDeployTemplate):
+ def setUp(self):
+ super(TestShowBaremetalDeployTemplate, self).setUp()
+
+ self.baremetal_mock.deploy_template.get.return_value = (
+ baremetal_fakes.FakeBaremetalResource(
+ None,
+ copy.deepcopy(baremetal_fakes.DEPLOY_TEMPLATE),
+ loaded=True))
+
+ self.cmd = baremetal_deploy_template.ShowBaremetalDeployTemplate(
+ self.app, None)
+
+ def test_baremetal_deploy_template_show(self):
+ arglist = [baremetal_fakes.baremetal_deploy_template_uuid]
+ verifylist = [('template',
+ baremetal_fakes.baremetal_deploy_template_uuid)]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ args = [baremetal_fakes.baremetal_deploy_template_uuid]
+ self.baremetal_mock.deploy_template.get.assert_called_with(
+ *args, fields=None)
+
+ collist = (
+ 'extra',
+ 'name',
+ 'steps',
+ 'uuid')
+ self.assertEqual(collist, columns)
+
+ datalist = (
+ baremetal_fakes.baremetal_deploy_template_extra,
+ baremetal_fakes.baremetal_deploy_template_name,
+ baremetal_fakes.baremetal_deploy_template_steps,
+ baremetal_fakes.baremetal_deploy_template_uuid)
+ self.assertEqual(datalist, tuple(data))
+
+ def test_baremetal_deploy_template_show_no_template(self):
+ arglist = []
+ verifylist = []
+
+ self.assertRaises(osctestutils.ParserException,
+ self.check_parser,
+ self.cmd, arglist, verifylist)
+
+
+class TestBaremetalDeployTemplateSet(TestBaremetalDeployTemplate):
+ def setUp(self):
+ super(TestBaremetalDeployTemplateSet, self).setUp()
+
+ self.baremetal_mock.deploy_template.update.return_value = (
+ baremetal_fakes.FakeBaremetalResource(
+ None,
+ copy.deepcopy(baremetal_fakes.DEPLOY_TEMPLATE),
+ loaded=True))
+
+ self.cmd = baremetal_deploy_template.SetBaremetalDeployTemplate(
+ self.app, None)
+
+ def test_baremetal_deploy_template_set_name(self):
+ new_name = 'foo'
+ arglist = [
+ baremetal_fakes.baremetal_deploy_template_uuid,
+ '--name', new_name]
+ verifylist = [
+ ('template', baremetal_fakes.baremetal_deploy_template_uuid),
+ ('name', new_name)]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.cmd.take_action(parsed_args)
+ self.baremetal_mock.deploy_template.update.assert_called_once_with(
+ baremetal_fakes.baremetal_deploy_template_uuid,
+ [{'path': '/name', 'value': new_name, 'op': 'add'}])
+
+ def test_baremetal_deploy_template_set_steps(self):
+ arglist = [
+ baremetal_fakes.baremetal_deploy_template_uuid,
+ '--steps', baremetal_fakes.baremetal_deploy_template_steps]
+ verifylist = [
+ ('template', baremetal_fakes.baremetal_deploy_template_uuid),
+ ('steps', baremetal_fakes.baremetal_deploy_template_steps)]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ expected_steps = json.loads(
+ baremetal_fakes.baremetal_deploy_template_steps)
+ self.cmd.take_action(parsed_args)
+ self.baremetal_mock.deploy_template.update.assert_called_once_with(
+ baremetal_fakes.baremetal_deploy_template_uuid,
+ [{'path': '/steps', 'value': expected_steps, 'op': 'add'}])
+
+ def test_baremetal_deploy_template_set_no_options(self):
+ arglist = []
+ verifylist = []
+ self.assertRaises(osctestutils.ParserException,
+ self.check_parser,
+ self.cmd, arglist, verifylist)
+
+
+class TestBaremetalDeployTemplateUnset(TestBaremetalDeployTemplate):
+ def setUp(self):
+ super(TestBaremetalDeployTemplateUnset, self).setUp()
+
+ self.baremetal_mock.deploy_template.update.return_value = (
+ baremetal_fakes.FakeBaremetalResource(
+ None,
+ copy.deepcopy(baremetal_fakes.DEPLOY_TEMPLATE),
+ loaded=True))
+
+ self.cmd = baremetal_deploy_template.UnsetBaremetalDeployTemplate(
+ self.app, None)
+
+ def test_baremetal_deploy_template_unset_extra(self):
+ arglist = [
+ baremetal_fakes.baremetal_deploy_template_uuid, '--extra', 'key1']
+ verifylist = [('template',
+ baremetal_fakes.baremetal_deploy_template_uuid),
+ ('extra', ['key1'])]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.cmd.take_action(parsed_args)
+ self.baremetal_mock.deploy_template.update.assert_called_once_with(
+ baremetal_fakes.baremetal_deploy_template_uuid,
+ [{'path': '/extra/key1', 'op': 'remove'}])
+
+ def test_baremetal_deploy_template_unset_multiple_extras(self):
+ arglist = [
+ baremetal_fakes.baremetal_deploy_template_uuid,
+ '--extra', 'key1', '--extra', 'key2']
+ verifylist = [('template',
+ baremetal_fakes.baremetal_deploy_template_uuid),
+ ('extra', ['key1', 'key2'])]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.cmd.take_action(parsed_args)
+ self.baremetal_mock.deploy_template.update.assert_called_once_with(
+ baremetal_fakes.baremetal_deploy_template_uuid,
+ [{'path': '/extra/key1', 'op': 'remove'},
+ {'path': '/extra/key2', 'op': 'remove'}])
+
+ def test_baremetal_deploy_template_unset_no_options(self):
+ arglist = []
+ verifylist = []
+ self.assertRaises(osctestutils.ParserException,
+ self.check_parser,
+ self.cmd, arglist, verifylist)
+
+ def test_baremetal_deploy_template_unset_no_property(self):
+ uuid = baremetal_fakes.baremetal_deploy_template_uuid
+ arglist = [uuid]
+ verifylist = [('template', uuid)]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.cmd.take_action(parsed_args)
+ self.assertFalse(self.baremetal_mock.deploy_template.update.called)
+
+
+class TestBaremetalDeployTemplateDelete(TestBaremetalDeployTemplate):
+ def setUp(self):
+ super(TestBaremetalDeployTemplateDelete, self).setUp()
+
+ self.cmd = baremetal_deploy_template.DeleteBaremetalDeployTemplate(
+ self.app, None)
+
+ def test_baremetal_deploy_template_delete(self):
+ arglist = ['zzz-zzzzzz-zzzz']
+ verifylist = []
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.cmd.take_action(parsed_args)
+
+ args = 'zzz-zzzzzz-zzzz'
+ self.baremetal_mock.deploy_template.delete.assert_called_with(args)
+
+ def test_baremetal_deploy_template_delete_multiple(self):
+ arglist = ['zzz-zzzzzz-zzzz', 'fakename']
+ verifylist = []
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.cmd.take_action(parsed_args)
+
+ args = ['zzz-zzzzzz-zzzz', 'fakename']
+ self.baremetal_mock.deploy_template.delete.has_calls(
+ [mock.call(x) for x in args])
+ self.assertEqual(
+ 2, self.baremetal_mock.deploy_template.delete.call_count)
+
+ def test_baremetal_deploy_template_delete_multiple_with_fail(self):
+ arglist = ['zzz-zzzzzz-zzzz', 'badname']
+ verifylist = []
+
+ self.baremetal_mock.deploy_template.delete.side_effect = [
+ '', exc.ClientException]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.assertRaises(exc.ClientException,
+ self.cmd.take_action,
+ parsed_args)
+
+ args = ['zzz-zzzzzz-zzzz', 'badname']
+ self.baremetal_mock.deploy_template.delete.has_calls(
+ [mock.call(x) for x in args])
+ self.assertEqual(
+ 2, self.baremetal_mock.deploy_template.delete.call_count)
+
+ def test_baremetal_deploy_template_delete_no_template(self):
+ arglist = []
+ verifylist = []
+
+ self.assertRaises(osctestutils.ParserException,
+ self.check_parser,
+ self.cmd, arglist, verifylist)
+
+
+class TestBaremetalDeployTemplateList(TestBaremetalDeployTemplate):
+ def setUp(self):
+ super(TestBaremetalDeployTemplateList, self).setUp()
+
+ self.baremetal_mock.deploy_template.list.return_value = [
+ baremetal_fakes.FakeBaremetalResource(
+ None,
+ copy.deepcopy(baremetal_fakes.DEPLOY_TEMPLATE),
+ loaded=True)
+ ]
+
+ self.cmd = baremetal_deploy_template.ListBaremetalDeployTemplate(
+ self.app, None)
+
+ def test_baremetal_deploy_template_list(self):
+ arglist = []
+ verifylist = []
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ kwargs = {
+ 'marker': None,
+ 'limit': None}
+ self.baremetal_mock.deploy_template.list.assert_called_with(**kwargs)
+
+ collist = (
+ "UUID",
+ "Name")
+ self.assertEqual(collist, columns)
+
+ datalist = ((
+ baremetal_fakes.baremetal_deploy_template_uuid,
+ baremetal_fakes.baremetal_deploy_template_name
+ ), )
+ self.assertEqual(datalist, tuple(data))
+
+ def test_baremetal_deploy_template_list_long(self):
+ arglist = ['--long']
+ verifylist = [('detail', True)]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ kwargs = {
+ 'detail': True,
+ 'marker': None,
+ 'limit': None,
+ }
+ self.baremetal_mock.deploy_template.list.assert_called_with(**kwargs)
+
+ collist = ('UUID', 'Name', 'Steps', 'Extra', 'Created At',
+ 'Updated At')
+ self.assertEqual(collist, columns)
+
+ datalist = ((
+ baremetal_fakes.baremetal_deploy_template_uuid,
+ baremetal_fakes.baremetal_deploy_template_name,
+ baremetal_fakes.baremetal_deploy_template_steps,
+ baremetal_fakes.baremetal_deploy_template_extra,
+ '',
+ '',
+ ), )
+ self.assertEqual(datalist, tuple(data))
+
+ def test_baremetal_deploy_template_list_fields(self):
+ arglist = ['--fields', 'uuid', 'steps']
+ verifylist = [('fields', [['uuid', 'steps']])]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.cmd.take_action(parsed_args)
+
+ kwargs = {
+ 'marker': None,
+ 'limit': None,
+ 'detail': False,
+ 'fields': ('uuid', 'steps')
+ }
+ self.baremetal_mock.deploy_template.list.assert_called_with(**kwargs)
+
+ def test_baremetal_deploy_template_list_fields_multiple(self):
+ arglist = ['--fields', 'uuid', 'name', '--fields', 'steps']
+ verifylist = [('fields', [['uuid', 'name'], ['steps']])]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ self.cmd.take_action(parsed_args)
+
+ kwargs = {
+ 'marker': None,
+ 'limit': None,
+ 'detail': False,
+ 'fields': ('uuid', 'name', 'steps')
+ }
+ self.baremetal_mock.deploy_template.list.assert_called_with(**kwargs)
+
+ def test_baremetal_deploy_template_list_invalid_fields(self):
+ arglist = ['--fields', 'uuid', 'invalid']
+ verifylist = [('fields', [['uuid', 'invalid']])]
+ self.assertRaises(osctestutils.ParserException,
+ self.check_parser,
+ self.cmd, arglist, verifylist)
diff --git a/ironicclient/tests/unit/v1/test_deploy_template.py b/ironicclient/tests/unit/v1/test_deploy_template.py
new file mode 100644
index 0000000..0999bb3
--- /dev/null
+++ b/ironicclient/tests/unit/v1/test_deploy_template.py
@@ -0,0 +1,291 @@
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+import testtools
+from testtools.matchers import HasLength
+
+from ironicclient import exc
+from ironicclient.tests.unit import utils
+import ironicclient.v1.deploy_template
+
+DEPLOY_TEMPLATE = {'uuid': '11111111-2222-3333-4444-555555555555',
+ 'name': 'fake-template',
+ 'steps': {},
+ 'extra': {}}
+
+DEPLOY_TEMPLATE2 = {'uuid': '55555555-4444-3333-2222-111111111111',
+ 'name': 'fake-template2',
+ 'steps': {},
+ 'extra': {}}
+
+CREATE_DEPLOY_TEMPLATE = copy.deepcopy(DEPLOY_TEMPLATE)
+del CREATE_DEPLOY_TEMPLATE['uuid']
+
+CREATE_DEPLOY_TEMPLATE_WITH_UUID = copy.deepcopy(DEPLOY_TEMPLATE)
+
+UPDATED_DEPLOY_TEMPLATE = copy.deepcopy(DEPLOY_TEMPLATE)
+NEW_NAME = 'fake-template3'
+UPDATED_DEPLOY_TEMPLATE['name'] = NEW_NAME
+
+fake_responses = {
+ '/v1/deploy_templates':
+ {
+ 'GET': (
+ {},
+ {"deploy_templates": [DEPLOY_TEMPLATE]},
+ ),
+ 'POST': (
+ {},
+ CREATE_DEPLOY_TEMPLATE,
+ ),
+ },
+ '/v1/deploy_templates/?detail=True':
+ {
+ 'GET': (
+ {},
+ {"deploy_templates": [DEPLOY_TEMPLATE]},
+ ),
+ },
+ '/v1/deploy_templates/?fields=uuid,name':
+ {
+ 'GET': (
+ {},
+ {"deploy_templates": [DEPLOY_TEMPLATE]},
+ ),
+ },
+ '/v1/deploy_templates/%s' % DEPLOY_TEMPLATE['uuid']:
+ {
+ 'GET': (
+ {},
+ DEPLOY_TEMPLATE,
+ ),
+ 'DELETE': (
+ {},
+ None,
+ ),
+ 'PATCH': (
+ {},
+ UPDATED_DEPLOY_TEMPLATE,
+ ),
+ },
+ '/v1/deploy_templates/%s?fields=uuid,name' % DEPLOY_TEMPLATE['uuid']:
+ {
+ 'GET': (
+ {},
+ DEPLOY_TEMPLATE,
+ ),
+ },
+}
+
+fake_responses_pagination = {
+ '/v1/deploy_templates':
+ {
+ 'GET': (
+ {},
+ {"deploy_templates": [DEPLOY_TEMPLATE],
+ "next": "http://127.0.0.1:6385/v1/deploy_templates/?limit=1"}
+ ),
+ },
+ '/v1/deploy_templates/?limit=1':
+ {
+ 'GET': (
+ {},
+ {"deploy_templates": [DEPLOY_TEMPLATE2]}
+ ),
+ },
+ '/v1/deploy_templates/?marker=%s' % DEPLOY_TEMPLATE['uuid']:
+ {
+ 'GET': (
+ {},
+ {"deploy_templates": [DEPLOY_TEMPLATE2]}
+ ),
+ },
+}
+
+fake_responses_sorting = {
+ '/v1/deploy_templates/?sort_key=updated_at':
+ {
+ 'GET': (
+ {},
+ {"deploy_templates": [DEPLOY_TEMPLATE2, DEPLOY_TEMPLATE]}
+ ),
+ },
+ '/v1/deploy_templates/?sort_dir=desc':
+ {
+ 'GET': (
+ {},
+ {"deploy_templates": [DEPLOY_TEMPLATE2, DEPLOY_TEMPLATE]}
+ ),
+ },
+}
+
+
+class DeployTemplateManagerTest(testtools.TestCase):
+
+ def setUp(self):
+ super(DeployTemplateManagerTest, self).setUp()
+ self.api = utils.FakeAPI(fake_responses)
+ self.mgr = ironicclient.v1.deploy_template.DeployTemplateManager(
+ self.api)
+
+ def test_deploy_templates_list(self):
+ deploy_templates = self.mgr.list()
+ expect = [
+ ('GET', '/v1/deploy_templates', {}, None),
+ ]
+ self.assertEqual(expect, self.api.calls)
+ self.assertEqual(1, len(deploy_templates))
+
+ def test_deploy_templates_list_detail(self):
+ deploy_templates = self.mgr.list(detail=True)
+ expect = [
+ ('GET', '/v1/deploy_templates/?detail=True', {}, None),
+ ]
+ self.assertEqual(expect, self.api.calls)
+ self.assertEqual(1, len(deploy_templates))
+
+ def test_deploy_template_list_fields(self):
+ deploy_templates = self.mgr.list(fields=['uuid', 'name'])
+ expect = [
+ ('GET', '/v1/deploy_templates/?fields=uuid,name', {}, None),
+ ]
+ self.assertEqual(expect, self.api.calls)
+ self.assertEqual(1, len(deploy_templates))
+
+ def test_deploy_template_list_detail_and_fields_fail(self):
+ self.assertRaises(exc.InvalidAttribute, self.mgr.list,
+ detail=True, fields=['uuid', 'name'])
+
+ def test_deploy_templates_list_limit(self):
+ self.api = utils.FakeAPI(fake_responses_pagination)
+ self.mgr = ironicclient.v1.deploy_template.DeployTemplateManager(
+ self.api)
+ deploy_templates = self.mgr.list(limit=1)
+ expect = [
+ ('GET', '/v1/deploy_templates/?limit=1', {}, None),
+ ]
+ self.assertEqual(expect, self.api.calls)
+ self.assertThat(deploy_templates, HasLength(1))
+
+ def test_deploy_templates_list_marker(self):
+ self.api = utils.FakeAPI(fake_responses_pagination)
+ self.mgr = ironicclient.v1.deploy_template.DeployTemplateManager(
+ self.api)
+ deploy_templates = self.mgr.list(marker=DEPLOY_TEMPLATE['uuid'])
+ expect = [
+ ('GET',
+ '/v1/deploy_templates/?marker=%s' % DEPLOY_TEMPLATE['uuid'], {},
+ None),
+ ]
+ self.assertEqual(expect, self.api.calls)
+ self.assertThat(deploy_templates, HasLength(1))
+
+ def test_deploy_templates_list_pagination_no_limit(self):
+ self.api = utils.FakeAPI(fake_responses_pagination)
+ self.mgr = ironicclient.v1.deploy_template.DeployTemplateManager(
+ self.api)
+ deploy_templates = self.mgr.list(limit=0)
+ expect = [
+ ('GET', '/v1/deploy_templates', {}, None),
+ ('GET', '/v1/deploy_templates/?limit=1', {}, None)
+ ]
+ self.assertEqual(expect, self.api.calls)
+ self.assertThat(deploy_templates, HasLength(2))
+
+ def test_deploy_templates_list_sort_key(self):
+ self.api = utils.FakeAPI(fake_responses_sorting)
+ self.mgr = ironicclient.v1.deploy_template.DeployTemplateManager(
+ self.api)
+ deploy_templates = self.mgr.list(sort_key='updated_at')
+ expect = [
+ ('GET', '/v1/deploy_templates/?sort_key=updated_at', {}, None)
+ ]
+ self.assertEqual(expect, self.api.calls)
+ self.assertEqual(2, len(deploy_templates))
+
+ def test_deploy_templates_list_sort_dir(self):
+ self.api = utils.FakeAPI(fake_responses_sorting)
+ self.mgr = ironicclient.v1.deploy_template.DeployTemplateManager(
+ self.api)
+ deploy_templates = self.mgr.list(sort_dir='desc')
+ expect = [
+ ('GET', '/v1/deploy_templates/?sort_dir=desc', {}, None)
+ ]
+ self.assertEqual(expect, self.api.calls)
+ self.assertEqual(2, len(deploy_templates))
+
+ def test_deploy_templates_show(self):
+ deploy_template = self.mgr.get(DEPLOY_TEMPLATE['uuid'])
+ expect = [
+ ('GET', '/v1/deploy_templates/%s' % DEPLOY_TEMPLATE['uuid'], {},
+ None),
+ ]
+ self.assertEqual(expect, self.api.calls)
+ self.assertEqual(DEPLOY_TEMPLATE['uuid'], deploy_template.uuid)
+ self.assertEqual(DEPLOY_TEMPLATE['name'], deploy_template.name)
+ self.assertEqual(DEPLOY_TEMPLATE['steps'], deploy_template.steps)
+ self.assertEqual(DEPLOY_TEMPLATE['extra'], deploy_template.extra)
+
+ def test_deploy_template_show_fields(self):
+ deploy_template = self.mgr.get(DEPLOY_TEMPLATE['uuid'],
+ fields=['uuid', 'name'])
+ expect = [
+ ('GET', '/v1/deploy_templates/%s?fields=uuid,name' %
+ DEPLOY_TEMPLATE['uuid'], {}, None),
+ ]
+ self.assertEqual(expect, self.api.calls)
+ self.assertEqual(DEPLOY_TEMPLATE['uuid'], deploy_template.uuid)
+ self.assertEqual(DEPLOY_TEMPLATE['name'], deploy_template.name)
+
+ def test_create(self):
+ deploy_template = self.mgr.create(**CREATE_DEPLOY_TEMPLATE)
+ expect = [
+ ('POST', '/v1/deploy_templates', {}, CREATE_DEPLOY_TEMPLATE),
+ ]
+ self.assertEqual(expect, self.api.calls)
+ self.assertTrue(deploy_template)
+
+ def test_create_with_uuid(self):
+ deploy_template = self.mgr.create(**CREATE_DEPLOY_TEMPLATE_WITH_UUID)
+ expect = [
+ ('POST', '/v1/deploy_templates', {},
+ CREATE_DEPLOY_TEMPLATE_WITH_UUID),
+ ]
+ self.assertEqual(expect, self.api.calls)
+ self.assertTrue(deploy_template)
+
+ def test_delete(self):
+ deploy_template = self.mgr.delete(
+ template_id=DEPLOY_TEMPLATE['uuid'])
+ expect = [
+ ('DELETE', '/v1/deploy_templates/%s' % DEPLOY_TEMPLATE['uuid'], {},
+ None),
+ ]
+ self.assertEqual(expect, self.api.calls)
+ self.assertIsNone(deploy_template)
+
+ def test_update(self):
+ patch = {'op': 'replace',
+ 'value': NEW_NAME,
+ 'path': '/name'}
+ deploy_template = self.mgr.update(
+ template_id=DEPLOY_TEMPLATE['uuid'], patch=patch)
+ expect = [
+ ('PATCH', '/v1/deploy_templates/%s' % DEPLOY_TEMPLATE['uuid'],
+ {}, patch),
+ ]
+ self.assertEqual(expect, self.api.calls)
+ self.assertEqual(NEW_NAME, deploy_template.name)
diff --git a/ironicclient/v1/client.py b/ironicclient/v1/client.py
index 76f6bf0..6f38964 100644
--- a/ironicclient/v1/client.py
+++ b/ironicclient/v1/client.py
@@ -23,6 +23,7 @@ from ironicclient import exc
from ironicclient.v1 import allocation
from ironicclient.v1 import chassis
from ironicclient.v1 import conductor
+from ironicclient.v1 import deploy_template
from ironicclient.v1 import driver
from ironicclient.v1 import events
from ironicclient.v1 import node
@@ -103,6 +104,8 @@ class Client(object):
self.conductor = conductor.ConductorManager(self.http_client)
self.events = events.EventManager(self.http_client)
self.allocation = allocation.AllocationManager(self.http_client)
+ self.deploy_template = deploy_template.DeployTemplateManager(
+ self.http_client)
@property
def current_api_version(self):
diff --git a/ironicclient/v1/deploy_template.py b/ironicclient/v1/deploy_template.py
new file mode 100644
index 0000000..4ae377d
--- /dev/null
+++ b/ironicclient/v1/deploy_template.py
@@ -0,0 +1,86 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from ironicclient.common import base
+from ironicclient.common.i18n import _
+from ironicclient.common import utils
+from ironicclient import exc
+
+
+class DeployTemplate(base.Resource):
+ def __repr__(self):
+ return "<DeployTemplate %s>" % self._info
+
+
+class DeployTemplateManager(base.CreateManager):
+ resource_class = DeployTemplate
+ _creation_attributes = ['extra', 'name', 'steps', 'uuid']
+ _resource_name = 'deploy_templates'
+
+ def list(self, limit=None, marker=None, sort_key=None, sort_dir=None,
+ detail=False, fields=None):
+ """Retrieve a list of deploy templates.
+
+ :param marker: Optional, the UUID of a deploy template, eg the last
+ template from a previous result set. Return the next
+ result set.
+ :param limit: The maximum number of results to return per
+ request, if:
+
+ 1) limit > 0, the maximum number of deploy templates to return.
+ 2) limit == 0, return the entire list of deploy templates.
+ 3) limit param is NOT specified (None), the number of items
+ returned respect the maximum imposed by the Ironic API
+ (see Ironic's api.max_limit option).
+
+ :param sort_key: Optional, field used for sorting.
+
+ :param sort_dir: Optional, direction of sorting, either 'asc' (the
+ default) or 'desc'.
+
+ :param detail: Optional, boolean whether to return detailed information
+ about deploy templates.
+
+ :param fields: Optional, a list with a specified set of fields
+ of the resource to be returned. Can not be used
+ when 'detail' is set.
+
+ :returns: A list of deploy templates.
+
+ """
+ if limit is not None:
+ limit = int(limit)
+
+ if detail and fields:
+ raise exc.InvalidAttribute(_("Can't fetch a subset of fields "
+ "with 'detail' set"))
+
+ filters = utils.common_filters(marker, limit, sort_key, sort_dir,
+ fields, detail=detail)
+ path = ''
+ if filters:
+ path += '?' + '&'.join(filters)
+
+ if limit is None:
+ return self._list(self._path(path), "deploy_templates")
+ else:
+ return self._list_pagination(self._path(path), "deploy_templates",
+ limit=limit)
+
+ def get(self, template_id, fields=None):
+ return self._get(resource_id=template_id, fields=fields)
+
+ def delete(self, template_id):
+ return self._delete(resource_id=template_id)
+
+ def update(self, template_id, patch):
+ return self._update(resource_id=template_id, patch=patch)
diff --git a/ironicclient/v1/resource_fields.py b/ironicclient/v1/resource_fields.py
index f9a18f5..38b6cc6 100644
--- a/ironicclient/v1/resource_fields.py
+++ b/ironicclient/v1/resource_fields.py
@@ -104,6 +104,7 @@ class Resource(object):
'reservation': 'Reservation',
'resource_class': 'Resource Class',
'state': 'State',
+ 'steps': 'Steps',
'target_power_state': 'Target Power State',
'target_provision_state': 'Target Provision State',
'target_raid_config': 'Target RAID configuration',
@@ -513,3 +514,21 @@ ALLOCATION_RESOURCE = Resource(
'node_uuid',
],
)
+
+# Deploy templates
+DEPLOY_TEMPLATE_DETAILED_RESOURCE = Resource(
+ ['uuid',
+ 'name',
+ 'steps',
+ 'extra',
+ 'created_at',
+ 'updated_at',
+ ],
+ sort_excluded=['extra', 'steps']
+)
+
+DEPLOY_TEMPLATE_RESOURCE = Resource(
+ ['uuid',
+ 'name',
+ ],
+)
diff --git a/releasenotes/notes/deploy-templates-df354ce825b00430.yaml b/releasenotes/notes/deploy-templates-df354ce825b00430.yaml
new file mode 100644
index 0000000..0b6ada1
--- /dev/null
+++ b/releasenotes/notes/deploy-templates-df354ce825b00430.yaml
@@ -0,0 +1,11 @@
+---
+features:
+ - |
+ Adds Python API and CLI for the deploy templates API introduced in API
+ version 1.55. The following new commands are available:
+
+ * ``openstack baremetal deploy template create``
+ * ``openstack baremetal deploy template delete``
+ * ``openstack baremetal deploy template list``
+ * ``openstack baremetal deploy template set``
+ * ``openstack baremetal deploy template show``
diff --git a/setup.cfg b/setup.cfg
index 34f5313..71e67b3 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -40,6 +40,12 @@ openstack.baremetal.v1 =
baremetal_chassis_show = ironicclient.osc.v1.baremetal_chassis:ShowBaremetalChassis
baremetal_chassis_unset = ironicclient.osc.v1.baremetal_chassis:UnsetBaremetalChassis
baremetal_create = ironicclient.osc.v1.baremetal_create:CreateBaremetal
+ baremetal_deploy_template_create = ironicclient.osc.v1.baremetal_deploy_template:CreateBaremetalDeployTemplate
+ baremetal_deploy_template_delete = ironicclient.osc.v1.baremetal_deploy_template:DeleteBaremetalDeployTemplate
+ baremetal_deploy_template_list = ironicclient.osc.v1.baremetal_deploy_template:ListBaremetalDeployTemplate
+ baremetal_deploy_template_set = ironicclient.osc.v1.baremetal_deploy_template:SetBaremetalDeployTemplate
+ baremetal_deploy_template_unset = ironicclient.osc.v1.baremetal_deploy_template:UnsetBaremetalDeployTemplate
+ baremetal_deploy_template_show = ironicclient.osc.v1.baremetal_deploy_template:ShowBaremetalDeployTemplate
baremetal_driver_list = ironicclient.osc.v1.baremetal_driver:ListBaremetalDriver
baremetal_driver_passthru_call = ironicclient.osc.v1.baremetal_driver:PassthruCallBaremetalDriver
baremetal_driver_passthru_list = ironicclient.osc.v1.baremetal_driver:PassthruListBaremetalDriver