diff options
author | Dmitry Tantsur <divius.inside@gmail.com> | 2019-02-12 16:24:05 +0100 |
---|---|---|
committer | Dmitry Tantsur <divius.inside@gmail.com> | 2019-02-16 15:51:38 +0100 |
commit | e0708a16efa48d872a9f088af856809f523f03dc (patch) | |
tree | b456e4c82c8402ccaa5a68e4dd268ed82ab07430 /ironicclient/osc | |
parent | e8a6d447f803c115ed57064e6fada3e9d6f30794 (diff) | |
download | python-ironicclient-e0708a16efa48d872a9f088af856809f523f03dc.tar.gz |
Allocation API: client API and CLI
Adds the Python API to create/list/view/delete allocations, as well
as the OpenStackClient commands.
Change-Id: Ib97ee888c4a7b6dfa38934f02372284aa4c781a0
Story: #2004341
Task: #28028
Diffstat (limited to 'ironicclient/osc')
-rw-r--r-- | ironicclient/osc/v1/baremetal_allocation.py | 269 |
1 files changed, 269 insertions, 0 deletions
diff --git a/ironicclient/osc/v1/baremetal_allocation.py b/ironicclient/osc/v1/baremetal_allocation.py new file mode 100644 index 0000000..31de0cf --- /dev/null +++ b/ironicclient/osc/v1/baremetal_allocation.py @@ -0,0 +1,269 @@ +# 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 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 + + +class CreateBaremetalAllocation(command.ShowOne): + """Create a new baremetal allocation.""" + + log = logging.getLogger(__name__ + ".CreateBaremetalAllocation") + + def get_parser(self, prog_name): + parser = super(CreateBaremetalAllocation, self).get_parser(prog_name) + + parser.add_argument( + '--resource-class', + dest='resource_class', + required=True, + help=_('Resource class to request.')) + parser.add_argument( + '--trait', + action='append', + dest='traits', + help=_('A trait to request. Can be specified multiple times.')) + parser.add_argument( + '--candidate-node', + action='append', + dest='candidate_nodes', + help=_('A candidate node for this allocation. Can be specified ' + 'multiple times. If at least one is specified, only the ' + 'provided candidate nodes are considered for the ' + 'allocation.')) + parser.add_argument( + '--name', + dest='name', + help=_('Unique name of the allocation.')) + parser.add_argument( + '--uuid', + dest='uuid', + help=_('UUID of the allocation.')) + parser.add_argument( + '--extra', + metavar="<key=value>", + action='append', + help=_("Record arbitrary key/value metadata. " + "Can be specified multiple times.")) + parser.add_argument( + '--wait', + type=int, + dest='wait_timeout', + default=None, + metavar='<time-out>', + const=0, + nargs='?', + help=_("Wait for the new allocation to become active. An error " + "is returned if allocation fails and --wait is used. " + "Optionally takes a timeout value (in seconds). The " + "default value is 0, meaning it will wait indefinitely.")) + + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + baremetal_client = self.app.client_manager.baremetal + + field_list = ['name', 'uuid', 'extra', 'resource_class', 'traits', + 'candidate_nodes'] + 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') + allocation = baremetal_client.allocation.create(**fields) + if parsed_args.wait_timeout is not None: + allocation = baremetal_client.allocation.wait( + allocation.uuid, timeout=parsed_args.wait_timeout) + + data = dict([(f, getattr(allocation, f, '')) for f in + res_fields.ALLOCATION_DETAILED_RESOURCE.fields]) + + return self.dict2columns(data) + + +class ShowBaremetalAllocation(command.ShowOne): + """Show baremetal allocation details.""" + + log = logging.getLogger(__name__ + ".ShowBaremetalAllocation") + + def get_parser(self, prog_name): + parser = super(ShowBaremetalAllocation, self).get_parser(prog_name) + parser.add_argument( + "allocation", + metavar="<id>", + help=_("UUID or name of the allocation")) + parser.add_argument( + '--fields', + nargs='+', + dest='fields', + metavar='<field>', + action='append', + choices=res_fields.ALLOCATION_DETAILED_RESOURCE.fields, + default=[], + help=_("One or more allocation 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 + + allocation = baremetal_client.allocation.get( + parsed_args.allocation, fields=fields)._info + + allocation.pop("links", None) + return zip(*sorted(allocation.items())) + + +class ListBaremetalAllocation(command.Lister): + """List baremetal allocations.""" + + log = logging.getLogger(__name__ + ".ListBaremetalAllocation") + + def get_parser(self, prog_name): + parser = super(ListBaremetalAllocation, self).get_parser(prog_name) + parser.add_argument( + '--limit', + metavar='<limit>', + type=int, + help=_('Maximum number of allocations to return per request, ' + '0 for no limit. Default is the maximum number used ' + 'by the Baremetal API Service.')) + parser.add_argument( + '--marker', + metavar='<allocation>', + help=_('Port group UUID (for example, of the last allocation in ' + 'the list from a previous request). Returns the list of ' + 'allocations after this UUID.')) + parser.add_argument( + '--sort', + metavar="<key>[:<direction>]", + help=_('Sort output by specified allocation fields and directions ' + '(asc or desc) (default: asc). Multiple fields and ' + 'directions can be specified, separated by comma.')) + parser.add_argument( + '--node', + metavar='<node>', + help=_("Only list allocations of this node (name or UUID).")) + parser.add_argument( + '--resource-class', + metavar='<resource_class>', + help=_("Only list allocations with this resource class.")) + parser.add_argument( + '--state', + metavar='<state>', + help=_("Only list allocations in this state.")) + + # NOTE(dtantsur): the allocation API does not expose the 'detail' flag, + # but some fields are inconvenient to display in a table, so we emulate + # it on the client side. + display_group = parser.add_mutually_exclusive_group(required=False) + display_group.add_argument( + '--long', + default=False, + help=_("Show detailed information about the allocations."), + action='store_true') + display_group.add_argument( + '--fields', + nargs='+', + dest='fields', + metavar='<field>', + action='append', + default=[], + choices=res_fields.ALLOCATION_DETAILED_RESOURCE.fields, + help=_("One or more allocation 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 + + 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 + for field in ('node', 'resource_class', 'state'): + value = getattr(parsed_args, field) + if value is not None: + params[field] = value + + if parsed_args.long: + columns = res_fields.ALLOCATION_DETAILED_RESOURCE.fields + labels = res_fields.ALLOCATION_DETAILED_RESOURCE.labels + elif parsed_args.fields: + fields = itertools.chain.from_iterable(parsed_args.fields) + resource = res_fields.Resource(list(fields)) + columns = resource.fields + labels = resource.labels + params['fields'] = columns + else: + columns = res_fields.ALLOCATION_RESOURCE.fields + labels = res_fields.ALLOCATION_RESOURCE.labels + + self.log.debug("params(%s)", params) + data = client.allocation.list(**params) + + data = oscutils.sort_items(data, parsed_args.sort) + + return (labels, + (oscutils.get_item_properties(s, columns) for s in data)) + + +class DeleteBaremetalAllocation(command.Command): + """Unregister baremetal allocation(s).""" + + log = logging.getLogger(__name__ + ".DeleteBaremetalAllocation") + + def get_parser(self, prog_name): + parser = super(DeleteBaremetalAllocation, self).get_parser(prog_name) + parser.add_argument( + "allocations", + metavar="<allocation>", + nargs="+", + help=_("Allocations(s) to delete (name or UUID).")) + + 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 allocation in parsed_args.allocations: + try: + baremetal_client.allocation.delete(allocation) + print(_('Deleted allocation %s') % allocation) + except exc.ClientException as e: + failures.append(_("Failed to delete allocation " + "%(allocation)s: %(error)s") + % {'allocation': allocation, 'error': e}) + + if failures: + raise exc.ClientException("\n".join(failures)) |