Skip to content

Commit

Permalink
Add uuid to Service model
Browse files Browse the repository at this point in the history
This change adds a uuid column to the Service model and versioned
object.  This is a prerequisite to replacing id with uuid in the
services API.

The uuid is generated automatically for new services.  For existing
records, it is generated on read from the database.

Change-Id: I85c39ab422b9687bf032cbe12d17a2babbda1c07
Partially-Implements: blueprint service-hyper-uuid-in-api
  • Loading branch information
dpeschman-godaddy authored and mriedem committed Apr 11, 2017
1 parent fc3e91a commit 3674a42
Show file tree
Hide file tree
Showing 10 changed files with 108 additions and 6 deletions.
33 changes: 33 additions & 0 deletions nova/db/sqlalchemy/migrate_repo/versions/359_add_service_uuid.py
@@ -0,0 +1,33 @@
# 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 sqlalchemy import Column
from sqlalchemy.engine.reflection import Inspector
from sqlalchemy import Index
from sqlalchemy import MetaData
from sqlalchemy import String
from sqlalchemy import Table


def upgrade(migrate_engine):
meta = MetaData(bind=migrate_engine)
for prefix in ('', 'shadow_'):
services = Table(prefix + 'services', meta, autoload=True)
if not hasattr(services.c, 'uuid'):
services.create_column(Column('uuid', String(36), nullable=True))

uuid_index_name = 'services_uuid_idx'
indexes = Inspector(migrate_engine).get_indexes('services')
if uuid_index_name not in (i['name'] for i in indexes):
services = Table('services', meta, autoload=True)
Index(uuid_index_name, services.c.uuid, unique=True).create()
6 changes: 4 additions & 2 deletions nova/db/sqlalchemy/models.py
Expand Up @@ -81,10 +81,12 @@ class Service(BASE, NovaBase, models.SoftDeleteMixin):
schema.UniqueConstraint("host", "topic", "deleted",
name="uniq_services0host0topic0deleted"),
schema.UniqueConstraint("host", "binary", "deleted",
name="uniq_services0host0binary0deleted")
)
name="uniq_services0host0binary0deleted"),
Index('services_uuid_idx', 'uuid', unique=True),
)

id = Column(Integer, primary_key=True)
uuid = Column(String(36), nullable=True)
host = Column(String(255)) # , ForeignKey('hosts.id'))
binary = Column(String(255))
topic = Column(String(255))
Expand Down
25 changes: 24 additions & 1 deletion nova/objects/service.py
Expand Up @@ -13,6 +13,7 @@
# under the License.

from oslo_log import log as logging
from oslo_utils import uuidutils
from oslo_utils import versionutils

from nova import availability_zones
Expand Down Expand Up @@ -128,10 +129,12 @@ class Service(base.NovaPersistentObject, base.NovaObject,
# Version 1.18: ComputeNode version 1.14
# Version 1.19: Added get_minimum_version()
# Version 1.20: Added get_minimum_version_multi()
VERSION = '1.20'
# Version 1.21: Added uuid
VERSION = '1.21'

fields = {
'id': fields.IntegerField(read_only=True),
'uuid': fields.UUIDField(),
'host': fields.StringField(nullable=True),
'binary': fields.StringField(nullable=True),
'topic': fields.StringField(nullable=True),
Expand Down Expand Up @@ -171,6 +174,8 @@ def obj_make_compatible_from_manifest(self, primitive, target_version,
super(Service, self).obj_make_compatible_from_manifest(
primitive, target_version, version_manifest)
_target_version = versionutils.convert_version_to_tuple(target_version)
if _target_version < (1, 21) and 'uuid' in primitive:
del primitive['uuid']
if _target_version < (1, 16) and 'version' in primitive:
del primitive['version']
if _target_version < (1, 14) and 'forced_down' in primitive:
Expand Down Expand Up @@ -210,10 +215,23 @@ def _from_db_object(context, service, db_service):
# NOTE(danms): Special handling of the version field, since
# it is read_only and set in our init.
setattr(service, base.get_attrname(key), db_service[key])
elif key == 'uuid' and not db_service.get(key):
# Leave uuid off the object if undefined in the database
# so that it will be generated below.
continue
else:
service[key] = db_service[key]

service._context = context
service.obj_reset_changes()

# TODO(dpeschman): Drop this once all services have uuids in database
if 'uuid' not in service:
service.uuid = uuidutils.generate_uuid()
LOG.debug('Generated UUID %(uuid)s for service %(id)i',
dict(uuid=service.uuid, id=service.id))
service.save()

return service

def obj_load_attr(self, attrname):
Expand Down Expand Up @@ -311,6 +329,11 @@ def create(self):
reason='already created')
self._check_minimum_version()
updates = self.obj_get_changes()

if 'uuid' not in updates:
updates['uuid'] = uuidutils.generate_uuid()
self.uuid = updates['uuid']

db_service = db.service_create(self._context, updates)
self._from_db_object(self._context, self, db_service)

Expand Down
2 changes: 2 additions & 0 deletions nova/tests/unit/db/test_db_api.py
Expand Up @@ -3471,6 +3471,7 @@ def setUp(self):

def _get_base_values(self):
return {
'uuid': None,
'host': 'fake_host',
'binary': 'fake_binary',
'topic': 'fake_topic',
Expand Down Expand Up @@ -3514,6 +3515,7 @@ def test_service_destroy(self):
def test_service_update(self):
service = self._create_service({})
new_values = {
'uuid': uuidsentinel.service,
'host': 'fake_host1',
'binary': 'fake_binary1',
'topic': 'fake_topic1',
Expand Down
5 changes: 5 additions & 0 deletions nova/tests/unit/db/test_migrations.py
Expand Up @@ -947,6 +947,11 @@ def _check_358(self, engine, data):
self.assertColumnExists(engine, 'block_device_mapping',
'attachment_id')

def _check_359(self, engine, data):
self.assertColumnExists(engine, 'services', 'uuid')
self.assertIndexMembers(engine, 'services', 'services_uuid_idx',
['uuid'])


class TestNovaMigrationsSQLite(NovaMigrationsCheckers,
test_base.DbTestCase,
Expand Down
1 change: 1 addition & 0 deletions nova/tests/unit/notifications/objects/test_notification.py
Expand Up @@ -102,6 +102,7 @@ class TestNotificationEmptySchema(notification.NotificationBase):
'deleted_at': None,
'deleted': False,
'id': 123,
'uuid': uuids.service,
'host': 'fake-host',
'binary': 'nova-fake',
'topic': 'fake-service-topic',
Expand Down
2 changes: 1 addition & 1 deletion nova/tests/unit/objects/test_instance.py
Expand Up @@ -176,7 +176,7 @@ def test_get_with_expected(self, mock_get, mock_fault_get, mock_extra_get):
'topic': 'fake-service-topic', 'report_count': 1,
'forced_down': False, 'disabled': False,
'disabled_reason': None, 'last_seen_up': None,
'version': 1,
'version': 1, 'uuid': uuids.service,
}
fake_instance = dict(self.fake_instance,
services=[fake_service],
Expand Down
2 changes: 1 addition & 1 deletion nova/tests/unit/objects/test_objects.py
Expand Up @@ -1157,7 +1157,7 @@ def obj_name(cls):
'SecurityGroupList': '1.0-dc8bbea01ba09a2edb6e5233eae85cbc',
'SecurityGroupRule': '1.1-ae1da17b79970012e8536f88cb3c6b29',
'SecurityGroupRuleList': '1.2-0005c47fcd0fb78dd6d7fd32a1409f5b',
'Service': '1.20-0f9c0bf701e68640b78638fd09e2cddc',
'Service': '1.21-4ff88e4cd40f3b3ce923805f29d84ee0',
'ServiceList': '1.19-5325bce13eebcbf22edc9678285270cc',
'TaskLog': '1.0-78b0534366f29aa3eebb01860fbe18fe',
'TaskLogList': '1.0-cc8cce1af8a283b9d28b55fcd682e777',
Expand Down
36 changes: 36 additions & 0 deletions nova/tests/unit/objects/test_service.py
Expand Up @@ -27,6 +27,7 @@
from nova import test
from nova.tests.unit.objects import test_compute_node
from nova.tests.unit.objects import test_objects
from nova.tests import uuidsentinel

NOW = timeutils.utcnow().replace(microsecond=0)

Expand All @@ -38,6 +39,7 @@ def _fake_service(**kwargs):
'deleted_at': None,
'deleted': False,
'id': 123,
'uuid': uuidsentinel.service,
'host': 'fake-host',
'binary': 'nova-fake',
'topic': 'fake-service-topic',
Expand Down Expand Up @@ -123,13 +125,25 @@ def test_get_by_args(self):
def test_create(self, mock_service_create):
service_obj = service.Service(context=self.context)
service_obj.host = 'fake-host'
service_obj.uuid = uuidsentinel.service2
service_obj.create()
self.assertEqual(fake_service['id'], service_obj.id)
self.assertEqual(service.SERVICE_VERSION, service_obj.version)
mock_service_create.assert_called_once_with(
self.context, {'host': 'fake-host',
'uuid': uuidsentinel.service2,
'version': fake_service['version']})

@mock.patch('nova.objects.service.uuidutils.generate_uuid',
return_value=uuidsentinel.service3)
@mock.patch.object(db, 'service_create', return_value=fake_service)
def test_create_without_uuid_generates_one(
self, mock_service_create, generate_uuid):
service_obj = service.Service(context=self.context)
service_obj.create()
create_args = mock_service_create.call_args[0][1]
self.assertEqual(generate_uuid.return_value, create_args['uuid'])

@mock.patch.object(db, 'service_create', return_value=fake_service)
def test_recreate_fails(self, mock_service_create):
service_obj = service.Service(context=self.context)
Expand Down Expand Up @@ -392,6 +406,28 @@ def test_create_below_minimum(self, mock_get):
binary='nova-compute',
).create)

@mock.patch('nova.objects.base.NovaObject'
'.obj_make_compatible_from_manifest', new=mock.Mock())
def test_obj_make_compatible_from_manifest_strips_uuid(self):
s = service.Service()
primitive = {'uuid': uuidsentinel.service}
s.obj_make_compatible_from_manifest(primitive, '1.20', mock.Mock())
self.assertNotIn('uuid', primitive)

@mock.patch('nova.objects.service.uuidutils.generate_uuid',
return_value=uuidsentinel.service4)
def test_from_db_object_without_uuid_generates_one(self, generate_uuid):
values = _fake_service(uuid=None, id=None)
db_service = db.api.service_create(self.context, values)

s = service.Service()
service.Service._from_db_object(self.context, s, db_service)
self.assertEqual(uuidsentinel.service4, s.uuid)

# Check the DB too
db_service2 = db.api.service_get(self.context, s.id)
self.assertEqual(s.uuid, db_service2['uuid'])


class TestServiceObject(test_objects._LocalTest,
_TestServiceObject):
Expand Down
2 changes: 1 addition & 1 deletion nova/tests/unit/test_availability_zones.py
Expand Up @@ -61,7 +61,7 @@ def _update_az(self, aggregate, az_name):

def _create_service_with_topic(self, topic, host, disabled=False):
values = {
'binary': 'bin',
'binary': 'nova-bin',
'host': host,
'topic': topic,
'disabled': disabled,
Expand Down

0 comments on commit 3674a42

Please sign in to comment.