designate/designate/storage/impl_sqlalchemy/__init__.py

687 lines
23 KiB
Python

# Copyright 2012 Managed I.T.
#
# Author: Kiall Mac Innes <kiall@managedit.ie>
#
# 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.
import time
from sqlalchemy.orm import exc
from sqlalchemy import distinct, func
from oslo.config import cfg
from designate.openstack.common import log as logging
from designate.openstack.common.db.sqlalchemy.utils import paginate_query
from designate import exceptions
from designate.storage import base
from designate.storage.impl_sqlalchemy import models
from designate.sqlalchemy.models import SoftDeleteMixin
from designate.sqlalchemy.session import get_session
from designate.sqlalchemy.session import get_engine
from designate.sqlalchemy.session import SQLOPTS
LOG = logging.getLogger(__name__)
cfg.CONF.register_group(cfg.OptGroup(
name='storage:sqlalchemy', title="Configuration for SQLAlchemy Storage"
))
cfg.CONF.register_opts(SQLOPTS, group='storage:sqlalchemy')
class SQLAlchemyStorage(base.Storage):
""" SQLAlchemy connection """
__plugin_name__ = 'sqlalchemy'
def __init__(self):
super(SQLAlchemyStorage, self).__init__()
self.engine = get_engine(self.name)
self.session = get_session(self.name)
def begin(self):
self.session.begin(subtransactions=True)
def commit(self):
self.session.commit()
def rollback(self):
self.session.rollback()
def setup_schema(self):
""" Semi-Private Method to create the database schema """
models.Base.metadata.create_all(self.session.bind)
def teardown_schema(self):
""" Semi-Private Method to reset the database schema """
models.Base.metadata.drop_all(self.session.bind)
def _apply_criterion(self, model, query, criterion):
if criterion is not None:
for name, value in criterion.items():
column = getattr(model, name)
if isinstance(value, basestring) and '%' in value:
query = query.filter(column.like(value))
else:
query = query.filter(column == value)
return query
def _apply_tenant_criteria(self, context, model, query):
if hasattr(model, 'tenant_id'):
if context.all_tenants:
LOG.debug('Including all tenants items in query results')
else:
query = query.filter(model.tenant_id == context.tenant_id)
return query
def _apply_deleted_criteria(self, context, model, query):
if issubclass(model, SoftDeleteMixin):
if context.show_deleted:
LOG.debug('Including deleted items in query results')
else:
query = query.filter(model.deleted == "0")
return query
def _find(self, model, context, criterion, one=False,
marker=None, limit=None, sort_key=None, sort_dir=None):
"""
Base "finder" method
Used to abstract these details from all the _find_*() methods.
"""
# First up, create a query and apply the various filters
query = self.session.query(model)
query = self._apply_criterion(model, query, criterion)
query = self._apply_tenant_criteria(context, model, query)
query = self._apply_deleted_criteria(context, model, query)
if one:
# If we're asked to return exactly one record, but multiple or
# none match, raise a NotFound
try:
return query.one()
except (exc.NoResultFound, exc.MultipleResultsFound):
raise exceptions.NotFound()
else:
# If marker is not none and basestring we query it.
# Othwewise, return all matching records
if marker is not None:
try:
marker = self._find(model, context, {'id': marker},
one=True)
except exceptions.NotFound:
raise exceptions.MarkerNotFound(
'Marker %s could not be found' % marker)
sort_key = sort_key or 'created_at'
sort_dir = sort_dir or 'asc'
query = paginate_query(
query, model, limit,
[sort_key, 'id', 'created_at'], marker=marker,
sort_dir=sort_dir)
return query.all()
## CRUD for our resources (quota, server, tsigkey, tenant, domain & record)
## R - get_*, find_*s
##
## Standard Arguments
## self - python object for the class
## context - a dictionary of details about the request (http etc),
## provided by flask.
## criterion - dictionary of filters to be applied
##
# Quota Methods
def _find_quotas(self, context, criterion, one=False,
marker=None, limit=None, sort_key=None, sort_dir=None):
try:
return self._find(models.Quota, context, criterion, one=one,
marker=marker, limit=limit, sort_key=sort_key,
sort_dir=sort_dir)
except exceptions.NotFound:
raise exceptions.QuotaNotFound()
def create_quota(self, context, values):
quota = models.Quota()
quota.update(values)
try:
quota.save(self.session)
except exceptions.Duplicate:
raise exceptions.DuplicateQuota()
return dict(quota)
def get_quota(self, context, quota_id):
quota = self._find_quotas(context, {'id': quota_id}, one=True)
return dict(quota)
def find_quotas(self, context, criterion=None,
marker=None, limit=None, sort_key=None, sort_dir=None):
quotas = self._find_quotas(context, criterion, marker=marker,
limit=limit, sort_key=sort_key,
sort_dir=sort_dir)
return [dict(q) for q in quotas]
def find_quota(self, context, criterion):
quota = self._find_quotas(context, criterion, one=True)
return dict(quota)
def update_quota(self, context, quota_id, values):
quota = self._find_quotas(context, {'id': quota_id}, one=True)
quota.update(values)
try:
quota.save(self.session)
except exceptions.Duplicate:
raise exceptions.DuplicateQuota()
return dict(quota)
def delete_quota(self, context, quota_id):
quota = self._find_quotas(context, {'id': quota_id}, one=True)
quota.delete(self.session)
# Server Methods
def _find_servers(self, context, criterion, one=False,
marker=None, limit=None, sort_key=None, sort_dir=None):
try:
return self._find(models.Server, context, criterion, one,
marker=marker, limit=limit, sort_key=sort_key,
sort_dir=sort_dir)
except exceptions.NotFound:
raise exceptions.ServerNotFound()
def create_server(self, context, values):
server = models.Server()
server.update(values)
try:
server.save(self.session)
except exceptions.Duplicate:
raise exceptions.DuplicateServer()
return dict(server)
def find_servers(self, context, criterion=None,
marker=None, limit=None, sort_key=None, sort_dir=None):
servers = self._find_servers(context, criterion, marker=marker,
limit=limit, sort_key=sort_key,
sort_dir=sort_dir)
return [dict(s) for s in servers]
def get_server(self, context, server_id):
server = self._find_servers(context, {'id': server_id}, one=True)
return dict(server)
def update_server(self, context, server_id, values):
server = self._find_servers(context, {'id': server_id}, one=True)
server.update(values)
try:
server.save(self.session)
except exceptions.Duplicate:
raise exceptions.DuplicateServer()
return dict(server)
def delete_server(self, context, server_id):
server = self._find_servers(context, {'id': server_id}, one=True)
server.delete(self.session)
# TLD Methods
def _find_tlds(self, context, criterion, one=False,
marker=None, limit=None, sort_key=None, sort_dir=None):
try:
return self._find(models.Tld, context, criterion, one=one,
marker=marker, limit=limit, sort_key=sort_key,
sort_dir=sort_dir)
except exceptions.NotFound:
raise exceptions.TLDNotFound()
def create_tld(self, context, values):
tld = models.Tld()
tld.update(values)
try:
tld.save(self.session)
except exceptions.Duplicate:
raise exceptions.DuplicateTLD()
return dict(tld)
def find_tlds(self, context, criterion=None,
marker=None, limit=None, sort_key=None, sort_dir=None):
tlds = self._find_tlds(context, criterion, marker=marker, limit=limit,
sort_key=sort_key, sort_dir=sort_dir)
return [dict(s) for s in tlds]
def find_tld(self, context, criterion=None):
tld = self._find_tlds(context, criterion, one=True)
return dict(tld)
def get_tld(self, context, tld_id):
tld = self._find_tlds(context, {'id': tld_id}, one=True)
return dict(tld)
def update_tld(self, context, tld_id, values):
tld = self._find_tlds(context, {'id': tld_id}, one=True)
tld.update(values)
try:
tld.save(self.session)
except exceptions.Duplicate:
raise exceptions.DuplicateTLD()
return dict(tld)
def delete_tld(self, context, tld_id):
tld = self._find_tlds(context, {'id': tld_id}, one=True)
tld.delete(self.session)
# TSIG Key Methods
def _find_tsigkeys(self, context, criterion, one=False,
marker=None, limit=None, sort_key=None, sort_dir=None):
try:
return self._find(models.TsigKey, context, criterion, one=one,
marker=marker, limit=limit, sort_key=sort_key,
sort_dir=sort_dir)
except exceptions.NotFound:
raise exceptions.TsigKeyNotFound()
def create_tsigkey(self, context, values):
tsigkey = models.TsigKey()
tsigkey.update(values)
try:
tsigkey.save(self.session)
except exceptions.Duplicate:
raise exceptions.DuplicateTsigKey()
return dict(tsigkey)
def find_tsigkeys(self, context, criterion=None,
marker=None, limit=None, sort_key=None, sort_dir=None):
tsigkeys = self._find_tsigkeys(context, criterion, marker=marker,
limit=limit, sort_key=sort_key,
sort_dir=sort_dir)
return [dict(t) for t in tsigkeys]
def get_tsigkey(self, context, tsigkey_id):
tsigkey = self._find_tsigkeys(context, {'id': tsigkey_id}, one=True)
return dict(tsigkey)
def update_tsigkey(self, context, tsigkey_id, values):
tsigkey = self._find_tsigkeys(context, {'id': tsigkey_id}, one=True)
tsigkey.update(values)
try:
tsigkey.save(self.session)
except exceptions.Duplicate:
raise exceptions.DuplicateTsigKey()
return dict(tsigkey)
def delete_tsigkey(self, context, tsigkey_id):
tsigkey = self._find_tsigkeys(context, {'id': tsigkey_id}, one=True)
tsigkey.delete(self.session)
##
## Tenant Methods
##
def find_tenants(self, context):
# returns an array of tenant_id & count of their domains
query = self.session.query(models.Domain.tenant_id,
func.count(models.Domain.id))
query = self._apply_tenant_criteria(context, models.Domain, query)
query = self._apply_deleted_criteria(context, models.Domain, query)
query = query.group_by(models.Domain.tenant_id)
return [{'id': t[0], 'domain_count': t[1]} for t in query.all()]
def get_tenant(self, context, tenant_id):
# get list list & count of all domains owned by given tenant_id
query = self.session.query(models.Domain.name)
query = self._apply_tenant_criteria(context, models.Domain, query)
query = self._apply_deleted_criteria(context, models.Domain, query)
query = query.filter(models.Domain.tenant_id == tenant_id)
result = query.all()
return {
'id': tenant_id,
'domain_count': len(result),
'domains': [r[0] for r in result]
}
def count_tenants(self, context):
# tenants are the owner of domains, count the number of unique tenants
# select count(distinct tenant_id) from domains
query = self.session.query(distinct(models.Domain.tenant_id))
query = self._apply_tenant_criteria(context, models.Domain, query)
query = self._apply_deleted_criteria(context, models.Domain, query)
return query.count()
##
## Domain Methods
##
def _find_domains(self, context, criterion, one=False,
marker=None, limit=None, sort_key=None, sort_dir=None):
try:
return self._find(models.Domain, context, criterion, one=one,
marker=marker, limit=limit, sort_key=sort_key,
sort_dir=sort_dir)
except exceptions.NotFound:
raise exceptions.DomainNotFound()
def create_domain(self, context, values):
domain = models.Domain()
domain.update(values)
try:
domain.save(self.session)
except exceptions.Duplicate:
raise exceptions.DuplicateDomain()
return dict(domain)
def get_domain(self, context, domain_id):
domain = self._find_domains(context, {'id': domain_id}, one=True)
return dict(domain)
def find_domains(self, context, criterion=None,
marker=None, limit=None, sort_key=None, sort_dir=None):
domains = self._find_domains(context, criterion, marker=marker,
limit=limit, sort_key=sort_key,
sort_dir=sort_dir)
return [dict(d) for d in domains]
def find_domain(self, context, criterion):
domain = self._find_domains(context, criterion, one=True)
return dict(domain)
def update_domain(self, context, domain_id, values):
domain = self._find_domains(context, {'id': domain_id}, one=True)
domain.update(values)
try:
domain.save(self.session)
except exceptions.Duplicate:
raise exceptions.DuplicateDomain()
return dict(domain)
def delete_domain(self, context, domain_id):
domain = self._find_domains(context, {'id': domain_id}, one=True)
domain.soft_delete(self.session)
return dict(domain)
def count_domains(self, context, criterion=None):
query = self.session.query(models.Domain)
query = self._apply_criterion(models.Domain, query, criterion)
query = self._apply_tenant_criteria(context, models.Domain, query)
query = self._apply_deleted_criteria(context, models.Domain, query)
return query.count()
# RecordSet Methods
def _find_recordsets(self, context, criterion, one=False,
marker=None, limit=None, sort_key=None,
sort_dir=None):
try:
return self._find(models.RecordSet, context, criterion, one=one,
marker=marker, limit=limit, sort_key=sort_key,
sort_dir=sort_dir)
except exceptions.NotFound:
raise exceptions.RecordSetNotFound()
def create_recordset(self, context, domain_id, values):
# Fetch the domain as we need the tenant_id
domain = self._find_domains(context, {'id': domain_id}, one=True)
recordset = models.RecordSet()
recordset.update(values)
recordset.tenant_id = domain['tenant_id']
recordset.domain_id = domain_id
try:
recordset.save(self.session)
except exceptions.Duplicate:
raise exceptions.DuplicateRecordSet()
return dict(recordset)
def get_recordset(self, context, recordset_id):
recordset = self._find_recordsets(context, {'id': recordset_id},
one=True)
return dict(recordset)
def find_recordsets(self, context, criterion=None,
marker=None, limit=None, sort_key=None, sort_dir=None):
recordsets = self._find_recordsets(
context, criterion, marker=marker, limit=limit, sort_key=sort_key,
sort_dir=sort_dir)
return [dict(r) for r in recordsets]
def find_recordset(self, context, criterion):
recordset = self._find_recordsets(context, criterion, one=True)
return dict(recordset)
def update_recordset(self, context, recordset_id, values):
recordset = self._find_recordsets(context, {'id': recordset_id},
one=True)
recordset.update(values)
try:
recordset.save(self.session)
except exceptions.Duplicate:
raise exceptions.DuplicateRecordSet()
return dict(recordset)
def delete_recordset(self, context, recordset_id):
recordset = self._find_recordsets(context, {'id': recordset_id},
one=True)
recordset.delete(self.session)
return dict(recordset)
def count_recordsets(self, context, criterion=None):
query = self.session.query(models.RecordSet)
query = self._apply_criterion(models.RecordSet, query, criterion)
return query.count()
# Record Methods
def _find_records(self, context, criterion, one=False,
marker=None, limit=None, sort_key=None, sort_dir=None):
try:
return self._find(models.Record, context, criterion, one=one,
marker=marker, limit=limit, sort_key=sort_key,
sort_dir=sort_dir)
except exceptions.NotFound:
raise exceptions.RecordNotFound()
def create_record(self, context, domain_id, recordset_id, values):
# Fetch the domain as we need the tenant_id
domain = self._find_domains(context, {'id': domain_id}, one=True)
record = models.Record()
# Create and populate the new Record model
record = models.Record()
record.update(values)
record.tenant_id = domain['tenant_id']
record.domain_id = domain_id
record.recordset_id = recordset_id
try:
# Save the new Record model
record.save(self.session)
except exceptions.Duplicate:
raise exceptions.DuplicateRecord()
return dict(record)
def find_records(self, context, criterion=None,
marker=None, limit=None, sort_key=None, sort_dir=None):
records = self._find_records(
context, criterion, marker=marker, limit=limit, sort_key=sort_key,
sort_dir=sort_dir)
return [dict(r) for r in records]
def get_record(self, context, record_id):
record = self._find_records(context, {'id': record_id}, one=True)
return dict(record)
def find_record(self, context, criterion=None):
record = self._find_records(context, criterion, one=True)
return dict(record)
def update_record(self, context, record_id, values):
record = self._find_records(context, {'id': record_id}, one=True)
record.update(values)
try:
record.save(self.session)
except exceptions.Duplicate:
raise exceptions.DuplicateRecord()
return dict(record)
def delete_record(self, context, record_id):
record = self._find_records(context, {'id': record_id}, one=True)
record.delete(self.session)
return dict(record)
def count_records(self, context, criterion=None):
query = self.session.query(models.Record)
query = self._apply_tenant_criteria(context, models.Record, query)
query = self._apply_criterion(models.Record, query, criterion)
return query.count()
#
# Blacklist Methods
#
def _find_blacklist(self, context, criterion, one=False,
marker=None, limit=None, sort_key=None, sort_dir=None):
try:
return self._find(models.Blacklists, context, criterion, one=one,
marker=marker, limit=limit, sort_key=sort_key,
sort_dir=sort_dir)
except exceptions.NotFound:
raise exceptions.BlacklistNotFound()
def create_blacklist(self, context, values):
blacklist = models.Blacklists()
blacklist.update(values)
try:
blacklist.save(self.session)
except exceptions.Duplicate:
raise exceptions.DuplicateBlacklist()
return dict(blacklist)
def find_blacklists(self, context, criterion=None,
marker=None, limit=None, sort_key=None, sort_dir=None):
blacklists = self._find_blacklist(
context, criterion, marker=marker, limit=limit, sort_key=sort_key,
sort_dir=sort_dir)
return [dict(b) for b in blacklists]
def get_blacklist(self, context, blacklist_id):
blacklist = self._find_blacklist(context,
{'id': blacklist_id}, one=True)
return dict(blacklist)
def find_blacklist(self, context, criterion):
blacklist = self._find_blacklist(context, criterion, one=True)
return dict(blacklist)
def update_blacklist(self, context, blacklist_id, values):
blacklist = self._find_blacklist(context, {'id': blacklist_id},
one=True)
blacklist.update(values)
try:
blacklist.save(self.session)
except exceptions.Duplicate:
raise exceptions.DuplicateBlacklist()
return dict(blacklist)
def delete_blacklist(self, context, blacklist_id):
blacklist = self._find_blacklist(context, {'id': blacklist_id},
one=True)
blacklist.delete(self.session)
# diagnostics
def ping(self, context):
start_time = time.time()
try:
result = self.engine.execute('SELECT 1').first()
except Exception:
status = False
else:
status = True if result[0] == 1 else False
return {
'status': status,
'rtt': "%f" % (time.time() - start_time)
}