Merge "Add etcd db for VolumeMapping"

This commit is contained in:
Zuul
2017-12-10 08:53:49 +00:00
committed by Gerrit Code Review
4 changed files with 301 additions and 11 deletions

View File

@@ -82,6 +82,8 @@ def translate_etcd_result(etcd_result, model_type):
ret = models.Capsule(data) ret = models.Capsule(data)
elif model_type == 'pcidevice': elif model_type == 'pcidevice':
ret = models.PciDevice(data) ret = models.PciDevice(data)
elif model_type == 'volume_mapping':
ret = models.VolumeMapping(data)
else: else:
raise exception.InvalidParameterValue( raise exception.InvalidParameterValue(
_('The model_type value: %s is invalid.'), model_type) _('The model_type value: %s is invalid.'), model_type)
@@ -861,3 +863,87 @@ class EtcdAPI(object):
raise raise
return translate_etcd_result(target, 'pcidevice') return translate_etcd_result(target, 'pcidevice')
def list_volume_mappings(self, context, filters=None, limit=None,
marker=None, sort_key=None, sort_dir=None):
try:
res = getattr(self.client.read(
'/volume_mappings'), 'children', None)
except etcd.EtcdKeyNotFound:
return []
except Exception as e:
LOG.error(
"Error occurred while reading from etcd server: %s",
six.text_type(e))
raise
volume_mappings = []
for vm in res:
if vm.value is not None:
volume_mappings.append(
translate_etcd_result(vm, 'volume_mapping'))
filters = self._add_tenant_filters(context, filters)
filtered_vms = self._filter_resources(volume_mappings, filters)
return self._process_list_result(filtered_vms, limit=limit,
sort_key=sort_key)
def create_volume_mapping(self, context, volume_mapping_data):
if not volume_mapping_data.get('uuid'):
volume_mapping_data['uuid'] = uuidutils.generate_uuid()
volume_mapping = models.VolumeMapping(volume_mapping_data)
try:
volume_mapping.save()
except Exception as e:
LOG.error('Error occurred while creating volume mapping: %s',
six.text_type(e))
raise
return volume_mapping
def get_volume_mapping_by_uuid(self, context, volume_mapping_uuid):
try:
res = self.client.read('/volume_mappings/' + volume_mapping_uuid)
volume_mapping = translate_etcd_result(res, 'volume_mapping')
filtered_vms = self._filter_resources(
[volume_mapping], self._add_tenant_filters(context, {}))
if filtered_vms:
return filtered_vms[0]
else:
raise exception.VolumeMappingNotFound(
volume_mapping=volume_mapping_uuid)
except etcd.EtcdKeyNotFound:
raise exception.VolumeMappingNotFound(
volume_mapping=volume_mapping_uuid)
except Exception as e:
LOG.error('Error occurred while retrieving volume mapping: %s',
six.text_type(e))
raise
def destroy_volume_mapping(self, context, volume_mapping_uuid):
volume_mapping = self.get_volume_mapping_by_uuid(
context, volume_mapping_uuid)
self.client.delete('/volume_mappings/' + volume_mapping.uuid)
def update_volume_mapping(self, context, volume_mapping_uuid, values):
if 'uuid' in values:
msg = _('Cannot overwrite UUID for an existing VolumeMapping.')
raise exception.InvalidParameterValue(err=msg)
try:
target_uuid = self.get_volume_mapping_by_uuid(
context, volume_mapping_uuid).uuid
target = self.client.read('/volume_mapping/' + target_uuid)
target_value = json.loads(target.value)
target_value.update(values)
target.value = json.dump_as_bytes(target_value)
self.client.update(target)
except etcd.EtcdKeyNotFound:
raise exception.VolumeMappingNotFound(
volume_mapping=volume_mapping_uuid)
except Exception as e:
LOG.error('Error occurred while updating volume mappping: %s',
six.text_type(e))
raise
return translate_etcd_result(target, 'volume_mapping')

View File

@@ -290,3 +290,25 @@ class PciDevice(Base):
@classmethod @classmethod
def fields(cls): def fields(cls):
return cls._fields return cls._fields
class VolumeMapping(Base):
"""Represents a VolumeMapping."""
_path = '/volume_mapping'
_fields = objects.VolumeMapping.fields.keys()
def __init__(self, volume_mapping_data):
self.path = VolumeMapping.path()
for f in VolumeMapping.fields():
setattr(self, f, None)
self.id = 1
self.update(volume_mapping_data)
@classmethod
def path(cls):
return cls._path
@classmethod
def fields(cls):
return cls._fields

View File

@@ -274,41 +274,42 @@ class Connection(object):
value=values['uuid']) value=values['uuid'])
return volume_mapping return volume_mapping
def get_volume_mapping_by_uuid(self, context, vm_uuid): def get_volume_mapping_by_uuid(self, context, volume_mapping_uuid):
query = model_query(models.VolumeMapping) query = model_query(models.VolumeMapping)
query = self._add_tenant_filters(context, query) query = self._add_tenant_filters(context, query)
query = query.filter_by(uuid=vm_uuid) query = query.filter_by(uuid=volume_mapping_uuid)
try: try:
return query.one() return query.one()
except NoResultFound: except NoResultFound:
raise exception.VolumeMappingNotFound(vm_uuid) raise exception.VolumeMappingNotFound(volume_mapping_uuid)
def destroy_volume_mapping(self, context, vm_id): def destroy_volume_mapping(self, context, volume_mapping_uuid):
session = get_session() session = get_session()
with session.begin(): with session.begin():
query = model_query(models.VolumeMapping, session=session) query = model_query(models.VolumeMapping, session=session)
query = add_identity_filter(query, vm_id) query = add_identity_filter(query, volume_mapping_uuid)
count = query.delete() count = query.delete()
if count != 1: if count != 1:
raise exception.VolumeMappingNotFound(vm_id) raise exception.VolumeMappingNotFound(
volume_mapping_uuid)
def update_volume_mapping(self, context, vm_id, values): def update_volume_mapping(self, context, volume_mapping_uuid, values):
# NOTE(dtantsur): this can lead to very strange errors # NOTE(dtantsur): this can lead to very strange errors
if 'uuid' in values: if 'uuid' in values:
msg = _("Cannot overwrite UUID for an existing VolumeMapping.") msg = _("Cannot overwrite UUID for an existing VolumeMapping.")
raise exception.InvalidParameterValue(err=msg) raise exception.InvalidParameterValue(err=msg)
return self._do_update_volume_mapping(vm_id, values) return self._do_update_volume_mapping(volume_mapping_uuid, values)
def _do_update_volume_mapping(self, vm_id, values): def _do_update_volume_mapping(self, volume_mapping_uuid, values):
session = get_session() session = get_session()
with session.begin(): with session.begin():
query = model_query(models.VolumeMapping, session=session) query = model_query(models.VolumeMapping, session=session)
query = add_identity_filter(query, vm_id) query = add_identity_filter(query, volume_mapping_uuid)
try: try:
ref = query.with_lockmode('update').one() ref = query.with_lockmode('update').one()
except NoResultFound: except NoResultFound:
raise exception.VolumeMappingNotFound(vm_id) raise exception.VolumeMappingNotFound(volume_mapping_uuid)
ref.update(values) ref.update(values)
return ref return ref

View File

@@ -10,6 +10,11 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import json
import etcd
from etcd import Client as etcd_client
import mock
from oslo_config import cfg from oslo_config import cfg
from oslo_utils import uuidutils from oslo_utils import uuidutils
import six import six
@@ -19,6 +24,8 @@ import zun.conf
from zun.db import api as dbapi from zun.db import api as dbapi
from zun.tests.unit.db import base from zun.tests.unit.db import base
from zun.tests.unit.db import utils from zun.tests.unit.db import utils
from zun.tests.unit.db.utils import FakeEtcdMultipleResult
from zun.tests.unit.db.utils import FakeEtcdResult
CONF = zun.conf.CONF CONF = zun.conf.CONF
@@ -149,3 +156,177 @@ class DbVolumeMappingTestCase(base.DbTestCase):
self.assertRaises(exception.InvalidParameterValue, self.assertRaises(exception.InvalidParameterValue,
dbapi.update_volume_mapping, self.context, dbapi.update_volume_mapping, self.context,
volume_mapping.id, {'uuid': ''}) volume_mapping.id, {'uuid': ''})
class EtcdDbVolumeMappingTestCase(base.DbTestCase):
def setUp(self):
cfg.CONF.set_override('db_type', 'etcd')
super(EtcdDbVolumeMappingTestCase, self).setUp()
@mock.patch.object(etcd_client, 'read')
@mock.patch.object(etcd_client, 'write')
def test_create_volume_mapping(self, mock_write, mock_read):
mock_read.side_effect = etcd.EtcdKeyNotFound
utils.create_test_volume_mapping(context=self.context)
@mock.patch.object(etcd_client, 'read')
@mock.patch.object(etcd_client, 'write')
def test_create_volume_mapping_already_exists(self, mock_write,
mock_read):
mock_read.side_effect = etcd.EtcdKeyNotFound
utils.create_test_volume_mapping(context=self.context)
mock_read.side_effect = lambda *args: None
self.assertRaises(exception.ResourceExists,
utils.create_test_volume_mapping,
context=self.context)
@mock.patch.object(etcd_client, 'read')
@mock.patch.object(etcd_client, 'write')
def test_get_volume_mapping_by_uuid(self, mock_write, mock_read):
mock_read.side_effect = etcd.EtcdKeyNotFound
volume_mapping = utils.create_test_volume_mapping(
context=self.context)
mock_read.side_effect = lambda *args: FakeEtcdResult(
volume_mapping.as_dict())
res = dbapi.get_volume_mapping_by_uuid(self.context,
volume_mapping.uuid)
self.assertEqual(volume_mapping.id, res.id)
self.assertEqual(volume_mapping.uuid, res.uuid)
@mock.patch.object(etcd_client, 'read')
def test_get_volume_mapping_that_does_not_exist(self, mock_read):
mock_read.side_effect = etcd.EtcdKeyNotFound
self.assertRaises(exception.VolumeMappingNotFound,
dbapi.get_volume_mapping_by_uuid,
self.context,
uuidutils.generate_uuid())
@mock.patch.object(etcd_client, 'read')
@mock.patch.object(etcd_client, 'write')
def test_list_volume_mappings(self, mock_write, mock_read):
uuids = []
volume_mappings = []
mock_read.side_effect = etcd.EtcdKeyNotFound
for i in range(0, 6):
volume_mapping = utils.create_test_volume_mapping(
uuid=uuidutils.generate_uuid(),
context=self.context,
name='volume_mapping' + str(i))
volume_mappings.append(volume_mapping.as_dict())
uuids.append(six.text_type(volume_mapping['uuid']))
mock_read.side_effect = lambda *args: FakeEtcdMultipleResult(
volume_mappings)
res = dbapi.list_volume_mappings(self.context)
res_uuids = [r.uuid for r in res]
self.assertEqual(sorted(uuids), sorted(res_uuids))
@mock.patch.object(etcd_client, 'read')
@mock.patch.object(etcd_client, 'write')
def test_list_volume_mappings_sorted(self, mock_write, mock_read):
uuids = []
volume_mappings = []
mock_read.side_effect = etcd.EtcdKeyNotFound
for i in range(0, 6):
volume_mapping = utils.create_test_volume_mapping(
uuid=uuidutils.generate_uuid(),
context=self.context,
name='volume_mapping' + str(i))
volume_mappings.append(volume_mapping.as_dict())
uuids.append(six.text_type(volume_mapping['uuid']))
mock_read.side_effect = lambda *args: FakeEtcdMultipleResult(
volume_mappings)
res = dbapi.list_volume_mappings(self.context, sort_key='uuid')
res_uuids = [r.uuid for r in res]
self.assertEqual(sorted(uuids), res_uuids)
self.assertRaises(exception.InvalidParameterValue,
dbapi.list_volume_mappings,
self.context,
sort_key='wrong_key')
@mock.patch.object(etcd_client, 'read')
@mock.patch.object(etcd_client, 'write')
def test_list_volume_mappings_with_filters(self, mock_write, mock_read):
mock_read.side_effect = etcd.EtcdKeyNotFound
volume_mapping1 = utils.create_test_volume_mapping(
name='volume_mapping1',
uuid=uuidutils.generate_uuid(),
context=self.context)
volume_mapping2 = utils.create_test_volume_mapping(
name='volume_mapping2',
uuid=uuidutils.generate_uuid(),
context=self.context,)
mock_read.side_effect = lambda *args: FakeEtcdMultipleResult(
[volume_mapping1.as_dict(), volume_mapping2.as_dict()])
res = dbapi.list_volume_mappings(
self.context, filters={'uuid': volume_mapping1.uuid})
self.assertEqual([volume_mapping1.id], [r.id for r in res])
res = dbapi.list_volume_mappings(
self.context, filters={'uuid': volume_mapping2.uuid})
self.assertEqual([volume_mapping2.id], [r.id for r in res])
res = dbapi.list_volume_mappings(
self.context, filters={'uuid': 'unknow-uuid'})
self.assertEqual([], [r.id for r in res])
@mock.patch.object(etcd_client, 'read')
@mock.patch.object(etcd_client, 'write')
@mock.patch.object(etcd_client, 'delete')
def test_destroy_volume_mapping_by_uuid(self, mock_delete,
mock_write, mock_read):
mock_read.side_effect = etcd.EtcdKeyNotFound
volume_mapping = utils.create_test_volume_mapping(
context=self.context)
mock_read.side_effect = lambda *args: FakeEtcdResult(
volume_mapping.as_dict())
dbapi.destroy_volume_mapping(self.context, volume_mapping.uuid)
mock_delete.assert_called_once_with(
'/volume_mappings/%s' % volume_mapping.uuid)
@mock.patch.object(etcd_client, 'read')
def test_destroy_volume_mapping_that_does_not_exist(self, mock_read):
mock_read.side_effect = etcd.EtcdKeyNotFound
self.assertRaises(exception.VolumeMappingNotFound,
dbapi.destroy_volume_mapping, self.context,
uuidutils.generate_uuid())
@mock.patch.object(etcd_client, 'read')
@mock.patch.object(etcd_client, 'write')
@mock.patch.object(etcd_client, 'update')
def test_update_volume_mapping(self, mock_update, mock_write, mock_read):
mock_read.side_effect = etcd.EtcdKeyNotFound
volume_mapping = utils.create_test_volume_mapping(
context=self.context)
new_conn_info = 'new-conn-info'
mock_read.side_effect = lambda *args: FakeEtcdResult(
volume_mapping.as_dict())
dbapi.update_volume_mapping(self.context, volume_mapping.uuid,
{'container_info': new_conn_info})
self.assertEqual(new_conn_info, json.loads(
mock_update.call_args_list[0][0][0].value.decode('utf-8'))
['container_info'])
@mock.patch.object(etcd_client, 'read')
def test_update_volume_mapping_not_found(self, mock_read):
volume_mapping_uuid = uuidutils.generate_uuid()
new_conn_info = 'new-conn-info'
mock_read.side_effect = etcd.EtcdKeyNotFound
self.assertRaises(exception.VolumeMappingNotFound,
dbapi.update_volume_mapping, self.context,
volume_mapping_uuid,
{'container_info': new_conn_info})
@mock.patch.object(etcd_client, 'read')
@mock.patch.object(etcd_client, 'write')
def test_update_volume_mapping_uuid(self, mock_write, mock_read):
mock_read.side_effect = etcd.EtcdKeyNotFound
volume_mapping = utils.create_test_volume_mapping(
context=self.context)
self.assertRaises(exception.InvalidParameterValue,
dbapi.update_volume_mapping, self.context,
volume_mapping.uuid, {'uuid': ''})