summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Doffman <mjdoffma@us.ibm.com>2016-03-01 13:38:19 -0600
committerMark Doffman <mjdoffma@us.ibm.com>2016-06-01 12:00:56 -0500
commit35748e7a9b3445a60522e088e38bf25e6bfef25e (patch)
treef4b1fdfe4bc4cc49775d09dec40ce4658107b1d1
parent8fac8cf5c7bddae16cc2b8a1e91a1b072f2157e4 (diff)
downloadnova-35748e7a9b3445a60522e088e38bf25e6bfef25e.tar.gz
Add aggregates tables to the API db.
CellsV2 requires that aggregates be available in the API db. Create the 'aggregates', 'aggregate_metadata' and 'aggregate_hosts' tables in the API db. blueprint cells-aggregate-api-db Change-Id: I19fa3a28181831c5cd7b19cd2a5a2ea0d40e45f8
-rw-r--r--nova/db/sqlalchemy/api_migrations/migrate_repo/versions/017_aggregates.py73
-rw-r--r--nova/db/sqlalchemy/api_models.py62
-rw-r--r--nova/tests/functional/db/api/test_migrations.py37
-rw-r--r--nova/tests/functional/db/test_aggregate_model.py57
4 files changed, 229 insertions, 0 deletions
diff --git a/nova/db/sqlalchemy/api_migrations/migrate_repo/versions/017_aggregates.py b/nova/db/sqlalchemy/api_migrations/migrate_repo/versions/017_aggregates.py
new file mode 100644
index 0000000000..482a6aa9d0
--- /dev/null
+++ b/nova/db/sqlalchemy/api_migrations/migrate_repo/versions/017_aggregates.py
@@ -0,0 +1,73 @@
+# 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.
+"""API Database migrations for aggregates"""
+
+from migrate import UniqueConstraint
+from sqlalchemy import Column
+from sqlalchemy import DateTime
+from sqlalchemy import ForeignKey
+from sqlalchemy import Index
+from sqlalchemy import Integer
+from sqlalchemy import MetaData
+from sqlalchemy import String
+from sqlalchemy import Table
+
+
+def upgrade(migrate_engine):
+ meta = MetaData()
+ meta.bind = migrate_engine
+
+ aggregates = Table('aggregates', meta,
+ Column('created_at', DateTime),
+ Column('updated_at', DateTime),
+ Column('id', Integer, primary_key=True, nullable=False),
+ Column('uuid', String(length=36)),
+ Column('name', String(length=255)),
+ Index('aggregate_uuid_idx', 'uuid'),
+ UniqueConstraint('name', name='uniq_aggregate0name'),
+ mysql_engine='InnoDB',
+ mysql_charset='utf8'
+ )
+
+ aggregates.create(checkfirst=True)
+
+ aggregate_hosts = Table('aggregate_hosts', meta,
+ Column('created_at', DateTime),
+ Column('updated_at', DateTime),
+ Column('id', Integer, primary_key=True, nullable=False),
+ Column('host', String(length=255)),
+ Column('aggregate_id', Integer, ForeignKey('aggregates.id'),
+ nullable=False),
+ UniqueConstraint('host', 'aggregate_id',
+ name='uniq_aggregate_hosts0host0aggregate_id'),
+ mysql_engine='InnoDB',
+ mysql_charset='utf8'
+ )
+
+ aggregate_hosts.create(checkfirst=True)
+
+ aggregate_metadata = Table('aggregate_metadata', meta,
+ Column('created_at', DateTime),
+ Column('updated_at', DateTime),
+ Column('id', Integer, primary_key=True, nullable=False),
+ Column('aggregate_id', Integer, ForeignKey('aggregates.id'),
+ nullable=False),
+ Column('key', String(length=255), nullable=False),
+ Column('value', String(length=255), nullable=False),
+ UniqueConstraint('aggregate_id', 'key',
+ name='uniq_aggregate_metadata0aggregate_id0key'),
+ Index('aggregate_metadata_key_idx', 'key'),
+ mysql_engine='InnoDB',
+ mysql_charset='utf8'
+ )
+
+ aggregate_metadata.create(checkfirst=True)
diff --git a/nova/db/sqlalchemy/api_models.py b/nova/db/sqlalchemy/api_models.py
index ca568066b0..e3dc379621 100644
--- a/nova/db/sqlalchemy/api_models.py
+++ b/nova/db/sqlalchemy/api_models.py
@@ -37,6 +37,68 @@ class _NovaAPIBase(models.ModelBase, models.TimestampMixin):
API_BASE = declarative_base(cls=_NovaAPIBase)
+class AggregateHost(API_BASE):
+ """Represents a host that is member of an aggregate."""
+ __tablename__ = 'aggregate_hosts'
+ __table_args__ = (schema.UniqueConstraint(
+ "host", "aggregate_id",
+ name="uniq_aggregate_hosts0host0aggregate_id"
+ ),
+ )
+ id = Column(Integer, primary_key=True, autoincrement=True)
+ host = Column(String(255))
+ aggregate_id = Column(Integer, ForeignKey('aggregates.id'), nullable=False)
+
+
+class AggregateMetadata(API_BASE):
+ """Represents a metadata key/value pair for an aggregate."""
+ __tablename__ = 'aggregate_metadata'
+ __table_args__ = (
+ schema.UniqueConstraint("aggregate_id", "key",
+ name="uniq_aggregate_metadata0aggregate_id0key"
+ ),
+ Index('aggregate_metadata_key_idx', 'key'),
+ )
+ id = Column(Integer, primary_key=True)
+ key = Column(String(255), nullable=False)
+ value = Column(String(255), nullable=False)
+ aggregate_id = Column(Integer, ForeignKey('aggregates.id'), nullable=False)
+
+
+class Aggregate(API_BASE):
+ """Represents a cluster of hosts that exists in this zone."""
+ __tablename__ = 'aggregates'
+ __table_args__ = (Index('aggregate_uuid_idx', 'uuid'),
+ schema.UniqueConstraint(
+ "name", name="uniq_aggregate0name")
+ )
+ id = Column(Integer, primary_key=True, autoincrement=True)
+ uuid = Column(String(36))
+ name = Column(String(255))
+ _hosts = orm.relationship(AggregateHost,
+ primaryjoin='Aggregate.id == AggregateHost.aggregate_id')
+ _metadata = orm.relationship(AggregateMetadata,
+ primaryjoin='Aggregate.id == AggregateMetadata.aggregate_id')
+
+ @property
+ def _extra_keys(self):
+ return ['hosts', 'metadetails', 'availability_zone']
+
+ @property
+ def hosts(self):
+ return [h.host for h in self._hosts]
+
+ @property
+ def metadetails(self):
+ return {m.key: m.value for m in self._metadata}
+
+ @property
+ def availability_zone(self):
+ if 'availability_zone' not in self.metadetails:
+ return None
+ return self.metadetails['availability_zone']
+
+
class CellMapping(API_BASE):
"""Contains information on communicating with a cell"""
__tablename__ = 'cell_mappings'
diff --git a/nova/tests/functional/db/api/test_migrations.py b/nova/tests/functional/db/api/test_migrations.py
index eeb254f7e6..d464b5206a 100644
--- a/nova/tests/functional/db/api/test_migrations.py
+++ b/nova/tests/functional/db/api/test_migrations.py
@@ -348,6 +348,43 @@ class NovaAPIMigrationsWalk(test_migrations.WalkVersionsMixin):
self.assertColumnExists(engine, 'resource_provider_aggregates',
'aggregate_id')
+ def _check_017(self, engine, data):
+ # aggregate_metadata
+ for column in ['created_at',
+ 'updated_at',
+ 'id',
+ 'aggregate_id',
+ 'key',
+ 'value']:
+ self.assertColumnExists(engine, 'aggregate_metadata', column)
+
+ self.assertUniqueConstraintExists(engine, 'aggregate_metadata',
+ ['aggregate_id', 'key'])
+ self.assertIndexExists(engine, 'aggregate_metadata',
+ 'aggregate_metadata_key_idx')
+
+ # aggregate_hosts
+ for column in ['created_at',
+ 'updated_at',
+ 'id',
+ 'host',
+ 'aggregate_id']:
+ self.assertColumnExists(engine, 'aggregate_hosts', column)
+
+ self.assertUniqueConstraintExists(engine, 'aggregate_hosts',
+ ['host', 'aggregate_id'])
+
+ # aggregates
+ for column in ['created_at',
+ 'updated_at',
+ 'id',
+ 'name']:
+ self.assertColumnExists(engine, 'aggregates', column)
+
+ self.assertIndexExists(engine, 'aggregates',
+ 'aggregate_uuid_idx')
+ self.assertUniqueConstraintExists(engine, 'aggregates', ['name'])
+
class TestNovaAPIMigrationsWalkSQLite(NovaAPIMigrationsWalk,
test_base.DbTestCase,
diff --git a/nova/tests/functional/db/test_aggregate_model.py b/nova/tests/functional/db/test_aggregate_model.py
new file mode 100644
index 0000000000..774436bc55
--- /dev/null
+++ b/nova/tests/functional/db/test_aggregate_model.py
@@ -0,0 +1,57 @@
+# 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 nova.db.sqlalchemy import api_models
+from nova.db.sqlalchemy import models
+from nova import test
+
+
+class AggregateTablesCompareTestCase(test.NoDBTestCase):
+ def _get_column_list(self, model):
+ column_list = [m.key for m in model.__table__.columns]
+ return column_list
+
+ def _check_column_list(self,
+ columns_new,
+ columns_old,
+ added=None,
+ removed=None):
+ for c in added or []:
+ columns_new.remove(c)
+ for c in removed or []:
+ columns_old.remove(c)
+ intersect = set(columns_new).intersection(set(columns_old))
+ if intersect != set(columns_new) or intersect != set(columns_old):
+ return False
+ return True
+
+ def _compare_models(self, m_a, m_b,
+ added=None, removed=None):
+ added = added or []
+ removed = removed or ['deleted_at', 'deleted']
+ c_a = self._get_column_list(m_a)
+ c_b = self._get_column_list(m_b)
+ self.assertTrue(self._check_column_list(c_a, c_b,
+ added=added,
+ removed=removed))
+
+ def test_tables_aggregate_hosts(self):
+ self._compare_models(api_models.AggregateHost(),
+ models.AggregateHost())
+
+ def test_tables_aggregate_metadata(self):
+ self._compare_models(api_models.AggregateMetadata(),
+ models.AggregateMetadata())
+
+ def test_tables_aggregates(self):
+ self._compare_models(api_models.Aggregate(),
+ models.Aggregate())