"""SQLAlchemy backend implementation.""" import sys from oslo_config import cfg from oslo_db import exception as db_exc from oslo_db import options as db_options from oslo_db.sqlalchemy import session from oslo_db.sqlalchemy import utils as db_utils from oslo_log import log from oslo_utils import uuidutils import sqlalchemy.orm.exc as sa_exc from sqlalchemy.orm import with_polymorphic from craton import exceptions from craton.db.sqlalchemy import models CONF = cfg.CONF LOG = log.getLogger(__name__) _FACADE = None _DEFAULT_SQL_CONNECTION = 'sqlite://' db_options.set_defaults(cfg.CONF, connection=_DEFAULT_SQL_CONNECTION) def _create_facade_lazily(): global _FACADE if _FACADE is None: _FACADE = session.EngineFacade.from_config(cfg.CONF) return _FACADE def get_engine(): facade = _create_facade_lazily() return facade.get_engine() def get_session(**kwargs): facade = _create_facade_lazily() return facade.get_session(**kwargs) def get_backend(): """The backend is this module itself.""" return sys.modules[__name__] def is_admin_context(context): """Check if this request had admin project context.""" if (context.is_admin and context.is_admin_project): return True return False def is_project_admin_context(context): """Check if this request has admin context with in the project.""" if context.is_admin: return True return False def require_admin_context(f): """Decorator that ensures admin request context.""" def wrapper(*args, **kwargs): if not is_admin_context(args[0]): raise exceptions.AdminRequired() return f(*args, **kwargs) return wrapper def require_project_admin_context(f): """Decorator that ensures admin or project_admin request context.""" def wrapper(*args, **kwargs): if is_admin_context(args[0]): return f(*args, **kwargs) elif is_project_admin_context(args[0]): return f(*args, **kwargs) else: raise exceptions.AdminRequired() return wrapper def model_query(context, model, *args, **kwargs): """Query helper that accounts for context's `read_deleted` field. :param context: context to query under :param model: model to query. Must be a subclass of ModelBase. :param session: if present, the session to use :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 get_session() project_only = kwargs.get('project_only') kwargs = dict() if project_only and not context.is_admin: kwargs['project_id'] = context.tenant return db_utils.model_query( model=model, session=session, args=args, **kwargs) def get_user_info(context, username): """Get user info.""" query = model_query(context, models.User, project_only=True) query = query.filter_by(username=username) try: return query.one() except sa_exc.NoResultFound: raise exceptions.NotFound() except Exception as err: raise exceptions.UnknownException(message=err) def _device_labels_update(context, device_type, device_id, labels): """Update labels for the given device. Add the label if it is not present in host labels list, otherwise do nothing.""" session = get_session() with session.begin(): devices = with_polymorphic(models.Device, '*') query = model_query(context, devices, session=session, project_only=True) query = query.filter_by(type=device_type) query = query.filter_by(id=device_id) try: device = query.one() except sa_exc.NoResultFound: raise exceptions.NotFound() device.labels.update(labels["labels"]) device.save(session) return device def _device_labels_delete(context, device_type, device_id, labels): """Delete labels from the device labels list if it matches the given label in the query, otherwise do nothing.""" session = get_session() with session.begin(): devices = with_polymorphic(models.Device, '*') query = model_query(context, devices, session=session, project_only=True) query = query.filter_by(type=device_type) query = query.filter_by(id=device_id) try: device = query.one() except sa_exc.NoResultFound: raise exceptions.NotFound() for label in labels["labels"]: device.labels.discard(label) device.save(session) return device def cells_get_all(context, region): """Get all cells.""" query = model_query(context, models.Cell, project_only=True) if region is not None: query = query.filter_by(region_id=region) try: return query.all() except sa_exc.NoResultFound: raise exceptions.NotFound() except Exception as err: raise exceptions.UnknownException(message=err) def cells_get_by_name(context, region_id, cell_id): """Get cell details given for a given cell in a region.""" try: query = model_query(context, models.Cell).\ filter_by(region_id=region_id).\ filter_by(name=cell_id) return query.one() except sa_exc.NoResultFound: raise exceptions.NotFound() def cells_get_by_id(context, cell_id): """Get cell details given for a given cell id.""" try: query = model_query(context, models.Cell).\ filter_by(id=cell_id) return query.one() except sa_exc.NoResultFound: raise exceptions.NotFound() def cells_create(context, values): """Create a new cell.""" session = get_session() cell = models.Cell() with session.begin(): cell.update(values) cell.save(session) return cell def cells_update(context, cell_id, values): """Update an existing cell.""" session = get_session() with session.begin(): query = model_query(context, models.Cell, session=session, project_only=True) query = query.filter_by(id=cell_id) try: cell_ref = query.with_lockmode('update').one() except Exception: raise cell_ref.update(values) cell_ref.save(session) return cell_ref def cells_delete(context, cell_id): """Delete an existing cell.""" session = get_session() with session.begin(): query = model_query(context, models.Cell, session=session, project_only=True) query = query.filter_by(id=cell_id) query.delete() def cells_data_update(context, cell_id, data): """Update existing cells variables or create when its not present. """ session = get_session() with session.begin(): query = model_query(context, models.Cell, session=session, project_only=True) query = query.filter_by(id=cell_id) try: cell_ref = query.with_lockmode('update').one() except sa_exc.NoResultFound: # cell does not exist so can't do this raise for key in data: cell_ref.variables[key] = data[key] return cell_ref def cells_data_delete(context, cell_id, data): """Delete the existing key (variable) from cells data.""" session = get_session() with session.begin(): query = model_query(context, models.Cell, session=session, project_only=True) query = query.filter_by(id=cell_id) try: cell_ref = query.with_lockmode('update').one() except sa_exc.NoResultFound: # cell does not exist so can't do this raise for key in data: try: del cell_ref.variables[data[key]] except KeyError: # This key does not exist so just ignore pass return cell_ref def regions_get_all(context): """Get all available regions.""" query = model_query(context, models.Region, project_only=True) try: return query.all() except sa_exc.NoResultFound: raise exceptions.NotFound() def regions_get_by_name(context, name): """Get cell detail for the region with given name.""" query = model_query(context, models.Region, project_only=True) query = query.filter_by(name=name) try: return query.one() except sa_exc.NoResultFound: raise exceptions.NotFound() def regions_get_by_id(context, region_id): """Get cell detail for the region with given id.""" query = model_query(context, models.Region, project_only=True) query = query.filter_by(id=region_id) try: return query.one() except sa_exc.NoResultFound: raise exceptions.NotFound() def regions_create(context, values): """Create a new region.""" session = get_session() region = models.Region() with session.begin(): region.update(values) region.save(session) return region def regions_update(context, region_id, values): """Update an existing region.""" # We dont have anything to update right now pass def regions_delete(context, region_id): """Delete an existing region.""" session = get_session() with session.begin(): query = model_query(context, models.Region, session=session, project_only=True) query = query.filter_by(id=region_id) query.delete() return def regions_data_update(context, region_id, data): """ Update existing region variables or create when its not present. """ session = get_session() with session.begin(): query = model_query(context, models.Region, session=session, project_only=True) query = query.filter_by(id=region_id) try: region_ref = query.with_lockmode('update').one() except sa_exc.NoResultFound: # region does not exist so can't do this raise for key in data: region_ref.variables[key] = data[key] return region_ref def regions_data_delete(context, region_id, data): """Delete the existing key (variable) from region data.""" session = get_session() with session.begin(): query = model_query(context, models.Region, session=session, project_only=True) query = query.filter_by(id=region_id) try: region_ref = query.with_lockmode('update').one() except sa_exc.NoResultFound: # region does not exist so can't do this raise for key in data: try: del region_ref.variables[data[key]] except KeyError: # This key does not exist so just ignore pass return region_ref def hosts_get_by_region(context, region_id, filters): """Get all hosts for this region. :param region_id: ID for the region :param filters: filters wich contains differnt keys/values to match. Supported filters are by name, ip_address, id and cell_id. """ host_devices = with_polymorphic(models.Device, [models.Host]) query = model_query(context, host_devices, project_only=True) query = query.filter_by(region_id=region_id) if "name" in filters: query = query.filter_by(name=filters["name"]) if "ip_address" in filters: query = query.filter_by(ip_address=filters["ip_address"]) if "id" in filters: query = query.filter_by(id=filters["id"]) if "cell" in filters: query = query.filter_by(cell_id=filters["cell"]) if "device_type" in filters: query = query.filter_by(device_type=filters["device_type"]) try: result = query.all() except sa_exc.NoResultFound: raise exceptions.NotFound() except Exception as err: raise exceptions.UnknownException(message=err) return result def hosts_get_by_id(context, host_id): """Get details for the host with given id.""" host_devices = with_polymorphic(models.Device, '*') query = model_query(context, host_devices, project_only=True).\ filter_by(id=host_id) try: result = query.one() LOG.info("Result by host id %s" % result) except sa_exc.NoResultFound: LOG.error("No result found for host with id %s" % host_id) raise exceptions.NotFound() except Exception as err: raise exceptions.UnknownException(message=err) return result def hosts_create(context, values): """Create a new host.""" session = get_session() host = models.Host() with session.begin(): host.update(values) host.save(session) return host def hosts_update(context, host_id, values): """Update an existing host.""" return None def hosts_delete(context, host_id): """Delete an existing host.""" session = get_session() with session.begin(): host_devices = with_polymorphic(models.Device, '*') query = model_query(context, host_devices, session=session, project_only=True) query = query.filter_by(id=host_id) query.delete() return def hosts_data_update(context, host_id, data): """ Update existing host variables or create when its not present. """ session = get_session() with session.begin(): host_devices = with_polymorphic(models.Device, '*') query = model_query(context, host_devices, session=session, project_only=True) query = query.filter_by(id=host_id) try: host_ref = query.with_lockmode('update').one() except sa_exc.NoResultFound: raise exceptions.NotFound() for key in data: host_ref.variables[key] = data[key] return host_ref def hosts_data_delete(context, host_id, data): """Delete the existing key (variable) from region data.""" session = get_session() with session.begin(): host_devices = with_polymorphic(models.Device, '*') query = model_query(context, host_devices, session=session, project_only=True) query = query.filter_by(id=host_id) try: host_ref = query.with_lockmode('update').one() except sa_exc.NoResultFound: raise exceptions.NotFound() for key in data: try: del host_ref.variables[data[key]] except KeyError: pass return host_ref def hosts_labels_update(context, host_id, labels): """Update labels for host. Add the label if it is not present in host labels list, otherwise do nothing.""" return _device_labels_update(context, 'hosts', host_id, labels) def hosts_labels_delete(context, host_id, labels): """Delete labels from the host labels list if it matches the given label in the query, otherwise do nothing.""" return _device_labels_delete(context, 'hosts', host_id, labels) @require_admin_context def projects_get_all(context): """Get all the projects.""" query = model_query(context, models.Project) try: return query.all() except sa_exc.NoResultFound: raise exceptions.NotFound() except Exception as err: raise exceptions.UnknownException(message=err) @require_admin_context def projects_get_by_name(context, project_name): """Get all projects that match the given name.""" query = model_query(context, models.Project) query = query.filter(models.Project.name.like(project_name)) try: return query.all() except sa_exc.NoResultFound: raise exceptions.NotFound() except Exception as err: raise exceptions.UnknownException(message=err) @require_admin_context def projects_get_by_id(context, project_id): """Get project by its id.""" query = model_query(context, models.Project) query = query.filter_by(id=project_id) try: return query.one() except sa_exc.NoResultFound: raise exceptions.NotFound() except Exception as err: raise exceptions.UnknownException(message=err) @require_admin_context def projects_create(context, values): """Create a new project with given values.""" session = get_session() project = models.Project() if not values.get('id'): values['id'] = uuidutils.generate_uuid() with session.begin(): project.update(values) project.save(session) return project @require_admin_context def projects_delete(context, project_id): """Delete an existing project given by its id.""" session = get_session() with session.begin(): query = model_query(context, models.Project, session=session) query = query.filter_by(id=project_id) query.delete() @require_project_admin_context def users_get_all(context): """Get all the users.""" if is_admin_context(context): LOG.info("Getting all users as root user") query = model_query(context, models.User) else: LOG.info("Getting all users as project admin user") query = model_query(context, models.User, project_only=True) query = query.filter_by(project_id=context.tenant) return query.all() @require_project_admin_context def users_get_by_name(context, user_name): """Get all users that match the given username.""" if is_admin_context(context): query = model_query(context, models.User) else: query = model_query(context, models.User, project_only=True) query = query.filter_by(username=user_name) return query.all() @require_project_admin_context def users_get_by_id(context, user_id): """Get user by its id.""" if is_admin_context(context): query = model_query(context, models.User) else: query = model_query(context, models.User, project_only=True) query = query.filter_by(id=user_id) try: return query.one() except sa_exc.NoResultFound: raise exceptions.NotFound() @require_project_admin_context def users_create(context, values): """Create a new user with given values.""" session = get_session() user = models.User() with session.begin(): user.update(values) user.save(session) return user @require_project_admin_context def users_delete(context, user_id): """Delete an existing user given by its id.""" LOG.info("Deleting user with id %s" % user_id) session = get_session() with session.begin(): query = model_query(context, models.User, session=session) query = query.filter_by(id=user_id) query.delete() return def networks_get_by_region(context, region_id, filters): """Get all networks for the given region.""" query = model_query(context, models.Network, project_only=True) query = query.filter_by(region_id=region_id) if "id" in filters: query = query.filter_by(id=filters["id"]) if "network_type" in filters: query = query.filter_by(network_type=filters["network_type"]) if "cell_id" in filters: query = query.filter_by(cell_id=filters["cell_id"]) if "name" in filters: query = query.filter_by(name=filters["name"]) result = query.all() return result def networks_get_by_id(context, network_id): """Get a given network by its id.""" query = model_query(context, models.Network, project_only=True) query = query.filter_by(id=network_id) try: result = query.one() except sa_exc.NoResultFound: raise exceptions.NotFound() return result def networks_create(context, values): """Create a new network.""" session = get_session() network = models.Network() with session.begin(): try: network.update(values) network.save(session) except db_exc.DBDuplicateEntry: raise exceptions.DuplicateNetwork() return network def networks_delete(context, network_id): """Delete existing network.""" session = get_session() with session.begin(): query = model_query(context, models.Network, session=session, project_only=True) query = query.filter_by(id=network_id) query.delete() return def netdevices_get_by_region(context, region_id, filters): """Get all network devices for the given region.""" devices = with_polymorphic(models.Device, [models.NetDevice]) query = model_query(context, devices, project_only=True) query = query.filter_by(region_id=region_id) query = query.filter_by(type='net_devices') result = query.all() return result def netdevices_get_by_id(context, netdevice_id): """Get a given network device by its id.""" devices = with_polymorphic(models.Device, [models.NetDevice]) query = model_query(context, devices, project_only=True) query = query.filter_by(type='net_devices') query = query.filter_by(id=netdevice_id) try: result = query.one() except sa_exc.NoResultFound: raise exceptions.NotFound() return result def netdevices_create(context, values): """Create a new network device.""" session = get_session() device = models.NetDevice() with session.begin(): device.update(values) device.save(session) return device def netdevices_delete(context, netdevice_id): """Delete existing network device.""" session = get_session() with session.begin(): device = with_polymorphic(models.Device, '*') query = model_query(context, device, session=session, project_only=True) query = query.filter_by(type='net_devices') query = query.filter_by(id=netdevice_id) query.delete() def netdevices_labels_update(context, device_id, labels): """Update labels for a network device. Add the label if it is not present in host labels list, otherwise do nothing.""" return _device_labels_update(context, 'net_devices', device_id, labels) def netdevices_labels_delete(context, device_id, labels): """Delete labels from the network device labels list if it matches the given label in the query, otherwise do nothing.""" return _device_labels_delete(context, 'net_devices', device_id, labels) def net_interfaces_get_by_device(context, device_id, filters): """Get all network interfaces for the given host.""" query = model_query(context, models.NetInterface, project_only=True) query = query.filter_by(device_id=device_id) if "id" in filters: query = query.filter_by(id=filters["id"]) if "ip_address" in filters: query = query.filter_by(ip_address=filters["ip_address"]) if "interface_type" in filters: query = query.filter_by(interface_type=filters["interface_type"]) return query.all() def net_interfaces_get_by_id(context, interface_id): """Get a given network interface by its id.""" query = model_query(context, models.NetInterface, project_only=True) query = query.filter_by(id=interface_id) try: result = query.one() except sa_exc.NoResultFound: raise exceptions.NotFound() return result def net_interfaces_create(context, values): """Create a new network interface.""" session = get_session() interface = models.NetInterface() with session.begin(): interface.update(values) interface.save(session) return interface def net_interfaces_delete(context, interface_id): """Delete existing network interface.""" session = get_session() with session.begin(): query = model_query(context, models.NetInterface, session=session, project_only=True) query = query.filter_by(id=interface_id) query.delete()