Add CLI support for runbooks
Enable CRUD support, manual cleaning and servicing with runbooks from the command line interface. Demo Video: https://youtu.be/00PJS4SXFYQ Change-Id: Iec672c505a245991db72afbb9b668220f845ca81
This commit is contained in:
parent
85542f0caf
commit
1035b2b238
@ -37,7 +37,7 @@ from ironicclient import exc
|
|||||||
# http://specs.openstack.org/openstack/ironic-specs/specs/kilo/api-microversions.html # noqa
|
# http://specs.openstack.org/openstack/ironic-specs/specs/kilo/api-microversions.html # noqa
|
||||||
# for full details.
|
# for full details.
|
||||||
DEFAULT_VER = '1.9'
|
DEFAULT_VER = '1.9'
|
||||||
LAST_KNOWN_API_VERSION = 88
|
LAST_KNOWN_API_VERSION = 92
|
||||||
LATEST_VERSION = '1.{}'.format(LAST_KNOWN_API_VERSION)
|
LATEST_VERSION = '1.{}'.format(LAST_KNOWN_API_VERSION)
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
@ -214,7 +214,7 @@ def common_params_for_list(args, fields, field_labels):
|
|||||||
|
|
||||||
|
|
||||||
def common_filters(marker=None, limit=None, sort_key=None, sort_dir=None,
|
def common_filters(marker=None, limit=None, sort_key=None, sort_dir=None,
|
||||||
fields=None, detail=False):
|
fields=None, detail=False, project=None, public=None):
|
||||||
"""Generate common filters for any list request.
|
"""Generate common filters for any list request.
|
||||||
|
|
||||||
:param marker: entity ID from which to start returning entities.
|
:param marker: entity ID from which to start returning entities.
|
||||||
@ -237,6 +237,10 @@ def common_filters(marker=None, limit=None, sort_key=None, sort_dir=None,
|
|||||||
filters.append('sort_key=%s' % sort_key)
|
filters.append('sort_key=%s' % sort_key)
|
||||||
if sort_dir is not None:
|
if sort_dir is not None:
|
||||||
filters.append('sort_dir=%s' % sort_dir)
|
filters.append('sort_dir=%s' % sort_dir)
|
||||||
|
if project is not None:
|
||||||
|
filters.append('project=%s' % project)
|
||||||
|
if public is not None:
|
||||||
|
filters.append('public=True')
|
||||||
if fields is not None:
|
if fields is not None:
|
||||||
filters.append('fields=%s' % ','.join(fields))
|
filters.append('fields=%s' % ','.join(fields))
|
||||||
if detail:
|
if detail:
|
||||||
|
@ -80,6 +80,8 @@ class ProvisionStateBaremetalNode(command.Command):
|
|||||||
|
|
||||||
baremetal_client = self.app.client_manager.baremetal
|
baremetal_client = self.app.client_manager.baremetal
|
||||||
|
|
||||||
|
runbook = getattr(parsed_args, 'runbook', None)
|
||||||
|
|
||||||
clean_steps = getattr(parsed_args, 'clean_steps', None)
|
clean_steps = getattr(parsed_args, 'clean_steps', None)
|
||||||
clean_steps = utils.handle_json_arg(clean_steps, 'clean steps')
|
clean_steps = utils.handle_json_arg(clean_steps, 'clean steps')
|
||||||
|
|
||||||
@ -109,7 +111,8 @@ class ProvisionStateBaremetalNode(command.Command):
|
|||||||
cleansteps=clean_steps,
|
cleansteps=clean_steps,
|
||||||
deploysteps=deploy_steps,
|
deploysteps=deploy_steps,
|
||||||
rescue_password=rescue_password,
|
rescue_password=rescue_password,
|
||||||
servicesteps=service_steps)
|
servicesteps=service_steps,
|
||||||
|
runbook=runbook)
|
||||||
|
|
||||||
|
|
||||||
class ProvisionStateWithWait(ProvisionStateBaremetalNode):
|
class ProvisionStateWithWait(ProvisionStateBaremetalNode):
|
||||||
@ -289,18 +292,22 @@ class CleanBaremetalNode(ProvisionStateWithWait):
|
|||||||
|
|
||||||
def get_parser(self, prog_name):
|
def get_parser(self, prog_name):
|
||||||
parser = super(CleanBaremetalNode, self).get_parser(prog_name)
|
parser = super(CleanBaremetalNode, self).get_parser(prog_name)
|
||||||
|
clean_group = parser.add_mutually_exclusive_group(required=True)
|
||||||
|
|
||||||
parser.add_argument(
|
clean_group.add_argument(
|
||||||
'--clean-steps',
|
'--clean-steps',
|
||||||
metavar='<clean-steps>',
|
metavar='<clean-steps>',
|
||||||
required=True,
|
|
||||||
default=None,
|
|
||||||
help=_("The clean steps. May be the path to a YAML file "
|
help=_("The clean steps. May be the path to a YAML file "
|
||||||
"containing the clean steps; OR '-', with the clean steps "
|
"containing the clean steps; OR '-', with the clean steps "
|
||||||
"being read from standard input; OR a JSON string. The "
|
"being read from standard input; OR a JSON string. The "
|
||||||
"value should be a list of clean-step dictionaries; each "
|
"value should be a list of clean-step dictionaries; each "
|
||||||
"dictionary should have keys 'interface' and 'step', and "
|
"dictionary should have keys 'interface' and 'step', and "
|
||||||
"optional key 'args'."))
|
"optional key 'args'."))
|
||||||
|
clean_group.add_argument(
|
||||||
|
'--runbook',
|
||||||
|
metavar='<runbook>',
|
||||||
|
help=_("The identifier of a predefined runbook to use for "
|
||||||
|
"cleaning."))
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
@ -312,18 +319,22 @@ class ServiceBaremetalNode(ProvisionStateWithWait):
|
|||||||
|
|
||||||
def get_parser(self, prog_name):
|
def get_parser(self, prog_name):
|
||||||
parser = super(ServiceBaremetalNode, self).get_parser(prog_name)
|
parser = super(ServiceBaremetalNode, self).get_parser(prog_name)
|
||||||
|
service_group = parser.add_mutually_exclusive_group(required=True)
|
||||||
|
|
||||||
parser.add_argument(
|
service_group.add_argument(
|
||||||
'--service-steps',
|
'--service-steps',
|
||||||
metavar='<service-steps>',
|
metavar='<service-steps>',
|
||||||
required=True,
|
|
||||||
default=None,
|
|
||||||
help=_("The service steps. May be the path to a YAML file "
|
help=_("The service steps. May be the path to a YAML file "
|
||||||
"containing the service steps; OR '-', with the service "
|
"containing the service steps; OR '-', with the service "
|
||||||
" steps being read from standard input; OR a JSON string. "
|
" steps being read from standard input; OR a JSON string. "
|
||||||
"The value should be a list of service-step dictionaries; "
|
"The value should be a list of service-step dictionaries; "
|
||||||
"each dictionary should have keys 'interface' and 'step', "
|
"each dictionary should have keys 'interface' and 'step', "
|
||||||
"and optional key 'args'."))
|
"and optional key 'args'."))
|
||||||
|
service_group.add_argument(
|
||||||
|
'--runbook',
|
||||||
|
metavar='<runbook>',
|
||||||
|
help=_("The identifier of a predefined runbook to use for "
|
||||||
|
"servicing."))
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
403
ironicclient/osc/v1/baremetal_runbook.py
Normal file
403
ironicclient/osc/v1/baremetal_runbook.py
Normal file
@ -0,0 +1,403 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
|
|
||||||
|
_RUNBOOK_STEPS_HELP = _(
|
||||||
|
"The runbook steps. May be the path to a YAML file containing the "
|
||||||
|
"runbook steps; OR '-', with the runbook steps being read from standard "
|
||||||
|
"input; OR a JSON string. The value should be a list of runbook step "
|
||||||
|
"dictionaries; each dictionary should have keys 'interface', 'step', "
|
||||||
|
"'args' and 'order'.")
|
||||||
|
|
||||||
|
|
||||||
|
class CreateBaremetalRunbook(command.ShowOne):
|
||||||
|
"""Create a new runbook"""
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__ + ".CreateBaremetalRunbook")
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(CreateBaremetalRunbook, self).get_parser(
|
||||||
|
prog_name)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'--name',
|
||||||
|
metavar='<name>',
|
||||||
|
required=True,
|
||||||
|
help=_('Unique name for this runbook. Must be a valid '
|
||||||
|
'trait name')
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--uuid',
|
||||||
|
dest='uuid',
|
||||||
|
metavar='<uuid>',
|
||||||
|
help=_('UUID of the runbook.'))
|
||||||
|
parser.add_argument(
|
||||||
|
'--public',
|
||||||
|
metavar='<public>',
|
||||||
|
help=_('Whether the runbook will be private or public.')
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--owner',
|
||||||
|
metavar='<owner>',
|
||||||
|
help=_('Owner of the runbook.')
|
||||||
|
)
|
||||||
|
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=_RUNBOOK_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, 'runbook steps')
|
||||||
|
|
||||||
|
field_list = ['name', 'uuid', 'owner', 'public', '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')
|
||||||
|
runbook = baremetal_client.runbook.create(steps=steps,
|
||||||
|
**fields)
|
||||||
|
|
||||||
|
data = dict([(f, getattr(runbook, f, '')) for f in
|
||||||
|
res_fields.RUNBOOK_DETAILED_RESOURCE.fields])
|
||||||
|
|
||||||
|
return self.dict2columns(data)
|
||||||
|
|
||||||
|
|
||||||
|
class ShowBaremetalRunbook(command.ShowOne):
|
||||||
|
"""Show baremetal runbook details."""
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__ + ".ShowBaremetalRunbook")
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(ShowBaremetalRunbook, self).get_parser(prog_name)
|
||||||
|
parser.add_argument(
|
||||||
|
"runbook",
|
||||||
|
metavar="<runbook>",
|
||||||
|
help=_("Name or UUID of the runbook.")
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--fields',
|
||||||
|
nargs='+',
|
||||||
|
dest='fields',
|
||||||
|
metavar='<field>',
|
||||||
|
action='append',
|
||||||
|
choices=res_fields.RUNBOOK_DETAILED_RESOURCE.fields,
|
||||||
|
default=[],
|
||||||
|
help=_("One or more runbook 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
|
||||||
|
|
||||||
|
runbook = baremetal_client.runbook.get(
|
||||||
|
parsed_args.runbook, fields=fields)._info
|
||||||
|
|
||||||
|
runbook.pop("links", None)
|
||||||
|
return zip(*sorted(runbook.items()))
|
||||||
|
|
||||||
|
|
||||||
|
class SetBaremetalRunbook(command.Command):
|
||||||
|
"""Set baremetal runbook properties."""
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__ + ".SetBaremetalRunbook")
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(SetBaremetalRunbook, self).get_parser(prog_name)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'runbook',
|
||||||
|
metavar='<runbook>',
|
||||||
|
help=_("Name or UUID of the runbook")
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--name',
|
||||||
|
metavar='<name>',
|
||||||
|
help=_('Set unique name of the runbook. Must be a valid '
|
||||||
|
'trait name.')
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--public',
|
||||||
|
metavar='<public>',
|
||||||
|
help=_('Make a private runbook public.')
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--owner',
|
||||||
|
metavar='<owner>',
|
||||||
|
help=_('Set owner of a runbook.')
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--steps',
|
||||||
|
metavar="<steps>",
|
||||||
|
help=_RUNBOOK_STEPS_HELP
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--extra",
|
||||||
|
metavar="<key=value>",
|
||||||
|
action='append',
|
||||||
|
help=_('Extra to set on this baremetal runbook '
|
||||||
|
'(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.owner:
|
||||||
|
owner = ["owner=%s" % parsed_args.owner]
|
||||||
|
properties.extend(utils.args_array_to_patch('add', owner))
|
||||||
|
if parsed_args.public:
|
||||||
|
public = ["public=%s" % parsed_args.public]
|
||||||
|
properties.extend(utils.args_array_to_patch('add', public))
|
||||||
|
if parsed_args.steps:
|
||||||
|
steps = utils.handle_json_arg(parsed_args.steps, 'runbook 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.runbook.update(parsed_args.runbook,
|
||||||
|
properties)
|
||||||
|
else:
|
||||||
|
self.log.warning("Please specify what to set.")
|
||||||
|
|
||||||
|
|
||||||
|
class UnsetBaremetalRunbook(command.Command):
|
||||||
|
"""Unset baremetal runbook properties."""
|
||||||
|
log = logging.getLogger(__name__ + ".UnsetBaremetalRunbook")
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(UnsetBaremetalRunbook, self).get_parser(
|
||||||
|
prog_name)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'runbook',
|
||||||
|
metavar='<runbook>',
|
||||||
|
help=_("Name or UUID of the runbook")
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--name',
|
||||||
|
action='store_true',
|
||||||
|
help=_('Unset the name of the runbook.')
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--public',
|
||||||
|
dest='public',
|
||||||
|
action='store_true',
|
||||||
|
help=_('Make a public runbook private.')
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--owner',
|
||||||
|
dest='owner',
|
||||||
|
action='store_true',
|
||||||
|
help=_('Unset owner of a runbook.')
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--step",
|
||||||
|
metavar="<key>",
|
||||||
|
action='append',
|
||||||
|
help=_('Step to unset on this baremetal runbook '
|
||||||
|
'(repeat option to unset multiple steps).'),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--extra",
|
||||||
|
metavar="<key>",
|
||||||
|
action='append',
|
||||||
|
help=_('Extra to unset on this baremetal runbook '
|
||||||
|
'(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 = []
|
||||||
|
for field in ['name', 'owner', 'public']:
|
||||||
|
if getattr(parsed_args, field):
|
||||||
|
properties.extend(utils.args_array_to_patch('remove', [field]))
|
||||||
|
|
||||||
|
if parsed_args.extra:
|
||||||
|
properties.extend(utils.args_array_to_patch('remove',
|
||||||
|
['extra/' + x for x in parsed_args.extra]))
|
||||||
|
if parsed_args.step:
|
||||||
|
properties.extend(utils.args_array_to_patch('remove',
|
||||||
|
['step/' + x for x in parsed_args.step]))
|
||||||
|
|
||||||
|
if properties:
|
||||||
|
baremetal_client.runbook.update(parsed_args.runbook,
|
||||||
|
properties)
|
||||||
|
else:
|
||||||
|
self.log.warning("Please specify what to unset.")
|
||||||
|
|
||||||
|
|
||||||
|
class DeleteBaremetalRunbook(command.Command):
|
||||||
|
"""Delete runbook(s)."""
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__ + ".DeleteBaremetalRunbook")
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(DeleteBaremetalRunbook, self).get_parser(
|
||||||
|
prog_name)
|
||||||
|
parser.add_argument(
|
||||||
|
"runbooks",
|
||||||
|
metavar="<runbook>",
|
||||||
|
nargs="+",
|
||||||
|
help=_("Name(s) or UUID(s) of the runbook(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 runbook in parsed_args.runbooks:
|
||||||
|
try:
|
||||||
|
baremetal_client.runbook.delete(runbook)
|
||||||
|
print(_('Deleted runbook %s') % runbook)
|
||||||
|
except exc.ClientException as e:
|
||||||
|
failures.append(_("Failed to delete runbook "
|
||||||
|
"%(runbook)s: %(error)s")
|
||||||
|
% {'runbook': runbook, 'error': e})
|
||||||
|
|
||||||
|
if failures:
|
||||||
|
raise exc.ClientException("\n".join(failures))
|
||||||
|
|
||||||
|
|
||||||
|
class ListBaremetalRunbook(command.Lister):
|
||||||
|
"""List baremetal runbooks."""
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__ + ".ListBaremetalRunbook")
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(ListBaremetalRunbook, self).get_parser(prog_name)
|
||||||
|
parser.add_argument(
|
||||||
|
'--limit',
|
||||||
|
metavar='<limit>',
|
||||||
|
type=int,
|
||||||
|
help=_('Maximum number of runbooks to return per request, '
|
||||||
|
'0 for no limit. Default is the maximum number used '
|
||||||
|
'by the Baremetal API Service.')
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--marker',
|
||||||
|
metavar='<runbook>',
|
||||||
|
help=_('Runbook UUID (for example, of the last runbook '
|
||||||
|
'in the list from a previous request). Returns '
|
||||||
|
'the list of runbooks after this UUID.')
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--sort',
|
||||||
|
metavar="<key>[:<direction>]",
|
||||||
|
help=_('Sort output by specified runbook 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 runbooks.")
|
||||||
|
)
|
||||||
|
display_group.add_argument(
|
||||||
|
'--fields',
|
||||||
|
nargs='+',
|
||||||
|
dest='fields',
|
||||||
|
metavar='<field>',
|
||||||
|
action='append',
|
||||||
|
default=[],
|
||||||
|
choices=res_fields.RUNBOOK_DETAILED_RESOURCE.fields,
|
||||||
|
help=_("One or more runbook 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.RUNBOOK_RESOURCE.fields
|
||||||
|
labels = res_fields.RUNBOOK_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.RUNBOOK_DETAILED_RESOURCE.fields
|
||||||
|
labels = res_fields.RUNBOOK_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.runbook.list(**params)
|
||||||
|
|
||||||
|
data = oscutils.sort_items(data, parsed_args.sort)
|
||||||
|
|
||||||
|
return (labels,
|
||||||
|
(oscutils.get_item_properties(s, columns) for s in data))
|
@ -235,6 +235,26 @@ DEPLOY_TEMPLATE = {
|
|||||||
'steps': baremetal_deploy_template_steps,
|
'steps': baremetal_deploy_template_steps,
|
||||||
'extra': baremetal_deploy_template_extra,
|
'extra': baremetal_deploy_template_extra,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
baremetal_runbook_uuid = 'ddd-tttttt-dddd'
|
||||||
|
baremetal_runbook_name = 'CUSTOM_AWESOME'
|
||||||
|
baremetal_runbook_owner = 'some_user'
|
||||||
|
baremetal_runbook_public = False
|
||||||
|
baremetal_runbook_steps = json.dumps([{
|
||||||
|
'interface': 'raid',
|
||||||
|
'step': 'create_configuration',
|
||||||
|
'args': {},
|
||||||
|
'order': 1
|
||||||
|
}])
|
||||||
|
baremetal_runbook_extra = {'key1': 'value1', 'key2': 'value2'}
|
||||||
|
RUNBOOK = {
|
||||||
|
'uuid': baremetal_runbook_uuid,
|
||||||
|
'name': baremetal_runbook_name,
|
||||||
|
'owner': baremetal_runbook_owner,
|
||||||
|
'public': baremetal_runbook_public,
|
||||||
|
'steps': baremetal_runbook_steps,
|
||||||
|
'extra': baremetal_runbook_extra,
|
||||||
|
}
|
||||||
NODE_HISTORY = [
|
NODE_HISTORY = [
|
||||||
{
|
{
|
||||||
'uuid': 'abcdef1',
|
'uuid': 'abcdef1',
|
||||||
|
@ -59,7 +59,8 @@ class TestAbort(TestBaremetal):
|
|||||||
|
|
||||||
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
|
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
|
||||||
'node_uuid', 'abort', cleansteps=None, configdrive=None,
|
'node_uuid', 'abort', cleansteps=None, configdrive=None,
|
||||||
deploysteps=None, rescue_password=None, servicesteps=None)
|
deploysteps=None, rescue_password=None, servicesteps=None,
|
||||||
|
runbook=None)
|
||||||
|
|
||||||
|
|
||||||
class TestAdopt(TestBaremetal):
|
class TestAdopt(TestBaremetal):
|
||||||
@ -83,7 +84,7 @@ class TestAdopt(TestBaremetal):
|
|||||||
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
|
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
|
||||||
'node_uuid', 'adopt',
|
'node_uuid', 'adopt',
|
||||||
cleansteps=None, deploysteps=None, configdrive=None,
|
cleansteps=None, deploysteps=None, configdrive=None,
|
||||||
rescue_password=None, servicesteps=None)
|
rescue_password=None, servicesteps=None, runbook=None)
|
||||||
self.baremetal_mock.node.wait_for_provision_state.assert_not_called()
|
self.baremetal_mock.node.wait_for_provision_state.assert_not_called()
|
||||||
|
|
||||||
def test_adopt_baremetal_provision_state_active_and_wait(self):
|
def test_adopt_baremetal_provision_state_active_and_wait(self):
|
||||||
@ -103,7 +104,7 @@ class TestAdopt(TestBaremetal):
|
|||||||
test_node.set_provision_state.assert_called_once_with(
|
test_node.set_provision_state.assert_called_once_with(
|
||||||
'node_uuid', 'adopt',
|
'node_uuid', 'adopt',
|
||||||
cleansteps=None, deploysteps=None, configdrive=None,
|
cleansteps=None, deploysteps=None, configdrive=None,
|
||||||
rescue_password=None, servicesteps=None)
|
rescue_password=None, servicesteps=None, runbook=None)
|
||||||
test_node.wait_for_provision_state.assert_called_once_with(
|
test_node.wait_for_provision_state.assert_called_once_with(
|
||||||
['node_uuid'], expected_state='active',
|
['node_uuid'], expected_state='active',
|
||||||
poll_interval=2, timeout=15)
|
poll_interval=2, timeout=15)
|
||||||
@ -125,7 +126,7 @@ class TestAdopt(TestBaremetal):
|
|||||||
test_node.set_provision_state.assert_called_once_with(
|
test_node.set_provision_state.assert_called_once_with(
|
||||||
'node_uuid', 'adopt',
|
'node_uuid', 'adopt',
|
||||||
cleansteps=None, deploysteps=None, configdrive=None,
|
cleansteps=None, deploysteps=None, configdrive=None,
|
||||||
rescue_password=None, servicesteps=None)
|
rescue_password=None, servicesteps=None, runbook=None)
|
||||||
test_node.wait_for_provision_state.assert_called_once_with(
|
test_node.wait_for_provision_state.assert_called_once_with(
|
||||||
['node_uuid'], expected_state='active',
|
['node_uuid'], expected_state='active',
|
||||||
poll_interval=2, timeout=0)
|
poll_interval=2, timeout=0)
|
||||||
@ -174,7 +175,55 @@ class TestClean(TestBaremetal):
|
|||||||
|
|
||||||
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
|
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
|
||||||
'node_uuid', 'clean', cleansteps=steps_dict, configdrive=None,
|
'node_uuid', 'clean', cleansteps=steps_dict, configdrive=None,
|
||||||
deploysteps=None, rescue_password=None, servicesteps=None)
|
deploysteps=None, rescue_password=None, servicesteps=None,
|
||||||
|
runbook=None)
|
||||||
|
|
||||||
|
def test_clean_with_runbook(self):
|
||||||
|
runbook_name = 'runbook_name'
|
||||||
|
arglist = ['--runbook', runbook_name, 'node_uuid']
|
||||||
|
verifylist = [
|
||||||
|
('runbook', runbook_name),
|
||||||
|
('provision_state', 'clean'),
|
||||||
|
('nodes', ['node_uuid']),
|
||||||
|
]
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
|
||||||
|
'node_uuid', 'clean', cleansteps=None, configdrive=None,
|
||||||
|
deploysteps=None, rescue_password=None, servicesteps=None,
|
||||||
|
runbook=runbook_name)
|
||||||
|
|
||||||
|
def test_clean_with_runbook_and_steps(self):
|
||||||
|
runbook_name = 'runbook_name'
|
||||||
|
|
||||||
|
steps_dict = {
|
||||||
|
"clean_steps": [{
|
||||||
|
"interface": "raid",
|
||||||
|
"step": "create_configuration",
|
||||||
|
"args": {"create_nonroot_volumes": False}
|
||||||
|
}, {
|
||||||
|
"interface": "deploy",
|
||||||
|
"step": "erase_devices"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
steps_json = json.dumps(steps_dict)
|
||||||
|
|
||||||
|
arglist = ['--runbook', runbook_name, '--clean-steps', steps_json,
|
||||||
|
'node_uuid']
|
||||||
|
|
||||||
|
verifylist = [
|
||||||
|
('clean_steps', steps_json),
|
||||||
|
('runbook', runbook_name),
|
||||||
|
('provision_state', 'clean'),
|
||||||
|
('nodes', ['node_uuid']),
|
||||||
|
]
|
||||||
|
|
||||||
|
self.assertRaises(oscutils.ParserException,
|
||||||
|
self.check_parser,
|
||||||
|
self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
|
||||||
class TestService(TestBaremetal):
|
class TestService(TestBaremetal):
|
||||||
@ -210,7 +259,54 @@ class TestService(TestBaremetal):
|
|||||||
|
|
||||||
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
|
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
|
||||||
'node_uuid', 'service', cleansteps=None, configdrive=None,
|
'node_uuid', 'service', cleansteps=None, configdrive=None,
|
||||||
deploysteps=None, rescue_password=None, servicesteps=steps_dict)
|
deploysteps=None, rescue_password=None, servicesteps=steps_dict,
|
||||||
|
runbook=None)
|
||||||
|
|
||||||
|
def test_service_with_runbook(self):
|
||||||
|
runbook_name = 'runbook_name'
|
||||||
|
arglist = ['--runbook', runbook_name, 'node_uuid']
|
||||||
|
verifylist = [
|
||||||
|
('runbook', runbook_name),
|
||||||
|
('provision_state', 'service'),
|
||||||
|
('nodes', ['node_uuid']),
|
||||||
|
]
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
|
||||||
|
'node_uuid', 'service', cleansteps=None, configdrive=None,
|
||||||
|
deploysteps=None, rescue_password=None, servicesteps=None,
|
||||||
|
runbook=runbook_name)
|
||||||
|
|
||||||
|
def test_service_with_runbook_and_steps(self):
|
||||||
|
runbook_name = 'runbook_name'
|
||||||
|
steps_dict = {
|
||||||
|
"service_steps": [{
|
||||||
|
"interface": "raid",
|
||||||
|
"step": "create_configuration",
|
||||||
|
"args": {"create_nonroot_volumes": False}
|
||||||
|
}, {
|
||||||
|
"interface": "deploy",
|
||||||
|
"step": "erase_devices"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
steps_json = json.dumps(steps_dict)
|
||||||
|
|
||||||
|
arglist = ['--service-steps', steps_json, '--runbook', runbook_name,
|
||||||
|
'node_uuid']
|
||||||
|
|
||||||
|
verifylist = [
|
||||||
|
('service_steps', steps_json),
|
||||||
|
('runbook', runbook_name),
|
||||||
|
('provision_state', 'service'),
|
||||||
|
('nodes', ['node_uuid']),
|
||||||
|
]
|
||||||
|
|
||||||
|
self.assertRaises(oscutils.ParserException,
|
||||||
|
self.check_parser,
|
||||||
|
self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
|
||||||
class TestInspect(TestBaremetal):
|
class TestInspect(TestBaremetal):
|
||||||
@ -233,7 +329,8 @@ class TestInspect(TestBaremetal):
|
|||||||
|
|
||||||
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
|
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
|
||||||
'node_uuid', 'inspect', cleansteps=None, configdrive=None,
|
'node_uuid', 'inspect', cleansteps=None, configdrive=None,
|
||||||
deploysteps=None, rescue_password=None, servicesteps=None)
|
deploysteps=None, rescue_password=None, servicesteps=None,
|
||||||
|
runbook=None)
|
||||||
|
|
||||||
|
|
||||||
class TestManage(TestBaremetal):
|
class TestManage(TestBaremetal):
|
||||||
@ -256,7 +353,8 @@ class TestManage(TestBaremetal):
|
|||||||
|
|
||||||
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
|
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
|
||||||
'node_uuid', 'manage', cleansteps=None, configdrive=None,
|
'node_uuid', 'manage', cleansteps=None, configdrive=None,
|
||||||
deploysteps=None, rescue_password=None, servicesteps=None)
|
deploysteps=None, rescue_password=None, servicesteps=None,
|
||||||
|
runbook=None)
|
||||||
|
|
||||||
|
|
||||||
class TestProvide(TestBaremetal):
|
class TestProvide(TestBaremetal):
|
||||||
@ -279,7 +377,8 @@ class TestProvide(TestBaremetal):
|
|||||||
|
|
||||||
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
|
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
|
||||||
'node_uuid', 'provide', cleansteps=None, configdrive=None,
|
'node_uuid', 'provide', cleansteps=None, configdrive=None,
|
||||||
deploysteps=None, rescue_password=None, servicesteps=None)
|
deploysteps=None, rescue_password=None, servicesteps=None,
|
||||||
|
runbook=None)
|
||||||
|
|
||||||
|
|
||||||
class TestRebuild(TestBaremetal):
|
class TestRebuild(TestBaremetal):
|
||||||
@ -302,7 +401,8 @@ class TestRebuild(TestBaremetal):
|
|||||||
|
|
||||||
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
|
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
|
||||||
'node_uuid', 'rebuild', cleansteps=None, configdrive=None,
|
'node_uuid', 'rebuild', cleansteps=None, configdrive=None,
|
||||||
deploysteps=None, rescue_password=None, servicesteps=None)
|
deploysteps=None, rescue_password=None, servicesteps=None,
|
||||||
|
runbook=None)
|
||||||
|
|
||||||
|
|
||||||
class TestUndeploy(TestBaremetal):
|
class TestUndeploy(TestBaremetal):
|
||||||
@ -325,7 +425,8 @@ class TestUndeploy(TestBaremetal):
|
|||||||
|
|
||||||
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
|
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
|
||||||
'node_uuid', 'deleted', cleansteps=None, configdrive=None,
|
'node_uuid', 'deleted', cleansteps=None, configdrive=None,
|
||||||
deploysteps=None, rescue_password=None, servicesteps=None)
|
deploysteps=None, rescue_password=None, servicesteps=None,
|
||||||
|
runbook=None)
|
||||||
|
|
||||||
|
|
||||||
class TestBootdeviceSet(TestBaremetal):
|
class TestBootdeviceSet(TestBaremetal):
|
||||||
@ -1912,7 +2013,7 @@ class TestDeployBaremetalProvisionState(TestBaremetal):
|
|||||||
'node_uuid', 'active',
|
'node_uuid', 'active',
|
||||||
cleansteps=None, deploysteps=[{"interface": "deploy"}],
|
cleansteps=None, deploysteps=[{"interface": "deploy"}],
|
||||||
configdrive='path/to/drive', rescue_password=None,
|
configdrive='path/to/drive', rescue_password=None,
|
||||||
servicesteps=None)
|
servicesteps=None, runbook=None)
|
||||||
|
|
||||||
def test_deploy_baremetal_provision_state_active_and_configdrive_dict(
|
def test_deploy_baremetal_provision_state_active_and_configdrive_dict(
|
||||||
self):
|
self):
|
||||||
@ -1931,7 +2032,7 @@ class TestDeployBaremetalProvisionState(TestBaremetal):
|
|||||||
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
|
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
|
||||||
'node_uuid', 'active',
|
'node_uuid', 'active',
|
||||||
cleansteps=None, deploysteps=None, configdrive={'meta_data': {}},
|
cleansteps=None, deploysteps=None, configdrive={'meta_data': {}},
|
||||||
rescue_password=None, servicesteps=None)
|
rescue_password=None, servicesteps=None, runbook=None)
|
||||||
|
|
||||||
def test_deploy_no_wait(self):
|
def test_deploy_no_wait(self):
|
||||||
arglist = ['node_uuid']
|
arglist = ['node_uuid']
|
||||||
@ -1996,7 +2097,7 @@ class TestDeployBaremetalProvisionState(TestBaremetal):
|
|||||||
test_node.set_provision_state.assert_has_calls([
|
test_node.set_provision_state.assert_has_calls([
|
||||||
mock.call(n, 'active', cleansteps=None, deploysteps=None,
|
mock.call(n, 'active', cleansteps=None, deploysteps=None,
|
||||||
configdrive=None, rescue_password=None,
|
configdrive=None, rescue_password=None,
|
||||||
servicesteps=None)
|
servicesteps=None, runbook=None)
|
||||||
for n in ['node_uuid', 'node_name']
|
for n in ['node_uuid', 'node_name']
|
||||||
])
|
])
|
||||||
test_node.wait_for_provision_state.assert_called_once_with(
|
test_node.wait_for_provision_state.assert_called_once_with(
|
||||||
@ -2220,7 +2321,7 @@ class TestRescueBaremetalProvisionState(TestBaremetal):
|
|||||||
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
|
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
|
||||||
'node_uuid', 'rescue', cleansteps=None, deploysteps=None,
|
'node_uuid', 'rescue', cleansteps=None, deploysteps=None,
|
||||||
configdrive=None, rescue_password='supersecret',
|
configdrive=None, rescue_password='supersecret',
|
||||||
servicesteps=None)
|
servicesteps=None, runbook=None)
|
||||||
|
|
||||||
def test_rescue_baremetal_provision_state_rescue_and_wait(self):
|
def test_rescue_baremetal_provision_state_rescue_and_wait(self):
|
||||||
arglist = ['node_uuid',
|
arglist = ['node_uuid',
|
||||||
@ -2412,7 +2513,7 @@ class TestRebuildBaremetalProvisionState(TestBaremetal):
|
|||||||
'node_uuid', 'rebuild',
|
'node_uuid', 'rebuild',
|
||||||
cleansteps=None, deploysteps=[{"interface": "deploy"}],
|
cleansteps=None, deploysteps=[{"interface": "deploy"}],
|
||||||
configdrive='path/to/drive', rescue_password=None,
|
configdrive='path/to/drive', rescue_password=None,
|
||||||
servicesteps=None)
|
servicesteps=None, runbook=None)
|
||||||
|
|
||||||
def test_rebuild_no_wait(self):
|
def test_rebuild_no_wait(self):
|
||||||
arglist = ['node_uuid']
|
arglist = ['node_uuid']
|
||||||
@ -2428,7 +2529,7 @@ class TestRebuildBaremetalProvisionState(TestBaremetal):
|
|||||||
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
|
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
|
||||||
'node_uuid', 'rebuild',
|
'node_uuid', 'rebuild',
|
||||||
cleansteps=None, deploysteps=None, configdrive=None,
|
cleansteps=None, deploysteps=None, configdrive=None,
|
||||||
rescue_password=None, servicesteps=None)
|
rescue_password=None, servicesteps=None, runbook=None)
|
||||||
|
|
||||||
self.baremetal_mock.node.wait_for_provision_state.assert_not_called()
|
self.baremetal_mock.node.wait_for_provision_state.assert_not_called()
|
||||||
|
|
||||||
@ -2546,7 +2647,8 @@ class TestUnrescueBaremetalProvisionState(TestBaremetal):
|
|||||||
|
|
||||||
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
|
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
|
||||||
'node_uuid', 'unrescue', cleansteps=None, deploysteps=None,
|
'node_uuid', 'unrescue', cleansteps=None, deploysteps=None,
|
||||||
configdrive=None, rescue_password=None, servicesteps=None)
|
configdrive=None, rescue_password=None, servicesteps=None,
|
||||||
|
runbook=None)
|
||||||
|
|
||||||
def test_unrescue_baremetal_provision_state_active_and_wait(self):
|
def test_unrescue_baremetal_provision_state_active_and_wait(self):
|
||||||
arglist = ['node_uuid',
|
arglist = ['node_uuid',
|
||||||
@ -4661,7 +4763,8 @@ class TestUnholdBaremetalProvisionState(TestBaremetal):
|
|||||||
|
|
||||||
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
|
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
|
||||||
'node_uuid', 'unhold', cleansteps=None, deploysteps=None,
|
'node_uuid', 'unhold', cleansteps=None, deploysteps=None,
|
||||||
configdrive=None, rescue_password=None, servicesteps=None)
|
configdrive=None, rescue_password=None, servicesteps=None,
|
||||||
|
runbook=None)
|
||||||
|
|
||||||
|
|
||||||
class TestListFirmwareComponents(TestBaremetal):
|
class TestListFirmwareComponents(TestBaremetal):
|
||||||
|
462
ironicclient/tests/unit/osc/v1/test_baremetal_runbook.py
Normal file
462
ironicclient/tests/unit/osc/v1/test_baremetal_runbook.py
Normal file
@ -0,0 +1,462 @@
|
|||||||
|
# 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
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
from osc_lib.tests import utils as osctestutils
|
||||||
|
|
||||||
|
from ironicclient import exc
|
||||||
|
from ironicclient.osc.v1 import baremetal_runbook
|
||||||
|
from ironicclient.tests.unit.osc.v1 import fakes as baremetal_fakes
|
||||||
|
|
||||||
|
|
||||||
|
class TestBaremetalRunbook(baremetal_fakes.TestBaremetal):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestBaremetalRunbook, self).setUp()
|
||||||
|
|
||||||
|
self.baremetal_mock = self.app.client_manager.baremetal
|
||||||
|
self.baremetal_mock.reset_mock()
|
||||||
|
|
||||||
|
|
||||||
|
class TestCreateBaremetalRunbook(TestBaremetalRunbook):
|
||||||
|
def setUp(self):
|
||||||
|
super(TestCreateBaremetalRunbook, self).setUp()
|
||||||
|
|
||||||
|
self.baremetal_mock.runbook.create.return_value = (
|
||||||
|
baremetal_fakes.FakeBaremetalResource(
|
||||||
|
None,
|
||||||
|
copy.deepcopy(baremetal_fakes.RUNBOOK),
|
||||||
|
loaded=True,
|
||||||
|
))
|
||||||
|
|
||||||
|
# Get the command object to test
|
||||||
|
self.cmd = baremetal_runbook.CreateBaremetalRunbook(
|
||||||
|
self.app, None)
|
||||||
|
|
||||||
|
def test_baremetal_runbook_create(self):
|
||||||
|
arglist = [
|
||||||
|
'--name', baremetal_fakes.baremetal_runbook_name,
|
||||||
|
'--steps', baremetal_fakes.baremetal_runbook_steps,
|
||||||
|
]
|
||||||
|
|
||||||
|
verifylist = [
|
||||||
|
('name', baremetal_fakes.baremetal_runbook_name),
|
||||||
|
('steps', baremetal_fakes.baremetal_runbook_steps),
|
||||||
|
]
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
# Set expected values
|
||||||
|
args = {
|
||||||
|
'name': baremetal_fakes.baremetal_runbook_name,
|
||||||
|
'steps': json.loads(
|
||||||
|
baremetal_fakes.baremetal_runbook_steps),
|
||||||
|
}
|
||||||
|
|
||||||
|
self.baremetal_mock.runbook.create.assert_called_once_with(
|
||||||
|
**args)
|
||||||
|
|
||||||
|
def test_baremetal_runbook_create_uuid(self):
|
||||||
|
arglist = [
|
||||||
|
'--name', baremetal_fakes.baremetal_runbook_name,
|
||||||
|
'--steps', baremetal_fakes.baremetal_runbook_steps,
|
||||||
|
'--uuid', baremetal_fakes.baremetal_runbook_uuid,
|
||||||
|
]
|
||||||
|
|
||||||
|
verifylist = [
|
||||||
|
('name', baremetal_fakes.baremetal_runbook_name),
|
||||||
|
('steps', baremetal_fakes.baremetal_runbook_steps),
|
||||||
|
('uuid', baremetal_fakes.baremetal_runbook_uuid),
|
||||||
|
]
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
# Set expected values
|
||||||
|
args = {
|
||||||
|
'name': baremetal_fakes.baremetal_runbook_name,
|
||||||
|
'steps': json.loads(
|
||||||
|
baremetal_fakes.baremetal_runbook_steps),
|
||||||
|
'uuid': baremetal_fakes.baremetal_runbook_uuid,
|
||||||
|
}
|
||||||
|
|
||||||
|
self.baremetal_mock.runbook.create.assert_called_once_with(
|
||||||
|
**args)
|
||||||
|
|
||||||
|
def test_baremetal_runbook_create_no_name(self):
|
||||||
|
arglist = [
|
||||||
|
'--steps', baremetal_fakes.baremetal_runbook_steps,
|
||||||
|
]
|
||||||
|
|
||||||
|
verifylist = [
|
||||||
|
('steps', baremetal_fakes.baremetal_runbook_steps),
|
||||||
|
]
|
||||||
|
|
||||||
|
self.assertRaises(osctestutils.ParserException,
|
||||||
|
self.check_parser,
|
||||||
|
self.cmd, arglist, verifylist)
|
||||||
|
self.assertFalse(self.baremetal_mock.runbook.create.called)
|
||||||
|
|
||||||
|
def test_baremetal_runbook_create_no_steps(self):
|
||||||
|
arglist = [
|
||||||
|
'--name', baremetal_fakes.baremetal_runbook_name,
|
||||||
|
]
|
||||||
|
|
||||||
|
verifylist = [
|
||||||
|
('name', baremetal_fakes.baremetal_runbook_name),
|
||||||
|
]
|
||||||
|
|
||||||
|
self.assertRaises(osctestutils.ParserException,
|
||||||
|
self.check_parser,
|
||||||
|
self.cmd, arglist, verifylist)
|
||||||
|
self.assertFalse(self.baremetal_mock.runbook.create.called)
|
||||||
|
|
||||||
|
|
||||||
|
class TestShowBaremetalRunbook(TestBaremetalRunbook):
|
||||||
|
def setUp(self):
|
||||||
|
super(TestShowBaremetalRunbook, self).setUp()
|
||||||
|
|
||||||
|
self.baremetal_mock.runbook.get.return_value = (
|
||||||
|
baremetal_fakes.FakeBaremetalResource(
|
||||||
|
None,
|
||||||
|
copy.deepcopy(baremetal_fakes.RUNBOOK),
|
||||||
|
loaded=True))
|
||||||
|
|
||||||
|
self.cmd = baremetal_runbook.ShowBaremetalRunbook(
|
||||||
|
self.app, None)
|
||||||
|
|
||||||
|
def test_baremetal_runbook_show(self):
|
||||||
|
arglist = [baremetal_fakes.baremetal_runbook_uuid]
|
||||||
|
verifylist = [('runbook',
|
||||||
|
baremetal_fakes.baremetal_runbook_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_runbook_uuid]
|
||||||
|
self.baremetal_mock.runbook.get.assert_called_with(
|
||||||
|
*args, fields=None)
|
||||||
|
|
||||||
|
collist = (
|
||||||
|
'extra',
|
||||||
|
'name',
|
||||||
|
'owner',
|
||||||
|
'public',
|
||||||
|
'steps',
|
||||||
|
'uuid')
|
||||||
|
self.assertEqual(collist, columns)
|
||||||
|
|
||||||
|
datalist = (
|
||||||
|
baremetal_fakes.baremetal_runbook_extra,
|
||||||
|
baremetal_fakes.baremetal_runbook_name,
|
||||||
|
baremetal_fakes.baremetal_runbook_owner,
|
||||||
|
baremetal_fakes.baremetal_runbook_public,
|
||||||
|
baremetal_fakes.baremetal_runbook_steps,
|
||||||
|
baremetal_fakes.baremetal_runbook_uuid)
|
||||||
|
self.assertEqual(datalist, tuple(data))
|
||||||
|
|
||||||
|
def test_baremetal_runbook_show_no_template(self):
|
||||||
|
arglist = []
|
||||||
|
verifylist = []
|
||||||
|
|
||||||
|
self.assertRaises(osctestutils.ParserException,
|
||||||
|
self.check_parser,
|
||||||
|
self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
|
||||||
|
class TestBaremetalRunbookSet(TestBaremetalRunbook):
|
||||||
|
def setUp(self):
|
||||||
|
super(TestBaremetalRunbookSet, self).setUp()
|
||||||
|
|
||||||
|
self.baremetal_mock.runbook.update.return_value = (
|
||||||
|
baremetal_fakes.FakeBaremetalResource(
|
||||||
|
None,
|
||||||
|
copy.deepcopy(baremetal_fakes.RUNBOOK),
|
||||||
|
loaded=True))
|
||||||
|
|
||||||
|
self.cmd = baremetal_runbook.SetBaremetalRunbook(
|
||||||
|
self.app, None)
|
||||||
|
|
||||||
|
def test_baremetal_runbook_set_name(self):
|
||||||
|
new_name = 'foo'
|
||||||
|
arglist = [
|
||||||
|
baremetal_fakes.baremetal_runbook_uuid,
|
||||||
|
'--name', new_name]
|
||||||
|
verifylist = [
|
||||||
|
('runbook', baremetal_fakes.baremetal_runbook_uuid),
|
||||||
|
('name', new_name)]
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
self.cmd.take_action(parsed_args)
|
||||||
|
self.baremetal_mock.runbook.update.assert_called_once_with(
|
||||||
|
baremetal_fakes.baremetal_runbook_uuid,
|
||||||
|
[{'path': '/name', 'value': new_name, 'op': 'add'}])
|
||||||
|
|
||||||
|
def test_baremetal_runbook_set_steps(self):
|
||||||
|
arglist = [
|
||||||
|
baremetal_fakes.baremetal_runbook_uuid,
|
||||||
|
'--steps', baremetal_fakes.baremetal_runbook_steps]
|
||||||
|
verifylist = [
|
||||||
|
('runbook', baremetal_fakes.baremetal_runbook_uuid),
|
||||||
|
('steps', baremetal_fakes.baremetal_runbook_steps)]
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
expected_steps = json.loads(
|
||||||
|
baremetal_fakes.baremetal_runbook_steps)
|
||||||
|
self.cmd.take_action(parsed_args)
|
||||||
|
self.baremetal_mock.runbook.update.assert_called_once_with(
|
||||||
|
baremetal_fakes.baremetal_runbook_uuid,
|
||||||
|
[{'path': '/steps', 'value': expected_steps, 'op': 'add'}])
|
||||||
|
|
||||||
|
def test_baremetal_runbook_set_no_options(self):
|
||||||
|
arglist = []
|
||||||
|
verifylist = []
|
||||||
|
self.assertRaises(osctestutils.ParserException,
|
||||||
|
self.check_parser,
|
||||||
|
self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
|
||||||
|
class TestBaremetalRunbookUnset(TestBaremetalRunbook):
|
||||||
|
def setUp(self):
|
||||||
|
super(TestBaremetalRunbookUnset, self).setUp()
|
||||||
|
|
||||||
|
self.baremetal_mock.runbook.update.return_value = (
|
||||||
|
baremetal_fakes.FakeBaremetalResource(
|
||||||
|
None,
|
||||||
|
copy.deepcopy(baremetal_fakes.RUNBOOK),
|
||||||
|
loaded=True))
|
||||||
|
|
||||||
|
self.cmd = baremetal_runbook.UnsetBaremetalRunbook(
|
||||||
|
self.app, None)
|
||||||
|
|
||||||
|
def test_baremetal_runbook_unset_extra(self):
|
||||||
|
arglist = [
|
||||||
|
baremetal_fakes.baremetal_runbook_uuid, '--extra', 'key1']
|
||||||
|
verifylist = [('runbook',
|
||||||
|
baremetal_fakes.baremetal_runbook_uuid),
|
||||||
|
('extra', ['key1'])]
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
self.cmd.take_action(parsed_args)
|
||||||
|
self.baremetal_mock.runbook.update.assert_called_once_with(
|
||||||
|
baremetal_fakes.baremetal_runbook_uuid,
|
||||||
|
[{'path': '/extra/key1', 'op': 'remove'}])
|
||||||
|
|
||||||
|
def test_baremetal_runbook_unset_multiple_extras(self):
|
||||||
|
arglist = [
|
||||||
|
baremetal_fakes.baremetal_runbook_uuid,
|
||||||
|
'--extra', 'key1', '--extra', 'key2']
|
||||||
|
verifylist = [('runbook',
|
||||||
|
baremetal_fakes.baremetal_runbook_uuid),
|
||||||
|
('extra', ['key1', 'key2'])]
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
self.cmd.take_action(parsed_args)
|
||||||
|
self.baremetal_mock.runbook.update.assert_called_once_with(
|
||||||
|
baremetal_fakes.baremetal_runbook_uuid,
|
||||||
|
[{'path': '/extra/key1', 'op': 'remove'},
|
||||||
|
{'path': '/extra/key2', 'op': 'remove'}])
|
||||||
|
|
||||||
|
def test_baremetal_runbook_unset_no_options(self):
|
||||||
|
arglist = []
|
||||||
|
verifylist = []
|
||||||
|
self.assertRaises(osctestutils.ParserException,
|
||||||
|
self.check_parser,
|
||||||
|
self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
def test_baremetal_runbook_unset_no_property(self):
|
||||||
|
uuid = baremetal_fakes.baremetal_runbook_uuid
|
||||||
|
arglist = [uuid]
|
||||||
|
verifylist = [('runbook', uuid)]
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
self.cmd.take_action(parsed_args)
|
||||||
|
self.assertFalse(self.baremetal_mock.runbook.update.called)
|
||||||
|
|
||||||
|
|
||||||
|
class TestBaremetalRunbookDelete(TestBaremetalRunbook):
|
||||||
|
def setUp(self):
|
||||||
|
super(TestBaremetalRunbookDelete, self).setUp()
|
||||||
|
|
||||||
|
self.cmd = baremetal_runbook.DeleteBaremetalRunbook(
|
||||||
|
self.app, None)
|
||||||
|
|
||||||
|
def test_baremetal_runbook_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.runbook.delete.assert_called_with(args)
|
||||||
|
|
||||||
|
def test_baremetal_runbook_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.runbook.delete.assert_has_calls(
|
||||||
|
[mock.call(x) for x in args])
|
||||||
|
self.assertEqual(
|
||||||
|
2, self.baremetal_mock.runbook.delete.call_count)
|
||||||
|
|
||||||
|
def test_baremetal_runbook_delete_multiple_with_fail(self):
|
||||||
|
arglist = ['zzz-zzzzzz-zzzz', 'badname']
|
||||||
|
verifylist = []
|
||||||
|
|
||||||
|
self.baremetal_mock.runbook.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.runbook.delete.assert_has_calls(
|
||||||
|
[mock.call(x) for x in args])
|
||||||
|
self.assertEqual(
|
||||||
|
2, self.baremetal_mock.runbook.delete.call_count)
|
||||||
|
|
||||||
|
def test_baremetal_runbook_delete_no_template(self):
|
||||||
|
arglist = []
|
||||||
|
verifylist = []
|
||||||
|
|
||||||
|
self.assertRaises(osctestutils.ParserException,
|
||||||
|
self.check_parser,
|
||||||
|
self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
|
||||||
|
class TestBaremetalRunbookList(TestBaremetalRunbook):
|
||||||
|
def setUp(self):
|
||||||
|
super(TestBaremetalRunbookList, self).setUp()
|
||||||
|
|
||||||
|
self.baremetal_mock.runbook.list.return_value = [
|
||||||
|
baremetal_fakes.FakeBaremetalResource(
|
||||||
|
None,
|
||||||
|
copy.deepcopy(baremetal_fakes.RUNBOOK),
|
||||||
|
loaded=True)
|
||||||
|
]
|
||||||
|
|
||||||
|
self.cmd = baremetal_runbook.ListBaremetalRunbook(
|
||||||
|
self.app, None)
|
||||||
|
|
||||||
|
def test_baremetal_runbook_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.runbook.list.assert_called_with(**kwargs)
|
||||||
|
|
||||||
|
collist = (
|
||||||
|
"UUID",
|
||||||
|
"Name")
|
||||||
|
self.assertEqual(collist, columns)
|
||||||
|
|
||||||
|
datalist = ((
|
||||||
|
baremetal_fakes.baremetal_runbook_uuid,
|
||||||
|
baremetal_fakes.baremetal_runbook_name
|
||||||
|
), )
|
||||||
|
self.assertEqual(datalist, tuple(data))
|
||||||
|
|
||||||
|
def test_baremetal_runbook_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.runbook.list.assert_called_with(**kwargs)
|
||||||
|
|
||||||
|
collist = ('UUID',
|
||||||
|
'Name',
|
||||||
|
'Owner',
|
||||||
|
'Public',
|
||||||
|
'Steps',
|
||||||
|
'Extra',
|
||||||
|
'Created At',
|
||||||
|
'Updated At')
|
||||||
|
self.assertEqual(collist, columns)
|
||||||
|
|
||||||
|
datalist = ((
|
||||||
|
baremetal_fakes.baremetal_runbook_uuid,
|
||||||
|
baremetal_fakes.baremetal_runbook_name,
|
||||||
|
baremetal_fakes.baremetal_runbook_owner,
|
||||||
|
baremetal_fakes.baremetal_runbook_public,
|
||||||
|
baremetal_fakes.baremetal_runbook_steps,
|
||||||
|
baremetal_fakes.baremetal_runbook_extra,
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
), )
|
||||||
|
self.assertEqual(datalist, tuple(data))
|
||||||
|
|
||||||
|
def test_baremetal_runbook_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.runbook.list.assert_called_with(**kwargs)
|
||||||
|
|
||||||
|
def test_baremetal_runbook_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.runbook.list.assert_called_with(**kwargs)
|
||||||
|
|
||||||
|
def test_baremetal_runbook_list_invalid_fields(self):
|
||||||
|
arglist = ['--fields', 'uuid', 'invalid']
|
||||||
|
verifylist = [('fields', [['uuid', 'invalid']])]
|
||||||
|
self.assertRaises(osctestutils.ParserException,
|
||||||
|
self.check_parser,
|
||||||
|
self.cmd, arglist, verifylist)
|
291
ironicclient/tests/unit/v1/test_runbook.py
Normal file
291
ironicclient/tests/unit/v1/test_runbook.py
Normal file
@ -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.runbook
|
||||||
|
|
||||||
|
RUNBOOK = {'uuid': '11111111-2222-3333-4444-555555555555',
|
||||||
|
'name': 'CUSTOM_RUNBOOK',
|
||||||
|
'steps': {},
|
||||||
|
'extra': {}}
|
||||||
|
|
||||||
|
RUNBOOK2 = {'uuid': '55555555-4444-3333-2222-111111111111',
|
||||||
|
'name': 'CUSTOM_RUNBOOK2',
|
||||||
|
'steps': {},
|
||||||
|
'extra': {}}
|
||||||
|
|
||||||
|
CREATE_RUNBOOK = copy.deepcopy(RUNBOOK)
|
||||||
|
del CREATE_RUNBOOK['uuid']
|
||||||
|
|
||||||
|
CREATE_RUNBOOK_WITH_UUID = copy.deepcopy(RUNBOOK)
|
||||||
|
|
||||||
|
UPDATED_RUNBOOK = copy.deepcopy(RUNBOOK)
|
||||||
|
NEW_NAME = 'CUSTOM_RUNBOOK3'
|
||||||
|
UPDATED_RUNBOOK['name'] = NEW_NAME
|
||||||
|
|
||||||
|
fake_responses = {
|
||||||
|
'/v1/runbooks':
|
||||||
|
{
|
||||||
|
'GET': (
|
||||||
|
{},
|
||||||
|
{"runbooks": [RUNBOOK]},
|
||||||
|
),
|
||||||
|
'POST': (
|
||||||
|
{},
|
||||||
|
CREATE_RUNBOOK,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
'/v1/runbooks/?detail=True':
|
||||||
|
{
|
||||||
|
'GET': (
|
||||||
|
{},
|
||||||
|
{"runbooks": [RUNBOOK]},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
'/v1/runbooks/?fields=uuid,name':
|
||||||
|
{
|
||||||
|
'GET': (
|
||||||
|
{},
|
||||||
|
{"runbooks": [RUNBOOK]},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
'/v1/runbooks/%s' % RUNBOOK['uuid']:
|
||||||
|
{
|
||||||
|
'GET': (
|
||||||
|
{},
|
||||||
|
RUNBOOK,
|
||||||
|
),
|
||||||
|
'DELETE': (
|
||||||
|
{},
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
'PATCH': (
|
||||||
|
{},
|
||||||
|
UPDATED_RUNBOOK,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
'/v1/runbooks/%s?fields=uuid,name' % RUNBOOK['uuid']:
|
||||||
|
{
|
||||||
|
'GET': (
|
||||||
|
{},
|
||||||
|
RUNBOOK,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
fake_responses_pagination = {
|
||||||
|
'/v1/runbooks':
|
||||||
|
{
|
||||||
|
'GET': (
|
||||||
|
{},
|
||||||
|
{"runbooks": [RUNBOOK],
|
||||||
|
"next": "http://127.0.0.1:6385/v1/runbooks/?limit=1"}
|
||||||
|
),
|
||||||
|
},
|
||||||
|
'/v1/runbooks/?limit=1':
|
||||||
|
{
|
||||||
|
'GET': (
|
||||||
|
{},
|
||||||
|
{"runbooks": [RUNBOOK2]}
|
||||||
|
),
|
||||||
|
},
|
||||||
|
'/v1/runbooks/?marker=%s' % RUNBOOK['uuid']:
|
||||||
|
{
|
||||||
|
'GET': (
|
||||||
|
{},
|
||||||
|
{"runbooks": [RUNBOOK2]}
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
fake_responses_sorting = {
|
||||||
|
'/v1/runbooks/?sort_key=updated_at':
|
||||||
|
{
|
||||||
|
'GET': (
|
||||||
|
{},
|
||||||
|
{"runbooks": [RUNBOOK2, RUNBOOK]}
|
||||||
|
),
|
||||||
|
},
|
||||||
|
'/v1/runbooks/?sort_dir=desc':
|
||||||
|
{
|
||||||
|
'GET': (
|
||||||
|
{},
|
||||||
|
{"runbooks": [RUNBOOK2, RUNBOOK]}
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class RunbookManagerTest(testtools.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(RunbookManagerTest, self).setUp()
|
||||||
|
self.api = utils.FakeAPI(fake_responses)
|
||||||
|
self.mgr = ironicclient.v1.runbook.RunbookManager(
|
||||||
|
self.api)
|
||||||
|
|
||||||
|
def test_runbooks_list(self):
|
||||||
|
runbooks = self.mgr.list()
|
||||||
|
expect = [
|
||||||
|
('GET', '/v1/runbooks', {}, None),
|
||||||
|
]
|
||||||
|
self.assertEqual(expect, self.api.calls)
|
||||||
|
self.assertEqual(1, len(runbooks))
|
||||||
|
|
||||||
|
def test_runbooks_list_detail(self):
|
||||||
|
runbooks = self.mgr.list(detail=True)
|
||||||
|
expect = [
|
||||||
|
('GET', '/v1/runbooks/?detail=True', {}, None),
|
||||||
|
]
|
||||||
|
self.assertEqual(expect, self.api.calls)
|
||||||
|
self.assertEqual(1, len(runbooks))
|
||||||
|
|
||||||
|
def test_runbook_list_fields(self):
|
||||||
|
runbooks = self.mgr.list(fields=['uuid', 'name'])
|
||||||
|
expect = [
|
||||||
|
('GET', '/v1/runbooks/?fields=uuid,name', {}, None),
|
||||||
|
]
|
||||||
|
self.assertEqual(expect, self.api.calls)
|
||||||
|
self.assertEqual(1, len(runbooks))
|
||||||
|
|
||||||
|
def test_runbook_list_detail_and_fields_fail(self):
|
||||||
|
self.assertRaises(exc.InvalidAttribute, self.mgr.list,
|
||||||
|
detail=True, fields=['uuid', 'name'])
|
||||||
|
|
||||||
|
def test_runbooks_list_limit(self):
|
||||||
|
self.api = utils.FakeAPI(fake_responses_pagination)
|
||||||
|
self.mgr = ironicclient.v1.runbook.RunbookManager(
|
||||||
|
self.api)
|
||||||
|
runbooks = self.mgr.list(limit=1)
|
||||||
|
expect = [
|
||||||
|
('GET', '/v1/runbooks/?limit=1', {}, None),
|
||||||
|
]
|
||||||
|
self.assertEqual(expect, self.api.calls)
|
||||||
|
self.assertThat(runbooks, HasLength(1))
|
||||||
|
|
||||||
|
def test_runbooks_list_marker(self):
|
||||||
|
self.api = utils.FakeAPI(fake_responses_pagination)
|
||||||
|
self.mgr = ironicclient.v1.runbook.RunbookManager(
|
||||||
|
self.api)
|
||||||
|
runbooks = self.mgr.list(marker=RUNBOOK['uuid'])
|
||||||
|
expect = [
|
||||||
|
('GET',
|
||||||
|
'/v1/runbooks/?marker=%s' % RUNBOOK['uuid'], {},
|
||||||
|
None),
|
||||||
|
]
|
||||||
|
self.assertEqual(expect, self.api.calls)
|
||||||
|
self.assertThat(runbooks, HasLength(1))
|
||||||
|
|
||||||
|
def test_runbooks_list_pagination_no_limit(self):
|
||||||
|
self.api = utils.FakeAPI(fake_responses_pagination)
|
||||||
|
self.mgr = ironicclient.v1.runbook.RunbookManager(
|
||||||
|
self.api)
|
||||||
|
runbooks = self.mgr.list(limit=0)
|
||||||
|
expect = [
|
||||||
|
('GET', '/v1/runbooks', {}, None),
|
||||||
|
('GET', '/v1/runbooks/?limit=1', {}, None)
|
||||||
|
]
|
||||||
|
self.assertEqual(expect, self.api.calls)
|
||||||
|
self.assertThat(runbooks, HasLength(2))
|
||||||
|
|
||||||
|
def test_runbooks_list_sort_key(self):
|
||||||
|
self.api = utils.FakeAPI(fake_responses_sorting)
|
||||||
|
self.mgr = ironicclient.v1.runbook.RunbookManager(
|
||||||
|
self.api)
|
||||||
|
runbooks = self.mgr.list(sort_key='updated_at')
|
||||||
|
expect = [
|
||||||
|
('GET', '/v1/runbooks/?sort_key=updated_at', {}, None)
|
||||||
|
]
|
||||||
|
self.assertEqual(expect, self.api.calls)
|
||||||
|
self.assertEqual(2, len(runbooks))
|
||||||
|
|
||||||
|
def test_runbooks_list_sort_dir(self):
|
||||||
|
self.api = utils.FakeAPI(fake_responses_sorting)
|
||||||
|
self.mgr = ironicclient.v1.runbook.RunbookManager(
|
||||||
|
self.api)
|
||||||
|
runbooks = self.mgr.list(sort_dir='desc')
|
||||||
|
expect = [
|
||||||
|
('GET', '/v1/runbooks/?sort_dir=desc', {}, None)
|
||||||
|
]
|
||||||
|
self.assertEqual(expect, self.api.calls)
|
||||||
|
self.assertEqual(2, len(runbooks))
|
||||||
|
|
||||||
|
def test_runbooks_show(self):
|
||||||
|
runbook = self.mgr.get(RUNBOOK['uuid'])
|
||||||
|
expect = [
|
||||||
|
('GET', '/v1/runbooks/%s' % RUNBOOK['uuid'], {},
|
||||||
|
None),
|
||||||
|
]
|
||||||
|
self.assertEqual(expect, self.api.calls)
|
||||||
|
self.assertEqual(RUNBOOK['uuid'], runbook.uuid)
|
||||||
|
self.assertEqual(RUNBOOK['name'], runbook.name)
|
||||||
|
self.assertEqual(RUNBOOK['steps'], runbook.steps)
|
||||||
|
self.assertEqual(RUNBOOK['extra'], runbook.extra)
|
||||||
|
|
||||||
|
def test_runbook_show_fields(self):
|
||||||
|
runbook = self.mgr.get(RUNBOOK['uuid'],
|
||||||
|
fields=['uuid', 'name'])
|
||||||
|
expect = [
|
||||||
|
('GET', '/v1/runbooks/%s?fields=uuid,name' %
|
||||||
|
RUNBOOK['uuid'], {}, None),
|
||||||
|
]
|
||||||
|
self.assertEqual(expect, self.api.calls)
|
||||||
|
self.assertEqual(RUNBOOK['uuid'], runbook.uuid)
|
||||||
|
self.assertEqual(RUNBOOK['name'], runbook.name)
|
||||||
|
|
||||||
|
def test_create(self):
|
||||||
|
runbook = self.mgr.create(**CREATE_RUNBOOK)
|
||||||
|
expect = [
|
||||||
|
('POST', '/v1/runbooks', {}, CREATE_RUNBOOK),
|
||||||
|
]
|
||||||
|
self.assertEqual(expect, self.api.calls)
|
||||||
|
self.assertTrue(runbook)
|
||||||
|
|
||||||
|
def test_create_with_uuid(self):
|
||||||
|
runbook = self.mgr.create(**CREATE_RUNBOOK_WITH_UUID)
|
||||||
|
expect = [
|
||||||
|
('POST', '/v1/runbooks', {},
|
||||||
|
CREATE_RUNBOOK_WITH_UUID),
|
||||||
|
]
|
||||||
|
self.assertEqual(expect, self.api.calls)
|
||||||
|
self.assertTrue(runbook)
|
||||||
|
|
||||||
|
def test_delete(self):
|
||||||
|
runbook = self.mgr.delete(
|
||||||
|
runbook_id=RUNBOOK['uuid'])
|
||||||
|
expect = [
|
||||||
|
('DELETE', '/v1/runbooks/%s' % RUNBOOK['uuid'], {},
|
||||||
|
None),
|
||||||
|
]
|
||||||
|
self.assertEqual(expect, self.api.calls)
|
||||||
|
self.assertIsNone(runbook)
|
||||||
|
|
||||||
|
def test_update(self):
|
||||||
|
patch = {'op': 'replace',
|
||||||
|
'value': NEW_NAME,
|
||||||
|
'path': '/name'}
|
||||||
|
runbook = self.mgr.update(
|
||||||
|
runbook_id=RUNBOOK['uuid'], patch=patch)
|
||||||
|
expect = [
|
||||||
|
('PATCH', '/v1/runbooks/%s' % RUNBOOK['uuid'],
|
||||||
|
{}, patch),
|
||||||
|
]
|
||||||
|
self.assertEqual(expect, self.api.calls)
|
||||||
|
self.assertEqual(NEW_NAME, runbook.name)
|
@ -27,6 +27,7 @@ from ironicclient.v1 import events
|
|||||||
from ironicclient.v1 import node
|
from ironicclient.v1 import node
|
||||||
from ironicclient.v1 import port
|
from ironicclient.v1 import port
|
||||||
from ironicclient.v1 import portgroup
|
from ironicclient.v1 import portgroup
|
||||||
|
from ironicclient.v1 import runbook
|
||||||
from ironicclient.v1 import volume_connector
|
from ironicclient.v1 import volume_connector
|
||||||
from ironicclient.v1 import volume_target
|
from ironicclient.v1 import volume_target
|
||||||
|
|
||||||
@ -98,6 +99,7 @@ class Client(object):
|
|||||||
self.volume_target = volume_target.VolumeTargetManager(
|
self.volume_target = volume_target.VolumeTargetManager(
|
||||||
self.http_client)
|
self.http_client)
|
||||||
self.driver = driver.DriverManager(self.http_client)
|
self.driver = driver.DriverManager(self.http_client)
|
||||||
|
self.runbook = runbook.RunbookManager(self.http_client)
|
||||||
self.portgroup = portgroup.PortgroupManager(self.http_client)
|
self.portgroup = portgroup.PortgroupManager(self.http_client)
|
||||||
self.conductor = conductor.ConductorManager(self.http_client)
|
self.conductor = conductor.ConductorManager(self.http_client)
|
||||||
self.events = events.EventManager(self.http_client)
|
self.events = events.EventManager(self.http_client)
|
||||||
|
@ -729,7 +729,7 @@ class NodeManager(base.CreateManager):
|
|||||||
self, node_uuid, state, configdrive=None, cleansteps=None,
|
self, node_uuid, state, configdrive=None, cleansteps=None,
|
||||||
rescue_password=None, os_ironic_api_version=None,
|
rescue_password=None, os_ironic_api_version=None,
|
||||||
global_request_id=None, deploysteps=None,
|
global_request_id=None, deploysteps=None,
|
||||||
servicesteps=None):
|
servicesteps=None, runbook=None):
|
||||||
"""Set the provision state for the node.
|
"""Set the provision state for the node.
|
||||||
|
|
||||||
:param node_uuid: The UUID or name of the node.
|
:param node_uuid: The UUID or name of the node.
|
||||||
@ -767,6 +767,8 @@ class NodeManager(base.CreateManager):
|
|||||||
dictionaries; each dictonary should have keys 'interface', 'step',
|
dictionaries; each dictonary should have keys 'interface', 'step',
|
||||||
and optional key 'args' when setting an 'active' nodes to
|
and optional key 'args' when setting an 'active' nodes to
|
||||||
'service'.
|
'service'.
|
||||||
|
:param runbook: The identifier of a predefined runbook to use for
|
||||||
|
provisioning.
|
||||||
:raises: InvalidAttribute if there was an error with the clean steps or
|
:raises: InvalidAttribute if there was an error with the clean steps or
|
||||||
deploy steps
|
deploy steps
|
||||||
:returns: The status of the request
|
:returns: The status of the request
|
||||||
@ -807,6 +809,9 @@ class NodeManager(base.CreateManager):
|
|||||||
if servicesteps:
|
if servicesteps:
|
||||||
body['service_steps'] = servicesteps
|
body['service_steps'] = servicesteps
|
||||||
|
|
||||||
|
if runbook:
|
||||||
|
body['runbook'] = runbook
|
||||||
|
|
||||||
return self.update(path, body, http_method='PUT',
|
return self.update(path, body, http_method='PUT',
|
||||||
os_ironic_api_version=os_ironic_api_version,
|
os_ironic_api_version=os_ironic_api_version,
|
||||||
global_request_id=global_request_id)
|
global_request_id=global_request_id)
|
||||||
|
@ -158,6 +158,7 @@ class Resource(object):
|
|||||||
'children': 'Child Nodes',
|
'children': 'Child Nodes',
|
||||||
'firmware_interface': 'Firmware Interface',
|
'firmware_interface': 'Firmware Interface',
|
||||||
'port_name': 'Port Name',
|
'port_name': 'Port Name',
|
||||||
|
'public': 'Public'
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, field_ids, sort_excluded=None, override_labels=None):
|
def __init__(self, field_ids, sort_excluded=None, override_labels=None):
|
||||||
@ -606,6 +607,25 @@ DEPLOY_TEMPLATE_RESOURCE = Resource(
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Runbooks
|
||||||
|
RUNBOOK_DETAILED_RESOURCE = Resource(
|
||||||
|
['uuid',
|
||||||
|
'name',
|
||||||
|
'owner',
|
||||||
|
'public',
|
||||||
|
'steps',
|
||||||
|
'extra',
|
||||||
|
'created_at',
|
||||||
|
'updated_at'
|
||||||
|
],
|
||||||
|
sort_excluded=['extra', 'steps']
|
||||||
|
)
|
||||||
|
|
||||||
|
RUNBOOK_RESOURCE = Resource(
|
||||||
|
['uuid',
|
||||||
|
'name',
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
NODE_HISTORY_RESOURCE = Resource(
|
NODE_HISTORY_RESOURCE = Resource(
|
||||||
['uuid',
|
['uuid',
|
||||||
|
106
ironicclient/v1/runbook.py
Normal file
106
ironicclient/v1/runbook.py
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
# 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 Runbook(base.Resource):
|
||||||
|
def __repr__(self):
|
||||||
|
return "<Runbook %s>" % self._info
|
||||||
|
|
||||||
|
|
||||||
|
class RunbookManager(base.CreateManager):
|
||||||
|
resource_class = Runbook
|
||||||
|
_creation_attributes = ['extra', 'name', 'owner', 'public', 'steps',
|
||||||
|
'uuid']
|
||||||
|
_resource_name = 'runbooks'
|
||||||
|
|
||||||
|
def list(self, limit=None, marker=None, sort_key=None, sort_dir=None,
|
||||||
|
detail=False, fields=None, os_ironic_api_version=None,
|
||||||
|
global_request_id=None, project=None, public=None):
|
||||||
|
"""Retrieve a list of runbooks.
|
||||||
|
|
||||||
|
: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 runbooks to return.
|
||||||
|
2) limit == 0, return the entire list of runbooks.
|
||||||
|
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 runbooks.
|
||||||
|
|
||||||
|
: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.
|
||||||
|
|
||||||
|
:param os_ironic_api_version: String version (e.g. "1.35") to use for
|
||||||
|
the request. If not specified, the client's default is used.
|
||||||
|
|
||||||
|
:param global_request_id: String containing global request ID header
|
||||||
|
value (in form "req-<UUID>") to use for the request.
|
||||||
|
|
||||||
|
:returns: A list of runbooks.
|
||||||
|
|
||||||
|
"""
|
||||||
|
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, public=public,
|
||||||
|
project=project)
|
||||||
|
path = ''
|
||||||
|
if filters:
|
||||||
|
path += '?' + '&'.join(filters)
|
||||||
|
header_values = {"os_ironic_api_version": os_ironic_api_version,
|
||||||
|
"global_request_id": global_request_id}
|
||||||
|
if limit is None:
|
||||||
|
return self._list(self._path(path), "runbooks",
|
||||||
|
**header_values)
|
||||||
|
else:
|
||||||
|
return self._list_pagination(self._path(path), "runbooks",
|
||||||
|
limit=limit, **header_values)
|
||||||
|
|
||||||
|
def get(self, runbook_id, fields=None, os_ironic_api_version=None,
|
||||||
|
global_request_id=None):
|
||||||
|
return self._get(resource_id=runbook_id, fields=fields,
|
||||||
|
os_ironic_api_version=os_ironic_api_version,
|
||||||
|
global_request_id=global_request_id)
|
||||||
|
|
||||||
|
def delete(self, runbook_id, os_ironic_api_version=None,
|
||||||
|
global_request_id=None):
|
||||||
|
return self._delete(resource_id=runbook_id,
|
||||||
|
os_ironic_api_version=os_ironic_api_version,
|
||||||
|
global_request_id=global_request_id)
|
||||||
|
|
||||||
|
def update(self, runbook_id, patch, os_ironic_api_version=None,
|
||||||
|
global_request_id=None):
|
||||||
|
return self._update(resource_id=runbook_id, patch=patch,
|
||||||
|
os_ironic_api_version=os_ironic_api_version,
|
||||||
|
global_request_id=global_request_id)
|
@ -132,6 +132,12 @@ openstack.baremetal.v1 =
|
|||||||
baremetal_volume_target_unset = ironicclient.osc.v1.baremetal_volume_target:UnsetBaremetalVolumeTarget
|
baremetal_volume_target_unset = ironicclient.osc.v1.baremetal_volume_target:UnsetBaremetalVolumeTarget
|
||||||
baremetal_conductor_list = ironicclient.osc.v1.baremetal_conductor:ListBaremetalConductor
|
baremetal_conductor_list = ironicclient.osc.v1.baremetal_conductor:ListBaremetalConductor
|
||||||
baremetal_conductor_show = ironicclient.osc.v1.baremetal_conductor:ShowBaremetalConductor
|
baremetal_conductor_show = ironicclient.osc.v1.baremetal_conductor:ShowBaremetalConductor
|
||||||
|
baremetal_runbook_create = ironicclient.osc.v1.baremetal_runbook:CreateBaremetalRunbook
|
||||||
|
baremetal_runbook_delete = ironicclient.osc.v1.baremetal_runbook:DeleteBaremetalRunbook
|
||||||
|
baremetal_runbook_list = ironicclient.osc.v1.baremetal_runbook:ListBaremetalRunbook
|
||||||
|
baremetal_runbook_set = ironicclient.osc.v1.baremetal_runbook:SetBaremetalRunbook
|
||||||
|
baremetal_runbook_unset = ironicclient.osc.v1.baremetal_runbook:UnsetBaremetalRunbook
|
||||||
|
baremetal_runbook_show = ironicclient.osc.v1.baremetal_runbook:ShowBaremetalRunbook
|
||||||
|
|
||||||
[extras]
|
[extras]
|
||||||
cli =
|
cli =
|
||||||
|
Loading…
Reference in New Issue
Block a user