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:
parent
9d4c933825
commit
0e91860e80
|
@ -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])
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
Loading…
Reference in New Issue