ironic/ironic/db/sqlalchemy/api.py

434 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 ironic.openstack.common.db import exception as db_exc
from ironic.openstack.common import timeutils
from ironic.openstack.common import uuidutils
from nova.virt.baremetal.db.sqlalchemy import models
from nova.virt.baremetal.db.sqlalchemy import session as db_session
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 is not 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 is 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 is 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 as 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