def get_parser(self, prog_name):
        parser = super(RebootBaremetalNode, self).get_parser(prog_name)

        parser.add_argument(
            'node',
            metavar='<node>',
            help="Name or UUID of the node."
        )
        parser.add_argument(
            '--soft',
            dest='soft',
            action='store_true',
            default=False,
            help=_("Request Graceful reboot.")
        )
        parser.add_argument(
            '--power-timeout',
            metavar='<power-timeout>',
            default=None,
            type=int,
            help=_("Timeout (in seconds, positive integer) to wait for the "
                   "target power state before erroring out.")
        )

        return parser
    def get_parser(self, prog_name):
        parser = super(UnsetBaremetalPort, self).get_parser(prog_name)

        parser.add_argument(
            'port',
            metavar='<port>',
            help=_("UUID of the port.")
        )
        parser.add_argument(
            "--extra",
            metavar="<key>",
            action='append',
            help=_('Extra to unset on this baremetal port '
                   '(repeat option to unset multiple extras)')
        )

        parser.add_argument(
            '--port-group',
            action='store_true',
            dest='portgroup',
            help=_("Remove port from the port group"))

        parser.add_argument(
            '--physical-network',
            action='store_true',
            dest='physical_network',
            help=_("Unset the physical network on this baremetal port."))

        parser.add_argument(
            '--is-smartnic',
            dest='is_smartnic',
            action='store_true',
            help=_("Set Port as not Smart NIC port"))

        return parser
Ejemplo n.º 3
0
    def set_power_state(self, node_id, state, soft=False, timeout=None):
        """Sets power state for a node.

        :param node_id: Node identifier
        :param state: One of target power state, 'on', 'off', or 'reboot'
        :param soft: The flag for graceful power 'off' or 'reboot'
        :param timeout: The timeout (in seconds) positive integer value (> 0)
        :raises: ValueError if 'soft' or 'timeout' option is invalid
        :returns: The status of the request
        """
        if state == 'on' and soft:
            raise ValueError(
                _("'soft' option is invalid for the power-state 'on'"))

        path = "%s/states/power" % node_id

        requested_state = 'soft ' + state if soft else state
        target = _power_states.get(requested_state, state)

        body = {'target': target}
        if timeout is not None:
            msg = _("'timeout' option for setting power state must have "
                    "positive integer value (> 0)")
            try:
                timeout = int(timeout)
            except (ValueError, TypeError):
                raise ValueError(msg)

            if timeout <= 0:
                raise ValueError(msg)
            body = {'target': target, 'timeout': timeout}

        return self.update(path, body, http_method='PUT')
Ejemplo n.º 4
0
def handle_json_or_file_arg(json_arg):
    """Attempts to read JSON argument from file or string.

    :param json_arg: May be a file name containing the JSON, or
        a JSON string.
    :returns: A list or dictionary parsed from JSON.
    :raises: InvalidAttribute if the argument cannot be parsed.
    """

    if os.path.isfile(json_arg):
        try:
            with open(json_arg, 'r') as f:
                json_arg = f.read().strip()
        except Exception as e:
            err = _("Cannot get JSON from file '%(file)s'. "
                    "Error: %(err)s") % {'err': e, 'file': json_arg}
            raise exc.InvalidAttribute(err)
    try:
        json_arg = json.loads(json_arg)
    except ValueError as e:
        err = (_("For JSON: '%(string)s', error: '%(err)s'") %
               {'err': e, 'string': json_arg})
        raise exc.InvalidAttribute(err)

    return json_arg
Ejemplo n.º 5
0
    def wait(self, allocation_id, timeout=0, poll_interval=1,
             poll_delay_function=None):
        """Wait for the Allocation to become active.

        :param timeout: timeout in seconds, no timeout if 0.
        :param poll_interval: interval in seconds between polls.
        :param poll_delay_function: function to use to wait between polls
            (defaults to time.sleep). Should take one argument - delay time
            in seconds. Any exceptions raised inside it will abort the wait.
        :return: updated :class:`Allocation` object.
        :raises: StateTransitionFailed if allocation reaches the error state.
        :raises: StateTransitionTimeout on timeout.
        """
        timeout_msg = _('Allocation %(allocation)s failed to become active '
                        'in %(timeout)s seconds') % {
                            'allocation': allocation_id,
                            'timeout': timeout}
        for _count in utils.poll(timeout, poll_interval, poll_delay_function,
                                 timeout_msg):
            allocation = self.get(allocation_id)
            if allocation.state == 'error':
                raise exc.StateTransitionFailed(
                    _('Allocation %(allocation)s failed: %(error)s') %
                    {'allocation': allocation_id,
                     'error': allocation.last_error})
            elif allocation.state == 'active':
                return allocation

            LOG.debug('Still waiting for allocation %(allocation)s to become '
                      'active, the current state is %(actual)s',
                      {'allocation': allocation_id,
                       'actual': allocation.state})
Ejemplo n.º 6
0
def do_node_set_provision_state(cc, args):
    """Initiate a provisioning state change for a node."""
    if args.config_drive and args.provision_state != 'active':
        raise exceptions.CommandError(_('--config-drive is only valid when '
                                        'setting provision state to "active"'))
    elif args.clean_steps and args.provision_state != 'clean':
        raise exceptions.CommandError(_('--clean-steps is only valid when '
                                        'setting provision state to "clean"'))
    elif args.provision_state == 'clean' and not args.clean_steps:
        raise exceptions.CommandError(_('--clean-steps must be specified when '
                                        'setting provision state to "clean"'))

    if args.wait_timeout is not None:
        wait_args = v1_utils.PROVISION_ACTIONS.get(args.provision_state)
        if wait_args is None:
            raise exceptions.CommandError(
                _("--wait is not supported for provision state '%s'")
                % args.provision_state)

    clean_steps = args.clean_steps
    if args.clean_steps == '-':
        clean_steps = utils.get_from_stdin('clean steps')
    if clean_steps:
        clean_steps = utils.handle_json_or_file_arg(clean_steps)
    cc.node.set_provision_state(args.node, args.provision_state,
                                configdrive=args.config_drive,
                                cleansteps=clean_steps)
    if args.wait_timeout is not None:
        print(_('Waiting for provision state %(state)s on node %(node)s') %
              {'state': wait_args['expected_state'], 'node': args.node})
        cc.node.wait_for_provision_state(args.node, timeout=args.wait_timeout,
                                         **wait_args)
 def get_parser(self, prog_name):
     parser = super(PassthruCallBaremetalDriver, self).get_parser(prog_name)
     parser.add_argument(
         'driver',
         metavar='<driver>',
         help=_('Name of the driver.')
     )
     parser.add_argument(
         'method',
         metavar='<method>',
         help=_("Vendor passthru method to be called.")
     )
     parser.add_argument(
         '--arg',
         metavar='<key=value>',
         action='append',
         help=_("Argument to pass to the passthru method (repeat option "
                "to specify multiple arguments).")
     )
     parser.add_argument(
         '--http-method',
         dest='http_method',
         metavar='<http-method>',
         choices=v1_utils.HTTP_METHODS,
         default='POST',
         help=_("The HTTP method to use in the passthru request. One of "
                "%s. Defaults to 'POST'.") %
              oscutils.format_list(v1_utils.HTTP_METHODS)
     )
     return parser
    def get_parser(self, prog_name):
        parser = (
            super(SetBaremetalVolumeConnector, self).get_parser(prog_name))

        parser.add_argument(
            'volume_connector',
            metavar='<volume connector>',
            help=_("UUID of the volume connector."))
        parser.add_argument(
            '--node',
            dest='node_uuid',
            metavar='<uuid>',
            help=_('UUID of the node that this volume connector belongs to.'))
        parser.add_argument(
            '--type',
            dest='type',
            metavar="<type>",
            choices=('iqn', 'ip', 'mac', 'wwnn', 'wwpn', 'port', 'portgroup'),
            help=_("Type of the volume connector. Can be 'iqn', 'ip', 'mac', "
                   "'wwnn', 'wwpn', 'port', 'portgroup'."))
        parser.add_argument(
            '--connector-id',
            dest='connector_id',
            metavar="<connector id>",
            help=_("ID of the volume connector in the specified type."))
        parser.add_argument(
            '--extra',
            dest='extra',
            metavar="<key=value>",
            action='append',
            help=_("Record arbitrary key/value metadata. "
                   "Can be specified multiple times."))

        return parser
    def get_parser(self, prog_name):
        parser = super(UnsetBaremetalPortGroup, self).get_parser(prog_name)

        parser.add_argument(
            'portgroup',
            metavar='<port group>',
            help=_("Name or UUID of the port group.")
        )
        parser.add_argument(
            "--name",
            action='store_true',
            help=_("Unset the name of the port group."),
        )
        parser.add_argument(
            "--address",
            action='store_true',
            help=_("Unset the address of the port group."),
        )
        parser.add_argument(
            "--extra",
            metavar="<key>",
            action='append',
            help=_('Extra to unset on this baremetal port group '
                   '(repeat option to unset multiple extras).'),
        )
        parser.add_argument(
            "--property",
            dest='properties',
            metavar="<key>",
            action='append',
            help=_('Property to unset on this baremetal port group '
                   '(repeat option to unset multiple properties).'),
        )

        return parser
Ejemplo n.º 10
0
def _handle_clean_steps_arg(clean_steps):
    """Attempts to read clean steps argument.

    :param clean_steps: May be a file name containing the clean steps, or
        a JSON string representing the clean steps.
    :returns: A list of dictionaries representing clean steps.
    :raises: InvalidAttribute if the argument cannot be parsed.
    """

    if os.path.isfile(clean_steps):
        try:
            with open(clean_steps, 'r') as f:
                clean_steps = f.read().strip()
        except Exception as e:
            err = _("Cannot get clean steps from file '%(file)s'. "
                    "Error: %(err)s") % {'err': e, 'file': clean_steps}
            raise exc.InvalidAttribute(err)
    try:
        clean_steps = json.loads(clean_steps)
    except ValueError as e:
        err = (_("For clean steps: '%(steps)s', error: '%(err)s'") %
               {'err': e, 'steps': clean_steps})
        raise exc.InvalidAttribute(err)

    return clean_steps
Ejemplo n.º 11
0
 def get_parser(self, prog_name):
     parser = super(ShowBaremetalPort, self).get_parser(prog_name)
     parser.add_argument(
         "port",
         metavar="<id>",
         help=_("UUID of the port (or MAC address if --address is "
                "specified).")
     )
     parser.add_argument(
         '--address',
         dest='address',
         action='store_true',
         default=False,
         help=_('<id> is the MAC address (instead of the UUID) of the '
                'port.')
     )
     parser.add_argument(
         '--fields',
         nargs='+',
         dest='fields',
         metavar='<field>',
         action='append',
         choices=res_fields.PORT_DETAILED_RESOURCE.fields,
         default=[],
         help=_("One or more port fields. Only these fields will be "
                "fetched from the server.")
     )
     return parser
    def get_parser(self, prog_name):
        parser = super(PowerBaremetalNode, self).get_parser(prog_name)

        parser.add_argument(
            'power_state',
            metavar='<on|off>',
            choices=['on', 'off'],
            help="Power node on or off"
        )
        parser.add_argument(
            'node',
            metavar='<node>',
            help="Name or UUID of the node."
        )
        parser.add_argument(
            '--soft',
            dest='soft',
            action='store_true',
            default=False,
            help=_("Request graceful power-off.")
        )
        parser.add_argument(
            '--power-timeout',
            metavar='<power-timeout>',
            default=None,
            type=int,
            help=_("Timeout (in seconds, positive integer) to wait for the "
                   "target power state before erroring out.")
        )
        return parser
Ejemplo n.º 13
0
def do_node_delete(cc, args):
    """Unregister node(s) from the Ironic service."""
    for n in args.node:
        try:
            cc.node.delete(n)
            print(_('Deleted node %s') % n)
        except exceptions.ClientException as e:
            print(_("Failed to delete node %(node)s: %(error)s")
                  % {'node': n, 'error': e})
    def get_parser(self, prog_name):
        parser = super(SetBaremetalPortGroup, self).get_parser(prog_name)

        parser.add_argument(
            'portgroup',
            metavar='<port group>',
            help=_("Name or UUID of the port group."),
        )
        parser.add_argument(
            '--node',
            dest='node_uuid',
            metavar='<uuid>',
            help=_('Update UUID of the node that this port group belongs to.')
        )
        parser.add_argument(
            "--address",
            metavar="<mac-address>",
            help=_("MAC address for this port group."),
        )
        parser.add_argument(
            "--name",
            metavar="<name>",
            help=_("Name of the port group."),
        )
        parser.add_argument(
            "--extra",
            metavar="<key=value>",
            action='append',
            help=_('Extra to set on this baremetal port group '
                   '(repeat option to set multiple extras).'),
        )
        parser.add_argument(
            '--mode',
            help=_('Mode of the port group. For possible values, refer to '
                   'https://www.kernel.org/doc/Documentation/networking'
                   '/bonding.txt.'))
        parser.add_argument(
            '--property',
            dest='properties',
            metavar="<key=value>",
            action='append',
            help=_("Key/value property related to this port group's "
                   "configuration (repeat option to set multiple "
                   "properties)."))
        standalone_ports_group = parser.add_mutually_exclusive_group()
        standalone_ports_group.add_argument(
            '--support-standalone-ports',
            action='store_true',
            default=None,
            help=_("Ports that are members of this port group "
                   "can be used as stand-alone ports.")
        )
        standalone_ports_group.add_argument(
            '--unsupport-standalone-ports',
            action='store_true',
            help=_("Ports that are members of this port group "
                   "cannot be used as stand-alone ports.")
        )

        return parser
Ejemplo n.º 15
0
def do_port_delete(cc, args):
    """Delete a port."""
    failures = []
    for p in args.port:
        try:
            cc.port.delete(p)
            print(_('Deleted port %s') % p)
        except exceptions.ClientException as e:
            failures.append(_("Failed to delete port %(port)s: %(error)s")
                            % {'port': p, 'error': e})
    if failures:
        raise exceptions.ClientException("\n".join(failures))
def do_volume_target_delete(cc, args):
    """Delete a volume target."""
    failures = []
    for vt in args.volume_target:
        try:
            cc.volume_target.delete(vt)
            print(_('Deleted volume target %s') % vt)
        except exceptions.ClientException as e:
            failures.append(_("Failed to delete volume target %(vt)s: "
                              "%(error)s")
                            % {'vt': vt, 'error': e})
    if failures:
        raise exceptions.ClientException("\n".join(failures))
def do_volume_connector_delete(cc, args):
    """Delete a volume connector."""
    failures = []
    for vc in args.volume_connector:
        try:
            cc.volume_connector.delete(vc)
            print(_('Deleted volume connector %s') % vc)
        except exceptions.ClientException as e:
            failures.append(_("Failed to delete volume connector %(vc)s: "
                              "%(error)s")
                            % {'vc': vc, 'error': e})
    if failures:
        raise exceptions.ClientException("\n".join(failures))
Ejemplo n.º 18
0
    def take_action(self, parsed_args):
        self.log.debug("take_action(%s)" % parsed_args)
        client = self.app.client_manager.baremetal

        columns = res_fields.NODE_RESOURCE

        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.associated:
            params['associated'] = parsed_args.associated
        if parsed_args.maintenance:
            params['maintenance'] = parsed_args.maintenance

        if parsed_args.long:
            columns = res_fields.NODE_DETAILED_RESOURCE
        params['detail'] = parsed_args.long

        self.log.debug("params(%s)" % params)
        data = client.node.list(**params)

        data = oscutils.sort_items(data, parsed_args.sort)

        return (columns.labels,
                (oscutils.get_item_properties(s, columns.fields, formatters={
                    'Properties': oscutils.format_dict},) for s in data))
Ejemplo n.º 19
0
def do_node_set_maintenance(cc, args):
    """Enable or disable maintenance mode for a node."""
    if args.reason and args.maintenance_mode.lower() in ('false', 'off'):
        raise exceptions.CommandError(_('Cannot set "reason" when turning off '
                                        'maintenance mode.'))
    cc.node.set_maintenance(args.node, args.maintenance_mode.lower(),
                            maint_reason=args.reason)
    def take_action(self, parsed_args):
        self.log.debug("take_action(%s)", parsed_args)
        client = self.app.client_manager.baremetal

        columns = res_fields.CHASSIS_RESOURCE.fields
        labels = res_fields.CHASSIS_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.long:
            params['detail'] = parsed_args.long
            columns = res_fields.CHASSIS_DETAILED_RESOURCE.fields
            labels = res_fields.CHASSIS_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.chassis.list(**params)

        data = oscutils.sort_items(data, parsed_args.sort)

        return (labels,
                (oscutils.get_item_properties(s, columns, formatters={
                    'Properties': oscutils.format_dict},) for s in data))
Ejemplo n.º 21
0
    def _discover_auth_versions(self, session, auth_url):
        # discover the API versions the server is supporting base on the
        # given URL
        v2_auth_url = None
        v3_auth_url = None
        try:
            ks_discover = discover.Discover(session=session, auth_url=auth_url)
            v2_auth_url = ks_discover.url_for('2.0')
            v3_auth_url = ks_discover.url_for('3.0')
        except ks_exc.ClientException:
            # Identity service may not support discover API version.
            # Let's try to figure out the API version from the original URL.
            url_parts = urlparse.urlparse(auth_url)
            (scheme, netloc, path, params, query, fragment) = url_parts
            path = path.lower()
            if path.startswith('/v3'):
                v3_auth_url = auth_url
            elif path.startswith('/v2'):
                v2_auth_url = auth_url
            else:
                # not enough information to determine the auth version
                msg = _('Unable to determine the Keystone version '
                        'to authenticate with using the given '
                        'auth_url. Identity service may not support API '
                        'version discovery. Please provide a versioned '
                        'auth_url instead. %s') % auth_url
                raise exc.CommandError(msg)

        return (v2_auth_url, v3_auth_url)
Ejemplo n.º 22
0
def get_client(api_version, **kwargs):
    """Get an authenticated client, based on the credentials in args.

    :param api_version: the API version to use. Valid value: '1'.
    :param kwargs: keyword args containing credentials, either:
            * os_auth_token: pre-existing token to re-use
            * ironic_url: ironic API endpoint
            or:
            * os_username: name of user
            * os_password: user's password
            * os_auth_url: endpoint to authenticate against
            * insecure: allow insecure SSL (no cert verification)
            * os_tenant_{name|id}: name or ID of tenant
    """

    if kwargs.get("os_auth_token") and kwargs.get("ironic_url"):
        token = kwargs.get("os_auth_token")
        endpoint = kwargs.get("ironic_url")
        auth_ref = None
    elif (
        kwargs.get("os_username")
        and kwargs.get("os_password")
        and kwargs.get("os_auth_url")
        and (kwargs.get("os_tenant_id") or kwargs.get("os_tenant_name"))
    ):

        ks_kwargs = {
            "username": kwargs.get("os_username"),
            "password": kwargs.get("os_password"),
            "tenant_id": kwargs.get("os_tenant_id"),
            "tenant_name": kwargs.get("os_tenant_name"),
            "auth_url": kwargs.get("os_auth_url"),
            "service_type": kwargs.get("os_service_type"),
            "endpoint_type": kwargs.get("os_endpoint_type"),
            "insecure": kwargs.get("insecure"),
        }
        _ksclient = _get_ksclient(**ks_kwargs)
        token = kwargs.get("os_auth_token") if kwargs.get("os_auth_token") else _ksclient.auth_token

        ks_kwargs["region_name"] = kwargs.get("os_region_name")
        endpoint = kwargs.get("ironic_url") or _get_endpoint(_ksclient, **ks_kwargs)

        auth_ref = _ksclient.auth_ref

    else:
        e = _("Must provide Keystone credentials or user-defined endpoint " "and token")
        raise exc.AmbiguousAuthSystem(e)

    cli_kwargs = {
        "token": token,
        "insecure": kwargs.get("insecure"),
        "timeout": kwargs.get("timeout"),
        "ca_file": kwargs.get("ca_file"),
        "cert_file": kwargs.get("cert_file"),
        "key_file": kwargs.get("key_file"),
        "auth_ref": auth_ref,
        "os_ironic_api_version": kwargs.get("os_ironic_api_version"),
    }

    return Client(api_version, endpoint, **cli_kwargs)
    def get_parser(self, prog_name):
        parser = (
            super(UnsetBaremetalVolumeConnector, self).get_parser(prog_name))

        parser.add_argument(
            'volume_connector',
            metavar='<volume connector>',
            help=_("UUID of the volume connector."))
        parser.add_argument(
            '--extra',
            dest='extra',
            metavar="<key>",
            action='append',
            help=_('Extra to unset (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

        failures = []
        for port in parsed_args.ports:
            try:
                baremetal_client.port.delete(port)
                print(_('Deleted port %s') % port)
            except exc.ClientException as e:
                failures.append(_("Failed to delete port %(port)s: %(error)s")
                                % {'port': port, 'error': e})

        if failures:
            raise exc.ClientException("\n".join(failures))
Ejemplo n.º 25
0
def do_node_set_provision_state(cc, args):
    """Provision, rebuild, delete, inspect, provide or manage an instance."""
    if args.config_drive and args.provision_state != 'active':
        raise exceptions.CommandError(_('--config-drive is only valid when '
                                        'setting provision state to "active"'))
    cc.node.set_provision_state(args.node, args.provision_state,
                                configdrive=args.config_drive)
Ejemplo n.º 26
0
    def vendor_passthru(self, node_id, method, args=None,
                        http_method=None):
        """Issue requests for vendor-specific actions on a given node.

        :param node_id: The UUID of the node.
        :param method: Name of the vendor method.
        :param args: Optional. The arguments to be passed to the method.
        :param http_method: The HTTP method to use on the request.
                            Defaults to POST.
        """
        if args is None:
            args = {}

        if http_method is None:
            http_method = 'POST'

        http_method = http_method.upper()

        path = "%s/vendor_passthru/%s" % (node_id, method)
        if http_method in ('POST', 'PUT', 'PATCH'):
            return self.update(path, args, http_method=http_method)
        elif http_method == 'DELETE':
            return self.delete(path)
        elif http_method == 'GET':
            return self.get(path)
        else:
            raise exc.InvalidAttribute(
                _('Unknown HTTP method: %s') % http_method)
Ejemplo n.º 27
0
    def set_maintenance(self, node_id, state, maint_reason=None):
        """Set the maintenance mode for the node.

        :param node_id: The UUID of the node.
        :param state: the maintenance mode; either a Boolean or a string
                      representation of a Boolean (eg, 'true', 'on', 'false',
                      'off'). True to put the node in maintenance mode; False
                      to take the node out of maintenance mode.
        :param maint_reason: Optional string. Reason for putting node
                             into maintenance mode.
        :raises: InvalidAttribute if state is an invalid string (that doesn't
                 represent a Boolean).

        """
        if isinstance(state, bool):
            maintenance_mode = state
        else:
            try:
                maintenance_mode = strutils.bool_from_string(state, True)
            except ValueError as e:
                raise exc.InvalidAttribute(_("Argument 'state': %(err)s") %
                                           {'err': e})
        path = "%s/maintenance" % node_id
        if maintenance_mode:
            reason = {'reason': maint_reason}
            return self.update(path, reason, http_method='PUT')
        else:
            return self.delete(path)
Ejemplo n.º 28
0
    def take_action(self, parsed_args):
        self.log.debug("take_action(%s)", parsed_args)

        baremetal_client = self.app.client_manager.baremetal

        failures = []
        for node in parsed_args.nodes:
            try:
                baremetal_client.node.delete(node)
                print(_('Deleted node %s') % node)
            except exc.ClientException as e:
                failures.append(_("Failed to delete node %(node)s: %(error)s")
                                % {'node': node, 'error': e})

        if failures:
            raise exc.ClientException("\n".join(failures))
Ejemplo n.º 29
0
    def _check_version(self, api_version):
        if api_version == 'latest':
            return LATEST_API_VERSION
        else:
            try:
                versions = tuple(int(i) for i in api_version.split('.'))
            except ValueError:
                versions = ()
            if len(versions) == 1:
                # Default value of ironic_api_version is '1'.
                # If user not specify the value of api version, not passing
                # headers at all.
                os_ironic_api_version = None
            elif len(versions) == 2:
                os_ironic_api_version = api_version
                # In the case of '1.0'
                if versions[1] == 0:
                    os_ironic_api_version = None
            else:
                msg = _("The requested API version %(ver)s is an unexpected "
                        "format. Acceptable formats are 'X', 'X.Y', or the "
                        "literal string '%(latest)s'."
                        ) % {'ver': api_version, 'latest': 'latest'}
                raise exc.CommandError(msg)

            api_major_version = versions[0]
            return (api_major_version, os_ironic_api_version)
Ejemplo n.º 30
0
    def __init__(self, endpoint, **kwargs):
        self.endpoint = endpoint
        self.endpoint_trimmed = _trim_endpoint_api_version(endpoint)
        self.auth_token = kwargs.get('token')
        self.auth_ref = kwargs.get('auth_ref')
        self.os_ironic_api_version = kwargs.get('os_ironic_api_version',
                                                DEFAULT_VER)
        self.api_version_select_state = kwargs.get(
            'api_version_select_state', 'default')
        self.conflict_max_retries = kwargs.pop('max_retries',
                                               DEFAULT_MAX_RETRIES)
        self.conflict_retry_interval = kwargs.pop('retry_interval',
                                                  DEFAULT_RETRY_INTERVAL)
        self.session = requests.Session()

        parts = urlparse.urlparse(endpoint)
        if parts.scheme not in SUPPORTED_ENDPOINT_SCHEME:
            msg = _('Unsupported scheme: %s') % parts.scheme
            raise exc.EndpointException(msg)

        if parts.scheme == 'https':
            if kwargs.get('insecure') is True:
                self.session.verify = False
            elif kwargs.get('ca_file'):
                self.session.verify = kwargs['ca_file']
            self.session.cert = (kwargs.get('cert_file'),
                                 kwargs.get('key_file'))
Ejemplo n.º 31
0
    def get_parser(self, prog_name):
        parser = super(CreateBaremetalPort, self).get_parser(prog_name)

        parser.add_argument('address',
                            metavar='<address>',
                            help=_('MAC address for this port.'))
        parser.add_argument(
            '--node',
            dest='node_uuid',
            metavar='<uuid>',
            required=True,
            help=_('UUID of the node that this port belongs to.'))
        parser.add_argument('--extra',
                            metavar="<key=value>",
                            action='append',
                            help=_("Record arbitrary key/value metadata. "
                                   "Can be specified multiple times."))
        parser.add_argument(
            '--local-link-connection',
            metavar="<key=value>",
            action='append',
            help=_("Key/value metadata describing Local link connection "
                   "information. Valid keys are switch_info, switch_id, "
                   "port_id; switch_id and port_id are obligatory. Can be "
                   "specified multiple times."))
        parser.add_argument(
            '-l',
            dest='local_link_connection_deprecated',
            metavar="<key=value>",
            action='append',
            help=_("DEPRECATED. Please use --local-link-connection instead. "
                   "Key/value metadata describing Local link connection "
                   "information. Valid keys are switch_info, switch_id, "
                   "port_id; switch_id and port_id are obligatory. Can be "
                   "specified multiple times."))
        parser.add_argument(
            '--pxe-enabled',
            metavar='<boolean>',
            help=_('Indicates whether this Port should be used when '
                   'PXE booting this Node.'))

        parser.add_argument(
            '--port-group',
            dest='portgroup_uuid',
            metavar='<uuid>',
            help=_("UUID of the port group that this port belongs to."))

        return parser
Ejemplo n.º 32
0
    def vendor_passthru(self,
                        driver_name,
                        method,
                        args=None,
                        http_method=None,
                        os_ironic_api_version=None,
                        global_request_id=None):
        """Issue requests for vendor-specific actions on a given driver.

        :param driver_name: The name of the driver.
        :param method: Name of the vendor method.
        :param args: Optional. The arguments to be passed to the method.
        :param http_method: The HTTP method to use on the request.
                            Defaults to POST.
        :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.
        """
        if args is None:
            args = {}

        if http_method is None:
            http_method = 'POST'

        http_method = http_method.upper()

        header_values = {
            "os_ironic_api_version": os_ironic_api_version,
            "global_request_id": global_request_id
        }

        path = "%s/vendor_passthru/%s" % (driver_name, method)
        if http_method in ('POST', 'PUT', 'PATCH'):
            return self.update(path,
                               args,
                               http_method=http_method,
                               **header_values)
        elif http_method == 'DELETE':
            return self.delete(path, **header_values)
        elif http_method == 'GET':
            return self.get(path, **header_values)
        else:
            raise exc.InvalidAttribute(
                _('Unknown HTTP method: %s') % http_method)
Ejemplo n.º 33
0
    def take_action(self, parsed_args):
        self.log.debug("take_action(%s)", parsed_args)
        client = self.app.client_manager.baremetal

        columns = res_fields.NODE_RESOURCE.fields
        labels = res_fields.NODE_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.associated:
            params['associated'] = parsed_args.associated
        if parsed_args.maintenance:
            params['maintenance'] = parsed_args.maintenance
        if parsed_args.provision_state:
            params['provision_state'] = parsed_args.provision_state
        if parsed_args.resource_class:
            params['resource_class'] = parsed_args.resource_class
        if parsed_args.chassis:
            params['chassis'] = parsed_args.chassis
        if parsed_args.long:
            params['detail'] = parsed_args.long
            columns = res_fields.NODE_DETAILED_RESOURCE.fields
            labels = res_fields.NODE_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.node.list(**params)

        data = oscutils.sort_items(data, parsed_args.sort)

        return (labels, (oscutils.get_item_properties(
            s,
            columns,
            formatters={'Properties': oscutils.format_dict},
        ) for s in data))
Ejemplo n.º 34
0
    def find(self, **kwargs):
        """Find a single item with attributes matching ``**kwargs``.

        This isn't very efficient: it loads the entire list then filters on
        the Python side.
        """
        matches = self.findall(**kwargs)
        num_matches = len(matches)
        if num_matches == 0:
            msg = _("No %(name)s matching %(args)s.") % {
                'name': self.resource_class.__name__,
                'args': kwargs
            }
            raise exceptions.NotFound(msg)
        elif num_matches > 1:
            raise exceptions.NoUniqueMatch()
        else:
            return matches[0]
Ejemplo n.º 35
0
def get_client_class(version):
    version_map = {
        '1': 'ironicclient.v1.client.Client',
        '1.5': 'ironicclient.v1.client.Client',
        '1.6': 'ironicclient.v1.client.Client',
        '1.9': 'ironicclient.v1.client.Client',
    }
    try:
        client_path = version_map[str(version)]
    except (KeyError, ValueError):
        msg = _("Invalid client version '%(version)s'. must be one of: "
                "%(keys)s") % {
                    'version': version,
                    'keys': ', '.join(version_map)
                }
        raise exceptions.UnsupportedVersion(msg)

    return importutils.import_class(client_path)
Ejemplo n.º 36
0
def check_for_invalid_fields(fields, valid_fields):
    """Check for invalid fields.

    :param fields: A list of fields specified by the user.
    :param valid_fields: A list of valid fields.
    :raises CommandError: If invalid fields were specified by the user.
    """
    if not fields:
        return

    invalid_fields = set(fields) - set(valid_fields)
    if invalid_fields:
        raise exc.CommandError(
            _('Invalid field(s) requested: %(invalid)s. Valid fields '
              'are: %(valid)s.') % {
                  'invalid': ', '.join(invalid_fields),
                  'valid': ', '.join(valid_fields)
              })
Ejemplo n.º 37
0
    def get_parser(self, prog_name):
        parser = super(CreateBaremetalVolumeTarget, self).get_parser(prog_name)

        parser.add_argument(
            '--node',
            dest='node_uuid',
            metavar='<uuid>',
            required=True,
            help=_('UUID of the node that this volume target belongs to.'))
        parser.add_argument(
            '--type',
            dest='volume_type',
            metavar="<volume type>",
            required=True,
            help=_("Type of the volume target, e.g. 'iscsi', 'fibre_channel', "
                   "'rbd'."))
        parser.add_argument(
            '--property',
            dest='properties',
            metavar="<key=value>",
            action='append',
            help=_("Key/value property related to the type of this volume "
                   "target. Can be specified multiple times."))
        parser.add_argument('--boot-index',
                            dest='boot_index',
                            metavar="<boot index>",
                            type=int,
                            required=True,
                            help=_("Boot index of the volume target."))
        parser.add_argument(
            '--volume-id',
            dest='volume_id',
            metavar="<volume id>",
            required=True,
            help=_("ID of the volume associated with this target."))
        parser.add_argument('--uuid',
                            dest='uuid',
                            metavar='<uuid>',
                            help=_("UUID of the volume target."))
        parser.add_argument('--extra',
                            dest='extra',
                            metavar="<key=value>",
                            action='append',
                            help=_("Record arbitrary key/value metadata. "
                                   "Can be specified multiple times."))

        return parser
Ejemplo n.º 38
0
    def take_action(self, parsed_args):
        self.log.debug("take_action(%s)", parsed_args)
        client = self.app.client_manager.baremetal

        columns = res_fields.PORT_RESOURCE.fields
        labels = res_fields.PORT_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.address is not None:
            params['address'] = parsed_args.address
        if parsed_args.node is not None:
            params['node'] = parsed_args.node
        if parsed_args.portgroup is not None:
            params['portgroup'] = parsed_args.portgroup

        if parsed_args.detail:
            params['detail'] = parsed_args.detail
            columns = res_fields.PORT_DETAILED_RESOURCE.fields
            labels = res_fields.PORT_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.port.list(**params)

        data = oscutils.sort_items(data, parsed_args.sort)

        return (labels, (oscutils.get_item_properties(
            s,
            columns,
            formatters={'extra': oscutils.format_dict},
        ) for s in data))
Ejemplo n.º 39
0
    def set_maintenance(self,
                        node_id,
                        state,
                        maint_reason=None,
                        os_ironic_api_version=None,
                        global_request_id=None):
        """Set the maintenance mode for the node.

        :param node_id: The UUID of the node.
        :param state: the maintenance mode; either a Boolean or a string
                      representation of a Boolean (eg, 'true', 'on', 'false',
                      'off'). True to put the node in maintenance mode; False
                      to take the node out of maintenance mode.
        :param maint_reason: Optional string. Reason for putting node
                             into maintenance mode.
        :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.

        :raises: InvalidAttribute if state is an invalid string (that doesn't
                 represent a Boolean).
        """
        if isinstance(state, bool):
            maintenance_mode = state
        else:
            try:
                maintenance_mode = strutils.bool_from_string(state, True)
            except ValueError as e:
                raise exc.InvalidAttribute(
                    _("Argument 'state': %(err)s") % {'err': e})
        path = "%s/maintenance" % node_id
        header_values = {
            "os_ironic_api_version": os_ironic_api_version,
            "global_request_id": global_request_id
        }
        if maintenance_mode:
            reason = {'reason': maint_reason}
            return self.update(path,
                               reason,
                               http_method='PUT',
                               **header_values)
        else:
            return self.delete(path, **header_values)
    def get_parser(self, prog_name):
        parser = (super(ListBaremetalVolumeConnector,
                        self).get_parser(prog_name))

        parser.add_argument(
            '--node',
            dest='node',
            metavar='<node>',
            help=_("Only list volume connectors of this node (name or UUID)."))
        parser.add_argument(
            '--limit',
            dest='limit',
            metavar='<limit>',
            type=int,
            help=_('Maximum number of volume connectors to return per '
                   'request, 0 for no limit. Default is the maximum number '
                   'used by the Baremetal API Service.'))
        parser.add_argument(
            '--marker',
            dest='marker',
            metavar='<volume connector>',
            help=_('Volume connector UUID (for example, of the last volume '
                   'connector in the list from a previous request). Returns '
                   'the list of volume connectors after this UUID.'))
        parser.add_argument(
            '--sort',
            dest='sort',
            metavar='<key>[:<direction>]',
            help=_('Sort output by specified volume connector 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(required=False)
        display_group.add_argument(
            '--long',
            dest='detail',
            action='store_true',
            default=False,
            help=_("Show detailed information about volume connectors."))
        display_group.add_argument(
            '--fields',
            nargs='+',
            dest='fields',
            metavar='<field>',
            action='append',
            default=[],
            choices=res_fields.VOLUME_CONNECTOR_DETAILED_RESOURCE.fields,
            help=_("One or more volume connector fields. Only these fields "
                   "will be fetched from the server. Can not be used when "
                   "'--long' is specified."))
        return parser
Ejemplo n.º 41
0
class HttpError(ClientException):
    """The base exception class for all HTTP exceptions."""
    http_status = 0
    message = _("HTTP Error")

    def __init__(self, message=None, details=None,
                 response=None, request_id=None,
                 url=None, method=None, http_status=None):
        self.http_status = http_status or self.http_status
        self.message = message or self.message
        self.details = details
        self.request_id = request_id
        self.response = response
        self.url = url
        self.method = method
        formatted_string = "%s (HTTP %s)" % (self.message, self.http_status)
        if request_id:
            formatted_string += " (Request-ID: %s)" % request_id
        super(HttpError, self).__init__(formatted_string)
Ejemplo n.º 42
0
    def take_action(self, parsed_args):
        self.log.debug("take_action(%s)", parsed_args)

        baremetal_client = self.app.client_manager.baremetal

        if parsed_args.boot_index is not None and parsed_args.boot_index < 0:
            raise exc.CommandError(
                _('Expected non-negative --boot-index, got %s') %
                parsed_args.boot_index)

        properties = []
        if parsed_args.node_uuid:
            properties.extend(
                utils.args_array_to_patch(
                    'add', ["node_uuid=%s" % parsed_args.node_uuid]))
        if parsed_args.volume_type:
            properties.extend(
                utils.args_array_to_patch(
                    'add', ["volume_type=%s" % parsed_args.volume_type]))
        if parsed_args.boot_index:
            properties.extend(
                utils.args_array_to_patch(
                    'add', ["boot_index=%s" % parsed_args.boot_index]))
        if parsed_args.volume_id:
            properties.extend(
                utils.args_array_to_patch(
                    'add', ["volume_id=%s" % parsed_args.volume_id]))

        if parsed_args.properties:
            properties.extend(
                utils.args_array_to_patch(
                    'add',
                    ["properties/" + x for x in parsed_args.properties]))
        if parsed_args.extra:
            properties.extend(
                utils.args_array_to_patch(
                    'add', ["extra/" + x for x in parsed_args.extra]))

        if properties:
            baremetal_client.volume_target.update(parsed_args.volume_target,
                                                  properties)
        else:
            self.log.warning("Please specify what to set.")
Ejemplo n.º 43
0
def split_and_deserialize(string):
    """Split and try to JSON deserialize a string.

    Gets a string with the KEY=VALUE format, split it (using '=' as the
    separator) and try to JSON deserialize the VALUE.

    :returns: A tuple of (key, value).
    """
    try:
        key, value = string.split("=", 1)
    except ValueError:
        raise exc.CommandError(_('Attributes must be a list of '
                                 'PATH=VALUE not "%s"') % string)
    try:
        value = json.loads(value)
    except ValueError:
        pass

    return (key, value)
Ejemplo n.º 44
0
    def __init__(self, os_ironic_api_version, api_version_select_state,
                 max_retries, retry_interval, **kwargs):
        self.os_ironic_api_version = os_ironic_api_version
        self.api_version_select_state = api_version_select_state
        self.conflict_max_retries = max_retries
        self.conflict_retry_interval = retry_interval
        if isinstance(kwargs.get('endpoint_override'), six.string_types):
            kwargs['endpoint_override'] = _trim_endpoint_api_version(
                kwargs['endpoint_override'])

        super(SessionClient, self).__init__(**kwargs)

        endpoint_filter = self._get_endpoint_filter()
        endpoint = self.get_endpoint(**endpoint_filter)
        if endpoint is None:
            raise exc.EndpointNotFound(
                _('The Bare Metal API endpoint cannot be detected and was '
                  'not provided explicitly'))
        self.endpoint_trimmed = _trim_endpoint_api_version(endpoint)
Ejemplo n.º 45
0
def print_dict(dct, dict_property="Property", wrap=0, dict_value='Value',
               json_flag=False):
    """Print a `dict` as a table of two columns.

    :param dct: `dict` to print
    :param dict_property: name of the first column
    :param wrap: wrapping for the second column
    :param dict_value: header label for the value (second) column
    :param json_flag: print `dict` as JSON instead of table
    """
    if json_flag:
        print(json.dumps(dct, indent=4, separators=(',', ': ')))
        return
    pt = prettytable.PrettyTable([dict_property, dict_value])
    pt.align = 'l'
    for k, v in sorted(dct.items()):
        # convert dict to str to check length
        if isinstance(v, dict):
            v = six.text_type(v)
        if wrap > 0:
            v = textwrap.fill(six.text_type(v), wrap)
        elif wrap < 0:
            raise ValueError(_("wrap argument should be a non-negative "
                               "integer"))
        # if value has a newline, add in multiple rows
        # e.g. fault with stacktrace
        if v and isinstance(v, six.string_types) and r'\n' in v:
            lines = v.strip().split(r'\n')
            col1 = k
            for line in lines:
                pt.add_row([col1, line])
                col1 = ''
        else:
            pt.add_row([k, v])

    if six.PY3:
        print(encodeutils.safe_encode(pt.get_string()).decode())
    else:
        print(encodeutils.safe_encode(pt.get_string()))
Ejemplo n.º 46
0
    def _get_keystone_auth(self, session, auth_url, **kwargs):
        # FIXME(dhu): this code should come from keystoneclient

        # discover the supported keystone versions using the given url
        (v2_auth_url, v3_auth_url) = self._discover_auth_versions(
            session=session,
            auth_url=auth_url)

        # Determine which authentication plugin to use. First inspect the
        # auth_url to see the supported version. If both v3 and v2 are
        # supported, then use the highest version if possible.
        auth = None
        if v3_auth_url and v2_auth_url:
            user_domain_name = kwargs.get('user_domain_name', None)
            user_domain_id = kwargs.get('user_domain_id', None)
            project_domain_name = kwargs.get('project_domain_name', None)
            project_domain_id = kwargs.get('project_domain_id', None)

            # support both v2 and v3 auth. Use v3 if domain information is
            # provided.
            if (user_domain_name or user_domain_id or project_domain_name or
                    project_domain_id):
                auth = self._get_keystone_v3_auth(v3_auth_url, **kwargs)
            else:
                auth = self._get_keystone_v2_auth(v2_auth_url, **kwargs)
        elif v3_auth_url:
            # support only v3
            auth = self._get_keystone_v3_auth(v3_auth_url, **kwargs)
        elif v2_auth_url:
            # support only v2
            auth = self._get_keystone_v2_auth(v2_auth_url, **kwargs)
        else:
            msg = _('Unable to determine the Keystone version '
                    'to authenticate with using the given '
                    'auth_url.')
            raise exc.CommandError(msg)

        return auth
Ejemplo n.º 47
0
    def take_action(self, parsed_args):
        self.log.debug("take_action(%s)" % parsed_args)
        baremetal_client = self.app.client_manager.baremetal

        if parsed_args.boot_index < 0:
            raise exc.CommandError(
                _('Expected non-negative --boot-index, got %s') %
                parsed_args.boot_index)

        field_list = [
            'extra', 'volume_type', 'properties', 'boot_index', 'node_uuid',
            'volume_id', 'uuid'
        ]
        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, 'properties')
        fields = utils.args_array_to_dict(fields, 'extra')
        volume_target = baremetal_client.volume_target.create(**fields)

        data = dict([(f, getattr(volume_target, f, ''))
                     for f in res_fields.VOLUME_TARGET_DETAILED_RESOURCE.fields
                     ])
        return self.dict2columns(data)
Ejemplo n.º 48
0
    def __init__(self, field_ids, sort_excluded=None):
        """Create a Resource object

        :param field_ids:  A list of strings that the Resource object will
                           contain.  Each string must match an existing key in
                           FIELDS.
        :param sort_excluded: Optional. A list of strings that will not be used
                              for sorting.  Must be a subset of 'field_ids'.

        :raises: ValueError if sort_excluded contains value not in field_ids
        """
        self._fields = tuple(field_ids)
        self._labels = tuple([self.FIELDS[x] for x in field_ids])
        if sort_excluded is None:
            sort_excluded = []
        not_existing = set(sort_excluded) - set(field_ids)
        if not_existing:
            raise ValueError(
                _("sort_excluded specified with value not contained in "
                  "field_ids.  Unknown value(s): %s") % ','.join(not_existing))
        self._sort_fields = tuple(
            [x for x in field_ids if x not in sort_excluded])
        self._sort_labels = tuple([self.FIELDS[x] for x in self._sort_fields])
Ejemplo n.º 49
0
def make_client(instance):
    """Returns a baremetal service client."""
    try:
        baremetal_client = ironic_client.get_client_class(
            instance._api_version[API_NAME])
    except Exception:
        msg = (_("Invalid %(api_name)s client version '%(ver)s'. Must be one "
                 "of %(supported_ver)s") % {
                     'api_name': API_NAME,
                     'ver': instance._api_version[API_NAME],
                     'supported_ver': ", ".join(sorted(API_VERSIONS))
                 })
        raise exceptions.UnsupportedVersion(msg)
    LOG.debug('Instantiating baremetal client: %s', baremetal_client)

    client = baremetal_client(
        os_ironic_api_version=instance._api_version[API_NAME],
        session=instance.session,
        region_name=instance._region_name,
        endpoint=instance.auth_ref.auth_url,
    )

    return client
Ejemplo n.º 50
0
    def get_connection_params(endpoint, **kwargs):
        parts = urlparse.urlparse(endpoint)

        path = _trim_endpoint_api_version(parts.path)

        _args = (parts.hostname, parts.port, path)
        _kwargs = {
            'timeout':
            (float(kwargs.get('timeout')) if kwargs.get('timeout') else 600)
        }

        if parts.scheme == 'https':
            _class = VerifiedHTTPSConnection
            _kwargs['ca_file'] = kwargs.get('ca_file', None)
            _kwargs['cert_file'] = kwargs.get('cert_file', None)
            _kwargs['key_file'] = kwargs.get('key_file', None)
            _kwargs['insecure'] = kwargs.get('insecure', False)
        elif parts.scheme == 'http':
            _class = six.moves.http_client.HTTPConnection
        else:
            msg = _('Unsupported scheme: %s') % parts.scheme
            raise exc.EndpointException(msg)

        return (_class, _args, _kwargs)
Ejemplo n.º 51
0
    def find(self, base_url=None, **kwargs):
        """Find a single item with attributes matching ``**kwargs``.

        :param base_url: if provided, the generated URL will be appended to it
        """
        kwargs = self._filter_kwargs(kwargs)

        rl = self._list(
            '%(base_url)s%(query)s' % {
                'base_url': self.build_url(base_url=base_url, **kwargs),
                'query': '?%s' % urlparse.urlencode(kwargs) if kwargs else '',
            }, self.collection_key)
        num = len(rl)

        if num == 0:
            msg = _("No %(name)s matching %(args)s.") % {
                'name': self.resource_class.__name__,
                'args': kwargs
            }
            raise exceptions.NotFound(msg)
        elif num > 1:
            raise exceptions.NoUniqueMatch
        else:
            return rl[0]
Ejemplo n.º 52
0
def check_empty_arg(arg, arg_descriptor):
    if not arg.strip():
        raise exc.CommandError(_('%(arg)s cannot be empty or only have blank'
                                 ' spaces') % {'arg': arg_descriptor})
Ejemplo n.º 53
0
    def negotiate_version(self, conn, resp):
        """Negotiate the server version

        Assumption: Called after receiving a 406 error when doing a request.

        :param conn: A connection object
        :param resp: The response object from http request
        """
        def _query_server(conn):
            if (self.os_ironic_api_version
                    and not isinstance(self.os_ironic_api_version, list)
                    and self.os_ironic_api_version != 'latest'):
                base_version = ("/v%s" %
                                str(self.os_ironic_api_version).split('.')[0])
            else:
                base_version = API_VERSION
            return self._make_simple_request(conn, 'GET', base_version)

        version_overridden = False

        if (resp and hasattr(resp, 'request')
                and hasattr(resp.request, 'headers')):
            orig_hdr = resp.request.headers
            # Get the version of the client's last request and fallback
            # to the default for things like unit tests to not cause
            # migraines.
            req_api_ver = orig_hdr.get('X-OpenStack-Ironic-API-Version',
                                       self.os_ironic_api_version)
        else:
            req_api_ver = self.os_ironic_api_version
        if (resp and req_api_ver != self.os_ironic_api_version
                and self.api_version_select_state == 'negotiated'):
            # If we have a non-standard api version on the request,
            # but we think we've negotiated, then the call was overriden.
            # We should report the error with the called version
            requested_version = req_api_ver
            # And then we shouldn't save the newly negotiated
            # version of this negotiation because we have been
            # overridden a request.
            version_overridden = True
        else:
            requested_version = self.os_ironic_api_version

        if not resp:
            resp = _query_server(conn)
        if self.api_version_select_state not in API_VERSION_SELECTED_STATES:
            raise RuntimeError(
                _('Error: self.api_version_select_state should be one of the '
                  'values in: "%(valid)s" but had the value: "%(value)s"') % {
                      'valid': ', '.join(API_VERSION_SELECTED_STATES),
                      'value': self.api_version_select_state
                  })
        min_ver, max_ver = self._parse_version_headers(resp)
        # NOTE: servers before commit 32fb6e99 did not return version headers
        # on error, so we need to perform a GET to determine
        # the supported version range
        if not max_ver:
            LOG.debug('No version header in response, requesting from server')
            resp = _query_server(conn)
            min_ver, max_ver = self._parse_version_headers(resp)
        # Reset the maximum version that we permit
        if StrictVersion(max_ver) > StrictVersion(LATEST_VERSION):
            LOG.debug(
                "Remote API version %(max_ver)s is greater than the "
                "version supported by ironicclient. Maximum available "
                "version is %(client_ver)s", {
                    'max_ver': max_ver,
                    'client_ver': LATEST_VERSION
                })
            max_ver = LATEST_VERSION

        # If the user requested an explicit version or we have negotiated a
        # version and still failing then error now.  The server could
        # support the version requested but the requested operation may not
        # be supported by the requested version.
        # TODO(TheJulia): We should break this method into several parts,
        # such as a sanity check/error method.
        if ((self.api_version_select_state == 'user'
             and not self._must_negotiate_version())
                or (self.api_version_select_state == 'negotiated'
                    and version_overridden)):
            raise exc.UnsupportedVersion(
                textwrap.fill(
                    _("Requested API version %(req)s is not supported by the "
                      "server, client, or the requested operation is not "
                      "supported by the requested version. "
                      "Supported version range is %(min)s to "
                      "%(max)s") % {
                          'req': requested_version,
                          'min': min_ver,
                          'max': max_ver
                      }))
        if (self.api_version_select_state == 'negotiated'):
            raise exc.UnsupportedVersion(
                textwrap.fill(
                    _("No API version was specified or the requested operation "
                      "was not supported by the client's negotiated API version "
                      "%(req)s.  Supported version range is: %(min)s to %(max)s"
                      ) % {
                          'req': requested_version,
                          'min': min_ver,
                          'max': max_ver
                      }))

        if isinstance(requested_version, six.string_types):
            if requested_version == 'latest':
                negotiated_ver = max_ver
            else:
                negotiated_ver = str(
                    min(StrictVersion(requested_version),
                        StrictVersion(max_ver)))

        elif isinstance(requested_version, list):
            if 'latest' in requested_version:
                raise ValueError(
                    textwrap.fill(
                        _("The 'latest' API version can not be requested "
                          "in a list of versions. Please explicitly request "
                          "'latest' or request only versios between "
                          "%(min)s to %(max)s") % {
                              'min': min_ver,
                              'max': max_ver
                          }))

            versions = []
            for version in requested_version:
                if min_ver <= StrictVersion(version) <= max_ver:
                    versions.append(StrictVersion(version))
            if versions:
                negotiated_ver = str(max(versions))
            else:
                raise exc.UnsupportedVersion(
                    textwrap.fill(
                        _("Requested API version specified and the requested "
                          "operation was not supported by the client's "
                          "requested API version %(req)s.  Supported "
                          "version range is: %(min)s to %(max)s") % {
                              'req': requested_version,
                              'min': min_ver,
                              'max': max_ver
                          }))

        else:
            raise ValueError(
                textwrap.fill(
                    _("Requested API version %(req)s type is unsupported. "
                      "Valid types are Strings such as '1.1', 'latest' "
                      "or a list of string values representing API versions.")
                    % {'req': requested_version}))

        if StrictVersion(negotiated_ver) < StrictVersion(min_ver):
            negotiated_ver = min_ver
        # server handles microversions, but doesn't support
        # the requested version, so try a negotiated version
        self.api_version_select_state = 'negotiated'
        self.os_ironic_api_version = negotiated_ver
        LOG.debug('Negotiated API version is %s', negotiated_ver)

        # Cache the negotiated version for this server
        # TODO(vdrok): get rid of self.endpoint attribute in Stein
        endpoint_override = (getattr(self, 'endpoint_override', None)
                             or getattr(self, 'endpoint', None))
        host, port = get_server(endpoint_override)
        filecache.save_data(host=host, port=port, data=negotiated_ver)

        return negotiated_ver
Ejemplo n.º 54
0
    def _http_request(self, url, method, **kwargs):
        """Send an http request with the specified characteristics.

        Wrapper around request.Session.request to handle tasks such
        as setting headers and error handling.
        """
        # NOTE(TheJulia): self.os_ironic_api_version is reset in
        # the self.negotiate_version() call if negotiation occurs.
        if self.os_ironic_api_version and self._must_negotiate_version():
            self.negotiate_version(self.session, None)
        # Copy the kwargs so we can reuse the original in case of redirects
        kwargs['headers'] = copy.deepcopy(kwargs.get('headers', {}))
        kwargs['headers'].setdefault('User-Agent', USER_AGENT)
        if self.os_ironic_api_version:
            kwargs['headers'].setdefault('X-OpenStack-Ironic-API-Version',
                                         self.os_ironic_api_version)
        if self.auth_token:
            kwargs['headers'].setdefault('X-Auth-Token', self.auth_token)

        self.log_curl_request(method, url, kwargs)

        # NOTE(aarefiev): This is for backwards compatibility, request
        # expected body in 'data' field, previously we used httplib,
        # which expected 'body' field.
        body = kwargs.pop('body', None)
        if body:
            kwargs['data'] = body

        conn_url = self._make_connection_url(url)
        try:
            resp = self.session.request(method, conn_url, **kwargs)

            # TODO(deva): implement graceful client downgrade when connecting
            # to servers that did not support microversions. Details here:
            # https://specs.openstack.org/openstack/ironic-specs/specs/kilo-implemented/api-microversions.html#use-case-3b-new-client-communicating-with-a-old-ironic-user-specified  # noqa

            if resp.status_code == http_client.NOT_ACCEPTABLE:
                negotiated_ver = self.negotiate_version(self.session, resp)
                kwargs['headers']['X-OpenStack-Ironic-API-Version'] = (
                    negotiated_ver)
                return self._http_request(url, method, **kwargs)

        except requests.exceptions.RequestException as e:
            message = (_("Error has occurred while handling "
                         "request for %(url)s: %(e)s") %
                       dict(url=conn_url, e=e))
            # NOTE(aarefiev): not valid request(invalid url, missing schema,
            # and so on), retrying is not needed.
            if isinstance(e, ValueError):
                raise exc.ValidationError(message)

            raise exc.ConnectionRefused(message)

        body_str = None
        if resp.headers.get('Content-Type') == 'application/octet-stream':
            body_iter = resp.iter_content(chunk_size=CHUNKSIZE)
            self.log_http_response(resp)
        else:
            # Read body into string if it isn't obviously image data
            body_str = resp.text
            self.log_http_response(resp, body_str)
            body_iter = six.StringIO(body_str)

        if resp.status_code >= http_client.BAD_REQUEST:
            error_json = _extract_error_json(body_str)
            raise exc.from_response(resp, error_json.get('error_message'),
                                    error_json.get('debuginfo'), method, url)
        elif resp.status_code in (http_client.MOVED_PERMANENTLY,
                                  http_client.FOUND, http_client.USE_PROXY):
            # Redirected. Reissue the request to the new location.
            return self._http_request(resp['location'], method, **kwargs)
        elif resp.status_code == http_client.MULTIPLE_CHOICES:
            raise exc.from_response(resp, method=method, url=url)

        return resp, body_iter
Ejemplo n.º 55
0
    def negotiate_version(self, conn, resp):
        """Negotiate the server version

        Assumption: Called after receiving a 406 error when doing a request.

        param conn: A connection object
        param resp: The response object from http request
        """
        if self.api_version_select_state not in API_VERSION_SELECTED_STATES:
            raise RuntimeError(
                _('Error: self.api_version_select_state should be one of the '
                  'values in: "%(valid)s" but had the value: "%(value)s"') %
                {'valid': ', '.join(API_VERSION_SELECTED_STATES),
                 'value': self.api_version_select_state})
        min_ver, max_ver = self._parse_version_headers(resp)
        # NOTE: servers before commit 32fb6e99 did not return version headers
        # on error, so we need to perform a GET to determine
        # the supported version range
        if not max_ver:
            LOG.debug('No version header in response, requesting from server')
            if self.os_ironic_api_version:
                base_version = ("/v%s" %
                                str(self.os_ironic_api_version).split('.')[0])
            else:
                base_version = API_VERSION
            resp = self._make_simple_request(conn, 'GET', base_version)
            min_ver, max_ver = self._parse_version_headers(resp)
        # If the user requested an explicit version or we have negotiated a
        # version and still failing then error now.  The server could
        # support the version requested but the requested operation may not
        # be supported by the requested version.
        if self.api_version_select_state == 'user':
            raise exc.UnsupportedVersion(textwrap.fill(
                _("Requested API version %(req)s is not supported by the "
                  "server or the requested operation is not supported by the "
                  "requested version.  Supported version range is %(min)s to "
                  "%(max)s")
                % {'req': self.os_ironic_api_version,
                   'min': min_ver, 'max': max_ver}))
        if self.api_version_select_state == 'negotiated':
            raise exc.UnsupportedVersion(textwrap.fill(
                _("No API version was specified and the requested operation "
                  "was not supported by the client's negotiated API version "
                  "%(req)s.  Supported version range is: %(min)s to %(max)s")
                % {'req': self.os_ironic_api_version,
                   'min': min_ver, 'max': max_ver}))

        negotiated_ver = str(min(StrictVersion(self.os_ironic_api_version),
                                 StrictVersion(max_ver)))
        if negotiated_ver < min_ver:
            negotiated_ver = min_ver
        # server handles microversions, but doesn't support
        # the requested version, so try a negotiated version
        self.api_version_select_state = 'negotiated'
        self.os_ironic_api_version = negotiated_ver
        LOG.debug('Negotiated API version is %s', negotiated_ver)

        # Cache the negotiated version for this server
        host, port = get_server(self.endpoint)
        filecache.save_data(host=host, port=port, data=negotiated_ver)

        return negotiated_ver
Ejemplo n.º 56
0
 def __init__(self, missing):
     self.missing = missing
     msg = _("Missing arguments: %s") % ", ".join(missing)
     super(MissingArgs, self).__init__(msg)
Ejemplo n.º 57
0
def print_list(objs, fields, formatters=None, sortby_index=0,
               mixed_case_fields=None, field_labels=None, json_flag=False):
    """Print a list of objects or dict as a table, one row per object or dict.

    :param objs: iterable of :class:`Resource`
    :param fields: attributes that correspond to columns, in order
    :param formatters: `dict` of callables for field formatting
    :param sortby_index: index of the field for sorting table rows
    :param mixed_case_fields: fields corresponding to object attributes that
        have mixed case names (e.g., 'serverId')
    :param field_labels: Labels to use in the heading of the table, default to
        fields.
    :param json_flag: print the list as JSON instead of table
    """
    def _get_name_and_data(field):
        if field in formatters:
            # The value of the field has to be modified.
            # For example, it can be used to add extra fields.
            return (field, formatters[field](o))

        field_name = field.replace(' ', '_')
        if field not in mixed_case_fields:
            field_name = field.lower()
        if isinstance(o, dict):
            data = o.get(field_name, '')
        else:
            data = getattr(o, field_name, '')
        return (field_name, data)

    formatters = formatters or {}
    mixed_case_fields = mixed_case_fields or []
    field_labels = field_labels or fields
    if len(field_labels) != len(fields):
        raise ValueError(_("Field labels list %(labels)s has different number "
                           "of elements than fields list %(fields)s"),
                         {'labels': field_labels, 'fields': fields})

    if sortby_index is None:
        kwargs = {}
    else:
        kwargs = {'sortby': field_labels[sortby_index]}
    pt = prettytable.PrettyTable(field_labels)
    pt.align = 'l'

    json_array = []

    for o in objs:
        row = []
        for field in fields:
            row.append(_get_name_and_data(field))
        if json_flag:
            json_array.append(dict(row))
        else:
            pt.add_row([r[1] for r in row])

    if json_flag:
        print(json.dumps(json_array, indent=4, separators=(',', ': ')))
    elif six.PY3:
        print(encodeutils.safe_encode(pt.get_string(**kwargs)).decode())
    else:
        print(encodeutils.safe_encode(pt.get_string(**kwargs)))
Ejemplo n.º 58
0
    def wait_for_provision_state(self,
                                 node_ident,
                                 expected_state,
                                 timeout=0,
                                 poll_interval=_DEFAULT_POLL_INTERVAL,
                                 poll_delay_function=None,
                                 fail_on_unexpected_state=True):
        """Helper function to wait for a node to reach a given state.

        Polls Ironic API in a loop until node gets to a requested state.

        Fails in the following cases:
        * Timeout (if provided) is reached
        * Node's last_error gets set to a non-empty value
        * Unexpected stable state is reached and fail_on_unexpected_state is on
        * Error state is reached (if it's not equal to expected_state)

        :param node_ident: node UUID or name
        :param expected_state: expected final provision state
        :param timeout: timeout in seconds, no timeout if 0
        :param poll_interval: interval in seconds between 2 poll
        :param poll_delay_function: function to use to wait between polls
            (defaults to time.sleep). Should take one argument - delay time
            in seconds. Any exceptions raised inside it will abort the wait.
        :param fail_on_unexpected_state: whether to fail if the nodes
            reaches a different stable state.
        :raises: StateTransitionFailed if node reached an error state
        :raises: StateTransitionTimeout on timeout
        """
        if not isinstance(timeout, (int, float)) or timeout < 0:
            raise ValueError(_('Timeout must be a non-negative number'))

        threshold = time.time() + timeout
        expected_state = expected_state.lower()
        poll_delay_function = (time.sleep if poll_delay_function is None else
                               poll_delay_function)
        if not callable(poll_delay_function):
            raise TypeError(_('poll_delay_function must be callable'))

        # TODO(dtantsur): use version negotiation to request API 1.8 and use
        # the "fields" argument to reduce amount of data sent.
        while not timeout or time.time() < threshold:
            node = self.get(node_ident)
            if node.provision_state == expected_state:
                LOG.debug('Node %(node)s reached provision state %(state)s', {
                    'node': node_ident,
                    'state': expected_state
                })
                return

            # Note that if expected_state == 'error' we still succeed
            if (node.last_error or node.provision_state == 'error'
                    or node.provision_state.endswith(' failed')):
                raise exc.StateTransitionFailed(
                    _('Node %(node)s failed to reach state %(state)s. '
                      'It\'s in state %(actual)s, and has error: %(error)s') %
                    {
                        'node': node_ident,
                        'state': expected_state,
                        'actual': node.provision_state,
                        'error': node.last_error
                    })

            if fail_on_unexpected_state and not node.target_provision_state:
                raise exc.StateTransitionFailed(
                    _('Node %(node)s failed to reach state %(state)s. '
                      'It\'s in unexpected stable state %(actual)s') % {
                          'node': node_ident,
                          'state': expected_state,
                          'actual': node.provision_state
                      })

            LOG.debug(
                'Still waiting for node %(node)s to reach state '
                '%(state)s, the current state is %(actual)s', {
                    'node': node_ident,
                    'state': expected_state,
                    'actual': node.provision_state
                })
            poll_delay_function(poll_interval)

        raise exc.StateTransitionTimeout(
            _('Node %(node)s failed to reach state %(state)s in '
              '%(timeout)s seconds') % {
                  'node': node_ident,
                  'state': expected_state,
                  'timeout': timeout
              })
Ejemplo n.º 59
0
    def list(self,
             associated=None,
             maintenance=None,
             marker=None,
             limit=None,
             detail=False,
             sort_key=None,
             sort_dir=None,
             fields=None,
             provision_state=None,
             driver=None,
             resource_class=None,
             chassis=None):
        """Retrieve a list of nodes.

        :param associated: Optional. Either a Boolean or a string
                           representation of a Boolean that indicates whether
                           to return a list of associated (True or "True") or
                           unassociated (False or "False") nodes.
        :param maintenance: Optional. Either a Boolean or a string
                            representation of a Boolean that indicates whether
                            to return nodes in maintenance mode (True or
                            "True"), or not in maintenance mode (False or
                            "False").
        :param provision_state: Optional. String value to get only nodes in
                                that provision state.
        :param marker: Optional, the UUID of a node, eg the last
                       node 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 nodes to return.
            2) limit == 0, return the entire list of nodes.
            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 detail: Optional, boolean whether to return detailed information
                       about nodes.

        :param sort_key: Optional, field used for sorting.

        :param sort_dir: Optional, direction of sorting, either 'asc' (the
                         default) or 'desc'.

        :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 driver: Optional. String value to get only nodes using that
                       driver.

        :param resource_class: Optional. String value to get only nodes
                               with the given resource class set.

        :param chassis: Optional, the UUID of a chassis. Used to get only
                        nodes of this chassis.

        :returns: A list of nodes.

        """
        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)
        if associated is not None:
            filters.append('associated=%s' % associated)
        if maintenance is not None:
            filters.append('maintenance=%s' % maintenance)
        if provision_state is not None:
            filters.append('provision_state=%s' % provision_state)
        if driver is not None:
            filters.append('driver=%s' % driver)
        if resource_class is not None:
            filters.append('resource_class=%s' % resource_class)
        if chassis is not None:
            filters.append('chassis_uuid=%s' % chassis)

        path = ''
        if detail:
            path += 'detail'
        if filters:
            path += '?' + '&'.join(filters)

        if limit is None:
            return self._list(self._path(path), "nodes")
        else:
            return self._list_pagination(self._path(path),
                                         "nodes",
                                         limit=limit)
Ejemplo n.º 60
0
    def list_volume_targets(self,
                            node_id,
                            marker=None,
                            limit=None,
                            sort_key=None,
                            sort_dir=None,
                            detail=False,
                            fields=None):
        """List all the volume targets for a given node.

        :param node_id: Name or UUID of the node.
        :param marker: Optional, the UUID of a volume target, eg the last
                       volume target 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 volume targets to return.
            2) limit == 0, return the entire list of volume targets.
            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 volume targets.

        :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 volume targets.

        """
        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=marker,
                                       limit=limit,
                                       sort_key=sort_key,
                                       sort_dir=sort_dir,
                                       fields=fields,
                                       detail=detail)

        path = "%s/volume/targets" % node_id
        if filters:
            path += '?' + '&'.join(filters)

        if limit is None:
            return self._list(self._path(path),
                              response_key="targets",
                              obj_class=volume_target.VolumeTarget)
        else:
            return self._list_pagination(self._path(path),
                                         response_key="targets",
                                         limit=limit,
                                         obj_class=volume_target.VolumeTarget)