Merge "Add etcd db for VolumeMapping"
This commit is contained in:
@@ -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')
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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': ''})
|
||||||
|
|||||||
Reference in New Issue
Block a user