From 028a86f7515a2bd0afa7a3ff1e5adadb475b80be Mon Sep 17 00:00:00 2001
From: Mark Hamzy <hamzy@us.ibm.com>
Date: Sat, 11 Nov 2017 14:05:09 -0600
Subject: [PATCH] fix_multi_arch tripleo-common

---
 tripleo_common/actions/baremetal.py       |   33 +-
 tripleo_common/actions/validations.py     |   69 +-
 tripleo_common/constants.py               |    4 -
 tripleo_common/tests/utils/test_glance.py |  220 ++++--
 tripleo_common/tests/utils/test_nodes.py  | 1133 +++++++++++++++++------------
 tripleo_common/utils/glance.py            |   39 +-
 tripleo_common/utils/nodes.py             |   60 +-
 workbooks/baremetal.yaml                  |   22 +-
 workbooks/validations.yaml                |   58 +-
 9 files changed, 1011 insertions(+), 627 deletions(-)

diff --git a/tripleo_common/actions/baremetal.py b/tripleo_common/actions/baremetal.py
index dc73355..99cf6ef 100644
--- a/tripleo_common/actions/baremetal.py
+++ b/tripleo_common/actions/baremetal.py
@@ -36,21 +36,18 @@ class RegisterOrUpdateNodes(base.TripleOAction):
 
     :param nodes_json: list of nodes & attributes in json format
     :param remove: Should nodes not in the list be removed?
-    :param kernel_name: Glance ID of the kernel to use for the nodes.
-    :param ramdisk_name: Glance ID of the ramdisk to use for the nodes.
     :param instance_boot_option: Whether to set instances for booting from
                                  local hard drive (local) or network (netboot).
     :return: list of node objects representing the new nodes.
     """
 
-    def __init__(self, nodes_json, remove=False, kernel_name=None,
-                 ramdisk_name=None, instance_boot_option='local'):
+    def __init__(self, nodes_json, remove=False, no_deploy_image=False,
+                 instance_boot_option='local'):
         super(RegisterOrUpdateNodes, self).__init__()
         self.nodes_json = nodes_json
         self.remove = remove
+        self.no_deploy_image = no_deploy_image
         self.instance_boot_option = instance_boot_option
-        self.kernel_name = kernel_name
-        self.ramdisk_name = ramdisk_name
 
     def run(self, context):
         for node in self.nodes_json:
@@ -67,9 +64,7 @@ class RegisterOrUpdateNodes(base.TripleOAction):
                 self.nodes_json,
                 client=baremetal_client,
                 remove=self.remove,
-                glance_client=image_client,
-                kernel_name=self.kernel_name,
-                ramdisk_name=self.ramdisk_name)
+                glance_client=image_client)
         except Exception as err:
             LOG.exception("Error registering nodes with ironic.")
             return actions.Result(error=six.text_type(err))
@@ -100,32 +95,26 @@ class ConfigureBootAction(base.TripleOAction):
     """Configure kernel and ramdisk.
 
     :param node_uuid: an Ironic node UUID
-    :param kernel_name: Glance name of the kernel to use for the nodes.
-    :param ramdisk_name: Glance name of the ramdisk to use for the nodes.
     :param instance_boot_option: Whether to set instances for booting from
                                  local hard drive (local) or network (netboot).
     """
 
-    def __init__(self, node_uuid, kernel_name='bm-deploy-kernel',
-                 ramdisk_name='bm-deploy-ramdisk', instance_boot_option=None):
+    def __init__(self, node_uuid, instance_boot_option=None):
         super(ConfigureBootAction, self).__init__()
         self.node_uuid = node_uuid
-        self.kernel_name = kernel_name
-        self.ramdisk_name = ramdisk_name
         self.instance_boot_option = instance_boot_option
 
     def run(self, context):
         baremetal_client = self.get_baremetal_client(context)
         image_client = self.get_image_client(context)
+        self.no_deploy_image = no_deploy_image
 
         try:
-            image_ids = {'kernel': None, 'ramdisk': None}
-            if self.kernel_name is not None and self.ramdisk_name is not None:
-                image_ids = glance.create_or_find_kernel_and_ramdisk(
-                    image_client, self.kernel_name, self.ramdisk_name)
-
             node = baremetal_client.node.get(self.node_uuid)
 
+            arches = glance.create_or_find_kernel_and_ramdisk(image_client,
+                baremetal_client)
+
             capabilities = node.properties.get('capabilities', {})
             capabilities = nodes.capabilities_to_dict(capabilities)
             if self.instance_boot_option is not None:
@@ -145,12 +134,12 @@ class ConfigureBootAction(base.TripleOAction):
                 {
                     'op': 'add',
                     'path': '/driver_info/deploy_ramdisk',
-                    'value': image_ids['ramdisk'],
+                    'value': arches[arch]['ramdisk'],
                 },
                 {
                     'op': 'add',
                     'path': '/driver_info/deploy_kernel',
-                    'value': image_ids['kernel'],
+                    'value': arches['arch']['kernel'],
                 },
             ])
             LOG.debug("Configuring boot option for Node %s", self.node_uuid)
diff --git a/tripleo_common/actions/validations.py b/tripleo_common/actions/validations.py
index 017f99d..18bfbe6 100644
--- a/tripleo_common/actions/validations.py
+++ b/tripleo_common/actions/validations.py
@@ -141,22 +141,25 @@ class CheckBootImagesAction(base.TripleOAction):
 
     # TODO(bcrochet): The validation actions are temporary. This logic should
     #                 move to the tripleo-validations project eventually.
-    def __init__(self, images,
-                 deploy_kernel_name=constants.DEFAULT_DEPLOY_KERNEL_NAME,
-                 deploy_ramdisk_name=constants.DEFAULT_DEPLOY_RAMDISK_NAME):
+    def __init__(self, nodes, images):
         super(CheckBootImagesAction, self).__init__()
+        self.nodes = nodes
         self.images = images
-        self.deploy_kernel_name = deploy_kernel_name
-        self.deploy_ramdisk_name = deploy_ramdisk_name
 
     def run(self, context):
         messages = []
-        kernel_id = self._check_for_image(self.deploy_kernel_name, messages)
-        ramdisk_id = self._check_for_image(self.deploy_ramdisk_name, messages)
+
+        arches = {}
+        for node in self.nodes:
+            properties = node['properties']
+            if 'cpu_arch' in properties:
+                arches[properties['cpu_arch']] = True
+
+        for arch in arches.keys():
+            self._check_for_image("%s-bm-deploy-kernel" % arch, messages)
+            self._check_for_image("%s-bm-deploy-ramdisk" % arch, messages)
 
         return_value = {
-            'kernel_id': kernel_id,
-            'ramdisk_id': ramdisk_id,
             'errors': messages,
             'warnings': []
         }
@@ -181,10 +184,6 @@ class CheckBootImagesAction(base.TripleOAction):
             messages.append(multiple_message.format(name))
         elif len(found_images) == 0:
             messages.append(missing_message.format(name))
-        else:
-            image_id = found_images[0]
-
-        return image_id
 
 
 class CheckFlavorsAction(base.TripleOAction):
@@ -265,31 +264,61 @@ class CheckNodeBootConfigurationAction(base.TripleOAction):
 
     # TODO(bcrochet): The validation actions are temporary. This logic should
     #                 move to the tripleo-validations project eventually.
-    def __init__(self, node, kernel_id, ramdisk_id):
+    def __init__(self, node, images):
         super(CheckNodeBootConfigurationAction, self).__init__()
 
         self.node = node
-        self.kernel_id = kernel_id
-        self.ramdisk_id = ramdisk_id
+        self.images = images
+
+    def _check_for_image(self, name):
+        found_images = [item['id'] for item in self.images
+                        if item['name'] == name]
+        if len(found_images) == 1:
+            return found_images[0]
+        else:
+            return None
 
     def run(self, context):
         warnings = []
         errors = []
+
+        cpu_arch = None
+        if self.node is None:
+            errors.append("self.node is None ")
+        elif 'properties' in self.node:
+            properties = self.node['properties']
+            if 'cpu_arch' in properties:
+                cpu_arch = properties['cpu_arch']
+            else:
+                errors.append("cpu_arch not in properties (%s) " % properties)
+        else:
+            errors.append("properties not in node (%s)" % self.node)
+
+        if cpu_arch is not None:
+            kernel_name = cpu_arch+'-bm-deploy-kernel'
+            ramdisk_name = cpu_arch+'-bm-deploy-ramdisk'
+            kernel_id = self._check_for_image(kernel_name)
+            ramdisk_id = self._check_for_image(ramdisk_name)
+            if kernel_id is None:
+                errors.append("%s is missing in image store" % kernel_name)
+            if ramdisk_id is None:
+                errors.append("%s is missing in image store" % ramdisk_name)
+
         message = ("Node {uuid} has an incorrectly configured "
                    "{property}. Expected \"{expected}\" but got "
                    "\"{actual}\".")
-        if self.node['driver_info'].get('deploy_ramdisk') != self.ramdisk_id:
+        if self.node['driver_info'].get('deploy_ramdisk') != ramdisk_id:
             errors.append(message.format(
                 uuid=self.node['uuid'],
                 property='driver_info/deploy_ramdisk',
-                expected=self.ramdisk_id,
+                expected=ramdisk_id,
                 actual=self.node['driver_info'].get('deploy_ramdisk')
             ))
-        if self.node['driver_info'].get('deploy_kernel') != self.kernel_id:
+        if self.node['driver_info'].get('deploy_kernel') != kernel_id:
             errors.append(message.format(
                 uuid=self.node['uuid'],
                 property='driver_info/deploy_kernel',
-                expected=self.kernel_id,
+                expected=kernel_id,
                 actual=self.node['driver_info'].get('deploy_kernel')
             ))
         capabilities = nodeutils.capabilities_to_dict(
diff --git a/tripleo_common/constants.py b/tripleo_common/constants.py
index e770412..4e00ec0 100644
--- a/tripleo_common/constants.py
+++ b/tripleo_common/constants.py
@@ -133,10 +133,6 @@ PLAN_ENVIRONMENT = 'plan-environment.yaml'
 CONTAINER_DEFAULTS_ENVIRONMENT = ('environments/'
                                   'containers-default-parameters.yaml')
 
-DEFAULT_DEPLOY_KERNEL_NAME = 'bm-deploy-kernel'
-
-DEFAULT_DEPLOY_RAMDISK_NAME = 'bm-deploy-ramdisk'
-
 # The name for the swift container to host the cache for tripleo
 TRIPLEO_CACHE_CONTAINER = "__cache__"
 
diff --git a/tripleo_common/tests/utils/test_glance.py b/tripleo_common/tests/utils/test_glance.py
index 6a3512e..8cb6821 100644
--- a/tripleo_common/tests/utils/test_glance.py
+++ b/tripleo_common/tests/utils/test_glance.py
@@ -20,81 +20,207 @@ from glanceclient import exc as exceptions
 import mock
 import testtools
 
-from tripleo_common.tests import base
+from tripleo_common.actions import base
+from tripleo_common.tests import base as tests_base
 from tripleo_common.utils import glance
+from tripleo_common.actions import baremetal
 
-
-class GlanceTest(base.TestCase):
+class GlanceTest(tests_base.TestCase):
 
     def setUp(self):
         super(GlanceTest, self).setUp()
+
+        self.baremetal = mock.MagicMock()
+        self.baremetal.node.list.side_effect = [ [
+            {'uuid': '49964b69-9d27-429d-a6bb-70e29352fd40',
+             'driver': 'pxe_ipmitool',
+             'driver_info': {
+                 'deploy_kernel': '6b42ff07-a5f0-474c-9659-b7f0b32d2a97',
+                 'ipmi_address': '10.0.0.1',
+                 'deploy_ramdisk': 'bf48a969-8ded-4693-8524-ab93a6b0d36f',
+                 'ipmi_password': '******',
+                 'ipmi_username': 'username'},
+             'properties': {
+                 'memory_mb': '1048576',
+                 'cpu_arch': 'x86_64',
+                 'local_gb': '1000',
+                 'cpus': '2',
+                 'capabilities': 'profile:control,boot_option:local'}},
+            {'uuid': '5eb338de-9d5b-4c98-b4fe-544e53708973',
+             'driver': 'pxe_ipmitool',
+             'driver_info': {
+                 'deploy_kernel': '7a2d3c4b-1a58-4731-b716-3390ce55881e',
+                 'ipmi_address': '10.0.0.2',
+                 'deploy_ramdisk': 'a2ca8956-4c9b-4824-8aee-eea12a7e3232',
+                 'ipmi_password': '******'},
+             'properties': {
+                 'memory_mb': '1048576',
+                 'cpu_arch': 'ppc64le',
+                 'local_gb': '1000',
+                 'cpus': '16',
+                 'capabilities': 'profile:compute,boot_option:local'}}
+        ] ]
+
+        self.glance = mock.MagicMock()
+        glance_patcher = mock.patch(
+            'tripleo_common.actions.base.TripleOAction.get_image_client',
+            return_value=self.glance)
+        self.mock_glance = glance_patcher.start()
+        self.addCleanup(glance_patcher.stop)
+
+        def mock_find(name, disk_format):
+            if name == 'x86_64-bm-deploy-kernel':
+                return mock.MagicMock(id='k_x_id')
+            elif name == 'x86_64-bm-deploy-ramdisk':
+                return mock.MagicMock(id='r_x_id')
+            elif name == 'ppc64le-bm-deploy-kernel':
+                return mock.MagicMock(id='k_p_id')
+            elif name == 'ppc64le-bm-deploy-ramdisk':
+                return mock.MagicMock(id='r_p_id')
+        self.glance.images.find = mock_find
+
+        self.glance_nok = mock.MagicMock()
+        glance_patcher2 = mock.patch(
+            'tripleo_common.actions.base.TripleOAction.get_image_client',
+            return_value=self.glance_nok)
+        self.mock_glance2 = glance_patcher2.start()
+        self.addCleanup(glance_patcher2.stop)
+
+        def mock_find_nok(name, disk_format):
+            if name == 'x86_64-bm-deploy-ramdisk':
+                return mock.MagicMock(id='r_x_id')
+            elif name == 'ppc64le-bm-deploy-kernel':
+                return mock.MagicMock(id='k_p_id')
+            elif name == 'ppc64le-bm-deploy-ramdisk':
+                return mock.MagicMock(id='r_p_id')
+        self.glance_nok.images.find = mock_find_nok
+
+        self.glance_nor = mock.MagicMock()
+        glance_patcher3 = mock.patch(
+            'tripleo_common.actions.base.TripleOAction.get_image_client',
+            return_value=self.glance_nor)
+        self.mock_glance3 = glance_patcher3.start()
+        self.addCleanup(glance_patcher3.stop)
+
+        def mock_find_nor(name, disk_format):
+            if name == 'x86_64-bm-deploy-kernel':
+                return mock.MagicMock(id='k_x_id')
+            elif name == 'ppc64le-bm-deploy-kernel':
+                return mock.MagicMock(id='k_p_id')
+            elif name == 'ppc64le-bm-deploy-ramdisk':
+                return mock.MagicMock(id='r_p_id')
+        self.glance_nor.images.find = mock_find_nor
+
+        self.glance_nokr = mock.MagicMock()
+        glance_patcher4 = mock.patch(
+            'tripleo_common.actions.base.TripleOAction.get_image_client',
+            return_value=self.glance_nokr)
+        self.mock_glance4 = glance_patcher4.start()
+        self.addCleanup(glance_patcher4.stop)
+
+        def mock_find_nokr(name, disk_format):
+            if name == 'ppc64le-bm-deploy-kernel':
+                return mock.MagicMock(id='k_p_id')
+            elif name == 'ppc64le-bm-deploy-ramdisk':
+                return mock.MagicMock(id='r_p_id')
+        self.glance_nokr.images.find = mock_find_nokr
+
+        self.context = mock.MagicMock()
+
         self.image = collections.namedtuple('image', ['id'])
 
+        self.action = base.TripleOAction()
+
     def test_return_existing_kernel_and_ramdisk(self):
-        client = mock.MagicMock()
-        expected = {'kernel': 'aaa', 'ramdisk': 'zzz'}
-        client.images.find.side_effect = (self.image('aaa'), self.image('zzz'))
-        ids = glance.create_or_find_kernel_and_ramdisk(client, 'bm-kernel',
-                                                       'bm-ramdisk')
-        client.images.create.assert_not_called()
+
+        expected = {
+            'ppc64le': {'ramdisk': 'r_p_id', 'kernel': 'k_p_id'},
+            'x86_64': {'ramdisk': 'r_x_id', 'kernel': 'k_x_id'}
+        }
+        ids = glance.create_or_find_kernel_and_ramdisk(self.glance,
+            self.baremetal)
+
+        self.glance.images.create.assert_not_called()
         self.assertEqual(expected, ids)
 
     def test_raise_exception_kernel(self):
-        client = mock.MagicMock()
-        client.images.find.side_effect = exceptions.NotFound
+
         message = "Kernel image not found in Glance, and no path specified."
         with testtools.ExpectedException(ValueError, message):
-            glance.create_or_find_kernel_and_ramdisk(client, 'bm-kernel',
-                                                     None)
+            glance.create_or_find_kernel_and_ramdisk(self.glance_nok,
+                                                     self.baremetal)
 
     def test_raise_exception_ramdisk(self):
-        client = mock.MagicMock()
-        client.images.find.side_effect = (self.image('aaa'),
-                                          exceptions.NotFound)
         message = "Ramdisk image not found in Glance, and no path specified."
         with testtools.ExpectedException(ValueError, message):
-            glance.create_or_find_kernel_and_ramdisk(client, 'bm-kernel',
-                                                     'bm-ramdisk')
+            glance.create_or_find_kernel_and_ramdisk(self.glance_nor,
+                                                     self.baremetal)
 
     def test_skip_missing_no_kernel(self):
-        client = mock.MagicMock()
-        client.images.find.side_effect = (exceptions.NotFound,
-                                          self.image('bbb'))
-        expected = {'kernel': None, 'ramdisk': 'bbb'}
-        ids = glance.create_or_find_kernel_and_ramdisk(
-            client, 'bm-kernel', 'bm-ramdisk', skip_missing=True)
+        expected = {
+            'ppc64le': {'ramdisk': 'r_p_id', 'kernel': 'k_p_id'},
+            'x86_64': {'ramdisk': 'r_x_id', 'kernel': None}
+        }
+        ids = glance.create_or_find_kernel_and_ramdisk(self.glance_nok,
+                                                       self.baremetal,
+                                                       skip_missing=True)
         self.assertEqual(ids, expected)
 
     def test_skip_missing_no_ramdisk(self):
-        client = mock.MagicMock()
-        client.images.find.side_effect = (self.image('aaa'),
-                                          exceptions.NotFound)
-        expected = {'kernel': 'aaa', 'ramdisk': None}
-        ids = glance.create_or_find_kernel_and_ramdisk(
-            client, 'bm-kernel', 'bm-ramdisk', skip_missing=True)
+        expected = {
+            'ppc64le': {'ramdisk': 'r_p_id', 'kernel': 'k_p_id'},
+            'x86_64': {'ramdisk': None, 'kernel': 'k_x_id'}
+        }
+        ids = glance.create_or_find_kernel_and_ramdisk(self.glance_nor,
+                                                       self.baremetal,
+                                                       skip_missing=True)
         self.assertEqual(ids, expected)
 
     def test_skip_missing_kernel_and_ramdisk(self):
-        client = mock.MagicMock()
-        client.images.find.side_effect = exceptions.NotFound
-        expected = {'kernel': None, 'ramdisk': None}
-        ids = glance.create_or_find_kernel_and_ramdisk(
-            client, 'bm-kernel', 'bm-ramdisk', skip_missing=True)
+        expected = {
+            'ppc64le': {'ramdisk': 'r_p_id', 'kernel': 'k_p_id'},
+            'x86_64': {'ramdisk': None, 'kernel': None}
+        }
+        ids = glance.create_or_find_kernel_and_ramdisk(self.glance_nokr,
+                                                       self.baremetal,
+                                                       skip_missing=True)
         self.assertEqual(ids, expected)
 
     def test_create_kernel_and_ramdisk(self):
-        client = mock.MagicMock()
-        client.images.find.side_effect = exceptions.NotFound
-        client.images.create.side_effect = (self.image('aaa'),
-                                            self.image('zzz'))
-        expected = {'kernel': 'aaa', 'ramdisk': 'zzz'}
+        glance_cnokr = mock.MagicMock()
+        glance_patcher = mock.patch(
+            'tripleo_common.actions.base.TripleOAction.get_image_client',
+            return_value=glance_cnokr)
+        mock_glance = glance_patcher.start()
+        self.addCleanup(glance_patcher.stop)
+
+        def mock_find_cnokr(name, disk_format):
+            if name == 'ppc64le-bm-deploy-kernel':
+                return mock.MagicMock(id='k_p_id')
+            elif name == 'ppc64le-bm-deploy-ramdisk':
+                return mock.MagicMock(id='r_p_id')
+        glance_cnokr.images.find = mock_find_cnokr
+        # NOTE: The kernel is created first if missing
+        glance_cnokr.images.create.side_effect = (self.image('new_kernel'),
+                                                  self.image('new_ramdisk'))
+
+        expected = {
+            'ppc64le': {'ramdisk': 'r_p_id', 'kernel': 'k_p_id'},
+            'x86_64': {'ramdisk': 'new_ramdisk', 'kernel': 'new_kernel'}
+        }
         with tempfile.NamedTemporaryFile() as imagefile:
-            ids = glance.create_or_find_kernel_and_ramdisk(
-                client, 'bm-kernel', 'bm-ramdisk', kernel_path=imagefile.name,
+            ids = glance.create_or_find_kernel_and_ramdisk(glance_cnokr,
+                self.baremetal,
+                kernel_path=imagefile.name,
                 ramdisk_path=imagefile.name)
-        kernel_create = mock.call(name='bm-kernel', disk_format='aki',
-                                  is_public=True, data=mock.ANY)
-        ramdisk_create = mock.call(name='bm-ramdisk', disk_format='ari',
-                                   is_public=True, data=mock.ANY)
-        client.images.create.assert_has_calls([kernel_create, ramdisk_create])
+        kernel_create = mock.call(name='x86_64-bm-deploy-kernel',
+                                  disk_format='aki',
+                                  is_public=True,
+                                  data=mock.ANY)
+        ramdisk_create = mock.call(name='x86_64-bm-deploy-ramdisk',
+                                   disk_format='ari',
+                                   is_public=True,
+                                   data=mock.ANY)
+        glance_cnokr.images.create.assert_has_calls([kernel_create,
+            ramdisk_create])
         self.assertEqual(expected, ids)
diff --git a/tripleo_common/tests/utils/test_nodes.py b/tripleo_common/tests/utils/test_nodes.py
index e56e25a..e2f77f5 100644
--- a/tripleo_common/tests/utils/test_nodes.py
+++ b/tripleo_common/tests/utils/test_nodes.py
@@ -17,11 +17,19 @@ import collections
 
 import mock
 from testtools import matchers
+import six
+import copy
+import uuid
 
 from tripleo_common import exception
 from tripleo_common.tests import base
 from tripleo_common.utils import nodes
 
+_KNOWN_INTERFACE_FIELDS = [
+    '%s_interface' % field for field in ('boot', 'console', 'deploy',
+                                         'inspect', 'management', 'network',
+                                         'power', 'raid', 'storage', 'vendor')
+]
 
 class DriverInfoTest(base.TestCase):
     def setUp(self):
@@ -256,14 +264,119 @@ class FindNodeHandlerTest(base.TestCase):
 
 class NodesTest(base.TestCase):
 
-    def _get_node(self):
-        return {'cpu': '1', 'memory': '2048', 'disk': '30', 'arch': 'amd64',
-                'mac': ['aaa'], 'pm_addr': 'foo.bar', 'pm_user': 'test',
-                'pm_password': 'random', 'pm_type': 'ipmi', 'name': 'node1',
-                'capabilities': 'num_nics:6'}
+    def _get_nodes(self):
+        return [
+            {
+                "pm_type":"pxe_ipmitool",
+                "mac":[
+                    "1a:f3:fc:36:dd:68"
+                ],
+                "cpu":"2",
+                "memory":"1048576",
+                "disk":"500",
+                "arch":"x86_64",
+                "pm_user":"USER",
+                "pm_password":"PASS",
+                "pm_addr":"10.0.0.1"
+            },
+            {
+                "pm_type":"pxe_ipmitool",
+                "mac":[
+                    "1a:90:fa:74:05:53"
+                ],
+                "cpu":"16",
+                "memory":"1048576",
+                "disk":"1000",
+                "arch":"ppc64le",
+                "pm_password":"SSAP",
+                "pm_addr":"10.0.0.2"
+            }
+        ]
+
+    def _get_glance_client(self):
+        self.glance = mock.MagicMock()
+        glance_patcher = mock.patch(
+            'tripleo_common.actions.base.TripleOAction.get_image_client',
+            return_value=self.glance)
+        self.mock_glance = glance_patcher.start()
+        self.addCleanup(glance_patcher.stop)
+
+        def mock_find(name, disk_format):
+            if name == 'x86_64-bm-deploy-kernel':
+                return mock.MagicMock(id='k_x_id')
+            elif name == 'x86_64-bm-deploy-ramdisk':
+                return mock.MagicMock(id='r_x_id')
+            elif name == 'ppc64le-bm-deploy-kernel':
+                return mock.MagicMock(id='k_p_id')
+            elif name == 'ppc64le-bm-deploy-ramdisk':
+                return mock.MagicMock(id='r_p_id')
+        self.glance.images.find = mock_find
+
+        return self.glance()
+
+    def _dict_to_capabilities(self, caps_dict):
+        return ','.join(["%s:%s" % (key, value)
+                         for key, value in caps_dict.items()
+                         if value is not None])
+
+    def _test_register_all_node(self, node, ironic, glance_client):
+        mapping1 = {'cpus': 'cpu',
+                    'memory_mb': 'memory',
+                    'local_gb': 'disk',
+                    'cpu_arch': 'arch'}
+        node_properties = {k: six.text_type(node.get(v))
+                           for k, v in mapping1.items()
+                           if node.get(v) is not None}
+
+        if node.get('capabilities'):
+            caps = node['capabilities']
+            node_properties['capabilities'] = self._dict_to_capabilities(caps)
+
+        mapping2 = {'ipmi_address': 'pm_addr',
+                    'ipmi_username': 'pm_user',
+                    'ipmi_password': 'pm_password'}
+        node_driver_info = {k: six.text_type(node.get(v))
+                            for k, v in mapping2.items()
+                            if node.get(v) is not None}
+
+        interface_fields = {field: node.pop(field)
+                            for field in _KNOWN_INTERFACE_FIELDS
+                            if field in node}
+
+        image = collections.namedtuple('image', ['id'])
+
+#       if not node.has_key('arch'):
+#           raise exception.InvalidNode('The arch field is missing')
+#
+#       if node['arch'] == 'x86_64':
+#           k_image = glance_client.find (image('k_x_id'))
+#           r_image = glance_client.find (image('r_x_id'))
+#       elif node['arch'] == 'ppc64le':
+#           k_image = glance_client.find (image('k_p_id'))
+#           r_image = glance_client.find (image('r_p_id'))
+#       else:
+#           raise Exception("Unknown arch (%s)" % node['arch'])
+
+        # Filling in a simulated glance image causes
+        # assert_has_calls to fail
+        node_driver_info['deploy_kernel'] = mock.ANY # k_image
+        node_driver_info['deploy_ramdisk'] = mock.ANY # r_image
+
+        register_call = mock.call(driver=node['pm_type'],
+                                  driver_info=node_driver_info,
+                                  resource_class='baremetal',
+                                  properties=node_properties,
+                                  **interface_fields)
+
+        node_uuid = ironic.node.create.return_value.uuid
+        port_call = mock.call(node_uuid=node_uuid,
+                              address=node['mac'][0])
+
+        ironic.node.create.assert_has_calls([register_call])
+        ironic.port.create.assert_has_calls([port_call])
 
     def test_register_all_nodes_ironic_no_hw_stats(self):
-        node_list = [self._get_node()]
+        node_list = self._get_nodes()
 
         # Remove the hardware stats from the node dictionary
         node_list[0].pop("cpu")
@@ -276,121 +389,42 @@ class NodesTest(base.TestCase):
         node_properties = {"capabilities": "num_nics:6"}
 
         ironic = mock.MagicMock()
-        new_nodes = nodes.register_all_nodes(node_list, client=ironic)
-        self.assertEqual([ironic.node.create.return_value], new_nodes)
-        pxe_node_driver_info = {"ipmi_address": "foo.bar",
-                                "ipmi_username": "test",
-                                "ipmi_password": "random"}
-        pxe_node = mock.call(driver="ipmi",
-                             name='node1',
-                             driver_info=pxe_node_driver_info,
-                             resource_class='baremetal',
-                             properties=node_properties)
-        port_call = mock.call(node_uuid=ironic.node.create.return_value.uuid,
-                              address='aaa')
-        ironic.node.create.assert_has_calls([pxe_node, mock.ANY])
-        ironic.port.create.assert_has_calls([port_call])
+        glance_client = self._get_glance_client()
+        self.assertRaisesRegexp(exception.InvalidNode,
+                                '^Invalid node data: The arch field is missing$',
+                                nodes.register_all_nodes,
+                                *[node_list],
+                                **{'client': ironic,
+                                   'glance_client': glance_client})
 
     def test_register_all_nodes(self):
-        node_list = [self._get_node()]
-        node_properties = {"cpus": "1",
-                           "memory_mb": "2048",
-                           "local_gb": "30",
-                           "cpu_arch": "amd64",
-                           "capabilities": "num_nics:6"}
-        ironic = mock.MagicMock()
-        nodes.register_all_nodes(node_list, client=ironic)
-        pxe_node_driver_info = {"ipmi_address": "foo.bar",
-                                "ipmi_username": "test",
-                                "ipmi_password": "random"}
-        pxe_node = mock.call(driver="ipmi",
-                             name='node1',
-                             driver_info=pxe_node_driver_info,
-                             resource_class='baremetal',
-                             properties=node_properties)
-        port_call = mock.call(node_uuid=ironic.node.create.return_value.uuid,
-                              address='aaa')
-        ironic.node.create.assert_has_calls([pxe_node, mock.ANY])
-        ironic.port.create.assert_has_calls([port_call])
+        node_list = self._get_nodes()
 
-    def test_register_all_nodes_kernel_ramdisk(self):
-        node_list = [self._get_node()]
-        node_properties = {"cpus": "1",
-                           "memory_mb": "2048",
-                           "local_gb": "30",
-                           "cpu_arch": "amd64",
-                           "capabilities": "num_nics:6"}
+        glance_client = self._get_glance_client()
         ironic = mock.MagicMock()
-        glance = mock.MagicMock()
-        image = collections.namedtuple('image', ['id'])
-        glance.images.find.side_effect = (image('kernel-123'),
-                                          image('ramdisk-999'))
+
         nodes.register_all_nodes(node_list, client=ironic,
-                                 glance_client=glance, kernel_name='bm-kernel',
-                                 ramdisk_name='bm-ramdisk')
-        pxe_node_driver_info = {"ipmi_address": "foo.bar",
-                                "ipmi_username": "test",
-                                "ipmi_password": "random",
-                                "deploy_kernel": "kernel-123",
-                                "deploy_ramdisk": "ramdisk-999"}
-        pxe_node = mock.call(driver="ipmi",
-                             name='node1',
-                             driver_info=pxe_node_driver_info,
-                             resource_class='baremetal',
-                             properties=node_properties)
-        port_call = mock.call(node_uuid=ironic.node.create.return_value.uuid,
-                              address='aaa')
-        ironic.node.create.assert_has_calls([pxe_node, mock.ANY])
-        ironic.port.create.assert_has_calls([port_call])
+                                 glance_client=glance_client)
 
-    def test_register_all_nodes_uuid(self):
-        node_list = [self._get_node()]
-        node_list[0]['uuid'] = 'abcdef'
-        node_properties = {"cpus": "1",
-                           "memory_mb": "2048",
-                           "local_gb": "30",
-                           "cpu_arch": "amd64",
-                           "capabilities": "num_nics:6"}
-        ironic = mock.MagicMock()
-        nodes.register_all_nodes(node_list, client=ironic)
-        pxe_node_driver_info = {"ipmi_address": "foo.bar",
-                                "ipmi_username": "test",
-                                "ipmi_password": "random"}
-        pxe_node = mock.call(driver="ipmi",
-                             name='node1',
-                             driver_info=pxe_node_driver_info,
-                             properties=node_properties,
-                             resource_class='baremetal',
-                             uuid="abcdef")
-        port_call = mock.call(node_uuid=ironic.node.create.return_value.uuid,
-                              address='aaa')
-        ironic.node.create.assert_has_calls([pxe_node, mock.ANY])
-        ironic.port.create.assert_has_calls([port_call])
+        for node in node_list:
+            self._test_register_all_node(node, ironic, glance_client)
 
     def test_register_all_nodes_caps_dict(self):
-        node_list = [self._get_node()]
-        node_list[0]['capabilities'] = {
-            'num_nics': 7
-        }
-        node_properties = {"cpus": "1",
-                           "memory_mb": "2048",
-                           "local_gb": "30",
-                           "cpu_arch": "amd64",
-                           "capabilities": "num_nics:7"}
+        node_list = self._get_nodes()
+
+        glance_client = self._get_glance_client()
         ironic = mock.MagicMock()
-        nodes.register_all_nodes(node_list, client=ironic)
-        pxe_node_driver_info = {"ipmi_address": "foo.bar",
-                                "ipmi_username": "test",
-                                "ipmi_password": "random"}
-        pxe_node = mock.call(driver="ipmi",
-                             name='node1',
-                             driver_info=pxe_node_driver_info,
-                             resource_class='baremetal',
-                             properties=node_properties)
-        port_call = mock.call(node_uuid=ironic.node.create.return_value.uuid,
-                              address='aaa')
-        ironic.node.create.assert_has_calls([pxe_node, mock.ANY])
-        ironic.port.create.assert_has_calls([port_call])
+
+        num_nics = 7
+        for node in node_list:
+            node['capabilities'] = {'num_nics': num_nics}
+            num_nics = num_nics+1
+
+        nodes.register_all_nodes(node_list, client=ironic,
+                                 glance_client=glance_client)
+
+        for node in node_list:
+            self._test_register_all_node(node, ironic, glance_client)
 
     def test_register_all_nodes_with_interfaces(self):
         interfaces = {'boot_interface': 'pxe',
@@ -404,30 +438,166 @@ class NodesTest(base.TestCase):
                       'storage_interface': 'cinder',
                       'vendor_interface': 'ipmitool'}
 
-        node_list = [self._get_node()]
+        node_list = self._get_nodes()
         node_list[0].update(interfaces)
-        node_properties = {"cpus": "1",
-                           "memory_mb": "2048",
-                           "local_gb": "30",
-                           "cpu_arch": "amd64",
-                           "capabilities": "num_nics:6"}
+
+        glance_client = self._get_glance_client()
         ironic = mock.MagicMock()
-        nodes.register_all_nodes(node_list, client=ironic)
-        pxe_node_driver_info = {"ipmi_address": "foo.bar",
-                                "ipmi_username": "test",
-                                "ipmi_password": "random"}
-        pxe_node = mock.call(driver="ipmi",
-                             name='node1',
-                             driver_info=pxe_node_driver_info,
-                             properties=node_properties,
-                             resource_class='baremetal',
-                             **interfaces)
-        port_call = mock.call(node_uuid=ironic.node.create.return_value.uuid,
-                              address='aaa')
-        ironic.node.create.assert_has_calls([pxe_node, mock.ANY])
-        ironic.port.create.assert_has_calls([port_call])
+
+        # Register all nodes can be destructive
+        new_node_list = copy.deepcopy(node_list)
+
+        nodes.register_all_nodes(new_node_list, client=ironic,
+                                 glance_client=glance_client)
+
+        for node in node_list:
+            self._test_register_all_node(node, ironic, glance_client)
+
+    def _test_update_register(self, ironic, node_map, node_list,
+                              pm_type='ipmi'):
+        if len(ironic.node.update.mock_calls) == 0:
+            raise Exception("No calls made to ironic.node.update")
+
+        # Loop through found calls to ironic.node.update
+        for mc in ironic.node.update.mock_calls:
+            # Break out the arguments to the call
+            name = None
+            args = None
+            kwargs = None
+            if len(mc) == 3:
+                (name, args, kwargs) = mc
+            elif len(mc) == 2:
+                (args, kwargs) = mc
+            else:
+                raise Exception("Unknown call tuple size %d" % len(mc))
+
+            (uuid, ops) = args
+
+            found_mac = None
+            for mac in node_map['mac'].keys():
+                if node_map['mac'][mac] == uuid:
+                    found_mac = mac
+            if found_mac is None:
+                raise Exception("uuid not found in node_map")
+
+            found_node = None
+            for node in node_list:
+                if node['mac'][0] == found_mac:
+                    found_node = node
+            if found_node is None:
+                raise Exception("mac not found in node_list")
+
+            expected_ops = []
+            expected_entries = {
+                '/driver_info/deploy_kernel': 'kernel_id',
+                '/driver_info/deploy_ramdisk': 'ramdisk_id',
+                '/driver_info/%_address': 'pm_addr',
+                '/driver_info/%_password': 'pm_password',
+                '/driver_info/%_username': 'pm_user',
+                '/driver_info/%_system_id': 'pm_system_id',
+                '/driver_info/%_vm_name': 'pm_vm_name',
+                '/properties/cpu_arch': 'arch',
+                '/properties/cpus': 'cpu',
+                '/properties/local_gb': 'disk',
+                '/properties/memory_mb': 'memory',
+            }
+
+            for key in expected_entries.keys():
+                node_key = expected_entries[key]
+                idx = key.find('%')
+                if idx > -1:
+                    key = key[:idx]+pm_type+key[idx+1:]
+                if found_node.has_key(node_key):
+                    node_value = found_node[node_key]
+                    # Avoid unknown matching problems:
+                    # reference = {'op': 'add',
+                    # 'path': '/driver_info/deploy_kernel',
+                    # 'value': <MagicMock name='mock().find()' id='78256'>}
+                    # actual    = {'op': 'add',
+                    # 'path': '/driver_info/deploy_kernel',
+                    # 'value': u"<MagicMock name='mock().find()' id='78256'>"}
+                    if node_key in ['kernel_id', 'ramdisk_id']:
+                        node_value = mock.ANY
+                    expected_ops.append({'path': key,
+                                         'value': node_value})
+
+            # Add xxx_interface entries
+            for (key, value) in found_node.items():
+                idx = key.find('_interface')
+                if idx > -1:
+                    expected_ops.append({'path': '/%s' % key, 'value': value})
+
+            for key in expected_ops:
+                key['op'] = 'add'
+
+            self.assertThat(ops,
+                            matchers.MatchesSetwise(*(map(matchers.Equals,
+                                                          expected_ops))))
 
     def test_register_update(self):
+        node_list = self._get_nodes()
+
+        ironic = mock.MagicMock()
+
+        # Create the mac addr to uuid mapping
+        node_map = {'mac': {}}
+        uuid = 1
+        for node in node_list:
+            mac = node['mac'][0]
+            node_map['mac'][mac] = uuid
+            uuid = uuid+1
+
+        # Register all nodes can be destructive
+        new_node_list = copy.deepcopy(node_list)
+
+        for node in new_node_list:
+            nodes._update_or_register_ironic_node(node,
+                                                  node_map,
+                                                  client=ironic)
+
+        self._test_update_register(ironic, node_map, node_list)
+
+    def test_register_update_with_images(self):
+        node_list = self._get_nodes()
+
+        glance_client = self._get_glance_client()
+        ironic = mock.MagicMock()
+
+        image = collections.namedtuple('image', ['id'])
+
+        # Give nodes images
+        for node in node_list:
+            if node['arch'] == 'x86_64':
+                k_image = glance_client.find (image('k_x_id'))
+                r_image = glance_client.find (image('r_x_id'))
+            elif node['arch'] == 'ppc64le':
+                k_image = glance_client.find (image('k_p_id'))
+                r_image = glance_client.find (image('r_p_id'))
+            else:
+                raise Exception("Unknown arch (%s)" % node['arch'])
+
+            node['kernel_id'] = k_image
+            node['ramdisk_id'] = r_image
+
+        # Create the mac addr to uuid mapping
+        node_map = {'mac': {}}
+        uuid = 1
+        for node in node_list:
+            mac = node['mac'][0]
+            node_map['mac'][mac] = uuid
+            uuid = uuid + 1
+
+        # Register all nodes can be destructive
+        new_node_list = copy.deepcopy(node_list)
+
+        for node in new_node_list:
+            nodes._update_or_register_ironic_node(node,
+                                                  node_map,
+                                                  client=ironic)
+
+        self._test_update_register(ironic, node_map, node_list)
+
+    def test_register_update_with_interfaces(self):
         interfaces = {'boot_interface': 'pxe',
                       'console_interface': 'ipmitool-socat',
                       'deploy_interface': 'direct',
@@ -439,389 +609,422 @@ class NodesTest(base.TestCase):
                       'storage_interface': 'cinder',
                       'vendor_interface': 'ipmitool'}
 
-        node = self._get_node()
-        node.update(interfaces)
+        node_list = self._get_nodes()
+
         ironic = mock.MagicMock()
-        node_map = {'mac': {'aaa': 1}}
-
-        def side_effect(*args, **kwargs):
-            update_patch = [
-                {'path': '/name', 'value': 'node1'},
-                {'path': '/driver_info/ipmi_password', 'value': 'random'},
-                {'path': '/driver_info/ipmi_address', 'value': 'foo.bar'},
-                {'path': '/properties/memory_mb', 'value': '2048'},
-                {'path': '/properties/local_gb', 'value': '30'},
-                {'path': '/properties/cpu_arch', 'value': 'amd64'},
-                {'path': '/properties/cpus', 'value': '1'},
-                {'path': '/properties/capabilities', 'value': 'num_nics:6'},
-                {'path': '/driver_info/ipmi_username', 'value': 'test'}]
-            for iface, value in interfaces.items():
-                update_patch.append({'path': '/%s' % iface, 'value': value})
-            for key in update_patch:
-                key['op'] = 'add'
-            self.assertThat(update_patch,
-                            matchers.MatchesSetwise(*(map(matchers.Equals,
-                                                          args[1]))))
-            return mock.Mock(uuid='uuid1')
 
-        ironic.node.update.side_effect = side_effect
-        nodes._update_or_register_ironic_node(node, node_map, client=ironic)
-        ironic.node.update.assert_called_once_with(1, mock.ANY)
+        # Give nodes interfaces
+        for node in node_list:
+            node.update(interfaces)
 
-    def test_register_update_with_images(self):
-        node = self._get_node()
-        node['kernel_id'] = 'image-k'
-        node['ramdisk_id'] = 'image-r'
-        ironic = mock.MagicMock()
-        node_map = {'mac': {'aaa': 1}}
-
-        def side_effect(*args, **kwargs):
-            update_patch = [
-                {'path': '/name', 'value': 'node1'},
-                {'path': '/driver_info/ipmi_password', 'value': 'random'},
-                {'path': '/driver_info/ipmi_address', 'value': 'foo.bar'},
-                {'path': '/properties/memory_mb', 'value': '2048'},
-                {'path': '/properties/local_gb', 'value': '30'},
-                {'path': '/properties/cpu_arch', 'value': 'amd64'},
-                {'path': '/properties/cpus', 'value': '1'},
-                {'path': '/properties/capabilities', 'value': 'num_nics:6'},
-                {'path': '/driver_info/deploy_kernel', 'value': 'image-k'},
-                {'path': '/driver_info/deploy_ramdisk', 'value': 'image-r'},
-                {'path': '/driver_info/ipmi_username', 'value': 'test'}]
-            for key in update_patch:
-                key['op'] = 'add'
-            self.assertThat(update_patch,
-                            matchers.MatchesSetwise(*(map(matchers.Equals,
-                                                          args[1]))))
-            return mock.Mock(uuid='uuid1')
+        # Create the mac addr to uuid mapping
+        node_map = {'mac': {}}
+        uuid = 1
+        for node in node_list:
+            mac = node['mac'][0]
+            node_map['mac'][mac] = uuid
+            uuid = uuid + 1
 
-        ironic.node.update.side_effect = side_effect
-        nodes._update_or_register_ironic_node(node, node_map, client=ironic)
-        ironic.node.update.assert_called_once_with(1, mock.ANY)
+        # Register all nodes can be destructive
+        new_node_list = copy.deepcopy(node_list)
 
-    def test_register_update_with_interfaces(self):
-        node = self._get_node()
-        ironic = mock.MagicMock()
-        node_map = {'mac': {'aaa': 1}}
-
-        def side_effect(*args, **kwargs):
-            update_patch = [
-                {'path': '/name', 'value': 'node1'},
-                {'path': '/driver_info/ipmi_password', 'value': 'random'},
-                {'path': '/driver_info/ipmi_address', 'value': 'foo.bar'},
-                {'path': '/properties/memory_mb', 'value': '2048'},
-                {'path': '/properties/local_gb', 'value': '30'},
-                {'path': '/properties/cpu_arch', 'value': 'amd64'},
-                {'path': '/properties/cpus', 'value': '1'},
-                {'path': '/properties/capabilities', 'value': 'num_nics:6'},
-                {'path': '/driver_info/ipmi_username', 'value': 'test'}]
-            for key in update_patch:
-                key['op'] = 'add'
-            self.assertThat(update_patch,
-                            matchers.MatchesSetwise(*(map(matchers.Equals,
-                                                          args[1]))))
-            return mock.Mock(uuid='uuid1')
+        for node in new_node_list:
+            nodes._update_or_register_ironic_node(node,
+                                                  node_map,
+                                                  client=ironic)
 
-        ironic.node.update.side_effect = side_effect
-        nodes._update_or_register_ironic_node(node, node_map, client=ironic)
-        ironic.node.update.assert_called_once_with(1, mock.ANY)
+        self._test_update_register(ironic, node_map, node_list)
+
+    def _update_by_type(self, node_pm_type, di_pm_type):
+        node_list = self._get_nodes()
 
-    def _update_by_type(self, pm_type):
         ironic = mock.MagicMock()
-        node_map = {'mac': {}, 'pm_addr': {}}
-        node = self._get_node()
-        node['pm_type'] = pm_type
-        node_map['pm_addr']['foo.bar'] = ironic.node.get.return_value.uuid
-        nodes._update_or_register_ironic_node(node, node_map, client=ironic)
-        ironic.node.update.assert_called_once_with(
-            ironic.node.get.return_value.uuid, mock.ANY)
+
+        # Change the node type
+        for node in node_list:
+            node['pm_type'] = node_pm_type
+
+        # Create the mac addr to uuid mapping
+        node_map = {'mac': {}}
+        uuid = 1
+        for node in node_list:
+            mac = node['mac'][0]
+            node_map['mac'][mac] = uuid
+            uuid = uuid+1
+
+        # Register all nodes can be destructive
+        new_node_list = copy.deepcopy(node_list)
+
+        for node in new_node_list:
+            nodes._update_or_register_ironic_node(node,
+                                                  node_map,
+                                                  client=ironic)
+
+        self._test_update_register(ironic,
+                                   node_map,
+                                   node_list,
+                                   pm_type=di_pm_type)
 
     def test_update_node_ironic_ipmi(self):
-        self._update_by_type('ipmi')
+        self._update_by_type('ipmi', 'ipmi')
 
     def test_update_node_ironic_pxe_ipmitool(self):
-        self._update_by_type('pxe_ipmitool')
+        self._update_by_type('pxe_ipmitool', 'ipmi')
 
     def test_update_node_ironic_idrac(self):
-        self._update_by_type('idrac')
+        self._update_by_type('idrac', 'drac')
 
     def test_update_node_ironic_pxe_drac(self):
-        self._update_by_type('pxe_drac')
+        self._update_by_type('pxe_drac', 'drac')
 
     def test_update_node_ironic_ilo(self):
-        self._update_by_type('ilo')
+        self._update_by_type('ilo', 'ilo')
 
     def test_update_node_ironic_pxe_ilo(self):
-        self._update_by_type('pxe_ilo')
+        self._update_by_type('pxe_ilo', 'ilo')
 
     def test_update_node_ironic_irmc(self):
-        self._update_by_type('irmc')
+        self._update_by_type('irmc', 'irmc')
 
     def test_update_node_ironic_pxe_irmc(self):
-        self._update_by_type('pxe_irmc')
+        self._update_by_type('pxe_irmc', 'irmc')
 
     def test_update_node_ironic_redfish(self):
+        node_list = self._get_nodes()
+
         ironic = mock.MagicMock()
+
+        # Change the node type
+        for node in node_list:
+            node['pm_type'] = 'redfish'
+            node['pm_system_id'] = '/path'
+
+        # Create the mac addr to uuid mapping
         node_map = {'mac': {}, 'pm_addr': {}}
-        node = self._get_node()
-        node.update({'pm_type': 'redfish',
-                     'pm_system_id': '/path'})
-        node_map['pm_addr']['foo.bar/path'] = ironic.node.get.return_value.uuid
-        nodes._update_or_register_ironic_node(node, node_map, client=ironic)
-        ironic.node.update.assert_called_once_with(
-            ironic.node.get.return_value.uuid, mock.ANY)
+        uuid = 1
+        for node in node_list:
+            mac = node['mac'][0]
+            node_map['mac'][mac] = uuid
+            node_map['pm_addr']['foo.bar/path'] = uuid
+            uuid = uuid+1
+
+        # Register all nodes can be destructive
+        new_node_list = copy.deepcopy(node_list)
+
+        for node in new_node_list:
+            nodes._update_or_register_ironic_node(node,
+                                                  node_map,
+                                                  client=ironic)
+
+        self._test_update_register(ironic,
+                                   node_map,
+                                   node_list,
+                                   pm_type='redfish')
 
     def test_update_node_ironic_ovirt(self):
+        node_list = self._get_nodes()
+
         ironic = mock.MagicMock()
-        node_map = {'mac': {}, 'pm_addr': {}}
-        node = self._get_node()
-        node.update({'pm_type': 'staging-ovirt',
-                     'pm_vm_name': 'VM1'})
-        node_map['pm_addr']['foo.bar:VM1'] = ironic.node.get.return_value.uuid
-        nodes._update_or_register_ironic_node(node, node_map, client=ironic)
-        ironic.node.update.assert_called_once_with(
-            ironic.node.get.return_value.uuid, mock.ANY)
-
-    def test_register_node_update(self):
-        node = self._get_node()
-        node['mac'][0] = node['mac'][0].upper()
-        ironic = mock.MagicMock()
-        node_map = {'mac': {'aaa': 1}}
-
-        def side_effect(*args, **kwargs):
-            update_patch = [
-                {'path': '/name', 'value': 'node1'},
-                {'path': '/driver_info/ipmi_password', 'value': 'random'},
-                {'path': '/driver_info/ipmi_address', 'value': 'foo.bar'},
-                {'path': '/properties/memory_mb', 'value': '2048'},
-                {'path': '/properties/local_gb', 'value': '30'},
-                {'path': '/properties/cpu_arch', 'value': 'amd64'},
-                {'path': '/properties/cpus', 'value': '1'},
-                {'path': '/properties/capabilities', 'value': 'num_nics:6'},
-                {'path': '/driver_info/ipmi_username', 'value': 'test'}]
-            for key in update_patch:
-                key['op'] = 'add'
-            self.assertThat(update_patch,
-                            matchers.MatchesSetwise(*(map(matchers.Equals,
-                                                          args[1]))))
-            return mock.Mock(uuid='uuid1')
 
-        ironic.node.update.side_effect = side_effect
-        nodes._update_or_register_ironic_node(node, node_map, client=ironic)
-        ironic.node.update.assert_called_once_with(1, mock.ANY)
+        # Change the node type
+        num_vm = 1
+        for node in node_list:
+            node['pm_type'] = 'staging-ovirt'
+            node['pm_vm_name'] = 'VM%d' % num_vm
+            num_vm = num_vm+1
+
+        # Create the mac addr to uuid mapping
+        node_map = {'mac': {}, 'pm_addr': {}}
+        uuid = 1
+        for node in node_list:
+            mac = node['mac'][0]
+            node_map['mac'][mac] = uuid
+            node_map['pm_addr']['foo.bar:%s' % node['pm_vm_name']] = uuid
+            uuid = uuid+1
+
+        # Register all nodes can be destructive
+        new_node_list = copy.deepcopy(node_list)
+
+        for node in new_node_list:
+            nodes._update_or_register_ironic_node(node,
+                                                  node_map,
+                                                  client=ironic)
+
+        self._test_update_register(ironic,
+                                   node_map,
+                                   node_list,
+                                   pm_type='ovirt')
 
     def test_register_node_update_with_uuid(self):
-        node = self._get_node()
-        node['uuid'] = 'abcdef'
+        node_list = self._get_nodes()
+
         ironic = mock.MagicMock()
-        node_map = {'uuids': {'abcdef'}}
-
-        def side_effect(*args, **kwargs):
-            update_patch = [
-                {'path': '/name', 'value': 'node1'},
-                {'path': '/driver_info/ipmi_password', 'value': 'random'},
-                {'path': '/driver_info/ipmi_address', 'value': 'foo.bar'},
-                {'path': '/properties/memory_mb', 'value': '2048'},
-                {'path': '/properties/local_gb', 'value': '30'},
-                {'path': '/properties/cpu_arch', 'value': 'amd64'},
-                {'path': '/properties/cpus', 'value': '1'},
-                {'path': '/properties/capabilities', 'value': 'num_nics:6'},
-                {'path': '/driver_info/ipmi_username', 'value': 'test'}]
-            for key in update_patch:
-                key['op'] = 'add'
-            self.assertThat(update_patch,
-                            matchers.MatchesSetwise(*(map(matchers.Equals,
-                                                          args[1]))))
-            return mock.Mock(uuid='abcdef')
 
-        ironic.node.update.side_effect = side_effect
-        nodes._update_or_register_ironic_node(node, node_map, client=ironic)
-        ironic.node.update.assert_called_once_with('abcdef', mock.ANY)
+        # Create random uuids for nodes
+        for node in node_list:
+            node['uuid'] = str(uuid.uuid4())
+
+        # Create the mac addr to uuid mapping
+        node_map = {'mac': {}, 'uuids': set()}
+        for node in node_list:
+            mac = node['mac'][0]
+            node_map['mac'][mac] = node['uuid']
+            node_map['uuids'].add(node['uuid'])
+
+        # Register all nodes can be destructive
+        new_node_list = copy.deepcopy(node_list)
+
+        for node in new_node_list:
+            nodes._update_or_register_ironic_node(node,
+                                                  node_map,
+                                                  client=ironic)
+
+        self._test_update_register(ironic, node_map, node_list)
+
+    def _test_register_node(self, ironic, node):
+        mapping1 = {'cpus': 'cpu',
+                    'memory_mb': 'memory',
+                    'local_gb': 'disk',
+                    'cpu_arch': 'arch'}
+        node_properties = {k: six.text_type(node.get(v))
+                           for k, v in mapping1.items()
+                           if node.get(v) is not None}
+
+        if node.get('capabilities'):
+            caps = node['capabilities']
+            node_properties['capabilities'] = self._dict_to_capabilities(caps)
+
+        pm_type = node['pm_type']
+
+        mapping2 = {
+            'ipmi': 'ipmi', 'pxe_ipmitool': 'ipmi', 'fake_pxe': 'ipmi',
+            'pxe_ucs': 'ucs', 'idrac': 'drac', 'pxe_drac': 'drac',
+            'ilo': 'ilo', 'redfish': 'redfish'
+        }
+        if not mapping2.has_key(pm_type):
+            raise Exception("Unknown type %s" % pm_type)
+        pm_field = mapping2[pm_type]
+
+        mapping3 = {}
+        mapping3['%s_address' % pm_field] = 'pm_addr'
+        mapping3['%s_username' % pm_field] = 'pm_user'
+        mapping3['%s_password' % pm_field] = 'pm_password'
+        mapping3['%s_port' % pm_field] = 'pm_port'
+        mapping3['%s_system_id' % pm_field] = 'pm_system_id'
+
+        node_driver_info = {k: six.text_type(node.get(v))
+                            for k, v in mapping3.items()
+                            if node.get(v) is not None}
+
+        interface_fields = {field: node.pop(field)
+                            for field in _KNOWN_INTERFACE_FIELDS
+                            if field in node}
+
+        register_call = mock.call(driver=node['pm_type'],
+                                  driver_info=node_driver_info,
+                                  resource_class='baremetal',
+                                  properties=node_properties,
+                                  **interface_fields)
+
+        node_uuid = ironic.node.create.return_value.uuid
+        port_call = mock.call(node_uuid=node_uuid,
+                              address=node['mac'][0])
+
+        ironic.node.create.assert_has_calls([register_call])
+        ironic.port.create.assert_has_calls([port_call])
 
     def test_register_ironic_node_int_values(self):
-        node_properties = {"cpus": "1",
-                           "memory_mb": "2048",
-                           "local_gb": "30",
-                           "cpu_arch": "amd64",
-                           "capabilities": "num_nics:6"}
-        node = self._get_node()
-        node['cpu'] = 1
-        node['memory'] = 2048
-        node['disk'] = 30
-        client = mock.MagicMock()
-        nodes.register_ironic_node(node, client=client)
-        client.node.create.assert_called_once_with(driver=mock.ANY,
-                                                   name='node1',
-                                                   properties=node_properties,
-                                                   resource_class='baremetal',
-                                                   driver_info=mock.ANY)
+        node_list = self._get_nodes()
+
+        ironic = mock.MagicMock()
+
+        # Convert node values to integers
+        for node in node_list:
+            node['cpu'] = int(node['cpu'])
+            node['memory'] = int(node['memory'])
+            node['disk'] = int(node['disk'])
+
+        # Register all nodes can be destructive
+        new_node_list = copy.deepcopy(node_list)
+        for node in new_node_list:
+            nodes.register_ironic_node(node, client=ironic)
+
+        for node in node_list:
+            self._test_register_node(ironic, node)
 
     def test_register_ironic_node_fake_pxe(self):
-        node_properties = {"cpus": "1",
-                           "memory_mb": "2048",
-                           "local_gb": "30",
-                           "cpu_arch": "amd64",
-                           "capabilities": "num_nics:6"}
-        node = self._get_node()
-        for v in ('pm_addr', 'pm_user', 'pm_password'):
-            del node[v]
-        node['pm_type'] = 'fake_pxe'
-        client = mock.MagicMock()
-        nodes.register_ironic_node(node, client=client)
-        client.node.create.assert_called_once_with(driver='fake_pxe',
-                                                   name='node1',
-                                                   properties=node_properties,
-                                                   resource_class='baremetal',
-                                                   driver_info={})
+        node_list = self._get_nodes()
+
+        ironic = mock.MagicMock()
+
+        # Convert node values to fake_pxe
+        for node in node_list:
+            for v in ('pm_addr', 'pm_user', 'pm_password'):
+                if node.has_key(v):
+                    del node[v]
+            node['pm_type'] = 'fake_pxe'
+
+        # Register all nodes can be destructive
+        new_node_list = copy.deepcopy(node_list)
+        for node in new_node_list:
+            nodes.register_ironic_node(node, client=ironic)
+
+        for node in node_list:
+            self._test_register_node(ironic, node)
 
     def test_register_ironic_node_pxe_ucs(self):
-        node_properties = {"cpus": "1",
-                           "memory_mb": "2048",
-                           "local_gb": "30",
-                           "cpu_arch": "amd64",
-                           "capabilities": "num_nics:6"}
-        node = self._get_node()
-        node['pm_type'] = 'pxe_ucs'
-        client = mock.MagicMock()
-        nodes.register_ironic_node(node, client=client)
-        client.node.create.assert_called_once_with(
-            driver='pxe_ucs', name='node1', properties=node_properties,
-            resource_class='baremetal',
-            driver_info={'ucs_password': 'random', 'ucs_address': 'foo.bar',
-                         'ucs_username': 'test'})
+        node_list = self._get_nodes()
+
+        ironic = mock.MagicMock()
+
+        # Convert node values to pxe_ucs
+        for node in node_list:
+            node['pm_type'] = 'pxe_ucs'
+
+        # Register all nodes can be destructive
+        new_node_list = copy.deepcopy(node_list)
+        for node in new_node_list:
+            nodes.register_ironic_node(node, client=ironic)
+
+        for node in node_list:
+            self._test_register_node(ironic, node)
 
     def test_register_ironic_node_ipmi(self):
-        node_properties = {"cpus": "1",
-                           "memory_mb": "2048",
-                           "local_gb": "30",
-                           "cpu_arch": "amd64",
-                           "capabilities": "num_nics:6"}
-        node = self._get_node()
-        node['pm_type'] = 'ipmi'
-        node['pm_port'] = '6230'
-        client = mock.MagicMock()
-        nodes.register_ironic_node(node, client=client)
-        client.node.create.assert_called_once_with(
-            driver='ipmi', name='node1', properties=node_properties,
-            resource_class='baremetal',
-            driver_info={'ipmi_password': 'random', 'ipmi_address': 'foo.bar',
-                         'ipmi_username': 'test', 'ipmi_port': '6230'})
+        node_list = self._get_nodes()
+
+        ironic = mock.MagicMock()
+
+        # Convert node values to ipmi
+        for node in node_list:
+            node['pm_type'] = 'ipmi'
+            node['pm_port'] = '6230'
+
+        # Register all nodes can be destructive
+        new_node_list = copy.deepcopy(node_list)
+        for node in new_node_list:
+            nodes.register_ironic_node(node, client=ironic)
+
+        for node in node_list:
+            self._test_register_node(ironic, node)
 
     def test_register_ironic_node_pxe_ipmitool(self):
-        node_properties = {"cpus": "1",
-                           "memory_mb": "2048",
-                           "local_gb": "30",
-                           "cpu_arch": "amd64",
-                           "capabilities": "num_nics:6"}
-        node = self._get_node()
-        node['pm_type'] = 'pxe_ipmitool'
-        node['pm_port'] = '6230'
-        client = mock.MagicMock()
-        nodes.register_ironic_node(node, client=client)
-        client.node.create.assert_called_once_with(
-            driver='pxe_ipmitool', name='node1', properties=node_properties,
-            resource_class='baremetal',
-            driver_info={'ipmi_password': 'random', 'ipmi_address': 'foo.bar',
-                         'ipmi_username': 'test', 'ipmi_port': '6230'})
+        node_list = self._get_nodes()
+
+        ironic = mock.MagicMock()
+
+        # Convert node values to pxe_ipmitool
+        for node in node_list:
+            node['pm_type'] = 'pxe_ipmitool'
+            node['pm_port'] = '6230'
+
+        # Register all nodes can be destructive
+        new_node_list = copy.deepcopy(node_list)
+        for node in new_node_list:
+            nodes.register_ironic_node(node, client=ironic)
+
+        for node in node_list:
+            self._test_register_node(ironic, node)
 
     def test_register_ironic_node_idrac(self):
-        node_properties = {"cpus": "1",
-                           "memory_mb": "2048",
-                           "local_gb": "30",
-                           "cpu_arch": "amd64",
-                           "capabilities": "num_nics:6"}
-        node = self._get_node()
-        node['pm_type'] = 'idrac'
-        node['pm_port'] = '6230'
-        client = mock.MagicMock()
-        nodes.register_ironic_node(node, client=client)
-        client.node.create.assert_called_once_with(
-            driver='idrac', name='node1', properties=node_properties,
-            resource_class='baremetal',
-            driver_info={'drac_password': 'random', 'drac_address': 'foo.bar',
-                         'drac_username': 'test', 'drac_port': '6230'})
+        node_list = self._get_nodes()
+
+        ironic = mock.MagicMock()
+
+        # Convert node values to idrac
+        for node in node_list:
+            node['pm_type'] = 'idrac'
+            node['pm_port'] = '6230'
+
+        # Register all nodes can be destructive
+        new_node_list = copy.deepcopy(node_list)
+        for node in new_node_list:
+            nodes.register_ironic_node(node, client=ironic)
+
+        for node in node_list:
+            self._test_register_node(ironic, node)
 
     def test_register_ironic_node_ilo(self):
-        node_properties = {"cpus": "1",
-                           "memory_mb": "2048",
-                           "local_gb": "30",
-                           "cpu_arch": "amd64",
-                           "capabilities": "num_nics:6"}
-        node = self._get_node()
-        node['pm_type'] = 'ilo'
-        node['pm_port'] = '1234'
-        client = mock.MagicMock()
-        nodes.register_ironic_node(node, client=client)
-        client.node.create.assert_called_once_with(
-            driver='ilo', name='node1', properties=node_properties,
-            resource_class='baremetal',
-            driver_info={'ilo_password': 'random', 'ilo_address': 'foo.bar',
-                         'ilo_username': 'test', 'ilo_port': '1234'})
+        node_list = self._get_nodes()
+
+        ironic = mock.MagicMock()
+
+        # Convert node values to ilo
+        for node in node_list:
+            node['pm_type'] = 'ilo'
+            node['pm_port'] = '1234'
+
+        # Register all nodes can be destructive
+        new_node_list = copy.deepcopy(node_list)
+        for node in new_node_list:
+            nodes.register_ironic_node(node, client=ironic)
+
+        for node in node_list:
+            self._test_register_node(ironic, node)
 
     def test_register_ironic_node_pxe_drac(self):
-        node_properties = {"cpus": "1",
-                           "memory_mb": "2048",
-                           "local_gb": "30",
-                           "cpu_arch": "amd64",
-                           "capabilities": "num_nics:6"}
-        node = self._get_node()
-        node['pm_type'] = 'pxe_drac'
-        node['pm_port'] = '6230'
-        client = mock.MagicMock()
-        nodes.register_ironic_node(node, client=client)
-        client.node.create.assert_called_once_with(
-            driver='pxe_drac', name='node1', properties=node_properties,
-            resource_class='baremetal',
-            driver_info={'drac_password': 'random', 'drac_address': 'foo.bar',
-                         'drac_username': 'test', 'drac_port': '6230'})
+        node_list = self._get_nodes()
+
+        ironic = mock.MagicMock()
+
+        # Convert node values to pxe_drac
+        for node in node_list:
+            node['pm_type'] = 'pxe_drac'
+            node['pm_port'] = '6230'
+
+        # Register all nodes can be destructive
+        new_node_list = copy.deepcopy(node_list)
+        for node in new_node_list:
+            nodes.register_ironic_node(node, client=ironic)
+
+        for node in node_list:
+            self._test_register_node(ironic, node)
 
     def test_register_ironic_node_redfish(self):
-        node_properties = {"cpus": "1",
-                           "memory_mb": "2048",
-                           "local_gb": "30",
-                           "cpu_arch": "amd64",
-                           "capabilities": "num_nics:6"}
-        node = self._get_node()
-        node['pm_type'] = 'redfish'
-        node['pm_system_id'] = '/redfish/v1/Systems/1'
-        client = mock.MagicMock()
-        nodes.register_ironic_node(node, client=client)
-        client.node.create.assert_called_once_with(
-            driver='redfish', name='node1', properties=node_properties,
-            resource_class='baremetal',
-            driver_info={'redfish_password': 'random',
-                         'redfish_address': 'foo.bar',
-                         'redfish_username': 'test',
-                         'redfish_system_id': '/redfish/v1/Systems/1'})
+        node_list = self._get_nodes()
+
+        ironic = mock.MagicMock()
+
+        # Convert node values to redfish
+        for node in node_list:
+            node['pm_type'] = 'redfish'
+            node['pm_system_id'] = '/redfish/v1/Systems/1'
+
+        # Register all nodes can be destructive
+        new_node_list = copy.deepcopy(node_list)
+        for node in new_node_list:
+            nodes.register_ironic_node(node, client=ironic)
+
+        for node in node_list:
+            self._test_register_node(ironic, node)
 
     def test_register_ironic_node_update_int_values(self):
-        node = self._get_node()
+        node_list = self._get_nodes()
+
         ironic = mock.MagicMock()
-        node['cpu'] = 1
-        node['memory'] = 2048
-        node['disk'] = 30
-        node_map = {'mac': {'aaa': 1}}
-
-        def side_effect(*args, **kwargs):
-            update_patch = [
-                {'path': '/name', 'value': 'node1'},
-                {'path': '/driver_info/ipmi_password', 'value': 'random'},
-                {'path': '/driver_info/ipmi_address', 'value': 'foo.bar'},
-                {'path': '/properties/memory_mb', 'value': '2048'},
-                {'path': '/properties/local_gb', 'value': '30'},
-                {'path': '/properties/cpu_arch', 'value': 'amd64'},
-                {'path': '/properties/cpus', 'value': '1'},
-                {'path': '/properties/capabilities', 'value': 'num_nics:6'},
-                {'path': '/driver_info/ipmi_username', 'value': 'test'}]
-            for key in update_patch:
-                key['op'] = 'add'
-            self.assertThat(update_patch,
-                            matchers.MatchesSetwise(*(map(matchers.Equals,
-                                                      args[1]))))
-            return mock.Mock(uuid='uuid1')
 
-        ironic.node.update.side_effect = side_effect
-        nodes._update_or_register_ironic_node(node, node_map, client=ironic)
+        # Save the nodes while the values are still strings
+        saved_node_list = copy.deepcopy(node_list)
+
+        # Convert node values to integers
+        for node in node_list:
+            node['cpu'] = int(node['cpu'])
+            node['memory'] = int(node['memory'])
+            node['disk'] = int(node['disk'])
+
+        # Create the mac addr to uuid mapping
+        node_map = {'mac': {}}
+        uuid = 1
+        for node in node_list:
+            mac = node['mac'][0]
+            node_map['mac'][mac] = uuid
+            uuid = uuid+1
+
+        for node in node_list:
+            nodes._update_or_register_ironic_node(node,
+                                                  node_map,
+                                                  client=ironic)
+
+        self._test_update_register(ironic, node_map, saved_node_list)
 
     def test_clean_up_extra_nodes_ironic(self):
         node = collections.namedtuple('node', ['uuid'])
@@ -832,26 +1035,26 @@ class NodesTest(base.TestCase):
         client.node.delete.assert_called_once_with('foobar')
 
     def test__get_node_id_fake_pxe(self):
-        node = self._get_node()
+        node = self._get_nodes()[0]
         node['pm_type'] = 'fake_pxe'
         handler = nodes.find_driver_handler('fake_pxe')
-        node_map = {'mac': {'aaa': 'abcdef'}, 'pm_addr': {}}
+        node_map = {'mac': {node['mac'][0]: 'abcdef'}, 'pm_addr': {}}
         self.assertEqual('abcdef', nodes._get_node_id(node, handler, node_map))
 
     def test__get_node_id_conflict(self):
-        node = self._get_node()
+        node = self._get_nodes()[0]
         handler = nodes.find_driver_handler('pxe_ipmitool')
-        node_map = {'mac': {'aaa': 'abcdef'},
-                    'pm_addr': {'foo.bar': 'defabc'}}
+        node_map = {'mac': {node['mac'][0]: 'abcdef'},
+                    'pm_addr': {node['pm_addr']: 'defabc'}}
         self.assertRaises(exception.InvalidNode,
                           nodes._get_node_id,
                           node, handler, node_map)
 
     def test_get_node_id_valid_duplicate(self):
-        node = self._get_node()
+        node = self._get_nodes()[0]
         handler = nodes.find_driver_handler('pxe_ipmitool')
-        node_map = {'mac': {'aaa': 'id'},
-                    'pm_addr': {'foo.bar': 'id'}}
+        node_map = {'mac': {node['mac'][0]: 'id'},
+                    'pm_addr': {node['pm_addr']: 'id'}}
         self.assertEqual('id', nodes._get_node_id(node, handler, node_map))
 
 
diff --git a/tripleo_common/utils/glance.py b/tripleo_common/utils/glance.py
index b92409b..db87073 100644
--- a/tripleo_common/utils/glance.py
+++ b/tripleo_common/utils/glance.py
@@ -19,7 +19,8 @@ from glanceclient import exc as exceptions
 from glanceclient.v2.client import Client as real_glance_client
 
 
-def create_or_find_kernel_and_ramdisk(glanceclient, kernel_name, ramdisk_name,
+def create_or_find_kernel_and_ramdisk(glanceclient,
+                                      baremetal_client,
                                       kernel_path=None, ramdisk_path=None,
                                       skip_missing=False):
     """Find or create a given kernel and ramdisk in Glance.
@@ -28,8 +29,7 @@ def create_or_find_kernel_and_ramdisk(glanceclient, kernel_name, ramdisk_name,
     and an exception will be raised if it does not exist in Glance.
 
     :param glanceclient: A client for Glance.
-    :param kernel_name: Name to search for or create for the kernel.
-    :param ramdisk_name: Name to search for or create for the ramdisk.
+    :param baremetal_client: A client for baremetal queries.
     :param kernel_path: Path to the kernel on disk.
     :param ramdisk_path: Path to the ramdisk on disk.
     :param skip_missing: If `True', do not raise an exception if either the
@@ -37,11 +37,30 @@ def create_or_find_kernel_and_ramdisk(glanceclient, kernel_name, ramdisk_name,
 
     :returns: A dictionary mapping kernel or ramdisk to the ID in Glance.
     """
-    kernel_image = _upload_file(glanceclient, kernel_name, kernel_path,
-                                'aki', 'Kernel', skip_missing=skip_missing)
-    ramdisk_image = _upload_file(glanceclient, ramdisk_name, ramdisk_path,
-                                 'ari', 'Ramdisk', skip_missing=skip_missing)
-    return {'kernel': kernel_image.id, 'ramdisk': ramdisk_image.id}
+
+
+    arches = {}
+    for node in baremetal_client.node.list(detail=True):
+        properties = node['properties']
+        if 'cpu_arch' in properties:
+            arches[properties['cpu_arch']] = {}
+
+    for arch in arches.keys():
+        kernel_name = "%s-bm-deploy-kernel" % arch
+        ramdisk_name = "%s-bm-deploy-ramdisk" % arch
+
+        kernel_image = _upload_file(glanceclient, kernel_name, kernel_path,
+                                    'aki', 'Kernel',
+                                    skip_missing=skip_missing)
+
+        ramdisk_image = _upload_file(glanceclient, ramdisk_name, ramdisk_path,
+                                     'ari', 'Ramdisk',
+                                     skip_missing=skip_missing)
+
+        arches[arch]['kernel'] = kernel_image.id
+        arches[arch]['ramdisk'] = ramdisk_image.id
+
+    return arches
 
 
 def _upload_file(glanceclient, name, path, disk_format, type_name,
@@ -56,8 +75,6 @@ def _upload_file(glanceclient, name, path, disk_format, type_name,
                 if ((img['name'] == name or img['id'] == name) and
                         img['disk_format'] == disk_format):
                     image = img
-            if not image:
-                raise exceptions.NotFound("No image found")
         else:
             # TODO(dprince) remove this
             # This code expects the python-openstackclient version of
@@ -66,6 +83,8 @@ def _upload_file(glanceclient, name, path, disk_format, type_name,
             # using the Mistral API for this functionality.
             image = glanceclient.images.find(name=name,
                                              disk_format=disk_format)
+        if not image:
+            raise exceptions.NotFound("No image found")
     except exceptions.NotFound:
         if path:
             image = glanceclient.images.create(
diff --git a/tripleo_common/utils/nodes.py b/tripleo_common/utils/nodes.py
index 18ad245..a0d7e51 100644
--- a/tripleo_common/utils/nodes.py
+++ b/tripleo_common/utils/nodes.py
@@ -23,6 +23,9 @@ from oslo_concurrency import processutils
 from tripleo_common import exception
 from tripleo_common.utils import glance
 
+from glanceclient.v2.client import Client as real_glance_client
+from glanceclient import exc as exceptions
+
 LOG = logging.getLogger(__name__)
 
 _KNOWN_INTERFACE_FIELDS = [
@@ -466,8 +469,8 @@ def _clean_up_extra_nodes(seen, client, remove=False):
             LOG.debug('Extra registered node %s found.' % node)
 
 
-def register_all_nodes(nodes_list, client, remove=False, glance_client=None,
-                       kernel_name=None, ramdisk_name=None):
+def register_all_nodes(nodes_list, client, remove=False,
+                       no_deploy_image=False, glance_client=None):
     """Register all nodes in nodes_list in the baremetal service.
 
     :param nodes_list: The list of nodes to register.
@@ -482,17 +485,54 @@ def register_all_nodes(nodes_list, client, remove=False, glance_client=None,
     LOG.debug('Registering all nodes.')
     node_map = _populate_node_mapping(client)
 
-    glance_ids = {'kernel': None, 'ramdisk': None}
-    if kernel_name and ramdisk_name:
-        glance_ids = glance.create_or_find_kernel_and_ramdisk(
-            glance_client, kernel_name, ramdisk_name)
+    def find_image(glanceclient, name, disk_format):
+        image = None
+
+        if isinstance(glanceclient, real_glance_client):
+            images = glanceclient.images.list(name=name,
+                                              disk_format=disk_format)
+            for img in images:
+                if ((img['name'] == name or img['id'] == name) and
+                        img['disk_format'] == disk_format):
+                    image = img
+            if not image:
+                raise exceptions.NotFound("No image found")
+        elif glanceclient is not None:
+            # TODO(dprince) remove this
+            # This code expects the python-openstackclient version of
+            # "glanceclient" (which isn't pure python-glanceclient) and is
+            # here for backwards compat until python-tripleoclient starts
+            # using the Mistral API for this functionality.
+            image = glanceclient.images.find(name=name,
+                                             disk_format=disk_format)
+        else:
+            return None
+
+        return image['id']
 
     seen = []
     for node in nodes_list:
-        if glance_ids['kernel'] and 'kernel_id' not in node:
-            node['kernel_id'] = glance_ids['kernel']
-        if glance_ids['ramdisk'] and 'ramdisk_id' not in node:
-            node['ramdisk_id'] = glance_ids['ramdisk']
+
+        if not node.has_key('arch'):
+            raise exception.InvalidNode('The arch field is missing')
+
+        kernel_name = node['arch']+'-bm-deploy-kernel'
+        kernel_id = find_image(glance_client,
+                               name=kernel_name,
+                               disk_format='aki')
+        if not kernel_id:
+            raise exceptions.NotFound("No image found for "+kernel_name)
+
+        ramdisk_name = node['arch']+'-bm-deploy-ramdisk'
+        ramdisk_id = find_image(glance_client,
+                                name=ramdisk_name,
+                                disk_format='ari')
+        if not ramdisk_id:
+            raise exceptions.NotFound("No image found for "+ramdisk_name)
+
+        if no_deploy_image == False:
+            node['kernel_id'] = kernel_id
+            node['ramdisk_id'] = ramdisk_id
 
         node = _update_or_register_ironic_node(node, node_map, client=client)
         seen.append(node)
diff --git a/workbooks/baremetal.yaml b/workbooks/baremetal.yaml
index 3aff200..74223ca 100644
--- a/workbooks/baremetal.yaml
+++ b/workbooks/baremetal.yaml
@@ -147,9 +147,8 @@ workflows:
      - nodes_json
      - remove: False
      - queue_name: tripleo
-     - kernel_name: null
-     - ramdisk_name: null
      - instance_boot_option: local
+     - no_deploy_image
      - initial_state: manageable
 
     tags:
@@ -181,8 +180,7 @@ workflows:
         input:
            nodes_json: <% $.nodes_json %>
            remove: <% $.remove %>
-           kernel_name: <% $.kernel_name %>
-           ramdisk_name: <% $.ramdisk_name %>
+           no_deploy_image: <% $.no_deploy_image %>
            instance_boot_option: <% $.instance_boot_option %>
         publish:
           registered_nodes: <% task().result %>
@@ -601,7 +599,7 @@ workflows:
 
       max_retry_attempts_reached:
         publish:
-          status: FAILED
+          status: ERROR
           message: <% 'Retry limit reached with {0} nodes still failing introspection'.format($.failed_introspection.len()) %>
         on-complete: send_message
 
@@ -613,7 +611,7 @@ workflows:
 
       unhandled_error:
         publish:
-          status: FAILED
+          status: ERROR
           message: "Unhandled workflow error"
         on-complete: send_message
 
@@ -700,8 +698,6 @@ workflows:
     input:
       - node_uuids
       - queue_name: tripleo
-      - kernel_name: bm-deploy-kernel
-      - ramdisk_name: bm-deploy-ramdisk
       - instance_boot_option: null
       - root_device: null
       - root_device_minimum_size: 4
@@ -716,7 +712,7 @@ workflows:
         on-success: configure_root_device
         on-error: set_status_failed_configure_boot
         with-items: node_uuid in <% $.node_uuids %>
-        action: tripleo.baremetal.configure_boot node_uuid=<% $.node_uuid %> kernel_name=<% $.kernel_name %> ramdisk_name=<% $.ramdisk_name %> instance_boot_option=<% $.instance_boot_option %>
+        action: tripleo.baremetal.configure_boot node_uuid=<% $.node_uuid %> instance_boot_option=<% $.instance_boot_option %>
 
       configure_root_device:
         on-success: send_message
@@ -759,8 +755,6 @@ workflows:
 
     input:
       - queue_name: tripleo
-      - kernel_name: 'bm-deploy-kernel'
-      - ramdisk_name: 'bm-deploy-ramdisk'
       - instance_boot_option: null
       - root_device: null
       - root_device_minimum_size: 4
@@ -785,8 +779,6 @@ workflows:
         input:
           node_uuids: <% $.managed_nodes %>
           queue_name: <% $.queue_name %>
-          kernel_name: <% $.kernel_name %>
-          ramdisk_name: <% $.ramdisk_name %>
           instance_boot_option: <% $.instance_boot_option %>
           root_device: <% $.root_device %>
           root_device_minimum_size: <% $.root_device_minimum_size %>
@@ -1174,8 +1166,6 @@ workflows:
       - ip_addresses
       - credentials
       - ports: [623]
-      - kernel_name: null
-      - ramdisk_name: null
       - instance_boot_option: local
       - initial_state: manageable
       - queue_name: tripleo
@@ -1207,8 +1197,6 @@ workflows:
         workflow: tripleo.baremetal.v1.register_or_update
         input:
           nodes_json: <% $.nodes_json %>
-          kernel_name: <% $.kernel_name %>
-          ramdisk_name: <% $.ramdisk_name %>
           instance_boot_option: <% $.instance_boot_option %>
           initial_state: <% $.initial_state %>
         on-success: send_message
diff --git a/workbooks/validations.yaml b/workbooks/validations.yaml
index 2ada12e..94d0f92 100644
--- a/workbooks/validations.yaml
+++ b/workbooks/validations.yaml
@@ -273,15 +273,11 @@ workflows:
 
   check_boot_images:
     input:
-      - deploy_kernel_name: 'bm-deploy-kernel'
-      - deploy_ramdisk_name: 'bm-deploy-ramdisk'
       - run_validations: true
       - queue_name: tripleo
     output:
       errors: <% $.errors %>
       warnings: <% $.warnings %>
-      kernel_id: <% $.kernel_id %>
-      ramdisk_id: <% $.ramdisk_id %>
     tags:
       - tripleo-common-managed
     tasks:
@@ -292,26 +288,37 @@ workflows:
 
       get_images:
         action: glance.images_list
-        on-success: check_images
+        on-success: get_ironic_nodes
         publish:
           images: <% task().result %>
 
+      get_ironic_nodes:
+        action: ironic.node_list
+        on-success: check_images
+        on-error: failed_get_ironic_nodes
+        input:
+          maintenance: false
+          detail: true
+        publish:
+          nodes: <% task().result %>
+
+      failed_get_ironic_nodes:
+        on-success: send_message
+        publish:
+          status: FAILED
+          message: <% task(get_ironic_nodes).result %>
+
       check_images:
         action: tripleo.validations.check_boot_images
         input:
+          nodes: <% $.nodes %>
           images: <% $.images %>
-          deploy_kernel_name: <% $.deploy_kernel_name %>
-          deploy_ramdisk_name: <% $.deploy_ramdisk_name %>
         on-success: send_message
         on-error: fail_check_images
         publish:
-          kernel_id: <% task().result.kernel_id %>
-          ramdisk_id: <% task().result.ramdisk_id %>
           warnings: <% task().result.warnings %>
           errors: <% task().result.errors %>
         publish-on-error:
-          kernel_id: <% task().result.kernel_id %>
-          ramdisk_id: <% task().result.ramdisk_id %>
           warnings: <% task().result.warnings %>
           errors: <% task().result.errors %>
 
@@ -333,8 +340,6 @@ workflows:
                 status: <% $.get('status', 'SUCCESS') %>
                 message: <% $.get('message', '') %>
                 execution: <% execution() %>
-                kernel_id: <% $.kernel_id %>
-                ramdisk_id: <% $.ramdisk_id %>
                 errors: <% $.errors %>
                 warnings: <% $.warnings %>
         on-success:
@@ -400,8 +405,6 @@ workflows:
 
   check_ironic_boot_configuration:
     input:
-      - kernel_id: null
-      - ramdisk_id: null
       - run_validations: true
       - queue_name: tripleo
     output:
@@ -419,7 +422,7 @@ workflows:
 
       get_ironic_nodes:
         action: ironic.node_list
-        on-success: check_node_boot_configuration
+        on-success: get_images
         on-error: failed_get_ironic_nodes
         input:
           provision_state: available
@@ -434,12 +437,17 @@ workflows:
           status: FAILED
           message: <% task(get_ironic_nodes).result %>
 
+      get_images:
+        action: glance.images_list
+        on-success: check_node_boot_configuration
+        publish:
+          images: <% task(get_images).result %>
+
       check_node_boot_configuration:
         action: tripleo.validations.check_node_boot_configuration
         input:
           node: <% $.node %>
-          kernel_id: <% $.kernel_id %>
-          ramdisk_id: <% $.ramdisk_id %>
+          images: <% $.images %>
         with-items: node in <% $.nodes %>
         on-success: send_message
         on-error: fail_check_node_boot_configuration
@@ -672,8 +680,6 @@ workflows:
 
   check_pre_deployment_validations:
     input:
-      - deploy_kernel_name: 'bm-deploy-kernel'
-      - deploy_ramdisk_name: 'bm-deploy-ramdisk'
       - roles_info: {}
       - stack_id: overcloud
       - parameters: {}
@@ -684,8 +690,6 @@ workflows:
     output:
       errors: <% $.errors %>
       warnings: <% $.warnings %>
-      kernel_id: <% $.kernel_id %>
-      ramdisk_id: <% $.ramdisk_id %>
       flavors: <% $.flavors %>
       statistics: <% $.statistics %>
     tags:
@@ -700,20 +704,14 @@ workflows:
       check_boot_images:
         workflow: check_boot_images
         input:
-          deploy_kernel_name: <% $.deploy_kernel_name %>
-          deploy_ramdisk_name: <% $.deploy_ramdisk_name %>
           run_validations: <% $.run_validations %>
           queue_name: <% $.queue_name %>
         publish:
           errors: <% $.errors + task().result.errors %>
           warnings: <% $.warnings + task().result.warnings %>
-          kernel_id: <% task().result.kernel_id %>
-          ramdisk_id: <% task().result.ramdisk_id %>
         publish-on-error:
           errors: <% $.errors + task().result.errors %>
           warnings: <% $.warnings + task().result.warnings %>
-          kernel_id: <% task().result.kernel_id %>
-          ramdisk_id: <% task().result.ramdisk_id %>
           status: FAILED
         on-success: collect_flavors
         on-error: collect_flavors
@@ -739,8 +737,6 @@ workflows:
       check_ironic_boot_configuration:
         workflow: check_ironic_boot_configuration
         input:
-          kernel_id: <% $.kernel_id %>
-          ramdisk_id: <% $.ramdisk_id %>
           run_validations: <% $.run_validations %>
           queue_name: <% $.queue_name %>
         publish:
@@ -801,8 +797,6 @@ workflows:
                 status: <% $.get('status', 'SUCCESS') %>
                 message: <% $.get('message', '') %>
                 execution: <% execution() %>
-                kernel_id: <% $.kernel_id %>
-                ramdisk_id: <% $.ramdisk_id %>
                 flavors: <% $.flavors %>
                 statistics: <% $.statistics %>
                 errors: <% $.errors %>
-- 
2.14.3

