Identify baremetal nodes by UUID.

- add a new 'uuid' column to 'bm_nodes' table
- provide a new db/api method for accessing nodes by their uuid
- return this to Nova as the nodename / hypervisor_hostname

In this way, a baremetal node is uniquely identified to the user,
whether they use a per-compute baremetal db or global baremetal db.

It will also allow for an instance to be booted on a specific baremetal
node using a means akin to the current force-hosts (TBD in later patch).

Also, creates two new Exceptions to disambiguate between
InstanceNotFound and NodeNotFound / NodeNotFoundByUUID.

Change-Id: I81105a201588fdef31cffabdae260bb43017bcd1
This commit is contained in:
Devananda van der Veen 2013-02-13 18:25:29 -08:00
parent 9d4c933825
commit 0e91860e80
9 changed files with 115 additions and 43 deletions

View File

@ -65,7 +65,7 @@ class BareMetalNodesTestCase(base.BMDBTestCase):
self.assertEquals(r['pm_address'], '1')
self.assertRaises(
exception.InstanceNotFound,
exception.NodeNotFound,
db.bm_node_get,
self.context, -1)
@ -113,7 +113,7 @@ class BareMetalNodesTestCase(base.BMDBTestCase):
db.bm_node_destroy(self.context, self.ids[0])
self.assertRaises(
exception.InstanceNotFound,
exception.NodeNotFound,
db.bm_node_get,
self.context, self.ids[0])
@ -147,7 +147,7 @@ class BareMetalNodesTestCase(base.BMDBTestCase):
self.assertEqual(self.ids[1], if_x['bm_node_id'])
self.assertRaises(
exception.InstanceNotFound,
exception.NodeNotFound,
db.bm_node_get,
self.context, self.ids[0])

View File

@ -22,6 +22,7 @@ from nova.virt.baremetal.db.sqlalchemy import models as bm_models
def new_bm_node(**kwargs):
h = bm_models.BareMetalNode()
h.id = kwargs.pop('id', None)
h.uuid = kwargs.pop('uuid', None)
h.service_host = kwargs.pop('service_host', None)
h.instance_uuid = kwargs.pop('instance_uuid', None)
h.cpus = kwargs.pop('cpus', 1)

View File

@ -111,7 +111,7 @@ class BareMetalDriverWithDBTestCase(bm_db_base.BMDBTestCase):
nic['port_no'],
)
result['instance'] = utils.get_test_instance()
result['instance']['node'] = result['node']['id']
result['instance']['node'] = result['node']['uuid']
result['spawn_params'] = dict(
admin_password='test_pass',
block_device_info=None,
@ -139,7 +139,7 @@ class BareMetalDriverWithDBTestCase(bm_db_base.BMDBTestCase):
self.assertEqual(stats['cpu_arch'], 'test')
self.assertEqual(stats['test_spec'], 'test_value')
self.assertEqual(stats['hypervisor_type'], 'baremetal')
self.assertEqual(stats['hypervisor_hostname'], '123')
self.assertEqual(stats['hypervisor_hostname'], node['node']['uuid'])
self.assertEqual(stats['host'], 'test_host')
self.assertEqual(stats['vcpus'], 2)
self.assertEqual(stats['host_memory_total'], 2048)
@ -153,7 +153,15 @@ class BareMetalDriverWithDBTestCase(bm_db_base.BMDBTestCase):
def test_macs_for_instance(self):
node = self._create_node()
expected = set(['01:23:45:67:89:01', '01:23:45:67:89:02'])
expected = set([nic['address'] for nic in node['nic_info']])
self.assertEqual(
expected, self.driver.macs_for_instance(node['instance']))
def test_macs_for_instance_after_spawn(self):
node = self._create_node()
self.driver.spawn(**node['spawn_params'])
expected = set([nic['address'] for nic in node['nic_info']])
self.assertEqual(
expected, self.driver.macs_for_instance(node['instance']))
@ -184,12 +192,12 @@ class BareMetalDriverWithDBTestCase(bm_db_base.BMDBTestCase):
def test_spawn_node_not_found(self):
node = self._create_node()
db.bm_node_update(self.context, node['node']['id'],
{'id': 9876})
{'uuid': 'hide-this-node'})
self.assertRaises(exception.NovaException,
self.driver.spawn, **node['spawn_params'])
row = db.bm_node_get(self.context, 9876)
row = db.bm_node_get(self.context, node['node']['id'])
self.assertEqual(row['task_state'], None)
def test_spawn_fails(self):
@ -247,18 +255,18 @@ class BareMetalDriverWithDBTestCase(bm_db_base.BMDBTestCase):
def test_get_available_resources(self):
node = self._create_node()
resources = self.driver.get_available_resource(node['node']['id'])
resources = self.driver.get_available_resource(node['node']['uuid'])
self.assertEqual(resources['memory_mb'],
node['node_info']['memory_mb'])
self.assertEqual(resources['memory_mb_used'], 0)
self.driver.spawn(**node['spawn_params'])
resources = self.driver.get_available_resource(node['node']['id'])
resources = self.driver.get_available_resource(node['node']['uuid'])
self.assertEqual(resources['memory_mb_used'],
node['node_info']['memory_mb'])
self.driver.destroy(**node['destroy_params'])
resources = self.driver.get_available_resource(node['node']['id'])
resources = self.driver.get_available_resource(node['node']['uuid'])
self.assertEqual(resources['memory_mb_used'], 0)
def test_get_available_nodes(self):
@ -281,7 +289,7 @@ class BareMetalDriverWithDBTestCase(bm_db_base.BMDBTestCase):
]
node2 = self._create_node(node_info=node_info, nic_info=nic_info)
self.assertEqual(2, len(self.driver.get_available_nodes()))
self.assertEqual(['123', '456'],
self.assertEqual([node1['node']['uuid'], node2['node']['uuid']],
self.driver.get_available_nodes())
node1['instance']['hostname'] = 'test-host-1'
@ -298,5 +306,5 @@ class BareMetalDriverWithDBTestCase(bm_db_base.BMDBTestCase):
self.driver.destroy(**node2['destroy_params'])
self.assertEqual(2, len(self.driver.get_available_nodes()))
self.assertEqual(['123', '456'],
self.assertEqual([node1['node']['uuid'], node2['node']['uuid']],
self.driver.get_available_nodes())

View File

@ -101,6 +101,10 @@ def bm_node_get_by_instance_uuid(context, instance_uuid):
instance_uuid)
def bm_node_get_by_node_uuid(context, node_uuid):
return IMPL.bm_node_get_by_node_uuid(context, node_uuid)
def bm_node_create(context, values):
return IMPL.bm_node_create(context, values)
@ -113,8 +117,8 @@ def bm_node_update(context, bm_node_id, values):
return IMPL.bm_node_update(context, bm_node_id, values)
def bm_node_set_uuid_safe(context, bm_node_id, uuid):
return IMPL.bm_node_set_uuid_safe(context, bm_node_id, uuid)
def bm_node_associate_and_update(context, node_uuid, values):
return IMPL.bm_node_associate_and_update(context, node_uuid, values)
def bm_pxe_ip_create(context, address, server_address):

View File

@ -20,6 +20,8 @@
"""Implementation of SQLAlchemy backend."""
import uuid
from sqlalchemy.sql.expression import asc
from sqlalchemy.sql.expression import literal_column
@ -132,7 +134,7 @@ def bm_node_get(context, bm_node_id):
first()
if not result:
raise exception.InstanceNotFound(instance_id=bm_node_id)
raise exception.NodeNotFound(node_id=bm_node_id)
return result
@ -152,8 +154,22 @@ def bm_node_get_by_instance_uuid(context, instance_uuid):
return result
@sqlalchemy_api.require_admin_context
def bm_node_get_by_node_uuid(context, bm_node_uuid):
result = model_query(context, models.BareMetalNode, read_deleted="no").\
filter_by(uuid=bm_node_uuid).\
first()
if not result:
raise exception.NodeNotFoundByUUID(node_uuid=bm_node_uuid)
return result
@sqlalchemy_api.require_admin_context
def bm_node_create(context, values):
if not values.get('uuid'):
values['uuid'] = str(uuid.uuid4())
bm_node_ref = models.BareMetalNode()
bm_node_ref.update(values)
_save(bm_node_ref)
@ -167,11 +183,11 @@ def bm_node_update(context, bm_node_id, values):
update(values)
if not rows:
raise exception.InstanceNotFound(instance_id=bm_node_id)
raise exception.NodeNotFound(node_id=bm_node_id)
@sqlalchemy_api.require_admin_context
def bm_node_set_uuid_safe(context, bm_node_id, values):
def bm_node_associate_and_update(context, node_uuid, values):
"""Associate an instance to a node safely
Associate an instance to a node only if that node is not yet assocated.
@ -182,21 +198,21 @@ def bm_node_set_uuid_safe(context, bm_node_id, values):
"""
if 'instance_uuid' not in values:
raise exception.NovaException(_(
"instance_uuid must be supplied to bm_node_set_uuid_safe"))
"instance_uuid must be supplied to bm_node_associate_and_update"))
session = db_session.get_session()
with session.begin():
query = model_query(context, models.BareMetalNode,
session=session, read_deleted="no").\
filter_by(id=bm_node_id)
filter_by(uuid=node_uuid)
count = query.filter_by(instance_uuid=None).\
update(values, synchronize_session=False)
if count != 1:
raise exception.NovaException(_(
"Failed to associate instance %(uuid)s to baremetal node "
"%(id)s.") % {'id': bm_node_id,
'uuid': values['instance_uuid']})
"Failed to associate instance %(i_uuid)s to baremetal node "
"%(n_uuid)s.") % {'i_uuid': values['instance_uuid'],
'n_uuid': node_uuid})
ref = query.first()
return ref
@ -270,7 +286,7 @@ def bm_pxe_ip_get_by_bm_node_id(context, bm_node_id):
first()
if not result:
raise exception.InstanceNotFound(instance_id=bm_node_id)
raise exception.NodeNotFound(node_id=bm_node_id)
return result
@ -285,7 +301,7 @@ def bm_pxe_ip_associate(context, bm_node_id):
filter_by(id=bm_node_id).\
first()
if not node_ref:
raise exception.InstanceNotFound(instance_id=bm_node_id)
raise exception.NodeNotFound(node_id=bm_node_id)
# Check if the node already has a pxe_ip
ip_ref = model_query(context, models.BareMetalPxeIp,
@ -408,6 +424,6 @@ def bm_interface_get_all_by_bm_node_id(context, bm_node_id):
all()
if not result:
raise exception.InstanceNotFound(instance_id=bm_node_id)
raise exception.NodeNotFound(node_id=bm_node_id)
return result

View File

@ -0,0 +1,40 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 Hewlett-Packard Development Company, L.P.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from sqlalchemy import Column, MetaData, String, Table, Index
def upgrade(migrate_engine):
meta = MetaData()
meta.bind = migrate_engine
t = Table('bm_nodes', meta, autoload=True)
uuid_col = Column('uuid', String(36))
t.create_column(uuid_col)
uuid_ux = Index('uuid_ux', t.c.uuid, unique=True)
uuid_ux.create(migrate_engine)
def downgrade(migrate_engine):
meta = MetaData()
meta.bind = migrate_engine
t = Table('bm_nodes', meta, autoload=True)
uuid_col = Column('uuid', String(length=36))
t.drop_column(uuid_col)

View File

@ -34,6 +34,7 @@ class BareMetalNode(BASE, models.NovaBase):
__tablename__ = 'bm_nodes'
id = Column(Integer, primary_key=True)
uuid = Column(String(36))
service_host = Column(String(255))
instance_uuid = Column(String(36), nullable=True)
cpus = Column(Integer)

View File

@ -186,17 +186,17 @@ class BareMetalDriver(driver.ComputeDriver):
return l
def _require_node(self, instance):
"""Get a node_id out of a manager instance dict.
"""Get a node's uuid out of a manager instance dict.
The compute manager is meant to know the node id, so a missing node is
The compute manager is meant to know the node uuid, so missing uuid
a significant issue - it may mean we've been passed someone elses data.
"""
node_id = instance.get('node')
if not node_id:
node_uuid = instance.get('node')
if not node_uuid:
raise exception.NovaException(_(
"Baremetal node id not supplied to driver for %r")
% instance['uuid'])
return node_id
return node_uuid
def _attach_block_devices(self, instance, block_device_info):
block_device_mapping = driver.\
@ -230,18 +230,19 @@ class BareMetalDriver(driver.ComputeDriver):
def macs_for_instance(self, instance):
context = nova_context.get_admin_context()
node_id = self._require_node(instance)
return set(iface['address'] for iface in
db.bm_interface_get_all_by_bm_node_id(context, node_id))
node_uuid = self._require_node(instance)
node = db.bm_node_get_by_node_uuid(context, node_uuid)
ifaces = db.bm_interface_get_all_by_bm_node_id(context, node['id'])
return set(iface['address'] for iface in ifaces)
def spawn(self, context, instance, image_meta, injected_files,
admin_password, network_info=None, block_device_info=None):
node_id = self._require_node(instance)
node_uuid = self._require_node(instance)
# NOTE(deva): this db method will raise an exception if the node is
# already in use. We call it here to ensure no one else
# allocates this node before we begin provisioning it.
node = db.bm_node_set_uuid_safe(context, node_id,
node = db.bm_node_associate_and_update(context, node_uuid,
{'instance_uuid': instance['uuid'],
'task_state': baremetal_states.BUILDING})
@ -265,7 +266,8 @@ class BareMetalDriver(driver.ComputeDriver):
with excutils.save_and_reraise_exception():
LOG.error(_("Error deploying instance %(instance)s "
"on baremetal node %(node)s.") %
{'instance': instance['uuid'], 'node': node['id']})
{'instance': instance['uuid'],
'node': node['uuid']})
# Do not set instance=None yet. This prevents another
# spawn() while we are cleaning up.
@ -408,7 +410,7 @@ class BareMetalDriver(driver.ComputeDriver):
'local_gb_used': local_gb_used,
'hypervisor_type': self.get_hypervisor_type(),
'hypervisor_version': self.get_hypervisor_version(),
'hypervisor_hostname': str(node['id']),
'hypervisor_hostname': str(node['uuid']),
'cpu_info': 'baremetal cpu',
}
return dic
@ -418,7 +420,7 @@ class BareMetalDriver(driver.ComputeDriver):
def get_available_resource(self, nodename):
context = nova_context.get_admin_context()
node = db.bm_node_get(context, nodename)
node = db.bm_node_get_by_node_uuid(context, nodename)
dic = self._node_resource(node)
return dic
@ -438,7 +440,7 @@ class BareMetalDriver(driver.ComputeDriver):
service_host=CONF.host)
for node in nodes:
res = self._node_resource(node)
nodename = str(node['id'])
nodename = str(node['uuid'])
data = {}
data['vcpus'] = res['vcpus']
data['vcpus_used'] = res['vcpus_used']
@ -489,5 +491,5 @@ class BareMetalDriver(driver.ComputeDriver):
def get_available_nodes(self):
context = nova_context.get_admin_context()
return [str(n['id']) for n in
return [str(n['uuid']) for n in
db.bm_node_get_unassociated(context, service_host=CONF.host)]

View File

@ -413,7 +413,7 @@ class PXE(base.NodeDriver):
'pxe_config_path': None,
'root_mb': 0,
'swap_mb': 0})
except exception.InstanceNotFound:
except exception.NodeNotFound:
pass
try:
@ -464,7 +464,7 @@ class PXE(base.NodeDriver):
raise utils.LoopingCallDone()
elif status == baremetal_states.DEPLOYFAIL:
locals['error'] = _("PXE deploy failed for instance %s")
except exception.InstanceNotFound:
except exception.NodeNotFound:
locals['error'] = _("Baremetal node deleted while waiting "
"for deployment of instance %s")