Commit b7ee9e3d authored by Christos Stavrakakis's avatar Christos Stavrakakis
Browse files

cyclades: Get Pithos objects by their UUID

Use the newly introduced 'get_object_by_uuid' method to get a Pithos
object by its UUID, instead of mapping a UUID to a (account, container,
name) tuple.

Also, remove the '--user' and '--public' options from 'image-show' and
'snapshot-show' management commands. Instead, use the
'check_permissions' option to bypass Pithos permission check.

Finally, fix plankton tests to mock the 'get_object_by_uuid' method.
parent 432129a3
......@@ -18,6 +18,12 @@ Astakos
* Explicitly specify the lists in which every notification will be sent.
Cyclades
--------
* Remove stale '--public' and '--user' options from 'image-show' and
'snapshot-show' management commands.
v0.16rc2
========
......
......@@ -152,30 +152,30 @@ class PlanktonBackend(object):
return False
@handle_pithos_backend
def get_image(self, uuid):
return self._get_image(uuid)
def get_image(self, uuid, check_permissions=True):
return self._get_image(uuid, check_permissions=check_permissions)
def _get_image(self, uuid):
location, metadata = self._get_raw_metadata(uuid)
permissions = self._get_raw_permissions(uuid, location)
def _get_image(self, uuid, check_permissions=True):
location, metadata, permissions = \
self.get_pithos_object(uuid, check_permissions=check_permissions)
return image_to_dict(location, metadata, permissions)
@handle_pithos_backend
def add_property(self, uuid, key, value):
location, _ = self._get_raw_metadata(uuid)
location, _m, _p = self.get_pithos_object(uuid)
properties = self._prefix_properties({key: value})
self._update_metadata(uuid, location, properties, replace=False)
@handle_pithos_backend
def remove_property(self, uuid, key):
location, _ = self._get_raw_metadata(uuid)
location, _m, _p = self.get_pithos_object(uuid)
# Use empty string to delete a property
properties = self._prefix_properties({key: ""})
self._update_metadata(uuid, location, properties, replace=False)
@handle_pithos_backend
def update_properties(self, uuid, properties, replace=False):
location, _ = self._get_raw_metadata(uuid)
location, _, _p = self.get_pithos_object(uuid)
properties = self._prefix_properties(properties)
self._update_metadata(uuid, location, properties, replace=replace)
......@@ -203,11 +203,18 @@ class PlanktonBackend(object):
@handle_pithos_backend
def update_metadata(self, uuid, metadata):
location, _ = self._get_raw_metadata(uuid)
location, _m, permissions = self.get_pithos_object(uuid)
is_public = metadata.pop("is_public", None)
if is_public is not None:
self._set_public(uuid, location, public=is_public)
assert(isinstance(is_public, bool))
read = set(permissions.get("read", []))
if is_public and "*" not in read:
read.add("*")
elif not is_public and "*" in read:
read.discard("*")
permissions["read"] = list(read)
self._update_permissions(uuid, location, permissions)
# Each property is stored as a separate prefixed metadata
meta = deepcopy(metadata)
......@@ -244,46 +251,41 @@ class PlanktonBackend(object):
prefixed[k] = v
return prefixed
def _get_raw_metadata(self, uuid, version=None, check_image=True):
"""Get info and metadata in Plankton doamin for the Pithos object.
def get_pithos_object(self, uuid, version=None, check_permissions=True,
check_image=True):
"""Get a Pithos object based on its UUID.
Return the location and the metadata of the Pithos object.
If 'check_image' is set, check that the Pithos object is a registered
Plankton Image.
If 'version' is not specified, the latest non-deleted version of this
object will be retrieved.
If 'check_permissions' is set to False, the Pithos backend will not
check if the user has permissions to access this object.
Finally, the 'check_image' option is used to check whether the Pithos
object is an image or not.
"""
# Convert uuid to location
account, container, path = self.backend.get_uuid(self.user, uuid)
try:
meta = self.backend.get_object_meta(self.user, account, container,
path, PLANKTON_DOMAIN, version)
meta["deleted"] = False
except NameError:
if version is not None:
raise
versions = self.backend.list_versions(self.user, account,
container, path)
assert(versions), ("Object without versions: %s/%s/%s" %
(account, container, path))
# Object was deleted, use the latest version
version, timestamp = versions[-1]
meta = self.backend.get_object_meta(self.user, account, container,
path, PLANKTON_DOMAIN, version)
meta["deleted"] = True
if check_permissions:
user = self.user
else:
user = None
meta, permissions, path = self.backend.get_object_by_uuid(
uuid=uuid, version=version, domain=PLANKTON_DOMAIN,
user=user, check_permissions=check_permissions)
account, container, path = path.split("/", 2)
location = Location(account, container, path)
if check_image and PLANKTON_PREFIX + "name" not in meta:
# Check that object is an image by checking if it has an Image name
# in Plankton metadata
raise faults.ItemNotFound("Image '%s' does not exist." % uuid)
raise faults.ItemNotFound("Object '%s' does not exist." % uuid)
return Location(account, container, path), meta
return location, meta, permissions
# Users and Permissions
@handle_pithos_backend
def add_user(self, uuid, user):
assert(isinstance(user, basestring))
location, _ = self._get_raw_metadata(uuid)
permissions = self._get_raw_permissions(uuid, location)
location, _, permissions = self.get_pithos_object(uuid)
read = set(permissions.get("read", []))
if user not in read:
read.add(user)
......@@ -293,8 +295,7 @@ class PlanktonBackend(object):
@handle_pithos_backend
def remove_user(self, uuid, user):
assert(isinstance(user, basestring))
location, _ = self._get_raw_metadata(uuid)
permissions = self._get_raw_permissions(uuid, location)
location, _, permissions = self.get_pithos_object(uuid)
read = set(permissions.get("read", []))
if user in read:
read.remove(user)
......@@ -304,8 +305,7 @@ class PlanktonBackend(object):
@handle_pithos_backend
def replace_users(self, uuid, users):
assert(isinstance(users, list))
location, _ = self._get_raw_metadata(uuid)
permissions = self._get_raw_permissions(uuid, location)
location, _, permissions = self.get_pithos_object(uuid)
read = set(permissions.get("read", []))
if "*" in read: # Retain public permissions
users.append("*")
......@@ -314,35 +314,9 @@ class PlanktonBackend(object):
@handle_pithos_backend
def list_users(self, uuid):
location, _ = self._get_raw_metadata(uuid)
permissions = self._get_raw_permissions(uuid, location)
location, _, permissions = self.get_pithos_object(uuid)
return [user for user in permissions.get('read', []) if user != '*']
def _set_public(self, uuid, location, public):
permissions = self._get_raw_permissions(uuid, location)
assert(isinstance(public, bool))
read = set(permissions.get("read", []))
if public and "*" not in read:
read.add("*")
elif not public and "*" in read:
read.discard("*")
permissions["read"] = list(read)
self._update_permissions(uuid, location, permissions)
return permissions
def _get_raw_permissions(self, uuid, location):
account, container, path = location
_a, path, permissions = \
self.backend.get_object_permissions(self.user, account, container,
path)
if path is None and permissions != {}:
raise Exception("Database Inconsistency Error:"
" Image '%s' got permissions from 'None' path." %
uuid)
return permissions
def _update_permissions(self, uuid, location, permissions):
account, container, path = location
self.backend.update_object_permissions(self.user, account, container,
......@@ -415,7 +389,7 @@ class PlanktonBackend(object):
domain. The Pithos file is not deleted.
"""
location, _ = self._get_raw_metadata(uuid)
location, _m, _p = self.get_pithos_object(uuid)
self._update_metadata(uuid, location, metadata={}, replace=True)
logger.debug("User '%s' unregistered image '%s'", self.user, uuid)
......@@ -493,8 +467,9 @@ class PlanktonBackend(object):
return [s for s in _snapshots if s["is_snapshot"]]
@handle_pithos_backend
def get_snapshot(self, snapshot_uuid):
snap = self._get_image(snapshot_uuid)
def get_snapshot(self, snapshot_uuid, check_permissions=True):
snap = self._get_image(snapshot_uuid,
check_permissions=check_permissions)
if snap.get("is_snapshot", False) is False:
raise faults.ItemNotFound("Snapshots '%s' does not exist" %
snapshot_uuid)
......
......@@ -14,7 +14,6 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from optparse import make_option
from django.core.management.base import CommandError
from snf_django.management.commands import SynnefoCommand
......@@ -26,20 +25,6 @@ from snf_django.management import utils
class Command(SynnefoCommand):
args = "<image_id>"
help = "Display available information about an image"
option_list = SynnefoCommand.option_list + (
make_option(
'--user',
dest='userid',
default=None,
help="The UUID of the owner of the image. Required"
" if image is not public"),
make_option(
'--public',
dest='public',
default=False,
action="store_true",
help="Use this option if the image is public"),
)
@common.convert_api_faults
def handle(self, *args, **options):
......@@ -47,15 +32,10 @@ class Command(SynnefoCommand):
if len(args) != 1:
raise CommandError("Please provide an image ID")
image_id = args[0]
user_id = options["userid"]
public = options["public"]
if (user_id is None) and (public is False):
raise CommandError("'user' option or 'public' option is required")
try:
with PlanktonBackend(user_id) as backend:
image = backend.get_image(image_id)
with PlanktonBackend(None) as backend:
image = backend.get_image(image_id, check_permissions=False)
except:
raise CommandError("An error occurred, verify that image or "
"user ID are valid")
......
......@@ -128,27 +128,21 @@ class PlanktonTest(BaseAPITest):
response = self.post(IMAGES_URL, **headers)
self.assertBadRequest(response)
backend().get_uuid.return_value =\
("4321-4321", u"\u2602", "foo")
backend().get_object_permissions.return_value = \
("foo", "foo", {"read": []})
backend().get_object_meta.side_effect = \
[{"uuid": "1234-1234-1234",
"bytes": 42,
"is_snapshot": True,
"hash": "unique_mapfile",
"mapfile": "unique_mapfile"},
{"uuid": "1234-1234-1234",
"bytes": 42,
"mapfile": "unique_mapfile",
"is_snapshot": True,
"hash": "unique_mapfile",
"version": 42,
'version_timestamp': Decimal('1392487853.863673'),
"plankton:name": u"TestImage\u2602",
"plankton:container_format": "bare",
"plankton:disk_format": "diskdump",
"plankton:status": u"AVAILABLE"}]
backend().get_object_by_uuid.return_value = (
{"uuid": "1234-1234-1234",
"bytes": 42,
"mapfile": "unique_mapfile",
"is_snapshot": True,
"hash": "unique_mapfile",
"version": 42,
'version_timestamp': Decimal('1392487853.863673'),
"plankton:name": u"TestImage\u2602",
"plankton:container_format": "bare",
"plankton:disk_format": "diskdump",
"plankton:status": u"AVAILABLE"},
{"read": []},
u"4321-4321/\u2602/foo",
)
headers = deepcopy(required)
response = self.post(IMAGES_URL, **headers)
self.assertSuccess(response)
......@@ -171,23 +165,21 @@ class PlanktonTest(BaseAPITest):
"2014-02-15 18:10:53")
# Extra headers,properties
backend().get_object_meta.side_effect = \
[{"uuid": "1234-1234-1234",
"bytes": 42,
"is_snapshot": True,
"hash": "unique_mapfile",
"mapfile": "unique_mapfile"},
{"uuid": "1234-1234-1234",
"bytes": 42,
"is_snapshot": True,
"hash": "unique_mapfile",
"mapfile": "unique_mapfile",
"version": 42,
'version_timestamp': Decimal('1392487853.863673'),
"plankton:name": u"TestImage\u2602",
"plankton:container_format": "bare",
"plankton:disk_format": "diskdump",
"plankton:status": u"AVAILABLE"}]
backend().get_object_by_uuid.return_value = (
{"uuid": "1234-1234-1234",
"bytes": 42,
"is_snapshot": True,
"hash": "unique_mapfile",
"mapfile": "unique_mapfile",
"version": 42,
'version_timestamp': Decimal('1392487853.863673'),
"plankton:name": u"TestImage\u2602",
"plankton:container_format": "bare",
"plankton:disk_format": "diskdump",
"plankton:status": u"AVAILABLE"},
{"read": []},
u"4321-4321/\u2602/foo",
)
headers = deepcopy(required)
headers["HTTP_X_IMAGE_META_IS_PUBLIC"] = True
headers["HTTP_X_IMAGE_META_PROPERTY_KEY1"] = "val1"
......@@ -200,10 +192,13 @@ class PlanktonTest(BaseAPITest):
self.assertSuccess(response)
def test_unregister_image(self, backend):
backend().get_uuid.return_value = ("img_owner", "images", "foo")
backend().get_object_meta.return_value = {"uuid": "img_uuid",
"bytes": 42,
"plankton:name": "test"}
backend().get_object_by_uuid.return_value = (
{"uuid": "img_uuid",
"bytes": 42,
"plankton:name": "test"},
{"read": []},
"img_owner/images/foo"
)
response = self.delete(join_urls(IMAGES_URL, "img_uuid"))
self.assertEqual(response.status_code, 204)
backend().update_object_meta.assert_called_once_with(
......@@ -213,12 +208,12 @@ class PlanktonTest(BaseAPITest):
"""Test adding/removing and replacing image members"""
# Add user
backend.reset_mock()
backend().get_uuid.return_value = ("img_owner", "images", "foo")
backend().get_object_permissions.return_value = \
("foo", "foo", {"read": []})
backend().get_object_meta.return_value = {"uuid": "img_uuid",
"bytes": 42,
"plankton:name": "test"}
backend().get_object_by_uuid.return_value = (
{"uuid": "img_uuid",
"bytes": 42,
"plankton:name": "test"},
{"read": []},
"img_owner/images/foo")
response = self.put(join_urls(IMAGES_URL, "img_uuid/members/user1"),
user="user1")
self.assertSuccess(response)
......@@ -227,8 +222,12 @@ class PlanktonTest(BaseAPITest):
# Remove user
backend().update_object_permissions.reset_mock()
backend().get_object_permissions.return_value = \
("foo", "foo", {"read": ["user1"]})
backend().get_object_by_uuid.return_value = (
{"uuid": "img_uuid",
"bytes": 42,
"plankton:name": "test"},
{"read": ["user1"]},
"img_owner/images/foo")
response = self.delete(join_urls(IMAGES_URL, "img_uuid/members/user1"),
user="user1")
self.assertSuccess(response)
......@@ -236,8 +235,12 @@ class PlanktonTest(BaseAPITest):
"user1", "img_owner", "images", "foo", {"read": []})
# Update users
backend().get_object_permissions.return_value = \
("foo", "foo", {"read": ["user1", "user2", "user3"]})
backend().get_object_by_uuid.return_value = (
{"uuid": "img_uuid",
"bytes": 42,
"plankton:name": "test"},
{"read": ["user1", "user2", "user3"]},
"img_owner/images/foo")
backend().update_object_permissions.reset_mock()
response = self.put(join_urls(IMAGES_URL, "img_uuid/members"),
params=json.dumps({"memberships":
......@@ -250,8 +253,13 @@ class PlanktonTest(BaseAPITest):
"user1", "img_owner", "images", "foo", {"read": ["foo1", "foo2"]})
# List users
backend().get_object_permissions.return_value = \
("foo", "foo", {"read": ["user1", "user2", "user3"]})
backend().get_object_by_uuid.return_value = (
{"uuid": "img_uuid",
"bytes": 42,
"plankton:name": "test"},
{"read": ["user1", "user2", "user3"]},
"img_owner/images/foo",
)
response = self.get(join_urls(IMAGES_URL, "img_uuid/members"))
self.assertSuccess(response)
res_members = [{"member_id": m, "can_share": False}
......@@ -259,8 +267,7 @@ class PlanktonTest(BaseAPITest):
self.assertEqual(json.loads(response.content)["members"], res_members)
def test_metadata(self, backend):
backend().get_uuid.return_value = ("img_owner", "images", "foo")
backend().get_object_meta.return_value = \
backend().get_object_by_uuid.return_value = (
{"uuid": "img_uuid",
"bytes": 42,
"is_snapshot": True,
......@@ -271,13 +278,14 @@ class PlanktonTest(BaseAPITest):
"plankton:name": u"TestImage\u2602",
"plankton:container_format": "bare",
"plankton:disk_format": "diskdump",
"plankton:status": u"AVAILABLE"}
backend().get_object_permissions.return_value = \
("foo", "foo", {"read": ["*", "user1"]})
"plankton:status": u"AVAILABLE"},
{"read": ["*", "user1"]},
"img_owner/images/foo/foo1/foo2/foo3",
)
response = self.head(join_urls(IMAGES_URL, "img_uuid2"))
self.assertSuccess(response)
self.assertEqual(response["x-image-meta-location"],
"pithos://img_owner/images/foo")
"pithos://img_owner/images/foo/foo1/foo2/foo3")
self.assertEqual(response["x-image-meta-id"], "img_uuid")
self.assertEqual(response["x-image-meta-status"], "AVAILABLE")
self.assertEqual(response["x-image-meta-deleted-at"], "")
......@@ -300,7 +308,8 @@ class PlanktonTest(BaseAPITest):
response = self.put(join_urls(IMAGES_URL, "img_uuid"), **headers)
self.assertSuccess(response)
backend().update_object_permissions.assert_called_once_with(
"user", "img_owner", "images", "foo", {"read": ["user1"]})
"user", "img_owner", "images", "foo/foo1/foo2/foo3",
{"read": ["user1"]})
def test_catch_wrong_api_paths(self, *args):
response = self.get(join_urls(PLANKTON_URL, 'nonexistent'))
......
......@@ -15,7 +15,6 @@
#
from snf_django.management.commands import SynnefoCommand, CommandError
from optparse import make_option
from synnefo.management import common
from synnefo.plankton.backend import PlanktonBackend
......@@ -25,20 +24,6 @@ from snf_django.management import utils
class Command(SynnefoCommand):
args = "<snapshot_id>"
help = "Display available information about a snapshot"
option_list = SynnefoCommand.option_list + (
make_option(
'--user',
dest='userid',
default=None,
help="The UUID of the owner of the snapshot. Required"
"if snapshot is not public"),
make_option(
'--public',
dest='public',
default=False,
action="store_true",
help="Use this option if the snapshot is public"),
)
@common.convert_api_faults
def handle(self, *args, **options):
......@@ -47,15 +32,11 @@ class Command(SynnefoCommand):
raise CommandError("Please provide a snapshot ID")
snapshot_id = args[0]
userid = options["userid"]
public = options["public"]
if (userid is None) and (public is False):
raise CommandError("'user' option or 'public' option is required")
try:
with PlanktonBackend(userid) as backend:
snapshot = backend.get_snapshot(snapshot_id)
with PlanktonBackend(None) as backend:
snapshot = backend.get_snapshot(snapshot_id,
check_permissions=False)
except:
raise CommandError("An error occurred, verify that snapshot and "
"user ID are valid")
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment