DB exceptions have moved to openstack/common/db/exception module so that they can be shared with multiple DB implementations. Deadlock checking was also added to oslo to consolidate with DuplicateKey checking. This allows us to clean up our _retry_on_deadlock decorator in sqlalchemy/api.py Fixes unrelated pep8 issue with duplicate test in test_compute also. Change-Id: I7e985b384d1ef345e0d67c919b84b4faff869699
437 lines
15 KiB
Python
437 lines
15 KiB
Python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
|
|
# Copyright (c) 2012 NTT DOCOMO, INC.
|
|
# Copyright (c) 2011 X.commerce, a business unit of eBay Inc.
|
|
# Copyright 2010 United States Government as represented by the
|
|
# Administrator of the National Aeronautics and Space Administration.
|
|
# 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.
|
|
|
|
"""Implementation of SQLAlchemy backend."""
|
|
|
|
import uuid
|
|
|
|
from sqlalchemy.sql.expression import asc
|
|
from sqlalchemy.sql.expression import literal_column
|
|
|
|
import nova.context
|
|
from nova.db.sqlalchemy import api as sqlalchemy_api
|
|
from nova import exception
|
|
from nova.openstack.common.db import exception as db_exc
|
|
from nova.openstack.common import log as logging
|
|
from nova.openstack.common import timeutils
|
|
from nova.openstack.common import uuidutils
|
|
from nova.virt.baremetal.db.sqlalchemy import models
|
|
from nova.virt.baremetal.db.sqlalchemy import session as db_session
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
def model_query(context, *args, **kwargs):
|
|
"""Query helper that accounts for context's `read_deleted` field.
|
|
|
|
:param context: context to query under
|
|
:param session: if present, the session to use
|
|
:param read_deleted: if present, overrides context's read_deleted field.
|
|
:param project_only: if present and context is user-type, then restrict
|
|
query to match the context's project_id.
|
|
"""
|
|
session = kwargs.get('session') or db_session.get_session()
|
|
read_deleted = kwargs.get('read_deleted') or context.read_deleted
|
|
project_only = kwargs.get('project_only')
|
|
|
|
query = session.query(*args)
|
|
|
|
if read_deleted == 'no':
|
|
query = query.filter_by(deleted=False)
|
|
elif read_deleted == 'yes':
|
|
pass # omit the filter to include deleted and active
|
|
elif read_deleted == 'only':
|
|
query = query.filter_by(deleted=True)
|
|
else:
|
|
raise Exception(
|
|
_("Unrecognized read_deleted value '%s'") % read_deleted)
|
|
|
|
if project_only and nova.context.is_user_context(context):
|
|
query = query.filter_by(project_id=context.project_id)
|
|
|
|
return query
|
|
|
|
|
|
def _save(ref, session=None):
|
|
if not session:
|
|
session = db_session.get_session()
|
|
# We must not call ref.save() with session=None, otherwise NovaBase
|
|
# uses nova-db's session, which cannot access bm-db.
|
|
ref.save(session=session)
|
|
|
|
|
|
def _build_node_order_by(query):
|
|
query = query.order_by(asc(models.BareMetalNode.memory_mb))
|
|
query = query.order_by(asc(models.BareMetalNode.cpus))
|
|
query = query.order_by(asc(models.BareMetalNode.local_gb))
|
|
return query
|
|
|
|
|
|
@sqlalchemy_api.require_admin_context
|
|
def bm_node_get_all(context, service_host=None):
|
|
query = model_query(context, models.BareMetalNode, read_deleted="no")
|
|
if service_host:
|
|
query = query.filter_by(service_host=service_host)
|
|
return query.all()
|
|
|
|
|
|
@sqlalchemy_api.require_admin_context
|
|
def bm_node_get_associated(context, service_host=None):
|
|
query = model_query(context, models.BareMetalNode, read_deleted="no").\
|
|
filter(models.BareMetalNode.instance_uuid != None)
|
|
if service_host:
|
|
query = query.filter_by(service_host=service_host)
|
|
return query.all()
|
|
|
|
|
|
@sqlalchemy_api.require_admin_context
|
|
def bm_node_get_unassociated(context, service_host=None):
|
|
query = model_query(context, models.BareMetalNode, read_deleted="no").\
|
|
filter(models.BareMetalNode.instance_uuid == None)
|
|
if service_host:
|
|
query = query.filter_by(service_host=service_host)
|
|
return query.all()
|
|
|
|
|
|
@sqlalchemy_api.require_admin_context
|
|
def bm_node_find_free(context, service_host=None,
|
|
cpus=None, memory_mb=None, local_gb=None):
|
|
query = model_query(context, models.BareMetalNode, read_deleted="no")
|
|
query = query.filter(models.BareMetalNode.instance_uuid == None)
|
|
if service_host:
|
|
query = query.filter_by(service_host=service_host)
|
|
if cpus is not None:
|
|
query = query.filter(models.BareMetalNode.cpus >= cpus)
|
|
if memory_mb is not None:
|
|
query = query.filter(models.BareMetalNode.memory_mb >= memory_mb)
|
|
if local_gb is not None:
|
|
query = query.filter(models.BareMetalNode.local_gb >= local_gb)
|
|
query = _build_node_order_by(query)
|
|
return query.first()
|
|
|
|
|
|
@sqlalchemy_api.require_admin_context
|
|
def bm_node_get(context, bm_node_id):
|
|
# bm_node_id may be passed as a string. Convert to INT to improve DB perf.
|
|
bm_node_id = int(bm_node_id)
|
|
result = model_query(context, models.BareMetalNode, read_deleted="no").\
|
|
filter_by(id=bm_node_id).\
|
|
first()
|
|
|
|
if not result:
|
|
raise exception.NodeNotFound(node_id=bm_node_id)
|
|
|
|
return result
|
|
|
|
|
|
@sqlalchemy_api.require_admin_context
|
|
def bm_node_get_by_instance_uuid(context, instance_uuid):
|
|
if not uuidutils.is_uuid_like(instance_uuid):
|
|
raise exception.InstanceNotFound(instance_id=instance_uuid)
|
|
|
|
result = model_query(context, models.BareMetalNode, read_deleted="no").\
|
|
filter_by(instance_uuid=instance_uuid).\
|
|
first()
|
|
|
|
if not result:
|
|
raise exception.InstanceNotFound(instance_id=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)
|
|
return bm_node_ref
|
|
|
|
|
|
@sqlalchemy_api.require_admin_context
|
|
def bm_node_update(context, bm_node_id, values):
|
|
rows = model_query(context, models.BareMetalNode, read_deleted="no").\
|
|
filter_by(id=bm_node_id).\
|
|
update(values)
|
|
|
|
if not rows:
|
|
raise exception.NodeNotFound(node_id=bm_node_id)
|
|
|
|
|
|
@sqlalchemy_api.require_admin_context
|
|
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.
|
|
Allow the caller to set any other fields they require in the same
|
|
operation. For example, this is used to set the node's task_state to
|
|
BUILDING at the beginning of driver.spawn().
|
|
|
|
"""
|
|
if 'instance_uuid' not in values:
|
|
raise exception.NovaException(_(
|
|
"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(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 %(i_uuid)s to baremetal node "
|
|
"%(n_uuid)s.") % {'i_uuid': values['instance_uuid'],
|
|
'n_uuid': node_uuid})
|
|
ref = query.first()
|
|
return ref
|
|
|
|
|
|
@sqlalchemy_api.require_admin_context
|
|
def bm_node_destroy(context, bm_node_id):
|
|
# First, delete all interfaces belonging to the node.
|
|
# Delete physically since these have unique columns.
|
|
session = db_session.get_session()
|
|
with session.begin():
|
|
model_query(context, models.BareMetalInterface, read_deleted="no").\
|
|
filter_by(bm_node_id=bm_node_id).\
|
|
delete()
|
|
rows = model_query(context, models.BareMetalNode, read_deleted="no").\
|
|
filter_by(id=bm_node_id).\
|
|
update({'deleted': True,
|
|
'deleted_at': timeutils.utcnow(),
|
|
'updated_at': literal_column('updated_at')})
|
|
|
|
if not rows:
|
|
raise exception.NodeNotFound(node_id=bm_node_id)
|
|
|
|
|
|
@sqlalchemy_api.require_admin_context
|
|
def bm_pxe_ip_get_all(context):
|
|
query = model_query(context, models.BareMetalPxeIp, read_deleted="no")
|
|
return query.all()
|
|
|
|
|
|
@sqlalchemy_api.require_admin_context
|
|
def bm_pxe_ip_create(context, address, server_address):
|
|
ref = models.BareMetalPxeIp()
|
|
ref.address = address
|
|
ref.server_address = server_address
|
|
_save(ref)
|
|
return ref
|
|
|
|
|
|
@sqlalchemy_api.require_admin_context
|
|
def bm_pxe_ip_create_direct(context, bm_pxe_ip):
|
|
ref = bm_pxe_ip_create(context,
|
|
address=bm_pxe_ip['address'],
|
|
server_address=bm_pxe_ip['server_address'])
|
|
return ref
|
|
|
|
|
|
@sqlalchemy_api.require_admin_context
|
|
def bm_pxe_ip_destroy(context, ip_id):
|
|
# Delete physically since it has unique columns
|
|
model_query(context, models.BareMetalPxeIp, read_deleted="no").\
|
|
filter_by(id=ip_id).\
|
|
delete()
|
|
|
|
|
|
@sqlalchemy_api.require_admin_context
|
|
def bm_pxe_ip_destroy_by_address(context, address):
|
|
# Delete physically since it has unique columns
|
|
model_query(context, models.BareMetalPxeIp, read_deleted="no").\
|
|
filter_by(address=address).\
|
|
delete()
|
|
|
|
|
|
@sqlalchemy_api.require_admin_context
|
|
def bm_pxe_ip_get(context, ip_id):
|
|
result = model_query(context, models.BareMetalPxeIp, read_deleted="no").\
|
|
filter_by(id=ip_id).\
|
|
first()
|
|
|
|
return result
|
|
|
|
|
|
@sqlalchemy_api.require_admin_context
|
|
def bm_pxe_ip_get_by_bm_node_id(context, bm_node_id):
|
|
result = model_query(context, models.BareMetalPxeIp, read_deleted="no").\
|
|
filter_by(bm_node_id=bm_node_id).\
|
|
first()
|
|
|
|
if not result:
|
|
raise exception.NodeNotFound(node_id=bm_node_id)
|
|
|
|
return result
|
|
|
|
|
|
@sqlalchemy_api.require_admin_context
|
|
def bm_pxe_ip_associate(context, bm_node_id):
|
|
session = db_session.get_session()
|
|
with session.begin():
|
|
# Check if the node really exists
|
|
node_ref = model_query(context, models.BareMetalNode,
|
|
read_deleted="no", session=session).\
|
|
filter_by(id=bm_node_id).\
|
|
first()
|
|
if not node_ref:
|
|
raise exception.NodeNotFound(node_id=bm_node_id)
|
|
|
|
# Check if the node already has a pxe_ip
|
|
ip_ref = model_query(context, models.BareMetalPxeIp,
|
|
read_deleted="no", session=session).\
|
|
filter_by(bm_node_id=bm_node_id).\
|
|
first()
|
|
if ip_ref:
|
|
return ip_ref.id
|
|
|
|
# with_lockmode('update') and filter_by(bm_node_id=None) will lock all
|
|
# records. It may cause a performance problem in high-concurrency
|
|
# environment.
|
|
ip_ref = model_query(context, models.BareMetalPxeIp,
|
|
read_deleted="no", session=session).\
|
|
filter_by(bm_node_id=None).\
|
|
with_lockmode('update').\
|
|
first()
|
|
|
|
# this exception is not caught in nova/compute/manager
|
|
if not ip_ref:
|
|
raise exception.NovaException(_("No more PXE IPs available"))
|
|
|
|
ip_ref.bm_node_id = bm_node_id
|
|
session.add(ip_ref)
|
|
return ip_ref.id
|
|
|
|
|
|
@sqlalchemy_api.require_admin_context
|
|
def bm_pxe_ip_disassociate(context, bm_node_id):
|
|
model_query(context, models.BareMetalPxeIp, read_deleted="no").\
|
|
filter_by(bm_node_id=bm_node_id).\
|
|
update({'bm_node_id': None})
|
|
|
|
|
|
@sqlalchemy_api.require_admin_context
|
|
def bm_interface_get(context, if_id):
|
|
result = model_query(context, models.BareMetalInterface,
|
|
read_deleted="no").\
|
|
filter_by(id=if_id).\
|
|
first()
|
|
|
|
if not result:
|
|
raise exception.NovaException(_("Baremetal interface %s "
|
|
"not found") % if_id)
|
|
|
|
return result
|
|
|
|
|
|
@sqlalchemy_api.require_admin_context
|
|
def bm_interface_get_all(context):
|
|
query = model_query(context, models.BareMetalInterface,
|
|
read_deleted="no")
|
|
return query.all()
|
|
|
|
|
|
@sqlalchemy_api.require_admin_context
|
|
def bm_interface_destroy(context, if_id):
|
|
# Delete physically since it has unique columns
|
|
model_query(context, models.BareMetalInterface, read_deleted="no").\
|
|
filter_by(id=if_id).\
|
|
delete()
|
|
|
|
|
|
@sqlalchemy_api.require_admin_context
|
|
def bm_interface_create(context, bm_node_id, address, datapath_id, port_no):
|
|
ref = models.BareMetalInterface()
|
|
ref.bm_node_id = bm_node_id
|
|
ref.address = address
|
|
ref.datapath_id = datapath_id
|
|
ref.port_no = port_no
|
|
_save(ref)
|
|
return ref.id
|
|
|
|
|
|
@sqlalchemy_api.require_admin_context
|
|
def bm_interface_set_vif_uuid(context, if_id, vif_uuid):
|
|
session = db_session.get_session()
|
|
with session.begin():
|
|
bm_interface = model_query(context, models.BareMetalInterface,
|
|
read_deleted="no", session=session).\
|
|
filter_by(id=if_id).\
|
|
with_lockmode('update').\
|
|
first()
|
|
if not bm_interface:
|
|
raise exception.NovaException(_("Baremetal interface %s "
|
|
"not found") % if_id)
|
|
|
|
bm_interface.vif_uuid = vif_uuid
|
|
try:
|
|
session.add(bm_interface)
|
|
session.flush()
|
|
except db_exc.DBError, e:
|
|
# TODO(deva): clean up when db layer raises DuplicateKeyError
|
|
if str(e).find('IntegrityError') != -1:
|
|
raise exception.NovaException(_("Baremetal interface %s "
|
|
"already in use") % vif_uuid)
|
|
else:
|
|
raise e
|
|
|
|
|
|
@sqlalchemy_api.require_admin_context
|
|
def bm_interface_get_by_vif_uuid(context, vif_uuid):
|
|
result = model_query(context, models.BareMetalInterface,
|
|
read_deleted="no").\
|
|
filter_by(vif_uuid=vif_uuid).\
|
|
first()
|
|
|
|
if not result:
|
|
raise exception.NovaException(_("Baremetal virtual interface %s "
|
|
"not found") % vif_uuid)
|
|
|
|
return result
|
|
|
|
|
|
@sqlalchemy_api.require_admin_context
|
|
def bm_interface_get_all_by_bm_node_id(context, bm_node_id):
|
|
result = model_query(context, models.BareMetalInterface,
|
|
read_deleted="no").\
|
|
filter_by(bm_node_id=bm_node_id).\
|
|
all()
|
|
|
|
if not result:
|
|
raise exception.NodeNotFound(node_id=bm_node_id)
|
|
|
|
return result
|