Add data module for checkpoint verification API

Change-Id: If672a14b932b33a4254f21486d2e3fbe56198a73
Implements: blueprint support-verify-the-checkpoint-api
This commit is contained in:
chenying 2017-09-30 10:56:24 +08:00
parent 84e22dee08
commit 33756861da
8 changed files with 491 additions and 2 deletions

View File

@ -533,6 +533,48 @@ def restore_get_all_by_project(context, project_id, marker, limit,
offset=offset)
def verification_get(context, verification_id):
"""Get a verification or raise if it does not exist."""
return IMPL.verification_get(context, verification_id)
def verification_create(context, values):
"""Create a verification from the values dictionary."""
return IMPL.verification_create(context, values)
def verification_update(context, verification_id, values):
"""Set the given properties on a verification and update it.
Raises NotFound if verification does not exist.
"""
return IMPL.verification_update(context, verification_id,
values)
def verification_destroy(context, verification_id):
"""Destroy the verification or raise if it does not exist."""
return IMPL.verification_destroy(context, verification_id)
def verification_get_all(context, marker, limit, sort_keys=None,
sort_dirs=None, filters=None, offset=None):
"""Get all verifications."""
return IMPL.verification_get_all(
context, marker, limit, sort_keys=sort_keys,
sort_dirs=sort_dirs, filters=filters, offset=offset)
def verification_get_all_by_project(context, project_id, marker, limit,
sort_keys=None, sort_dirs=None,
filters=None, offset=None):
"""Get all verifications belonging to a project."""
return IMPL.verification_get_all_by_project(
context, project_id, marker, limit, sort_keys=sort_keys,
sort_dirs=sort_dirs, filters=filters, offset=offset)
def operation_log_get(context, operation_log_id):
"""Get a operation log or raise if it does not exist."""
return IMPL.operation_log_get(context, operation_log_id)

View File

@ -1289,7 +1289,7 @@ def _operation_log_get(context, operation_log_id, session=None):
id=operation_log_id
).first()
if not result:
raise exception.OperationLogNotFound(restore_id=operation_log_id)
raise exception.OperationLogNotFound(operation_log_id=operation_log_id)
return result
@ -1413,6 +1413,154 @@ def _process_operation_log_filters(query, filters):
###############################
@require_context
def verification_create(context, values):
verification_ref = models.Verification()
if not values.get('id'):
values['id'] = uuidutils.generate_uuid()
verification_ref.update(values)
session = get_session()
with session.begin():
verification_ref.save(session)
return verification_ref
@require_context
def verification_get(context, verification_id):
return _verification_get(context, verification_id)
@require_context
def _verification_get(context, verification_id, session=None):
result = model_query(
context,
models.Verification,
session=session
).filter_by(
id=verification_id
).first()
if not result:
raise exception.VerificationNotFound(
verification_id=verification_id)
return result
@require_context
def verification_update(context, verification_id, values):
session = get_session()
with session.begin():
verification_ref = _verification_get(
context, verification_id, session=session)
verification_ref.update(values)
return verification_ref
@require_context
@_retry_on_deadlock
def verification_destroy(context, verification_id):
session = get_session()
with session.begin():
verification_ref = _verification_get(context,
verification_id,
session=session)
verification_ref.delete(session=session)
def _verification_get_query(context, session=None, project_only=False):
return model_query(context, models.Verification, session=session,
project_only=project_only)
@require_admin_context
def verification_get_all(context, marker, limit, sort_keys=None,
sort_dirs=None, filters=None, offset=None):
"""Retrieves all verifications.
If no sort parameters are specified then the returned plans are sorted
first by the 'created_at' key and then by the 'id' key in descending
order.
:param context: context to query under
:param marker: the last item of the previous page, used to determine the
next page of results to return
:param limit: maximum number of items to return
:param sort_keys: list of attributes by which results should be sorted,
paired with corresponding item in sort_dirs
:param sort_dirs: list of directions in which results should be sorted,
paired with corresponding item in sort_keys
:param filters: dictionary of filters; values that are in lists, tuples,
or sets cause an 'IN' operation, while exact matching
is used for other values, see _process_verification_filters
function for more information
:param offset: number of items to skip
:returns: list of matching verifications
"""
if filters and not is_valid_model_filters(models.Verification, filters):
return []
session = get_session()
with session.begin():
query = _generate_paginate_query(context, session, marker, limit,
sort_keys, sort_dirs, filters,
offset, models.Verification)
if query is None:
return []
return query.all()
@require_context
def verification_get_all_by_project(context, project_id, marker, limit,
sort_keys=None, sort_dirs=None,
filters=None, offset=None):
"""Retrieves all verifications in a project.
If no sort parameters are specified then the returned plans are sorted
first by the 'created_at' key and then by the 'id' key in descending
order.
:param context: context to query under
:param project_id: project for all verifications being retrieved
:param marker: the last item of the previous page, used to determine the
next page of results to return
:param limit: maximum number of items to return
:param sort_keys: list of attributes by which results should be sorted,
paired with corresponding item in sort_dirs
:param sort_dirs: list of directions in which results should be sorted,
paired with corresponding item in sort_keys
:param filters: dictionary of filters; values that are in lists, tuples,
or sets cause an 'IN' operation, while exact matching
is used for other values, see _process_verification_filters
function for more information
:param offset: number of items to skip
:returns: list of matching verifications
"""
if filters and not is_valid_model_filters(models.Verification, filters):
return []
session = get_session()
with session.begin():
authorize_project_context(context, project_id)
filters = filters.copy() if filters else {}
filters['project_id'] = project_id
query = _generate_paginate_query(context, session, marker, limit,
sort_keys, sort_dirs, filters,
offset, models.Verification)
if query is None:
return []
return query.all()
def _process_verification_filters(query, filters):
if filters:
if not is_valid_model_filters(models.Verification, filters):
return None
query = query.filter_by(**filters)
return query
###############################
@require_context
def checkpoint_record_create(context, values):
checkpoint_record_ref = models.CheckpointRecord()
@ -1585,6 +1733,10 @@ PAGINATION_HELPERS = {
models.Plan: (_plan_get_query, _process_plan_filters, _plan_get),
models.Restore: (_restore_get_query, _process_restore_filters,
_restore_get),
models.Verification: (
_verification_get_query,
_process_verification_filters,
_verification_get),
models.Trigger: (_trigger_list_query, _trigger_list_process_filters,
_trigger_get),
models.TriggerExecution: (_trigger_execution_list_query,
@ -1607,7 +1759,6 @@ PAGINATION_HELPERS = {
_scheduled_operation_log_list_query,
_scheduled_operation_log_list_process_filters,
_scheduled_operation_log_get),
models.CheckpointRecord: (
_checkpoint_record_list_query,
_checkpoint_record_list_process_filters,

View File

@ -266,6 +266,10 @@ class RestoreNotFound(NotFound):
message = _("Restore %(restore_id)s could not be found.")
class VerificationNotFound(NotFound):
message = _("Verification %(verification_id)s could not be found.")
class OperationLogNotFound(NotFound):
message = _("OperationLog %(restore_id)s could not be found.")

View File

@ -24,3 +24,4 @@ def register_all():
__import__('karbor.objects.restore')
__import__('karbor.objects.operation_log')
__import__('karbor.objects.checkpoint_record')
__import__('karbor.objects.verification')

View File

@ -0,0 +1,134 @@
# 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 oslo_serialization import jsonutils
from oslo_versionedobjects import fields
import six
from karbor import db
from karbor import exception
from karbor.i18n import _
from karbor import objects
from karbor.objects import base
@base.KarborObjectRegistry.register
class Verification(base.KarborPersistentObject, base.KarborObject,
base.KarborObjectDictCompat,
base.KarborComparableObject):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'id': fields.UUIDField(),
'project_id': fields.UUIDField(),
'provider_id': fields.UUIDField(),
'checkpoint_id': fields.UUIDField(),
'parameters': base.DictOfDictOfStringsField(nullable=True),
'status': fields.StringField(nullable=True),
'resources_status': fields.DictOfStringsField(nullable=True),
'resources_reason': fields.DictOfStringsField(nullable=True),
}
json_fields = ('parameters', 'resources_status', 'resources_reason')
@classmethod
def _from_db_object(cls, context, verification, db_verification):
for name, field in verification.fields.items():
value = db_verification.get(name)
if isinstance(field, fields.IntegerField):
value = value or 0
elif isinstance(field, fields.DateTimeField):
value = value or None
if name in cls.json_fields:
value = jsonutils.loads(value) if value else {}
verification[name] = value
verification._context = context
verification.obj_reset_changes()
return verification
@classmethod
def _convert_properties_to_db_format(cls, updates):
for attr in cls.json_fields:
value = updates.pop(attr, None)
if value:
updates[attr] = jsonutils.dumps(value)
@base.remotable
def create(self):
if self.obj_attr_is_set('id'):
raise exception.ObjectActionError(action='create',
reason=_('already created'))
updates = self.karbor_obj_get_changes()
self._convert_properties_to_db_format(updates)
db_verification = db.verification_create(self._context, updates)
self._from_db_object(self._context, self, db_verification)
@base.remotable
def save(self):
updates = self.karbor_obj_get_changes()
self._convert_properties_to_db_format(updates)
if updates:
db.verification_update(self._context, self.id, updates)
self.obj_reset_changes()
@base.remotable
def destroy(self):
with self.obj_as_admin():
db.verification_destroy(self._context, self.id)
@base.remotable
def update_resource_status(self, resource_type, resource_id, status,
reason=None):
key = '{}#{}'.format(resource_type, resource_id)
if not self.obj_attr_is_set('resources_status'):
self.resources_status = {}
self.resources_status[key] = status
self._changed_fields.add('resources_status')
if isinstance(reason, six.string_types):
if not self.obj_attr_is_set('resources_reason'):
self.resources_reason = {}
self.resources_reason[key] = reason
self._changed_fields.add('resources_reason')
@base.KarborObjectRegistry.register
class VerificationList(base.ObjectListBase, base.KarborObject):
VERSION = '1.0'
fields = {
'objects': fields.ListOfObjectsField('verification'),
}
@base.remotable_classmethod
def get_all(cls, context, marker, limit, sort_keys=None, sort_dirs=None,
filters=None, offset=None):
verifications = db.verification_get_all(context, marker, limit,
sort_keys=sort_keys,
sort_dirs=sort_dirs,
filters=filters, offset=offset)
return base.obj_make_list(context, cls(context), objects.Verification,
verifications)
@base.remotable_classmethod
def get_all_by_project(cls, context, project_id, marker, limit,
sort_keys=None, sort_dirs=None, filters=None,
offset=None):
verifications = db.verification_get_all_by_project(
context, project_id, marker,
limit, sort_keys=sort_keys,
sort_dirs=sort_dirs,
filters=filters,
offset=offset)
return base.obj_make_list(context, cls(context), objects.Verification,
verifications)

View File

@ -541,6 +541,56 @@ class RestoreDbTestCase(ModelBaseTestCase):
self.ctxt, 42, {})
class VerificationDbTestCase(ModelBaseTestCase):
"""Unit tests for karbor.db.api.verification_*."""
fake_verification = {
"id": "36ea41b2-c358-48a7-9117-70cb7617410a",
"project_id": "586cc6ce-e286-40bd-b2b5-dd32694d9944",
"provider_id": "2220f8b1-975d-4621-a872-fa9afb43cb6c",
"checkpoint_id": "09edcbdc-d1c2-49c1-a212-122627b20968",
"parameters": "{'username': 'admin'}",
"status": "SUCCESS"
}
def setUp(self):
super(VerificationDbTestCase, self).setUp()
self.ctxt = context.get_admin_context()
def test_verification_create(self):
verification = db.verification_create(self.ctxt,
self.fake_verification)
self.assertTrue(uuidutils.is_uuid_like(verification['id']))
self.assertEqual('SUCCESS', verification.status)
def test_verification_get(self):
verification = db.verification_create(self.ctxt,
self.fake_verification)
self._assertEqualObjects(verification, db.verification_get(
self.ctxt, verification['id']))
def test_verification_destroy(self):
verification = db.verification_create(self.ctxt,
self.fake_verification)
db.verification_destroy(self.ctxt, verification['id'])
self.assertRaises(exception.VerificationNotFound,
db.verification_get,
self.ctxt, verification['id'])
def test_verification_update(self):
verification = db.verification_create(self.ctxt,
self.fake_verification)
db.verification_update(self.ctxt, verification['id'],
{'status': 'INIT'})
verification = db.verification_get(self.ctxt, verification['id'])
self.assertEqual('INIT', verification['status'])
def test_verification_update_nonexistent(self):
self.assertRaises(exception.VerificationNotFound,
db.verification_update,
self.ctxt, 42, {})
class OperationLogTestCase(ModelBaseTestCase):
"""Unit tests for karbor.db.api.operation_log_*."""

View File

@ -0,0 +1,40 @@
# 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 oslo_versionedobjects import fields
from karbor import objects
def fake_db_verification(**updates):
db_verification = {
"id": "36ea41b2-c358-48a7-9117-70cb7617410a",
"project_id": "586cc6ce-e286-40bd-b2b5-dd32694d9944",
"provider_id": "2220f8b1-975d-4621-a872-fa9afb43cb6c",
"checkpoint_id": "09edcbdc-d1c2-49c1-a212-122627b20968",
"parameters": '{}',
"status": "SUCCESS"
}
for name, field in objects.Verification.fields.items():
if name in db_verification:
continue
if field.nullable:
db_verification[name] = None
elif field.default != fields.UnspecifiedDefault:
db_verification[name] = field.default
else:
raise Exception('fake_db_verification needs help with %s.' % name)
if updates:
db_verification.update(updates)
return db_verification

View File

@ -0,0 +1,67 @@
# Copyright 2015 SimpliVity Corp.
#
# 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 mock
from karbor import objects
from karbor.tests.unit import fake_verification
from karbor.tests.unit import objects as test_objects
class TestVerification(test_objects.BaseObjectsTestCase):
@staticmethod
def _compare(test, db, obj):
db = {k: v for k, v in db.items()}
test_objects.BaseObjectsTestCase._compare(test, db, obj)
@mock.patch('karbor.objects.Verification.get_by_id')
def test_get_by_id(self, verification_get):
db_verification = fake_verification.fake_db_verification()
verification_get.return_value = db_verification
verification = objects.Verification.get_by_id(self.context, "1")
verification_get.assert_called_once_with(self.context, "1")
self._compare(self, db_verification, verification)
@mock.patch('karbor.db.sqlalchemy.api.verification_create')
def test_create(self, verification_create):
db_verification = fake_verification.fake_db_verification()
verification_create.return_value = db_verification
verification = objects.Verification(context=self.context)
verification.create()
self.assertEqual(db_verification['id'], verification.id)
@mock.patch('karbor.db.sqlalchemy.api.verification_update')
def test_save(self, verification_update):
db_verification = fake_verification.fake_db_verification()
verification = objects.Verification._from_db_object(
self.context, objects.Verification(), db_verification)
verification.status = 'FAILED'
verification.save()
verification_update.assert_called_once_with(
self.context, verification.id, {'status': 'FAILED'})
@mock.patch('karbor.db.sqlalchemy.api.verification_destroy')
def test_destroy(self, verification_destroy):
db_verification = fake_verification.fake_db_verification()
verification = objects.Verification._from_db_object(
self.context, objects.Verification(), db_verification)
verification.destroy()
self.assertTrue(verification_destroy.called)
admin_context = verification_destroy.call_args[0][0]
self.assertTrue(admin_context.is_admin)
def test_obj_field_status(self):
verification = objects.Verification(context=self.context,
status='FAILED')
self.assertEqual('FAILED', verification.status)