Remove the ironic command
The ironic command is dead, long live the OSC baremetal command. Also removed the json_respose tests, which seemed ot be geared to checking json returned data on the command line. For ironic these tests seemed to be very explicit for our CLI where as OSC should be covering the return data handling, the only real end effect is field level expectation enforcement in the functional test... which seems excessive and un-needed. Story: 1726605 Task: 11974 Depends-on: https://review.opendev.org/677183 Change-Id: I3e3ae416a65d8e01956434ba51bd3187ab001f80
This commit is contained in:
parent
e3b0791941
commit
f62758cda5
47
README.rst
47
README.rst
@ -15,8 +15,7 @@ This is a client for the OpenStack `Bare Metal API
|
||||
<https://docs.openstack.org/api-ref/baremetal/>`_. It provides:
|
||||
|
||||
* a Python API: the ``ironicclient`` module, and
|
||||
* two command-line interfaces: ``openstack baremetal`` and ``ironic``
|
||||
(deprecated, please use ``openstack baremetal``).
|
||||
* a command-line interfaces: ``openstack baremetal``
|
||||
|
||||
Development takes place via the usual OpenStack processes as outlined in the
|
||||
`developer guide <https://docs.openstack.org/infra/manual/developers.html>`_.
|
||||
@ -83,47 +82,3 @@ For more information about the ``openstack baremetal`` command and
|
||||
the subcommands available, run::
|
||||
|
||||
$ openstack help baremetal
|
||||
|
||||
``ironic`` CLI (deprecated)
|
||||
---------------------------
|
||||
|
||||
This is deprecated and will be removed in the S* release. Please use the
|
||||
``openstack baremetal`` CLI instead.
|
||||
|
||||
This package will install the ``ironic`` command line interface that you
|
||||
can use to interact with the ``ironic`` API.
|
||||
|
||||
In order to use the ``ironic`` CLI you'll need to provide your OpenStack
|
||||
tenant, username, password and authentication endpoint. You can do this with
|
||||
the ``--os-tenant-name``, ``--os-username``, ``--os-password`` and
|
||||
``--os-auth-url`` parameters, though it may be easier to set them
|
||||
as environment variables::
|
||||
|
||||
$ export OS_PROJECT_NAME=project
|
||||
$ export OS_USERNAME=user
|
||||
$ export OS_PASSWORD=pass
|
||||
$ export OS_AUTH_URL=http://auth.example.com:5000/v2.0
|
||||
|
||||
To use a specific Ironic API endpoint::
|
||||
|
||||
$ export IRONIC_URL=http://ironic.example.com:6385
|
||||
|
||||
An example of creating a basic node with the ``ipmi`` driver::
|
||||
|
||||
$ ironic node-create -d ipmi
|
||||
|
||||
An example of creating a port on a node::
|
||||
|
||||
$ ironic port-create -a AA:BB:CC:DD:EE:FF -n nodeUUID
|
||||
|
||||
An example of updating driver properties for a node::
|
||||
|
||||
$ ironic node-update nodeUUID add driver_info/ipmi_address=<IPaddress>
|
||||
$ ironic node-update nodeUUID add driver_info/ipmi_username=<username>
|
||||
$ ironic node-update nodeUUID add driver_info/ipmi_password=<password>
|
||||
|
||||
|
||||
For more information about the ``ironic`` command and the subcommands
|
||||
available, run::
|
||||
|
||||
$ ironic help
|
||||
|
@ -5,4 +5,3 @@ python-ironicclient User Documentation
|
||||
.. toctree::
|
||||
|
||||
osc_plugin_cli
|
||||
ironic_client
|
||||
|
@ -1,100 +0,0 @@
|
||||
=======================================
|
||||
``ironic`` Command-Line Interface (CLI)
|
||||
=======================================
|
||||
|
||||
.. program:: ironic
|
||||
.. highlight:: bash
|
||||
|
||||
SYNOPSIS
|
||||
========
|
||||
|
||||
:program:`ironic` [options] <command> [command-options]
|
||||
|
||||
:program:`ironic help`
|
||||
|
||||
:program:`ironic help` <command>
|
||||
|
||||
|
||||
DESCRIPTION
|
||||
===========
|
||||
|
||||
.. WARNING::
|
||||
|
||||
The :program:`ironic` command-line interface is deprecated; no new features
|
||||
will be added. This CLI will be removed in the S* release. The `openstack
|
||||
baremetal <osc_plugin_cli>`_ command-line interface should be used instead.
|
||||
|
||||
The :program:`ironic` command-line interface (CLI) interacts with the
|
||||
OpenStack Bare Metal Service (Ironic).
|
||||
|
||||
In order to use the CLI, you must provide your OpenStack username, password,
|
||||
project (historically called tenant), and auth endpoint. You can use
|
||||
configuration options ``--os-username``, ``--os-password``,
|
||||
``--os-tenant-id`` (or ``--os-tenant-name``),
|
||||
and ``--os-auth-url``, or set the corresponding
|
||||
environment variables::
|
||||
|
||||
$ export OS_USERNAME=user
|
||||
$ export OS_PASSWORD=password
|
||||
$ export OS_PROJECT_ID=b363706f891f48019483f8bd6503c54b # or OS_PROJECT_NAME
|
||||
$ export OS_PROJECT_NAME=project # or OS_PROJECT_ID
|
||||
$ export OS_AUTH_URL=http://auth.example.com:5000/v2.0
|
||||
|
||||
The command-line tool will attempt to reauthenticate using the provided
|
||||
credentials for every request. You can override this behavior by manually
|
||||
supplying an auth token using ``--ironic-url`` and
|
||||
``--os-auth-token``, or by setting the corresponding environment
|
||||
variables::
|
||||
|
||||
$ export IRONIC_URL=http://ironic.example.org:6385/
|
||||
$ export OS_AUTH_TOKEN=3bcc3d3a03f44e3d8377f9247b0ad155
|
||||
|
||||
Since Keystone can return multiple regions in the Service Catalog, you can
|
||||
specify the one you want with ``--os-region-name`` or set the following
|
||||
environment variable. (It defaults to the first in the list returned.)
|
||||
::
|
||||
|
||||
export OS_REGION_NAME=region
|
||||
|
||||
Ironic CLI supports bash completion. The command-line tool can automatically
|
||||
fill partially typed commands. To use this feature, source the below file
|
||||
(available at
|
||||
https://opendev.org/openstack/python-ironicclient/src/branch/master/tools/ironic.bash_completion)
|
||||
to your terminal and then bash completion should work::
|
||||
|
||||
$ . ironic.bash_completion
|
||||
|
||||
To avoid doing this every time, add this to your ``.bashrc`` or copy the
|
||||
ironic.bash_completion file to the default bash completion scripts directory
|
||||
on your linux distribution.
|
||||
|
||||
OPTIONS
|
||||
=======
|
||||
|
||||
To get a list of available (sub)commands and options, run::
|
||||
|
||||
$ ironic help
|
||||
|
||||
To get usage and options of a command, run::
|
||||
|
||||
$ ironic help <command>
|
||||
|
||||
|
||||
EXAMPLES
|
||||
========
|
||||
|
||||
Get information about the node-create command::
|
||||
|
||||
$ ironic help node-create
|
||||
|
||||
Get a list of available drivers::
|
||||
|
||||
$ ironic driver-list
|
||||
|
||||
Enroll a node with the ``ipmi`` driver, specifying the IPMI address::
|
||||
|
||||
$ ironic node-create -d ipmi -i ipmi_address=1.2.3.4
|
||||
|
||||
Get a list of nodes::
|
||||
|
||||
$ ironic node-list
|
@ -5,8 +5,7 @@ Python Bindings to the OpenStack Ironic API
|
||||
This is a client for the OpenStack `Ironic`_ API. It provides:
|
||||
|
||||
* a Python API: the ``ironicclient`` module, and
|
||||
* two command-line interfaces: ``openstack baremetal`` and ``ironic``
|
||||
(deprecated, please use ``openstack baremetal`` instead).
|
||||
* command-line interface: ``openstack baremetal``
|
||||
|
||||
Contents
|
||||
========
|
||||
|
@ -3,7 +3,7 @@ Creating the Bare Metal service resources from file
|
||||
===================================================
|
||||
|
||||
It is possible to create a set of resources using their descriptions in JSON
|
||||
or YAML format. It can be done in one of three ways:
|
||||
or YAML format. It can be done in one of two ways:
|
||||
|
||||
1. Using OpenStackClient bare metal plugin CLI's command ``openstack baremetal
|
||||
create``::
|
||||
@ -17,25 +17,7 @@ or YAML format. It can be done in one of three ways:
|
||||
<file> File (.yaml or .json) containing descriptions of the
|
||||
resources to create. Can be specified multiple times.
|
||||
|
||||
2. Using ironic CLI's ``ironic create`` command (deprecated, please use
|
||||
``openstack baremetal create`` instead)::
|
||||
|
||||
$ ironic help create
|
||||
The "ironic" CLI is deprecated and will be removed in the S* release.
|
||||
Please use the "openstack baremetal" CLI instead.
|
||||
usage: ironic create <file> [<file> ...]
|
||||
|
||||
Create baremetal resources (chassis, nodes, port groups and ports). The
|
||||
resources may be described in one or more JSON or YAML files. If any file
|
||||
cannot be validated, no resources are created. An attempt is made to
|
||||
create all the resources; those that could not be created are skipped
|
||||
(with a corresponding error message).
|
||||
|
||||
Positional arguments:
|
||||
<file> File (.yaml or .json) containing descriptions of the resources
|
||||
to create. Can be specified multiple times.
|
||||
|
||||
3. Programmatically using the Python API:
|
||||
2. Programmatically using the Python API:
|
||||
|
||||
.. autofunction:: ironicclient.v1.create_resources.create_resources
|
||||
:noindex:
|
||||
|
@ -1,296 +0,0 @@
|
||||
# Copyright 2012 Red Hat, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
# W0603: Using the global statement
|
||||
# W0621: Redefining name %s from outer scope
|
||||
# pylint: disable=W0603,W0621
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import getpass
|
||||
import inspect
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import textwrap
|
||||
|
||||
from oslo_utils import encodeutils
|
||||
from oslo_utils import strutils
|
||||
import prettytable
|
||||
import six
|
||||
from six import moves
|
||||
|
||||
from ironicclient.common.i18n import _
|
||||
|
||||
|
||||
class MissingArgs(Exception):
|
||||
"""Supplied arguments are not sufficient for calling a function."""
|
||||
def __init__(self, missing):
|
||||
self.missing = missing
|
||||
msg = _("Missing arguments: %s") % ", ".join(missing)
|
||||
super(MissingArgs, self).__init__(msg)
|
||||
|
||||
|
||||
def validate_args(fn, *args, **kwargs):
|
||||
"""Check that the supplied args are sufficient for calling a function.
|
||||
|
||||
>>> validate_args(lambda a: None)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
MissingArgs: Missing argument(s): a
|
||||
>>> validate_args(lambda a, b, c, d: None, 0, c=1)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
MissingArgs: Missing argument(s): b, d
|
||||
|
||||
:param fn: the function to check
|
||||
:param args: the positional arguments supplied
|
||||
:param kwargs: the keyword arguments supplied
|
||||
"""
|
||||
argspec = inspect.getargspec(fn)
|
||||
|
||||
num_defaults = len(argspec.defaults or [])
|
||||
required_args = argspec.args[:len(argspec.args) - num_defaults]
|
||||
|
||||
def isbound(method):
|
||||
return getattr(method, '__self__', None) is not None
|
||||
|
||||
if isbound(fn):
|
||||
required_args.pop(0)
|
||||
|
||||
missing = [arg for arg in required_args if arg not in kwargs]
|
||||
missing = missing[len(args):]
|
||||
if missing:
|
||||
raise MissingArgs(missing)
|
||||
|
||||
|
||||
def arg(*args, **kwargs):
|
||||
"""Decorator for CLI args.
|
||||
|
||||
Example:
|
||||
|
||||
>>> @arg("name", help="Name of the new entity")
|
||||
... def entity_create(args):
|
||||
... pass
|
||||
"""
|
||||
def _decorator(func):
|
||||
add_arg(func, *args, **kwargs)
|
||||
return func
|
||||
return _decorator
|
||||
|
||||
|
||||
def env(*args, **kwargs):
|
||||
"""Returns the first environment variable set.
|
||||
|
||||
If all are empty, defaults to '' or keyword arg `default`.
|
||||
"""
|
||||
for arg in args:
|
||||
value = os.environ.get(arg)
|
||||
if value:
|
||||
return value
|
||||
return kwargs.get('default', '')
|
||||
|
||||
|
||||
def add_arg(func, *args, **kwargs):
|
||||
"""Bind CLI arguments to a shell.py `do_foo` function."""
|
||||
|
||||
if not hasattr(func, 'arguments'):
|
||||
func.arguments = []
|
||||
|
||||
# NOTE(sirp): avoid dups that can occur when the module is shared across
|
||||
# tests.
|
||||
if (args, kwargs) not in func.arguments:
|
||||
# Because of the semantics of decorator composition if we just append
|
||||
# to the options list positional options will appear to be backwards.
|
||||
func.arguments.insert(0, (args, kwargs))
|
||||
|
||||
|
||||
def unauthenticated(func):
|
||||
"""Adds 'unauthenticated' attribute to decorated function.
|
||||
|
||||
Usage:
|
||||
|
||||
>>> @unauthenticated
|
||||
... def mymethod(f):
|
||||
... pass
|
||||
"""
|
||||
func.unauthenticated = True
|
||||
return func
|
||||
|
||||
|
||||
def isunauthenticated(func):
|
||||
"""Checks if the function does not require authentication.
|
||||
|
||||
Mark such functions with the `@unauthenticated` decorator.
|
||||
|
||||
:returns: bool
|
||||
"""
|
||||
return getattr(func, 'unauthenticated', False)
|
||||
|
||||
|
||||
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)))
|
||||
|
||||
|
||||
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()))
|
||||
|
||||
|
||||
def get_password(max_password_prompts=3):
|
||||
"""Read password from TTY."""
|
||||
verify = strutils.bool_from_string(env("OS_VERIFY_PASSWORD"))
|
||||
pw = None
|
||||
if hasattr(sys.stdin, "isatty") and sys.stdin.isatty():
|
||||
# Check for Ctrl-D
|
||||
try:
|
||||
for __ in moves.range(max_password_prompts):
|
||||
pw1 = getpass.getpass("OS Password: ")
|
||||
if verify:
|
||||
pw2 = getpass.getpass("Please verify: ")
|
||||
else:
|
||||
pw2 = pw1
|
||||
if pw1 == pw2 and pw1:
|
||||
pw = pw1
|
||||
break
|
||||
except EOFError:
|
||||
pass
|
||||
return pw
|
||||
|
||||
|
||||
def service_type(stype):
|
||||
"""Adds 'service_type' attribute to decorated function.
|
||||
|
||||
Usage:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@service_type('volume')
|
||||
def mymethod(f):
|
||||
...
|
||||
"""
|
||||
def inner(f):
|
||||
f.service_type = stype
|
||||
return f
|
||||
return inner
|
||||
|
||||
|
||||
def get_service_type(f):
|
||||
"""Retrieves service type from function."""
|
||||
return getattr(f, 'service_type', None)
|
||||
|
||||
|
||||
def pretty_choice_list(l):
|
||||
return ', '.join("'%s'" % i for i in l)
|
||||
|
||||
|
||||
def exit(msg=''):
|
||||
if msg:
|
||||
print(msg, file=sys.stderr)
|
||||
sys.exit(1)
|
@ -1,496 +0,0 @@
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Command-line interface to the OpenStack Bare Metal Provisioning API.
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import getpass
|
||||
import logging
|
||||
import os
|
||||
import pkgutil
|
||||
import re
|
||||
import sys
|
||||
|
||||
from keystoneauth1.loading import session as kasession
|
||||
from oslo_utils import encodeutils
|
||||
from oslo_utils import importutils
|
||||
import six
|
||||
|
||||
import ironicclient
|
||||
from ironicclient.common.apiclient import exceptions
|
||||
from ironicclient.common import cliutils
|
||||
from ironicclient.common import http
|
||||
from ironicclient.common.i18n import _
|
||||
from ironicclient.common import utils
|
||||
from ironicclient import exc
|
||||
|
||||
|
||||
LAST_KNOWN_API_VERSION = http.LAST_KNOWN_API_VERSION
|
||||
LATEST_VERSION = '1.{}'.format(LAST_KNOWN_API_VERSION)
|
||||
|
||||
|
||||
class IronicShell(object):
|
||||
|
||||
def get_base_parser(self):
|
||||
parser = argparse.ArgumentParser(
|
||||
prog='ironic',
|
||||
description=__doc__.strip(),
|
||||
epilog=_('See "ironic help COMMAND" '
|
||||
'for help on a specific command.'),
|
||||
add_help=False,
|
||||
formatter_class=HelpFormatter,
|
||||
)
|
||||
|
||||
# Register global Keystone args first so their defaults are respected.
|
||||
# See https://bugs.launchpad.net/python-ironicclient/+bug/1463581
|
||||
kasession.register_argparse_arguments(parser)
|
||||
|
||||
# Global arguments
|
||||
parser.add_argument('-h', '--help',
|
||||
action='store_true',
|
||||
help=argparse.SUPPRESS,
|
||||
)
|
||||
|
||||
parser.add_argument('--version',
|
||||
action='version',
|
||||
version=ironicclient.__version__)
|
||||
|
||||
parser.add_argument('--debug',
|
||||
default=bool(cliutils.env('IRONICCLIENT_DEBUG')),
|
||||
action='store_true',
|
||||
help=_('Defaults to env[IRONICCLIENT_DEBUG]'))
|
||||
|
||||
parser.add_argument('--json',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help=_('Print JSON response without formatting.'))
|
||||
|
||||
parser.add_argument('-v', '--verbose',
|
||||
default=False, action="store_true",
|
||||
help=_('Print more verbose output'))
|
||||
|
||||
# for backward compatibility only
|
||||
parser.add_argument('--cert-file',
|
||||
dest='os_cert',
|
||||
help=_('DEPRECATED! Use --os-cert.'))
|
||||
|
||||
# for backward compatibility only
|
||||
parser.add_argument('--key-file',
|
||||
dest='os_key',
|
||||
help=_('DEPRECATED! Use --os-key.'))
|
||||
|
||||
# for backward compatibility only
|
||||
parser.add_argument('--ca-file',
|
||||
dest='os_cacert',
|
||||
help=_('DEPRECATED! Use --os-cacert.'))
|
||||
|
||||
parser.add_argument('--os-username',
|
||||
dest='username',
|
||||
default=cliutils.env('OS_USERNAME'),
|
||||
help=_('Defaults to env[OS_USERNAME]'))
|
||||
|
||||
parser.add_argument('--os_username',
|
||||
dest='username',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-password',
|
||||
dest='password',
|
||||
default=cliutils.env('OS_PASSWORD'),
|
||||
help=_('Defaults to env[OS_PASSWORD]'))
|
||||
|
||||
parser.add_argument('--os_password',
|
||||
dest='password',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-tenant-id',
|
||||
dest='tenant_id',
|
||||
default=cliutils.env('OS_TENANT_ID'),
|
||||
help=_('Defaults to env[OS_TENANT_ID]'))
|
||||
|
||||
parser.add_argument('--os_tenant_id',
|
||||
dest='tenant_id',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-tenant-name',
|
||||
dest='tenant_name',
|
||||
default=cliutils.env('OS_TENANT_NAME'),
|
||||
help=_('Defaults to env[OS_TENANT_NAME]'))
|
||||
|
||||
parser.add_argument('--os_tenant_name',
|
||||
dest='tenant_name',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-auth-url',
|
||||
dest='auth_url',
|
||||
default=cliutils.env('OS_AUTH_URL'),
|
||||
help=_('Defaults to env[OS_AUTH_URL]'))
|
||||
|
||||
parser.add_argument('--os_auth_url',
|
||||
dest='auth_url',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-region-name',
|
||||
dest='region_name',
|
||||
default=cliutils.env('OS_REGION_NAME'),
|
||||
help=_('Defaults to env[OS_REGION_NAME]'))
|
||||
|
||||
parser.add_argument('--os_region_name',
|
||||
dest='region_name',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-auth-token',
|
||||
dest='token',
|
||||
default=cliutils.env('OS_AUTH_TOKEN'),
|
||||
help=_('Defaults to env[OS_AUTH_TOKEN]'))
|
||||
|
||||
parser.add_argument('--os_auth_token',
|
||||
dest='token',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--ironic-url',
|
||||
dest='endpoint',
|
||||
default=cliutils.env('IRONIC_URL'),
|
||||
help=_('Defaults to env[IRONIC_URL]'))
|
||||
|
||||
parser.add_argument('--ironic_url',
|
||||
dest='endpoint',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--ironic-api-version',
|
||||
default=cliutils.env('IRONIC_API_VERSION',
|
||||
default="latest"),
|
||||
help=_('Accepts 1.x (where "x" is microversion), '
|
||||
'1 or "latest". Defaults to '
|
||||
'env[IRONIC_API_VERSION] or "latest".'))
|
||||
|
||||
parser.add_argument('--ironic_api_version',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-service-type',
|
||||
dest='service_type',
|
||||
default=cliutils.env('OS_SERVICE_TYPE'),
|
||||
help=_('Defaults to env[OS_SERVICE_TYPE] or '
|
||||
'"baremetal"'))
|
||||
|
||||
parser.add_argument('--os_service_type',
|
||||
dest='service_type',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-endpoint',
|
||||
dest='endpoint',
|
||||
default=cliutils.env('OS_SERVICE_ENDPOINT'),
|
||||
help=_('Specify an endpoint to use instead of '
|
||||
'retrieving one from the service catalog '
|
||||
'(via authentication). '
|
||||
'Defaults to env[OS_SERVICE_ENDPOINT].'))
|
||||
|
||||
parser.add_argument('--os_endpoint',
|
||||
dest='endpoint',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-endpoint-type',
|
||||
dest='interface',
|
||||
default=cliutils.env('OS_ENDPOINT_TYPE'),
|
||||
help=_('Defaults to env[OS_ENDPOINT_TYPE] or '
|
||||
'"publicURL"'))
|
||||
|
||||
parser.add_argument('--os_endpoint_type',
|
||||
dest='interface',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-user-domain-id',
|
||||
dest='user_domain_id',
|
||||
default=cliutils.env('OS_USER_DOMAIN_ID'),
|
||||
help=_('Defaults to env[OS_USER_DOMAIN_ID].'))
|
||||
|
||||
parser.add_argument('--os-user-domain-name',
|
||||
dest='user_domain_name',
|
||||
default=cliutils.env('OS_USER_DOMAIN_NAME'),
|
||||
help=_('Defaults to env[OS_USER_DOMAIN_NAME].'))
|
||||
|
||||
parser.add_argument('--os-project-id',
|
||||
dest='project_id',
|
||||
default=cliutils.env('OS_PROJECT_ID'),
|
||||
help=_('Another way to specify tenant ID. '
|
||||
'This option is mutually exclusive with '
|
||||
' --os-tenant-id. '
|
||||
'Defaults to env[OS_PROJECT_ID].'))
|
||||
|
||||
parser.add_argument('--os-project-name',
|
||||
dest='project_name',
|
||||
default=cliutils.env('OS_PROJECT_NAME'),
|
||||
help=_('Another way to specify tenant name. '
|
||||
'This option is mutually exclusive with '
|
||||
' --os-tenant-name. '
|
||||
'Defaults to env[OS_PROJECT_NAME].'))
|
||||
|
||||
parser.add_argument('--os-project-domain-id',
|
||||
dest='project_domain_id',
|
||||
default=cliutils.env('OS_PROJECT_DOMAIN_ID'),
|
||||
help=_('Defaults to env[OS_PROJECT_DOMAIN_ID].'))
|
||||
|
||||
parser.add_argument('--os-project-domain-name',
|
||||
dest='project_domain_name',
|
||||
default=cliutils.env('OS_PROJECT_DOMAIN_NAME'),
|
||||
help=_('Defaults to env[OS_PROJECT_DOMAIN_NAME].'))
|
||||
|
||||
msg = _('Maximum number of retries in case of conflict error '
|
||||
'(HTTP 409). Defaults to env[IRONIC_MAX_RETRIES] or %d. '
|
||||
'Use 0 to disable retrying.') % http.DEFAULT_MAX_RETRIES
|
||||
parser.add_argument('--max-retries', type=int, help=msg,
|
||||
default=cliutils.env(
|
||||
'IRONIC_MAX_RETRIES',
|
||||
default=str(http.DEFAULT_MAX_RETRIES)))
|
||||
|
||||
msg = _('Amount of time (in seconds) between retries '
|
||||
'in case of conflict error (HTTP 409). '
|
||||
'Defaults to env[IRONIC_RETRY_INTERVAL] '
|
||||
'or %d.') % http.DEFAULT_RETRY_INTERVAL
|
||||
parser.add_argument('--retry-interval', type=int, help=msg,
|
||||
default=cliutils.env(
|
||||
'IRONIC_RETRY_INTERVAL',
|
||||
default=str(http.DEFAULT_RETRY_INTERVAL)))
|
||||
|
||||
return parser
|
||||
|
||||
def get_available_major_versions(self):
|
||||
matcher = re.compile(r"^v[0-9]+$")
|
||||
submodules = pkgutil.iter_modules([os.path.dirname(__file__)])
|
||||
available_versions = [name[1:] for loader, name, ispkg in submodules
|
||||
if matcher.search(name)]
|
||||
|
||||
return available_versions
|
||||
|
||||
def get_subcommand_parser(self, version):
|
||||
parser = self.get_base_parser()
|
||||
|
||||
self.subcommands = {}
|
||||
subparsers = parser.add_subparsers(metavar='<subcommand>',
|
||||
dest='subparser_name')
|
||||
try:
|
||||
submodule = importutils.import_versioned_module('ironicclient',
|
||||
version, 'shell')
|
||||
except ImportError as e:
|
||||
msg = _("Invalid client version '%(version)s'. "
|
||||
"Major part must be one of: '%(major)s'") % {
|
||||
"version": version,
|
||||
"major": ", ".join(self.get_available_major_versions())}
|
||||
raise exceptions.UnsupportedVersion(
|
||||
_('%(message)s, error was: %(error)s') %
|
||||
{'message': msg, 'error': e})
|
||||
submodule.enhance_parser(parser, subparsers, self.subcommands)
|
||||
utils.define_commands_from_module(subparsers, self, self.subcommands)
|
||||
return parser
|
||||
|
||||
def _setup_debugging(self, debug):
|
||||
if debug:
|
||||
logging.basicConfig(
|
||||
format="%(levelname)s (%(module)s:%(lineno)d) %(message)s",
|
||||
level=logging.DEBUG)
|
||||
else:
|
||||
logging.basicConfig(
|
||||
format="%(levelname)s %(message)s",
|
||||
level=logging.CRITICAL)
|
||||
|
||||
def do_bash_completion(self):
|
||||
"""Prints all of the commands and options for bash-completion."""
|
||||
commands = set()
|
||||
options = set()
|
||||
for sc_str, sc in self.subcommands.items():
|
||||
commands.add(sc_str)
|
||||
for option in sc._optionals._option_string_actions.keys():
|
||||
options.add(option)
|
||||
|
||||
commands.remove('bash-completion')
|
||||
print(' '.join(commands | options))
|
||||
|
||||
def _check_version(self, api_version):
|
||||
"""Validate the supplied API (micro)version.
|
||||
|
||||
:param api_version: API version as a string ("1", "1.x" or "latest")
|
||||
:returns: tuple (major version, version string)
|
||||
"""
|
||||
if api_version in ('1', 'latest'):
|
||||
return (1, LATEST_VERSION)
|
||||
else:
|
||||
try:
|
||||
versions = tuple(int(i) for i in api_version.split('.'))
|
||||
except ValueError:
|
||||
versions = ()
|
||||
|
||||
if not versions or len(versions) > 2:
|
||||
msg = _("The requested API version %(ver)s is an unexpected "
|
||||
"format. Acceptable formats are 'X', 'X.Y', or the "
|
||||
"literal string 'latest'."
|
||||
) % {'ver': api_version}
|
||||
raise exc.CommandError(msg)
|
||||
|
||||
if versions == (1, 0):
|
||||
os_ironic_api_version = None
|
||||
else:
|
||||
os_ironic_api_version = api_version
|
||||
|
||||
api_major_version = versions[0]
|
||||
return (api_major_version, os_ironic_api_version)
|
||||
|
||||
def main(self, argv):
|
||||
# TODO(rloo): delete the ironic CLI in the S* cycle.
|
||||
print('The "ironic" CLI is deprecated and will be removed in the '
|
||||
'S* release. Please use the "openstack baremetal" CLI instead.',
|
||||
file=sys.stderr)
|
||||
# Parse args once to find version
|
||||
parser = self.get_base_parser()
|
||||
(options, args) = parser.parse_known_args(argv)
|
||||
self._setup_debugging(options.debug)
|
||||
|
||||
# build available subcommands based on version
|
||||
(api_major_version, os_ironic_api_version) = (
|
||||
self._check_version(options.ironic_api_version))
|
||||
|
||||
subcommand_parser = self.get_subcommand_parser(api_major_version)
|
||||
self.parser = subcommand_parser
|
||||
|
||||
# Handle top-level --help/-h before attempting to parse
|
||||
# a command off the command line
|
||||
if options.help or not argv:
|
||||
self.do_help(options)
|
||||
return 0
|
||||
|
||||
# Parse args again and call whatever callback was selected
|
||||
args = subcommand_parser.parse_args(argv)
|
||||
|
||||
# Short-circuit and deal with these commands right away.
|
||||
if args.func == self.do_help:
|
||||
self.do_help(args)
|
||||
return 0
|
||||
elif args.func == self.do_bash_completion:
|
||||
self.do_bash_completion()
|
||||
return 0
|
||||
|
||||
# Assume password auth if it does not seem like none, admin_token or
|
||||
# token auth
|
||||
if not args.endpoint and not (args.token and args.auth_url):
|
||||
if not args.username:
|
||||
raise exc.CommandError(_("You must provide a username via "
|
||||
"either --os-username or via "
|
||||
"env[OS_USERNAME]"))
|
||||
|
||||
if not args.password:
|
||||
# No password, If we've got a tty, try prompting for it
|
||||
if hasattr(sys.stdin, 'isatty') and sys.stdin.isatty():
|
||||
# Check for Ctl-D
|
||||
try:
|
||||
args.password = getpass.getpass(
|
||||
'OpenStack Password: ')
|
||||
except EOFError:
|
||||
pass
|
||||
# No password because we didn't have a tty or the
|
||||
# user Ctl-D when prompted.
|
||||
if not args.password:
|
||||
raise exc.CommandError(_("You must provide a password via "
|
||||
"either --os-password, "
|
||||
"env[OS_PASSWORD], "
|
||||
"or prompted response"))
|
||||
|
||||
if not (args.tenant_id or args.tenant_name or
|
||||
args.project_id or args.project_name):
|
||||
raise exc.CommandError(
|
||||
_("You must provide a project name or"
|
||||
" project id via --os-project-name, --os-project-id,"
|
||||
" env[OS_PROJECT_ID] or env[OS_PROJECT_NAME]."))
|
||||
|
||||
if not args.auth_url:
|
||||
raise exc.CommandError(_("You must provide an auth url via "
|
||||
"either --os-auth-url or via "
|
||||
"env[OS_AUTH_URL]"))
|
||||
|
||||
if args.max_retries < 0:
|
||||
raise exc.CommandError(_("You must provide value >= 0 for "
|
||||
"--max-retries"))
|
||||
if args.retry_interval < 1:
|
||||
raise exc.CommandError(_("You must provide value >= 1 for "
|
||||
"--retry-interval"))
|
||||
client_args = (
|
||||
'token', 'endpoint', 'username', 'password', 'auth_url',
|
||||
'project_id', 'project_name', 'tenant_id', 'tenant_name',
|
||||
'region_name', 'user_domain_id', 'user_domain_name',
|
||||
'project_domain_id', 'project_domain_name', 'service_type',
|
||||
'interface', 'max_retries', 'retry_interval', 'timeout', 'insecure'
|
||||
)
|
||||
kwargs = {}
|
||||
for key in client_args:
|
||||
value = getattr(args, key)
|
||||
# NOTE(vdrok): check for both None and ''. If the default value
|
||||
# for option is set using cliutils.env function, default empty
|
||||
# value is ''. If the default is not set explicitly, it is None.
|
||||
if value not in (None, ''):
|
||||
kwargs[key] = value
|
||||
# NOTE(vdrok): this is to workaround the fact that these options are
|
||||
# named differently in keystoneauth, depending on whether they are
|
||||
# provided through CLI or loaded from conf options, here we unify them.
|
||||
for cli_ssl_opt, conf_ssl_opt in [
|
||||
('os_cacert', 'cafile'), ('os_cert', 'certfile'),
|
||||
('os_key', 'keyfile')]:
|
||||
value = getattr(args, cli_ssl_opt)
|
||||
if value not in (None, ''):
|
||||
kwargs[conf_ssl_opt] = value
|
||||
kwargs['os_ironic_api_version'] = os_ironic_api_version
|
||||
client = ironicclient.client.get_client(api_major_version, **kwargs)
|
||||
if options.ironic_api_version in ('1', 'latest'):
|
||||
# Allow negotiating a lower version, if the latest version
|
||||
# supported by the client is higher than the latest version
|
||||
# supported by the server.
|
||||
client.http_client.api_version_select_state = 'default'
|
||||
|
||||
try:
|
||||
args.func(client, args)
|
||||
except exc.Unauthorized:
|
||||
raise exc.CommandError(_("Invalid OpenStack Identity credentials"))
|
||||
except exc.CommandError as e:
|
||||
subcommand_parser = self.subcommands[args.subparser_name]
|
||||
subcommand_parser.error(e)
|
||||
|
||||
@cliutils.arg('command', metavar='<subcommand>', nargs='?',
|
||||
help=_('Display help for <subcommand>'))
|
||||
def do_help(self, args):
|
||||
"""Display help about this program or one of its subcommands."""
|
||||
if getattr(args, 'command', None):
|
||||
if args.command in self.subcommands:
|
||||
self.subcommands[args.command].print_help()
|
||||
else:
|
||||
raise exc.CommandError(_("'%s' is not a valid subcommand") %
|
||||
args.command)
|
||||
else:
|
||||
self.parser.print_help()
|
||||
|
||||
|
||||
class HelpFormatter(argparse.HelpFormatter):
|
||||
def start_section(self, heading):
|
||||
super(HelpFormatter, self).start_section(heading.capitalize())
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
IronicShell().main(sys.argv[1:])
|
||||
except KeyboardInterrupt:
|
||||
print(_("... terminating ironic client"), file=sys.stderr)
|
||||
return 130
|
||||
except Exception as e:
|
||||
print(encodeutils.safe_encode(six.text_type(e)), file=sys.stderr)
|
||||
return 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
@ -1,213 +0,0 @@
|
||||
# Copyright (c) 2016 Mirantis, Inc.
|
||||
#
|
||||
# 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 six
|
||||
from tempest.lib.common.utils import data_utils
|
||||
from tempest.lib import exceptions
|
||||
|
||||
from ironicclient.tests.functional import base
|
||||
|
||||
|
||||
class ChassisSanityTestIronicClient(base.FunctionalTestBase):
|
||||
"""Sanity tests for testing actions with Chassis.
|
||||
|
||||
Smoke test for the Ironic CLI commands which checks basic actions with
|
||||
chassis command like create, show, update, delete etc.
|
||||
"""
|
||||
def setUp(self):
|
||||
super(ChassisSanityTestIronicClient, self).setUp()
|
||||
self.chassis = self.create_chassis()
|
||||
|
||||
def test_chassis_create(self):
|
||||
"""Test steps:
|
||||
|
||||
1) create chassis
|
||||
2) check that chassis has been successfully created
|
||||
"""
|
||||
chassis_list_uuid = self.get_chassis_uuids_from_chassis_list()
|
||||
self.assertIn(self.chassis['uuid'], chassis_list_uuid)
|
||||
|
||||
def test_chassis_delete(self):
|
||||
"""Test steps:
|
||||
|
||||
1) create chassis
|
||||
2) check that chassis has been successfully created
|
||||
3) delete chassis
|
||||
4) check that chassis has been successfully deleted
|
||||
"""
|
||||
self.delete_chassis(self.chassis['uuid'])
|
||||
chassis_list_uuid = self.get_chassis_uuids_from_chassis_list()
|
||||
|
||||
self.assertNotIn(self.chassis['uuid'], chassis_list_uuid)
|
||||
|
||||
def test_chassis_show(self):
|
||||
"""Test steps:
|
||||
|
||||
1) create chassis
|
||||
2) check that chassis-show returns the same chassis UUID
|
||||
3) chassis-create
|
||||
"""
|
||||
chassis_show = self.show_chassis(self.chassis['uuid'])
|
||||
self.assertEqual(self.chassis['uuid'], chassis_show['uuid'])
|
||||
|
||||
def test_chassis_show_field(self):
|
||||
"""Test steps:
|
||||
|
||||
1) create chassis
|
||||
2) show chassis with fields uuid
|
||||
3) check that fields is exist
|
||||
"""
|
||||
fields = ['uuid']
|
||||
chassis_show = self.show_chassis(self.chassis['uuid'],
|
||||
params='--fields {0}'
|
||||
.format(*fields))
|
||||
self.assertTableHeaders(fields, chassis_show.keys())
|
||||
|
||||
def test_chassis_update(self):
|
||||
"""Test steps:
|
||||
|
||||
1) create chassis
|
||||
2) update chassis
|
||||
3) check that chassis has been successfully updated
|
||||
"""
|
||||
updated_chassis = self.update_chassis(
|
||||
self.chassis['uuid'], 'add', 'description=test-chassis')
|
||||
self.assertEqual('test-chassis', updated_chassis['description'])
|
||||
self.assertNotEqual(self.chassis['description'],
|
||||
updated_chassis['description'])
|
||||
|
||||
def test_chassis_node_list(self):
|
||||
"""Test steps:
|
||||
|
||||
1) create chassis in setUp()
|
||||
2) create 3 nodes
|
||||
3) update 2 nodes to be included in chassis
|
||||
4) check if 2 nodes are added to chassis
|
||||
5) check if 1 nodes isn't added to chassis
|
||||
"""
|
||||
node1 = self.create_node()
|
||||
node2 = self.create_node()
|
||||
|
||||
# This node is created to show that it won't be present
|
||||
# in the chassis-node-list output
|
||||
|
||||
node3 = self.create_node()
|
||||
updated_node1 = self.update_node(node1['uuid'],
|
||||
'add chassis_uuid={0}'
|
||||
.format(self.chassis['uuid']))
|
||||
updated_node2 = self.update_node(node2['uuid'],
|
||||
'add chassis_uuid={0}'
|
||||
.format(self.chassis['uuid']))
|
||||
nodes = [updated_node1['uuid'], updated_node2['uuid']]
|
||||
nodes.sort()
|
||||
nodes_uuids = self.get_nodes_uuids_from_chassis_node_list(
|
||||
self.chassis['uuid'])
|
||||
nodes_uuids.sort()
|
||||
self.assertEqual(nodes, nodes_uuids)
|
||||
self.assertNotIn(node3['uuid'], nodes_uuids)
|
||||
|
||||
|
||||
class ChassisNegativeTestsIronicClient(base.FunctionalTestBase):
|
||||
"""Negative tests for testing actions with Chassis.
|
||||
|
||||
Negative tests for the Ironic CLI commands which checks actions with
|
||||
chassis command like show, update, delete either using with arguments
|
||||
or without arguments.
|
||||
"""
|
||||
|
||||
def test_chassis_delete_without_arguments(self):
|
||||
"""Test step:
|
||||
|
||||
1) check that chassis-delete command without arguments
|
||||
triggers an exception
|
||||
"""
|
||||
ex_text = r'chassis-delete: error:'
|
||||
|
||||
six.assertRaisesRegex(self, exceptions.CommandFailed,
|
||||
ex_text,
|
||||
self.delete_chassis, '')
|
||||
|
||||
def test_chassis_delete_with_incorrect_chassis_uuid(self):
|
||||
"""Test step:
|
||||
|
||||
1) check that deleting non-exist chassis triggers an exception
|
||||
triggers an exception
|
||||
"""
|
||||
uuid = data_utils.rand_uuid()
|
||||
ex_text = (r"Chassis {0} "
|
||||
r"could not be found. \(HTTP 404\)".format(uuid))
|
||||
|
||||
six.assertRaisesRegex(self, exceptions.CommandFailed,
|
||||
ex_text,
|
||||
self.delete_chassis,
|
||||
'{0}'.format(uuid))
|
||||
|
||||
def test_chassis_show_without_arguments(self):
|
||||
"""Test step:
|
||||
|
||||
1) check that chassis-show command without arguments
|
||||
triggers an exception
|
||||
"""
|
||||
ex_text = r'chassis-show: error:'
|
||||
|
||||
six.assertRaisesRegex(self, exceptions.CommandFailed,
|
||||
ex_text,
|
||||
self.show_chassis, '')
|
||||
|
||||
def test_chassis_show_with_incorrect_chassis_uuid(self):
|
||||
"""Test step:
|
||||
|
||||
1) check that chassis-show command with incorrect chassis
|
||||
uuid triggers an exception
|
||||
"""
|
||||
uuid = data_utils.rand_uuid()
|
||||
ex_text = (r"Chassis {0} "
|
||||
r"could not be found. \(HTTP 404\)".format(uuid))
|
||||
|
||||
six.assertRaisesRegex(self, exceptions.CommandFailed,
|
||||
ex_text,
|
||||
self.show_chassis,
|
||||
'{0}'.format(uuid))
|
||||
|
||||
def test_chassis_update_without_arguments(self):
|
||||
"""Test steps:
|
||||
|
||||
1) create chassis
|
||||
2) check that chassis-update command without arguments
|
||||
triggers an exception
|
||||
"""
|
||||
ex_text = r'chassis-update: error:'
|
||||
|
||||
six.assertRaisesRegex(self, exceptions.CommandFailed,
|
||||
ex_text,
|
||||
self.update_chassis,
|
||||
chassis_id='',
|
||||
operation='')
|
||||
|
||||
def test_chassis_update_with_incorrect_chassis_uuid(self):
|
||||
"""Test steps:
|
||||
|
||||
1) create chassis
|
||||
2) check that chassis-update command with incorrect arguments
|
||||
triggers an exception
|
||||
"""
|
||||
uuid = data_utils.rand_uuid()
|
||||
ex_text = r'chassis-update: error:'
|
||||
|
||||
six.assertRaisesRegex(self,
|
||||
exceptions.CommandFailed,
|
||||
ex_text,
|
||||
self.update_chassis,
|
||||
chassis_id='{0}'.format(uuid),
|
||||
operation='')
|
@ -1,129 +0,0 @@
|
||||
# Copyright (c) 2016 Mirantis, Inc.
|
||||
#
|
||||
# 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 six
|
||||
from tempest.lib.common.utils import data_utils
|
||||
from tempest.lib import exceptions
|
||||
|
||||
from ironicclient.tests.functional import base
|
||||
|
||||
|
||||
class NegativeChassisCreateTestsIronicClient(base.FunctionalTestBase):
|
||||
"""Negative tests for testing chassis-create command.
|
||||
|
||||
Negative tests for the Ironic CLI commands which check actions with
|
||||
chassis-create command like create chassis without arguments or with
|
||||
incorrect arguments and check that correct error message raised.
|
||||
"""
|
||||
|
||||
error_msg = r'ironic chassis-create: error:'
|
||||
expected_msg = r'expected one argument'
|
||||
|
||||
def test_description_no_value(self):
|
||||
"""Test steps:
|
||||
|
||||
1) create chassis using -d argument without the value
|
||||
2) create chassis using --description argument without the value
|
||||
3) check that command using -d argument triggers an exception
|
||||
4) check that command with --description arg triggers an exception
|
||||
"""
|
||||
ex_text = (r'{0} argument -d/--description: {1}'
|
||||
.format(self.error_msg, self.expected_msg))
|
||||
six.assertRaisesRegex(self, exceptions.CommandFailed, ex_text,
|
||||
self.create_chassis, '-d')
|
||||
six.assertRaisesRegex(self, exceptions.CommandFailed, ex_text,
|
||||
self.create_chassis, '--description')
|
||||
|
||||
def test_metadata_extra_no_value(self):
|
||||
"""Test steps:
|
||||
|
||||
1) create chassis using -e argument without the value
|
||||
2) create chassis using --extra argument without the value
|
||||
3) check that command using -e argument triggers an exception
|
||||
4) check that command with --extra argument triggers an exception
|
||||
"""
|
||||
ex_text = (r'{0} argument -e/--extra: {1}'
|
||||
.format(self.error_msg, self.expected_msg))
|
||||
six.assertRaisesRegex(self, exceptions.CommandFailed, ex_text,
|
||||
self.create_chassis, '-e')
|
||||
six.assertRaisesRegex(self, exceptions.CommandFailed, ex_text,
|
||||
self.create_chassis, '--extra')
|
||||
|
||||
def test_specific_uuid_no_value(self):
|
||||
"""Test steps:
|
||||
|
||||
1) create chassis using -u argument without the value
|
||||
2) create chassis using --uuid argument without the value
|
||||
3) check that command using -u argument triggers an exception
|
||||
4) check that command with --uuid argument triggers an exception
|
||||
"""
|
||||
ex_text = (r'{0} argument -u/--uuid: {1}'
|
||||
.format(self.error_msg, self.expected_msg))
|
||||
six.assertRaisesRegex(self, exceptions.CommandFailed, ex_text,
|
||||
self.create_chassis, '-u')
|
||||
six.assertRaisesRegex(self, exceptions.CommandFailed, ex_text,
|
||||
self.create_chassis, '--uuid')
|
||||
|
||||
def test_invalid_description(self):
|
||||
"""Test steps:
|
||||
|
||||
1) create chassis with invalid description using -d argument
|
||||
2) create chassis with invalid description using --description arg
|
||||
3) check that command using -d argument triggers an exception
|
||||
4) check that command using --uuid argument triggers an exception
|
||||
"""
|
||||
description = '--'
|
||||
ex_text = (r'{0} argument -d/--description: {1}'
|
||||
.format(self.error_msg, self.expected_msg))
|
||||
six.assertRaisesRegex(self, exceptions.CommandFailed, ex_text,
|
||||
self.create_chassis,
|
||||
params='-d {0}'.format(description))
|
||||
six.assertRaisesRegex(self, exceptions.CommandFailed, ex_text,
|
||||
self.create_chassis,
|
||||
params='--description {0}'.format(description))
|
||||
|
||||
def test_invalid_metadata_extra(self):
|
||||
"""Test steps:
|
||||
|
||||
1) create chassis with invalid metadata using -e argument
|
||||
2) create chassis with invalid metadata using --extra argument
|
||||
3) check that command using -e argument triggers an exception
|
||||
4) check that command using --extra argument triggers an exception
|
||||
"""
|
||||
extra = "HelloWorld"
|
||||
ex_text = (r'{0} Attributes must be a list of PATH=VALUE'
|
||||
.format(self.error_msg))
|
||||
six.assertRaisesRegex(self, exceptions.CommandFailed, ex_text,
|
||||
self.create_chassis,
|
||||
params='-e {0}'.format(extra))
|
||||
six.assertRaisesRegex(self, exceptions.CommandFailed, ex_text,
|
||||
self.create_chassis,
|
||||
params='--extra {0}'.format(extra))
|
||||
|
||||
def test_invalid_specific_uuid(self):
|
||||
"""Test steps:
|
||||
|
||||
1) create chassis with invalid specific uuid using -u argument
|
||||
2) create chassis with invalid specific uuid using --uuid argument
|
||||
3) check that command using -u argument triggers an exception
|
||||
4) check that command using --uuid argument triggers an exception
|
||||
"""
|
||||
invalid_uuid = data_utils.rand_uuid()[:-1]
|
||||
ex_text = r'Expected a UUID but received {0}'.format(invalid_uuid)
|
||||
six.assertRaisesRegex(self, exceptions.CommandFailed, ex_text,
|
||||
self.create_chassis,
|
||||
params='-u {0}'.format(invalid_uuid))
|
||||
six.assertRaisesRegex(self, exceptions.CommandFailed, ex_text,
|
||||
self.create_chassis,
|
||||
params='--uuid {0}'.format(invalid_uuid))
|
@ -1,56 +0,0 @@
|
||||
# Copyright (c) 2015 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from ironicclient.tests.functional import base
|
||||
|
||||
|
||||
class DriverSanityTestIronicClient(base.FunctionalTestBase):
|
||||
"""Sanity tests for testing actions with driver.
|
||||
|
||||
Smoke test for the Ironic CLI commands which checks basic actions with
|
||||
driver command like driver-show, driver-properties.
|
||||
"""
|
||||
|
||||
def test_driver_show(self):
|
||||
"""Test steps:
|
||||
|
||||
1) get drivers names
|
||||
2) check that each driver exists in driver-show output
|
||||
"""
|
||||
drivers_names = self.get_drivers_names()
|
||||
for driver in drivers_names:
|
||||
driver_show = self.show_driver(driver)
|
||||
self.assertEqual(driver, driver_show['name'])
|
||||
|
||||
def test_driver_properties(self):
|
||||
"""Test steps:
|
||||
|
||||
1) get drivers names
|
||||
2) check that each driver has some properties
|
||||
"""
|
||||
drivers_names = self.get_drivers_names()
|
||||
for driver in drivers_names:
|
||||
driver_properties = self.properties_driver(driver)
|
||||
self.assertNotEqual([], [x['Property'] for x in driver_properties])
|
||||
|
||||
def test_driver_list(self):
|
||||
"""Test steps:
|
||||
|
||||
1) get list of drivers
|
||||
2) check that list of drivers is not empty
|
||||
"""
|
||||
driver = 'fake-hardware'
|
||||
available_drivers = self.get_drivers_names()
|
||||
self.assertGreater(len(available_drivers), 0)
|
||||
self.assertIn(driver, available_drivers)
|
@ -1,69 +0,0 @@
|
||||
# Copyright (c) 2015 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from ironicclient.tests.functional import base
|
||||
|
||||
|
||||
class IronicClientHelp(base.FunctionalTestBase):
|
||||
"""Test for python-ironicclient help messages."""
|
||||
|
||||
def test_ironic_help(self):
|
||||
"""Check Ironic client main help message contents."""
|
||||
caption = ("Command-line interface to the "
|
||||
"OpenStack Bare Metal Provisioning API.")
|
||||
subcommands = {
|
||||
'bash-completion',
|
||||
'chassis-create',
|
||||
'chassis-delete',
|
||||
'chassis-list',
|
||||
'chassis-node-list',
|
||||
'chassis-show',
|
||||
'chassis-update',
|
||||
'driver-list',
|
||||
'driver-properties',
|
||||
'driver-show',
|
||||
'driver-vendor-passthru',
|
||||
'help',
|
||||
|