'source': JLeaf(int),
'value': JLeaf(str),
})),
+ 'metadata': JObj({}, allow_unknown=True),
'mirror_mode': JLeaf(str),
})
self.assertSchema(img, schema)
self.remove_image(pool, None, image_name)
+ def test_create_with_metadata(self):
+ pool = 'rbd'
+ image_name = 'image_with_meta'
+ size = 10240
+ metadata = {
+ 'test1': 'test',
+ 'test2': 'value',
+ }
+
+ self.create_image(pool, None, image_name, size, metadata=metadata)
+ self.assertStatus(201)
+ img = self.get_image('rbd', None, image_name)
+ self.assertStatus(200)
+ self.assertEqual(len(metadata), len(img['metadata']))
+ for meta in metadata:
+ self.assertIn(meta, img['metadata'])
+
+ self.remove_image(pool, None, image_name)
+
def test_create_rbd_in_data_pool(self):
if not self.bluestore_support:
self.skipTest('requires bluestore cluster')
self.remove_image(pool, None, image)
self.assertStatus(204)
+ def test_image_change_meta(self):
+ pool = 'rbd'
+ image = 'image_with_meta'
+ initial_meta = {
+ 'test1': 'test',
+ 'test2': 'value',
+ 'test3': None,
+ }
+ initial_expect = {
+ 'test1': 'test',
+ 'test2': 'value',
+ }
+ new_meta = {
+ 'test1': None,
+ 'test2': 'new_value',
+ 'test3': 'value',
+ 'test4': None,
+ }
+ new_expect = {
+ 'test2': 'new_value',
+ 'test3': 'value',
+ }
+
+ self.create_image(pool, None, image, 2**30, metadata=initial_meta)
+ self.assertStatus(201)
+ img = self.get_image(pool, None, image)
+ self.assertStatus(200)
+ self.assertEqual(len(initial_expect), len(img['metadata']))
+ for meta in initial_expect:
+ self.assertIn(meta, img['metadata'])
+
+ self.edit_image(pool, None, image, metadata=new_meta)
+ img = self.get_image(pool, None, image)
+ self.assertStatus(200)
+ self.assertEqual(len(new_expect), len(img['metadata']))
+ for meta in new_expect:
+ self.assertIn(meta, img['metadata'])
+
+ self.remove_image(pool, None, image)
+ self.assertStatus(204)
+
def test_update_snapshot(self):
self.create_snapshot('rbd', None, 'img1', 'snap5')
self.assertStatus(201)
self.assertStatus(204)
def test_clone(self):
- self.create_image('rbd', None, 'cimg', 2**30, features=["layering"])
+ self.create_image('rbd', None, 'cimg', 2**30, features=["layering"],
+ metadata={'key1': 'val1'})
self.assertStatus(201)
self.create_snapshot('rbd', None, 'cimg', 'snap1')
self.assertStatus(201)
self.assertStatus(200)
self.clone_image('rbd', None, 'cimg', 'snap1', 'rbd', None, 'cimg-clone',
features=["layering", "exclusive-lock", "fast-diff",
- "object-map"])
+ "object-map"],
+ metadata={'key1': None, 'key2': 'val2'})
self.assertStatus([200, 201])
img = self.get_image('rbd', None, 'cimg-clone')
'fast-diff', 'layering',
'object-map'],
parent={'pool_name': 'rbd', 'pool_namespace': '',
- 'image_name': 'cimg', 'snap_name': 'snap1'})
+ 'image_name': 'cimg', 'snap_name': 'snap1'},
+ metadata={'key2': 'val2'})
res = self.remove_image('rbd', None, 'cimg')
self.assertStatus(400)
def test_copy(self):
self.create_image('rbd', None, 'coimg', 2**30,
features=["layering", "exclusive-lock", "fast-diff",
- "object-map"])
+ "object-map"],
+ metadata={'key1': 'val1'})
self.assertStatus(201)
self._rbd_cmd(['bench', '--io-type', 'write', '--io-total', '5M',
self.copy_image('rbd', None, 'coimg', 'rbd_iscsi', None, 'coimg-copy',
features=["layering", "fast-diff", "exclusive-lock",
- "object-map"])
+ "object-map"],
+ metadata={'key1': None, 'key2': 'val2'})
self.assertStatus([200, 201])
img = self.get_image('rbd', None, 'coimg')
self.assertStatus(200)
self._validate_image(img, features_name=['layering', 'exclusive-lock',
- 'fast-diff', 'object-map'])
+ 'fast-diff', 'object-map'],
+ metadata={'key1': 'val1'})
img_copy = self.get_image('rbd_iscsi', None, 'coimg-copy')
self._validate_image(img_copy, features_name=['exclusive-lock',
'fast-diff', 'layering',
'object-map'],
+ metadata={'key2': 'val2'},
disk_usage=img['disk_usage'])
self.remove_image('rbd', None, 'coimg')
from ..services.ceph_service import CephService
from ..services.exception import handle_rados_error, handle_rbd_error, serialize_dashboard_exception
from ..services.rbd import MIRROR_IMAGE_MODE, RbdConfiguration, \
- RbdMirroringService, RbdService, RbdSnapshotService, format_bitmask, \
- format_features, get_image_spec, parse_image_spec, rbd_call, \
- rbd_image_call
+ RbdImageMetadataService, RbdMirroringService, RbdService, \
+ RbdSnapshotService, format_bitmask, format_features, get_image_spec, \
+ parse_image_spec, rbd_call, rbd_image_call
from ..tools import ViewCache, str_to_bool
from . import APIDoc, APIRouter, BaseController, CreatePermission, \
DeletePermission, Endpoint, EndpointDoc, ReadPermission, RESTController, \
images[i]['configuration'] = RbdConfiguration(
pool, image['namespace'], image['name']).list()
+ images[i]['metadata'] = rbd_image_call(
+ pool, image['namespace'], image['name'],
+ lambda ioctx, image: RbdImageMetadataService(image).list())
+
return list(pool_result.values())
@handle_rbd_error()
{'pool_name': '{pool_name}', 'namespace': '{namespace}', 'image_name': '{name}'}, 2.0)
def create(self, name, pool_name, size, namespace=None, schedule_interval='',
obj_size=None, features=None, stripe_unit=None, stripe_count=None,
- data_pool=None, configuration=None, mirror_mode=None):
+ data_pool=None, configuration=None, metadata=None,
+ mirror_mode=None):
size = int(size)
stripe_count=stripe_count, data_pool=data_pool)
RbdConfiguration(pool_ioctx=ioctx, namespace=namespace,
image_name=name).set_configuration(configuration)
+ if metadata:
+ with rbd.Image(ioctx, name) as image:
+ RbdImageMetadataService(image).set_metadata(metadata)
rbd_call(pool_name, namespace, _create)
+
if mirror_mode:
RbdMirroringService.enable_image(name, pool_name, namespace,
MIRROR_IMAGE_MODE[mirror_mode])
@RbdTask('edit', ['{image_spec}', '{name}'], 4.0)
def set(self, image_spec, name=None, size=None, features=None,
- configuration=None, enable_mirror=None, primary=None,
+ configuration=None, metadata=None, enable_mirror=None, primary=None,
resync=False, mirror_mode=None, schedule_interval='',
remove_scheduling=False):
RbdConfiguration(pool_ioctx=ioctx, image_name=image_name).set_configuration(
configuration)
+ if metadata:
+ RbdImageMetadataService(image).set_metadata(metadata)
mirror_image_info = image.mirror_image_get_info()
if enable_mirror and mirror_image_info['state'] == rbd.RBD_MIRROR_IMAGE_DISABLED:
@allow_empty_body
def copy(self, image_spec, dest_pool_name, dest_namespace, dest_image_name,
snapshot_name=None, obj_size=None, features=None,
- stripe_unit=None, stripe_count=None, data_pool=None, configuration=None):
+ stripe_unit=None, stripe_count=None, data_pool=None,
+ configuration=None, metadata=None):
pool_name, namespace, image_name = parse_image_spec(image_spec)
def _src_copy(s_ioctx, s_img):
stripe_unit, stripe_count, data_pool)
RbdConfiguration(pool_ioctx=d_ioctx, image_name=dest_image_name).set_configuration(
configuration)
+ if metadata:
+ with rbd.Image(d_ioctx, dest_image_name) as image:
+ RbdImageMetadataService(image).set_metadata(metadata)
return rbd_call(dest_pool_name, dest_namespace, _copy)
@allow_empty_body
def clone(self, image_spec, snapshot_name, child_pool_name,
child_image_name, child_namespace=None, obj_size=None, features=None,
- stripe_unit=None, stripe_count=None, data_pool=None, configuration=None):
+ stripe_unit=None, stripe_count=None, data_pool=None,
+ configuration=None, metadata=None):
"""
Clones a snapshot to an image
"""
RbdConfiguration(pool_ioctx=ioctx, image_name=child_image_name).set_configuration(
configuration)
+ if metadata:
+ with rbd.Image(ioctx, child_image_name) as image:
+ RbdImageMetadataService(image).set_metadata(metadata)
return rbd_call(child_pool_name, child_namespace, _clone)
type: string
features:
type: string
+ metadata:
+ type: string
mirror_mode:
type: string
name:
type: string
features:
type: string
+ metadata:
+ type: string
mirror_mode:
type: string
name:
type: string
features:
type: string
+ metadata:
+ type: string
obj_size:
type: integer
snapshot_name:
type: string
features:
type: string
+ metadata:
+ type: string
obj_size:
type: integer
stripe_count:
stat['configuration'] = RbdConfiguration(
pool_ioctx=ioctx, image_name=image_name, image_ioctx=img).list()
+ stat['metadata'] = RbdImageMetadataService(img).list()
+
return stat
@classmethod
@classmethod
def snapshot_schedule_remove(cls, image_spec: str):
_rbd_support_remote('mirror_snapshot_schedule_remove', image_spec)
+
+
+class RbdImageMetadataService(object):
+ def __init__(self, image):
+ self._image = image
+
+ def list(self):
+ result = self._image.metadata_list()
+ # filter out configuration metadata
+ return {v[0]: v[1] for v in result if not v[0].startswith('conf_')}
+
+ def get(self, name):
+ return self._image.metadata_get(name)
+
+ def set(self, name, value):
+ self._image.metadata_set(name, value)
+
+ def remove(self, name):
+ try:
+ self._image.metadata_remove(name)
+ except KeyError:
+ pass
+
+ def set_metadata(self, metadata):
+ for name, value in metadata.items():
+ if value is not None:
+ self.set(name, value)
+ else:
+ self.remove(name)