summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ironic/nova/tests/virt/ironic/test_driver.py7
-rw-r--r--ironic/nova/virt/ironic/client_wrapper.py98
-rw-r--r--ironic/nova/virt/ironic/driver.py20
3 files changed, 112 insertions, 13 deletions
diff --git a/ironic/nova/tests/virt/ironic/test_driver.py b/ironic/nova/tests/virt/ironic/test_driver.py
index 25af73a6a..80cf2fdcf 100644
--- a/ironic/nova/tests/virt/ironic/test_driver.py
+++ b/ironic/nova/tests/virt/ironic/test_driver.py
@@ -22,6 +22,7 @@ from ironicclient import exc as ironic_exception
import mock
from oslo.config import cfg
+from ironic.nova.virt.ironic import client_wrapper as cw
from ironic.nova.virt.ironic import driver as ironic_driver
from ironic.nova.virt.ironic import ironic_states
@@ -326,11 +327,12 @@ class IronicDriverTestCase(test.NoDBTestCase):
# append a node w/o instance_uuid which shouldn't be listed
nodes.append(get_test_node(instance_uuid=None))
- with mock.patch.object(FAKE_CLIENT.node, 'list') as mock_list:
+ with mock.patch.object(cw.IronicClientWrapper, 'call') as mock_list:
mock_list.return_value = nodes
expected = [n for n in nodes if n.instance_uuid]
instances = self.driver.list_instances()
+ mock_list.assert_called_with("node.list")
self.assertEqual(sorted(expected), sorted(instances))
self.assertEqual(num_nodes, len(instances))
@@ -343,11 +345,12 @@ class IronicDriverTestCase(test.NoDBTestCase):
# append a node w/o power_state which shouldn't be listed
nodes.append(get_test_node(power_state=None))
- with mock.patch.object(FAKE_CLIENT.node, 'list') as mock_list:
+ with mock.patch.object(cw.IronicClientWrapper, 'call') as mock_list:
mock_list.return_value = nodes
expected = [n.uuid for n in nodes if n.power_state]
available_nodes = self.driver.get_available_nodes()
+ mock_list.assert_called_with("node.list")
self.assertEqual(sorted(expected), sorted(available_nodes))
self.assertEqual(num_nodes, len(available_nodes))
diff --git a/ironic/nova/virt/ironic/client_wrapper.py b/ironic/nova/virt/ironic/client_wrapper.py
new file mode 100644
index 000000000..5120b991a
--- /dev/null
+++ b/ironic/nova/virt/ironic/client_wrapper.py
@@ -0,0 +1,98 @@
+# coding=utf-8
+#
+# Copyright 2014 Hewlett-Packard Development Company, L.P.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import time
+
+from ironicclient import client as ironic_client
+from ironicclient import exc as ironic_exception
+
+from nova import exception
+from nova.openstack.common.gettextutils import _
+from nova.openstack.common import log as logging
+from oslo.config import cfg
+
+LOG = logging.getLogger(__name__)
+CONF = cfg.CONF
+
+
+class IronicClientWrapper(object):
+ """Ironic client wrapper class that encapsulates retry logic."""
+
+ def _get_client(self):
+ # TODO(deva): save and reuse existing client & auth token
+ # until it expires or is no longer valid
+ auth_token = CONF.ironic.admin_auth_token
+ if auth_token is None:
+ kwargs = {'os_username': CONF.ironic.admin_username,
+ 'os_password': CONF.ironic.admin_password,
+ 'os_auth_url': CONF.ironic.admin_url,
+ 'os_tenant_name': CONF.ironic.admin_tenant_name,
+ 'os_service_type': 'baremetal',
+ 'os_endpoint_type': 'public'}
+ else:
+ kwargs = {'os_auth_token': auth_token,
+ 'ironic_url': CONF.ironic.api_endpoint}
+
+ try:
+ cli = ironic_client.get_client(CONF.ironic.api_version, **kwargs)
+ except ironic_exception.Unauthorized:
+ msg = (_("Unable to authenticate Ironic client."))
+ LOG.error(msg)
+ raise exception.NovaException(msg)
+
+ return cli
+
+ def _multi_getattr(self, obj, attr):
+ """Support nested attribute path for getattr().
+
+ :param obj: Root object.
+ :param attr: Path of final attribute to get. E.g., "a.b.c.d"
+
+ :returns: The value of the final named attribute.
+ :raises: AttributeError will be raised if the path is invalid.
+ """
+ for attribute in attr.split("."):
+ obj = getattr(obj, attribute)
+ return obj
+
+ def call(self, method, *args):
+ """Call an Ironic client method and retry on errors.
+
+ :param method: Name of the client method to call as a string.
+ :param args: Client method arguments.
+
+ :raises: NovaException if all retries failed.
+ """
+ retry_excs = (ironic_exception.HTTPServiceUnavailable,
+ ironic_exception.CommunicationError)
+ num_attempts = CONF.ironic.api_max_retries
+
+ for attempt in range(1, num_attempts + 1):
+ client = self._get_client()
+ try:
+ return self._multi_getattr(client, method)(*args)
+ except retry_excs:
+ msg = (_("Error contacting Ironic server for '%(method)s'. "
+ "Attempt %(attempt)d of %(total)d")
+ % {'method': method,
+ 'attempt': attempt,
+ 'total': num_attempts})
+ if attempt == num_attempts:
+ LOG.error(msg)
+ raise exception.NovaException(msg)
+ LOG.warning(msg)
+ time.sleep(CONF.ironic.api_retry_interval)
diff --git a/ironic/nova/virt/ironic/driver.py b/ironic/nova/virt/ironic/driver.py
index 154152f76..bb5a8e671 100644
--- a/ironic/nova/virt/ironic/driver.py
+++ b/ironic/nova/virt/ironic/driver.py
@@ -25,6 +25,7 @@ from ironicclient import client as ironic_client
from ironicclient import exc as ironic_exception
from oslo.config import cfg
+from ironic.nova.virt.ironic import client_wrapper
from ironic.nova.virt.ironic import ironic_states
from nova.compute import power_state
from nova import exception
@@ -331,19 +332,15 @@ class IronicDriver(virt_driver.ComputeDriver):
return CONF.ironic.api_version
def list_instances(self):
- try:
- icli = self._get_client()
- except ironic_exception.Unauthorized:
- LOG.error(_("Unable to authenticate Ironic client."))
- return []
-
- instances = [i for i in icli.node.list() if i.instance_uuid]
+ icli = client_wrapper.IronicClientWrapper()
+ node_list = icli.call("node.list")
+ instances = [i for i in node_list if i.instance_uuid]
return instances
def get_available_nodes(self, refresh=False):
nodes = []
- icli = self._get_client()
- node_list = icli.node.list()
+ icli = client_wrapper.IronicClientWrapper()
+ node_list = icli.call("node.list")
for n in node_list:
# for now we'll use the nodes power state. if power_state is None
@@ -590,8 +587,9 @@ class IronicDriver(virt_driver.ComputeDriver):
def get_host_stats(self, refresh=False):
caps = []
- icli = self._get_client()
- for node in icli.node.list():
+ icli = client_wrapper.IronicClientWrapper()
+ node_list = icli.call("node.list")
+ for node in node_list:
data = self._node_resource(node)
caps.append(data)
return caps