cinder/cinder/tests/unit/test_db_worker_api.py

305 lines
13 KiB
Python

# Copyright (c) 2016 Red Hat, Inc.
# 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.
"""Unit tests for cinder.db.api.Worker"""
from datetime import datetime
import time
from unittest import mock
import uuid
from oslo_db import exception as db_exception
from cinder import context
from cinder import db
from cinder import exception
from cinder.tests.unit import fake_constants as fake
from cinder.tests.unit import test
class DBAPIWorkerTestCase(test.TestCase, test.ModelsObjectComparatorMixin):
worker_fields = {'resource_type': 'Volume',
'resource_id': fake.VOLUME_ID,
'status': 'creating'}
def _uuid(self):
return str(uuid.uuid4())
def setUp(self):
super(DBAPIWorkerTestCase, self).setUp()
self.ctxt = context.get_admin_context()
def tearDown(self):
db.sqlalchemy.api.DB_SUPPORTS_SUBSECOND_RESOLUTION = True
super(DBAPIWorkerTestCase, self).tearDown()
def test_workers_init(self):
# SQLite supports subsecond resolution so result is True
db.sqlalchemy.api.DB_SUPPORTS_SUBSECOND_RESOLUTION = None
db.workers_init()
self.assertTrue(db.sqlalchemy.api.DB_SUPPORTS_SUBSECOND_RESOLUTION)
def test_workers_init_not_supported(self):
# Fake a Db that doesn't support sub-second resolution in datetimes
db.worker_update(
self.ctxt, None,
{'resource_type': 'SENTINEL', 'ignore_sentinel': False},
updated_at=datetime.utcnow().replace(microsecond=0))
db.workers_init()
self.assertFalse(db.sqlalchemy.api.DB_SUPPORTS_SUBSECOND_RESOLUTION)
def test_worker_create_and_get(self):
"""Test basic creation of a worker record."""
worker = db.worker_create(self.ctxt, **self.worker_fields)
db_worker = db.worker_get(self.ctxt, id=worker.id)
self._assertEqualObjects(worker, db_worker)
@mock.patch('oslo_utils.timeutils.utcnow',
return_value=datetime.utcnow().replace(microsecond=123))
def test_worker_create_no_subsecond(self, mock_utcnow):
"""Test basic creation of a worker record."""
db.sqlalchemy.api.DB_SUPPORTS_SUBSECOND_RESOLUTION = False
worker = db.worker_create(self.ctxt, **self.worker_fields)
db_worker = db.worker_get(self.ctxt, id=worker.id)
self._assertEqualObjects(worker, db_worker)
self.assertEqual(0, db_worker.updated_at.microsecond)
def test_worker_create_unique_constrains(self):
"""Test when we use an already existing resource type and id."""
db.worker_create(self.ctxt, **self.worker_fields)
self.assertRaises(exception.WorkerExists, db.worker_create,
self.ctxt,
resource_type=self.worker_fields['resource_type'],
resource_id=self.worker_fields['resource_id'],
status='not_' + self.worker_fields['status'])
def test_worker_create_missing_required_field(self):
"""Try creating a worker with a missing required field."""
for field in self.worker_fields:
params = self.worker_fields.copy()
del params[field]
self.assertRaises(db_exception.DBError, db.worker_create,
self.ctxt, **params)
def test_worker_create_invalid_field(self):
"""Try creating a worker with a non existent db field."""
self.assertRaises(TypeError, db.worker_create, self.ctxt,
myfield='123', **self.worker_fields)
def test_worker_get_non_existent(self):
"""Check basic non existent worker record get method."""
db.worker_create(self.ctxt, **self.worker_fields)
self.assertRaises(exception.WorkerNotFound, db.worker_get,
self.ctxt, service_id='1', **self.worker_fields)
def _create_workers(self, num, read_back=False, **fields):
workers = []
base_params = self.worker_fields.copy()
base_params.update(fields)
for i in range(num):
params = base_params.copy()
params['resource_id'] = self._uuid()
workers.append(db.worker_create(self.ctxt, **params))
if read_back:
for i in range(len(workers)):
workers[i] = db.worker_get(self.ctxt, id=workers[i].id)
return workers
def test_worker_get_all(self):
"""Test basic get_all method."""
self._create_workers(1)
service = db.service_create(self.ctxt, {})
workers = self._create_workers(3, service_id=service.id)
db_workers = db.worker_get_all(self.ctxt, service_id=service.id)
self._assertEqualListsOfObjects(workers, db_workers)
def test_worker_get_all_until(self):
"""Test get_all until a specific time."""
workers = self._create_workers(3, read_back=True)
timestamp = workers[-1].updated_at
time.sleep(0.1)
self._create_workers(3)
db_workers = db.worker_get_all(self.ctxt, until=timestamp)
self._assertEqualListsOfObjects(workers, db_workers)
def test_worker_get_all_returns_empty(self):
"""Test that get_all returns an empty list when there's no results."""
self._create_workers(3, deleted=True)
db_workers = db.worker_get_all(self.ctxt)
self.assertListEqual([], db_workers)
def test_worker_update_not_exists(self):
"""Test worker update when the worker doesn't exist."""
self.assertRaises(exception.WorkerNotFound, db.worker_update,
self.ctxt, 1)
def test_worker_update(self):
"""Test basic worker update."""
worker = self._create_workers(1)[0]
worker = db.worker_get(self.ctxt, id=worker.id)
res = db.worker_update(self.ctxt, worker.id, service_id=1)
self.assertEqual(1, res)
worker.service_id = 1
db_worker = db.worker_get(self.ctxt, id=worker.id)
self._assertEqualObjects(worker, db_worker,
['updated_at', 'race_preventer'])
self.assertEqual(worker.race_preventer + 1, db_worker.race_preventer)
def test_worker_update_no_subsecond(self):
"""Test basic worker update."""
db.sqlalchemy.api.DB_SUPPORTS_SUBSECOND_RESOLUTION = False
worker = self._create_workers(1)[0]
worker = db.worker_get(self.ctxt, id=worker.id)
now = datetime.utcnow().replace(microsecond=123)
with mock.patch('oslo_utils.timeutils.utcnow', return_value=now):
res = db.worker_update(self.ctxt, worker.id, service_id=1)
self.assertEqual(1, res)
worker.service_id = 1
db_worker = db.worker_get(self.ctxt, id=worker.id)
self._assertEqualObjects(worker, db_worker,
['updated_at', 'race_preventer'])
self.assertEqual(0, db_worker.updated_at.microsecond)
self.assertEqual(worker.race_preventer + 1, db_worker.race_preventer)
def test_worker_update_update_orm(self):
"""Test worker update updating the worker orm object."""
worker = self._create_workers(1)[0]
res = db.worker_update(self.ctxt, worker.id, orm_worker=worker,
service_id=1)
self.assertEqual(1, res)
db_worker = db.worker_get(self.ctxt, id=worker.id)
# If we are updating the ORM object we don't ignore the update_at field
# because it will get updated in the ORM instance.
self._assertEqualObjects(worker, db_worker)
def test_worker_destroy(self):
"""Test that worker destroy really deletes the DB entry."""
worker = self._create_workers(1)[0]
res = db.worker_destroy(self.ctxt, id=worker.id)
self.assertEqual(1, res)
db_workers = db.worker_get_all(self.ctxt, read_deleted='yes')
self.assertListEqual([], db_workers)
def test_worker_destroy_non_existent(self):
"""Test that worker destroy returns 0 when entry doesn't exist."""
res = db.worker_destroy(self.ctxt, id=100)
self.assertEqual(0, res)
def test_worker_claim(self):
"""Test worker claim of normal DB entry."""
service_id = 1
worker = db.worker_create(self.ctxt, resource_type='Volume',
resource_id=fake.VOLUME_ID,
status='deleting')
res = db.worker_claim_for_cleanup(self.ctxt, service_id, worker)
self.assertEqual(1, res)
db_worker = db.worker_get(self.ctxt, id=worker.id)
self._assertEqualObjects(worker, db_worker, ['updated_at'])
self.assertEqual(service_id, db_worker.service_id)
self.assertEqual(worker.service_id, db_worker.service_id)
def test_worker_claim_fails_status_change(self):
"""Test that claim fails if the work entry has changed its status."""
worker = db.worker_create(self.ctxt, resource_type='Volume',
resource_id=fake.VOLUME_ID,
status='deleting')
worker.status = 'creating'
res = db.worker_claim_for_cleanup(self.ctxt, 1, worker)
self.assertEqual(0, res)
db_worker = db.worker_get(self.ctxt, id=worker.id)
self._assertEqualObjects(worker, db_worker, ['status'])
self.assertIsNone(db_worker.service_id)
def test_worker_claim_fails_service_change(self):
"""Test that claim fails on worker service change."""
failed_service = 1
working_service = 2
this_service = 3
worker = db.worker_create(self.ctxt, resource_type='Volume',
resource_id=fake.VOLUME_ID,
status='deleting',
service_id=working_service)
worker.service_id = failed_service
res = db.worker_claim_for_cleanup(self.ctxt, this_service, worker)
self.assertEqual(0, res)
db_worker = db.worker_get(self.ctxt, id=worker.id)
self.assertEqual(working_service, db_worker.service_id)
def test_worker_claim_same_service(self):
"""Test worker claim of a DB entry that has our service_id."""
service_id = 1
worker = db.worker_create(self.ctxt, resource_type='Volume',
resource_id=fake.VOLUME_ID,
status='deleting', service_id=service_id)
# Read from DB to get updated_at field
worker = db.worker_get(self.ctxt, id=worker.id)
claimed_worker = db.worker_get(self.ctxt, id=worker.id)
res = db.worker_claim_for_cleanup(self.ctxt,
service_id,
claimed_worker)
self.assertEqual(1, res)
db_worker = db.worker_get(self.ctxt, id=worker.id)
self._assertEqualObjects(claimed_worker, db_worker)
self._assertEqualObjects(worker, db_worker,
['updated_at', 'race_preventer'])
self.assertNotEqual(worker.updated_at, db_worker.updated_at)
self.assertEqual(worker.race_preventer + 1, db_worker.race_preventer)
def test_worker_claim_fails_this_service_claimed(self):
"""Test claim fails when worker was already claimed by this service."""
service_id = 1
worker = db.worker_create(self.ctxt, resource_type='Volume',
resource_id=fake.VOLUME_ID,
status='creating',
service_id=service_id)
# Read it back to have the updated_at value
worker = db.worker_get(self.ctxt, id=worker.id)
claimed_worker = db.worker_get(self.ctxt, id=worker.id)
time.sleep(0.1)
# Simulate that this service starts processing this entry
res = db.worker_claim_for_cleanup(self.ctxt,
service_id,
claimed_worker)
self.assertEqual(1, res)
res = db.worker_claim_for_cleanup(self.ctxt, service_id, worker)
self.assertEqual(0, res)
db_worker = db.worker_get(self.ctxt, id=worker.id)
self._assertEqualObjects(claimed_worker, db_worker)
self._assertEqualObjects(worker, db_worker,
['updated_at', 'race_preventer'])
self.assertNotEqual(worker.updated_at, db_worker.updated_at)
self.assertEqual(worker.race_preventer + 1, db_worker.race_preventer)