summaryrefslogtreecommitdiff
path: root/ironicclient
diff options
context:
space:
mode:
Diffstat (limited to 'ironicclient')
-rw-r--r--ironicclient/common/utils.py11
-rw-r--r--ironicclient/osc/v1/baremetal_deploy_template.py6
-rwxr-xr-xironicclient/osc/v1/baremetal_node.py22
-rw-r--r--ironicclient/shell.py18
-rw-r--r--ironicclient/tests/functional/osc/v1/base.py12
-rw-r--r--ironicclient/tests/functional/osc/v1/test_baremetal_port_create.py75
-rw-r--r--ironicclient/tests/unit/common/test_utils.py21
-rw-r--r--ironicclient/tests/unit/osc/v1/test_baremetal_node.py1
-rw-r--r--ironicclient/v1/resource_fields.py1
9 files changed, 141 insertions, 26 deletions
diff --git a/ironicclient/common/utils.py b/ironicclient/common/utils.py
index 42d531a..97f1901 100644
--- a/ironicclient/common/utils.py
+++ b/ironicclient/common/utils.py
@@ -26,6 +26,7 @@ import tempfile
import time
from oslo_utils import strutils
+import yaml
from ironicclient.common.i18n import _
from ironicclient import exc
@@ -366,7 +367,7 @@ def get_from_stdin(info_desc):
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
+ :param json_arg: May be a file name containing the YAML or JSON, or
a JSON string.
:returns: A list or dictionary parsed from JSON.
:raises: InvalidAttribute if the argument cannot be parsed.
@@ -375,16 +376,16 @@ def handle_json_or_file_arg(json_arg):
if os.path.isfile(json_arg):
try:
with open(json_arg, 'r') as f:
- json_arg = f.read().strip()
+ return yaml.safe_load(f)
except Exception as e:
- err = _("Cannot get JSON from file '%(file)s'. "
+ err = _("Cannot get JSON/YAML 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})
+ err = (_("Value '%(string)s' is not a file and cannot be parsed "
+ "as JSON: '%(err)s'") % {'err': e, 'string': json_arg})
raise exc.InvalidAttribute(err)
return json_arg
diff --git a/ironicclient/osc/v1/baremetal_deploy_template.py b/ironicclient/osc/v1/baremetal_deploy_template.py
index 3a1a8c3..e624b0a 100644
--- a/ironicclient/osc/v1/baremetal_deploy_template.py
+++ b/ironicclient/osc/v1/baremetal_deploy_template.py
@@ -25,9 +25,9 @@ from ironicclient.v1 import resource_fields as res_fields
_DEPLOY_STEPS_HELP = _(
- "The deploy steps in JSON format. May be the path to a file containing "
- "the deploy steps; OR '-', with the deploy steps being read from standard "
- "input; OR a string. The value should be a list of deploy-step "
+ "The deploy steps. May be the path to a YAML file containing the deploy "
+ "steps; OR '-', with the deploy steps being read from standard "
+ "input; OR a JSON string. The value should be a list of deploy-step "
"dictionaries; each dictionary should have keys 'interface', 'step', "
"'args' and 'priority'.")
diff --git a/ironicclient/osc/v1/baremetal_node.py b/ironicclient/osc/v1/baremetal_node.py
index ffdfd74..45b3b30 100755
--- a/ironicclient/osc/v1/baremetal_node.py
+++ b/ironicclient/osc/v1/baremetal_node.py
@@ -40,7 +40,7 @@ CONFIG_DRIVE_ARG_HELP = _(
NETWORK_DATA_ARG_HELP = _(
- "JSON string or a file or '-' for stdin to read static network "
+ "JSON string or a YAML file or '-' for stdin to read static network "
"configuration for the baremetal node associated with this ironic node. "
"Format of this file should comply with Nova network data metadata "
"(network_data.json). Depending on ironic boot interface capabilities "
@@ -256,10 +256,10 @@ class CleanBaremetalNode(ProvisionStateWithWait):
metavar='<clean-steps>',
required=True,
default=None,
- help=_("The clean steps in JSON format. May be the path to a file "
+ help=_("The clean steps. May be the path to a YAML file "
"containing the clean steps; OR '-', with the clean steps "
- "being read from standard input; OR a string. The value "
- "should be a list of clean-step dictionaries; each "
+ "being read from standard input; OR a JSON string. The "
+ "value should be a list of clean-step dictionaries; each "
"dictionary should have keys 'interface' and 'step', and "
"optional key 'args'."))
return parser
@@ -571,12 +571,12 @@ class DeployBaremetalNode(ProvisionStateWithWait):
metavar='<deploy-steps>',
required=False,
default=None,
- help=_("The deploy steps in JSON format. May be the path to a "
- "file containing the deploy steps; OR '-', with the deploy "
- "steps being read from standard input; OR a string. The "
- "value should be a list of deploy-step dictionaries; each "
- "dictionary should have keys 'interface', 'step', "
- "'priority' and optional key 'args'."))
+ help=_("The deploy steps. May be the path to a YAML file "
+ "containing the deploy steps; OR '-', with the deploy "
+ "steps being read from standard input; OR a JSON string. "
+ "The value should be a list of deploy-step dictionaries; "
+ "each dictionary should have keys 'interface' and 'step', "
+ "and optional key 'args'."))
return parser
@@ -1262,7 +1262,7 @@ class SetBaremetalNode(command.Command):
'--target-raid-config',
metavar='<target_raid_config>',
help=_('Set the target RAID configuration (JSON) for the node. '
- 'This can be one of: 1. a file containing JSON data of the '
+ 'This can be one of: 1. a file containing YAML data of the '
'RAID configuration; 2. "-" to read the contents from '
'standard input; or 3. a valid JSON string.'),
)
diff --git a/ironicclient/shell.py b/ironicclient/shell.py
index fcb358f..0edc585 100644
--- a/ironicclient/shell.py
+++ b/ironicclient/shell.py
@@ -19,6 +19,7 @@ try:
import ironic_inspector_client
except ImportError:
ironic_inspector_client = None
+import openstack
from openstack import config as os_config
from osc_lib import utils
import pbr.version
@@ -168,8 +169,25 @@ class App(app.App):
)
return parser
+ def _configure_ironic_logging(self):
+ openstack.enable_logging(debug=self.options.debug)
+ # NOTE(dtantsur): I wish logging.basicConfig worked.. but it does not.
+ for name in ('ironicclient', 'ironic_inspector_client'):
+ logger = logging.getLogger(name)
+ logger.setLevel(
+ logging.DEBUG if self.options.debug else logging.WARNING)
+ # warnings are already configured by something else, only configure
+ # debug logging for ironic.
+ if not logger.handlers and self.options.debug:
+ handler = logging.StreamHandler()
+ handler.setFormatter(logging.Formatter(
+ # This is the openstacksdk default value
+ '%(asctime)s %(levelname)s: %(name)s %(message)s'))
+ logger.addHandler(handler)
+
def initialize_app(self, argv):
super(App, self).initialize_app(argv)
+ self._configure_ironic_logging()
self.cloud_region = self.config.get_one(argparse=self.options)
# Compatibility with OSC
self.client_manager = ClientManager(self.cloud_region, self.options)
diff --git a/ironicclient/tests/functional/osc/v1/base.py b/ironicclient/tests/functional/osc/v1/base.py
index 77308df..984c9eb 100644
--- a/ironicclient/tests/functional/osc/v1/base.py
+++ b/ironicclient/tests/functional/osc/v1/base.py
@@ -41,6 +41,18 @@ class TestCase(base.FunctionalTestBase):
def construct_cmd(*parts):
return ' '.join(str(x) for x in parts)
+ @staticmethod
+ def generate_params(argument, params):
+ """Generate parameters string.
+
+ :param argument: argument
+ :param params: values passed with argument
+ """
+ parts = []
+ for key, value in params.items():
+ parts.append('{} {}={}'.format(argument, key, value))
+ return ' '.join(parts)
+
def assert_dict_is_subset(self, expected, actual):
"""Check if expected keys/values exist in actual response body.
diff --git a/ironicclient/tests/functional/osc/v1/test_baremetal_port_create.py b/ironicclient/tests/functional/osc/v1/test_baremetal_port_create.py
new file mode 100644
index 0000000..04051f8
--- /dev/null
+++ b/ironicclient/tests/functional/osc/v1/test_baremetal_port_create.py
@@ -0,0 +1,75 @@
+# 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.
+
+from tempest.lib.common.utils import data_utils
+
+from ironicclient.tests.functional.osc.v1 import base
+
+
+class BaremetalPortCreate(base.TestCase):
+ """Detailed functional tests for baremetal port create command."""
+
+ def setUp(self):
+ super(BaremetalPortCreate, self).setUp()
+ self.node = self.node_create()
+
+ def test_extras(self):
+ """Check baremetal port create command with extra data.
+
+ Test steps:
+ 1) Create port using solitary and multiple --extra arguments.
+ 2) Check that port successfully created with right extras.
+ """
+ extras = [{'single_extra': 'yes'},
+ {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}]
+
+ for extra in extras:
+ params = self.generate_params('--extra', extra)
+ port = self.port_create(self.node['uuid'], params=params)
+ self.assert_dict_is_subset(extra, port['extra'])
+
+ def test_pxe_1_19(self):
+ """Check baremetal port create command with PXE option.
+
+ Test steps:
+ 1) Create port using --pxe-enabled argument.
+ 2) Check that port successfully created with right PXE option.
+ """
+ pxe_values = [True, False]
+ api_version = ' --os-baremetal-api-version 1.19'
+
+ for value in pxe_values:
+ port = self.port_create(
+ self.node['uuid'],
+ params='--pxe-enabled {0} {1}'.format(value, api_version))
+ self.assertEqual(value, port['pxe_enabled'])
+
+ def test_llc_1_19(self):
+ """Check baremetal port create command with LLC option.
+
+ Test steps:
+ 1) Create port using --local-link-connection argument.
+ 2) Check that port successfully created with right LLC data.
+ """
+ fake_port_id = data_utils.rand_name(prefix='ovs-node-')
+ fake_switch_id = data_utils.rand_mac_address()
+ llc_value = {"switch_info": "brbm",
+ "port_id": fake_port_id,
+ "switch_id": fake_switch_id}
+ api_version = ' --os-baremetal-api-version 1.19'
+
+ params = self.generate_params('--local-link-connection', llc_value)
+ port = self.port_create(self.node['uuid'],
+ params='{0} {1}'.format(params, api_version))
+ self.assert_dict_is_subset(llc_value, port['local_link_connection'])
diff --git a/ironicclient/tests/unit/common/test_utils.py b/ironicclient/tests/unit/common/test_utils.py
index d01818f..3d371a9 100644
--- a/ironicclient/tests/unit/common/test_utils.py
+++ b/ironicclient/tests/unit/common/test_utils.py
@@ -342,7 +342,7 @@ class HandleJsonFileTest(test_utils.BaseTestCase):
def test_handle_json_or_file_arg_bad_json(self):
cleansteps = 'foo'
self.assertRaisesRegex(exc.InvalidAttribute,
- 'For JSON',
+ 'is not a file and cannot be parsed as JSON',
utils.handle_json_or_file_arg, cleansteps)
def test_handle_json_or_file_arg_file(self):
@@ -355,17 +355,24 @@ class HandleJsonFileTest(test_utils.BaseTestCase):
self.assertEqual(json.loads(contents), steps)
+ def test_handle_yaml_or_file_arg_file(self):
+ contents = '''---
+- step: upgrade
+ interface: deploy'''
+
+ with tempfile.NamedTemporaryFile(mode='w') as f:
+ f.write(contents)
+ f.flush()
+ steps = utils.handle_json_or_file_arg(f.name)
+
+ self.assertEqual([{"step": "upgrade", "interface": "deploy"}], steps)
+
@mock.patch.object(builtins, 'open', autospec=True)
def test_handle_json_or_file_arg_file_fail(self, mock_open):
- mock_file_object = mock.MagicMock()
- mock_file_handle = mock.MagicMock()
- mock_file_handle.__enter__.return_value = mock_file_object
- mock_open.return_value = mock_file_handle
- mock_file_object.read.side_effect = IOError
+ mock_open.return_value.__enter__.side_effect = IOError
with tempfile.NamedTemporaryFile(mode='w') as f:
self.assertRaisesRegex(exc.InvalidAttribute,
"from file",
utils.handle_json_or_file_arg, f.name)
mock_open.assert_called_once_with(f.name, 'r')
- mock_file_object.read.assert_called_once_with()
diff --git a/ironicclient/tests/unit/osc/v1/test_baremetal_node.py b/ironicclient/tests/unit/osc/v1/test_baremetal_node.py
index 31dd0d5..76eea0e 100644
--- a/ironicclient/tests/unit/osc/v1/test_baremetal_node.py
+++ b/ironicclient/tests/unit/osc/v1/test_baremetal_node.py
@@ -847,6 +847,7 @@ class TestBaremetalList(TestBaremetal):
'Maintenance Reason',
'Management Interface',
'Name',
+ 'Network Configuration',
'Network Interface',
'Owner',
'Power Interface',
diff --git a/ironicclient/v1/resource_fields.py b/ironicclient/v1/resource_fields.py
index 98ad1e0..ff0f9a7 100644
--- a/ironicclient/v1/resource_fields.py
+++ b/ironicclient/v1/resource_fields.py
@@ -250,6 +250,7 @@ NODE_DETAILED_RESOURCE = Resource(
'maintenance_reason',
'management_interface',
'name',
+ 'network_data',
'network_interface',
'owner',
'power_interface',