Skip to content

Commit

Permalink
add image sharing
Browse files Browse the repository at this point in the history
  • Loading branch information
rmb938 committed Jan 15, 2018
1 parent dbb3fe6 commit 2ef6296
Show file tree
Hide file tree
Showing 9 changed files with 232 additions and 25 deletions.
26 changes: 22 additions & 4 deletions deli/counter/auth/policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,10 +189,7 @@
# Images
{
"name": "images:create",
"description": "Ability to create an image",
"tags": [
"project_member"
]
"description": "Ability to create an image"
},
{
"name": "images:create:public",
Expand Down Expand Up @@ -235,6 +232,27 @@
"project_member"
]
},
{
"name": "images:members:add",
"description": "Ability to add a member to an image",
"tags": [
"project_member"
]
},
{
"name": "images:members:list",
"description": "Ability to list image members",
"tags": [
"project_member"
]
},
{
"name": "images:members:delete",
"description": "Ability to delete a member from an image",
"tags": [
"project_member"
]
},

# Instances
{
Expand Down
113 changes: 104 additions & 9 deletions deli/counter/http/mounts/root/routes/v1/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
import cherrypy

from deli.counter.http.mounts.root.routes.v1.validation_models.images import RequestCreateImage, ResponseImage, \
ParamsImage, ParamsListImage
ParamsImage, ParamsListImage, ParamsImageMember, RequestAddMember, ResponseImageMember
from deli.http.request_methods import RequestMethods
from deli.http.route import Route
from deli.http.router import Router
from deli.kubernetes.resources.const import NAME_LABEL, PROJECT_LABEL, REGION_LABEL, IMAGE_VISIBILITY_LABEL, \
IMAGE_MEMBER_LABEL
from deli.kubernetes.resources.model import ResourceState
from deli.kubernetes.resources.project import Project
from deli.kubernetes.resources.v1alpha1.image.model import Image
from deli.kubernetes.resources.v1alpha1.image.model import Image, ImageVisibility
from deli.kubernetes.resources.v1alpha1.region.model import Region


Expand All @@ -26,25 +28,30 @@ def create(self):
request: RequestCreateImage = cherrypy.request.model
project: Project = cherrypy.request.project

image = Image.get_by_name(project, request.name)
if image is not None:
images = Image.list(
label_selector=PROJECT_LABEL + "=" + str(project.id) + "," + NAME_LABEL + "=" + request.name)
if len(images) > 0:
raise cherrypy.HTTPError(400, 'An image with the requested name already exists.')

region = Region.get(request.region_id)
if region is None:
raise cherrypy.HTTPError(404, 'A region with the requested id does not exist.')

if region.state != ResourceState.Created:
raise cherrypy.HTTPError(409, 'Can only create a network with a region in the following state: %s'.format(
raise cherrypy.HTTPError(409, 'Can only create a image with a region in the following state: %s'.format(
ResourceState.Created))

# TODO: check duplicate file name

if request.visibility == ImageVisibility.PUBLIC:
self.mount.enforce_policy("images:create:public")

image = Image()
image.name = request.name
image.file_name = request.file_name
image.project = project
image.region = region
image.visibility = request.visibility
image.create()

return ResponseImage.from_database(image)
Expand All @@ -56,23 +63,44 @@ def create(self):
@cherrypy.tools.resource_object(id_param="image_id", cls=Image)
@cherrypy.tools.enforce_policy(policy_name="images:get")
def get(self, **_):
return ResponseImage.from_database(cherrypy.request.resource_object)
image: Image = cherrypy.request.resource_object

if image.visibility == ImageVisibility.PRIVATE:
if image.project_id != cherrypy.request.project.id:
raise cherrypy.HTTPError(404, "The resource could not be found.")
elif image.visibility == ImageVisibility.SHARED:
if image.is_member(cherrypy.request.project.id) is False:
raise cherrypy.HTTPError(409, 'The requested image is not shared with the current project.')

return ResponseImage.from_database(image)

@Route()
@cherrypy.tools.project_scope()
@cherrypy.tools.model_params(cls=ParamsListImage)
@cherrypy.tools.model_out_pagination(cls=ResponseImage)
@cherrypy.tools.enforce_policy(policy_name="images:list")
def list(self, region_id, limit: int, marker: uuid.UUID):
def list(self, region_id, visibility: ImageVisibility, limit: int, marker: uuid.UUID):
kwargs = {
'project': cherrypy.request.project
'label_selector': []
}

if visibility == ImageVisibility.PRIVATE:
kwargs['label_selector'].append(IMAGE_VISIBILITY_LABEL + '=' + ImageVisibility.PRIVATE.value)
kwargs['label_selector'].append(PROJECT_LABEL + '=' + str(cherrypy.request.project.id))
elif visibility == ImageVisibility.SHARED:
kwargs['label_selector'].append(IMAGE_VISIBILITY_LABEL + '=' + ImageVisibility.SHARED.value)
kwargs['label_selector'].append(IMAGE_MEMBER_LABEL + "/" + str(cherrypy.request.project.id) + "=1")
else:
kwargs['label_selector'].append(IMAGE_VISIBILITY_LABEL + '=' + ImageVisibility.PUBLIC.value)

if region_id is not None:
region: Region = Region.get(region_id)
if region is None:
raise cherrypy.HTTPError(404, "A region with the requested id does not exist.")

kwargs['label_selector'] = 'sandwichcloud.io/region=' + region.id
kwargs['label_selector'].append(REGION_LABEL + '=' + region.id)

kwargs['label_selector'] = ",".join(kwargs['label_selector'])

return self.paginate(Image, ResponseImage, limit, marker, **kwargs)

Expand All @@ -85,10 +113,77 @@ def delete(self, **_):
cherrypy.response.status = 204
image: Image = cherrypy.request.resource_object

if image.project_id != cherrypy.request.project.id:
raise cherrypy.HTTPError(404, "The resource could not be found.")

if image.state == ResourceState.ToDelete or image.state == ResourceState.Deleting:
raise cherrypy.HTTPError(400, "Image is already being deleting")

if image.state == ResourceState.Deleted:
raise cherrypy.HTTPError(400, "Image has already been deleted")

image.delete()

@Route(route='{image_id}/members', methods=[RequestMethods.POST])
@cherrypy.tools.project_scope()
@cherrypy.tools.model_params(cls=ParamsImage)
@cherrypy.tools.model_in(cls=RequestAddMember)
@cherrypy.tools.resource_object(id_param="image_id", cls=Image)
@cherrypy.tools.enforce_policy(policy_name="images:members:add")
def add_member(self):
cherrypy.response.status = 204
request: RequestAddMember = cherrypy.request.model
image: Image = cherrypy.request.resource_object
if image.visibility != ImageVisibility.SHARED:
raise cherrypy.HTTPError(409, 'Cannot add a member to a non-shared image')

project = Project.get(request.project_id)
if project is None:
raise cherrypy.HTTPError(404, 'A project with the requested id does not exist.')

if image.is_member(request.project_id):
raise cherrypy.HTTPError(409, 'A project with the requested id is already a member.')

image.add_member(request.project_id)
image.save()

@Route(route='{image_id}/members')
@cherrypy.tools.project_scope()
@cherrypy.tools.model_params(cls=ParamsImage)
@cherrypy.tools.model_out_pagination(cls=ResponseImageMember)
@cherrypy.tools.resource_object(id_param="image_id", cls=Image)
@cherrypy.tools.enforce_policy(policy_name="images:members:list")
def list_members(self, **_):
image: Image = cherrypy.request.resource_object
if image.visibility != ImageVisibility.SHARED:
raise cherrypy.HTTPError(409, 'Cannot list members of a non-shared image')

members = []

for member_id in image.member_ids():
member = ResponseImageMember()
member.project_id = member_id
members.append(member)

return members, False

@Route(route='{image_id}/members/{project_id}', methods=[RequestMethods.DELETE])
@cherrypy.tools.project_scope()
@cherrypy.tools.model_params(cls=ParamsImageMember)
@cherrypy.tools.resource_object(id_param="image_id", cls=Image)
@cherrypy.tools.enforce_policy(policy_name="images:members:delete")
def delete_member(self, project_id, **_):
cherrypy.response.status = 204
image: Image = cherrypy.request.resource_object
if image.visibility != ImageVisibility.SHARED:
raise cherrypy.HTTPError(409, 'Cannot delete a member from a non-shared image')

project = Project.get(project_id)
if project is None:
raise cherrypy.HTTPError(404, 'A project with the requested id does not exist.')

if image.is_member(project_id) is False:
raise cherrypy.HTTPError(409, 'A project with the requested id is not a member.')

image.remove_member(project_id)
image.save()
23 changes: 16 additions & 7 deletions deli/counter/http/mounts/root/routes/v1/instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from deli.kubernetes.resources.model import ResourceState
from deli.kubernetes.resources.project import Project
from deli.kubernetes.resources.v1alpha1.flavor.model import Flavor
from deli.kubernetes.resources.v1alpha1.image.model import Image
from deli.kubernetes.resources.v1alpha1.image.model import Image, ImageVisibility
from deli.kubernetes.resources.v1alpha1.instance.model import Instance, VMPowerState
from deli.kubernetes.resources.v1alpha1.keypair.keypair import Keypair
from deli.kubernetes.resources.v1alpha1.network.model import NetworkPort, Network
Expand Down Expand Up @@ -67,9 +67,15 @@ def create(self):
raise cherrypy.HTTPError(400, 'Can only create a instance with a network in the following state: %s'.format(
ResourceState.Created))

image = Image.get(project, request.image_id)
image: Image = Image.get(project, request.image_id)
if image is None:
raise cherrypy.HTTPError(404, 'An image with the requested id does not exist.')
if image.visibility == ImageVisibility.PRIVATE:
if image.project_id != project.id:
raise cherrypy.HTTPError(404, 'An image with the requested id does not exist.')
elif image.visibility == ImageVisibility.SHARED:
if image.is_member(project.id) is False:
raise cherrypy.HTTPError(409, 'The requested image is not shared with the current project.')
if image.region.id != region.id:
raise cherrypy.HTTPError(409, 'The requested image is not within the requested region')
if image.state != ResourceState.Created:
Expand Down Expand Up @@ -190,7 +196,7 @@ def delete(self, **_):
@cherrypy.tools.project_scope()
@cherrypy.tools.model_params(cls=ParamsInstance)
@cherrypy.tools.resource_object(id_param="instance_id", cls=Instance)
@cherrypy.tools.enforce_policy(policy_name="instances:delete")
@cherrypy.tools.enforce_policy(policy_name="nstances:action:stop")
def action_start(self, **_):
cherrypy.response.status = 202

Expand All @@ -210,7 +216,7 @@ def action_start(self, **_):
@cherrypy.tools.model_params(cls=ParamsInstance)
@cherrypy.tools.model_in(cls=RequestInstancePowerOffRestart)
@cherrypy.tools.resource_object(id_param="instance_id", cls=Instance)
@cherrypy.tools.enforce_policy(policy_name="instances:delete")
@cherrypy.tools.enforce_policy(policy_name="nstances:action:start")
def action_stop(self, **_):
request: RequestInstancePowerOffRestart = cherrypy.request.model
cherrypy.response.status = 202
Expand All @@ -231,7 +237,7 @@ def action_stop(self, **_):
@cherrypy.tools.model_params(cls=ParamsInstance)
@cherrypy.tools.model_in(cls=RequestInstancePowerOffRestart)
@cherrypy.tools.resource_object(id_param="instance_id", cls=Instance)
@cherrypy.tools.enforce_policy(policy_name="instances:delete")
@cherrypy.tools.enforce_policy(policy_name="nstances:action:restart")
def action_restart(self, **_):
request: RequestInstancePowerOffRestart = cherrypy.request.model
cherrypy.response.status = 202
Expand All @@ -253,7 +259,7 @@ def action_restart(self, **_):
@cherrypy.tools.model_in(cls=RequestInstanceImage)
@cherrypy.tools.model_out(cls=ResponseImage)
@cherrypy.tools.resource_object(id_param="instance_id", cls=Instance)
@cherrypy.tools.enforce_policy(policy_name="instances:delete")
@cherrypy.tools.enforce_policy(policy_name="nstances:action:image")
def action_image(self, **_):
project: Project = cherrypy.request.project
request: RequestInstanceImage = cherrypy.request.model
Expand All @@ -270,6 +276,9 @@ def action_image(self, **_):
if Image.get_by_name(project, request.name) is not None:
raise cherrypy.HTTPError(400, 'An image with the requested name already exists.')

image = instance.action_image(request.name)
if request.visibility == ImageVisibility.PUBLIC:
self.mount.enforce_policy("instances:action:image:public")

image = instance.action_image(request.name, request.visibility)

return ResponseImage.from_database(image)
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,20 @@

from deli.http.schematics.types import KubeName, EnumType, ArrowType
from deli.kubernetes.resources.model import ResourceState
from deli.kubernetes.resources.v1alpha1.image.model import Image
from deli.kubernetes.resources.v1alpha1.image.model import Image, ImageVisibility


class ParamsImage(Model):
image_id = UUIDType(required=True)


class ParamsImageMember(Model):
image_id = UUIDType(required=True)
project_id = UUIDType(required=True)


class ParamsListImage(Model):
visibility = EnumType(ImageVisibility, default=ImageVisibility.PRIVATE)
region_id = UUIDType()
limit = IntType(default=100, max_value=100, min_value=1)
marker = UUIDType()
Expand All @@ -20,13 +26,24 @@ class RequestCreateImage(Model):
name = KubeName(required=True, min_length=3)
file_name = StringType(required=True)
region_id = KubeName(required=True)
visibility = EnumType(ImageVisibility, default=ImageVisibility.PRIVATE)


class RequestAddMember(Model):
project_id = UUIDType(required=True)


class ResponseImageMember(Model):
project_id = UUIDType(required=True)


class ResponseImage(Model):
id = UUIDType(required=True)
project_id = UUIDType(required=True)
name = KubeName(required=True, min_length=3)
file_name = StringType()
region_id = UUIDType(required=True)
visibility = EnumType(ImageVisibility)
state = EnumType(ResourceState, required=True)
error_message = StringType()
created_at = ArrowType(required=True)
Expand All @@ -35,10 +52,12 @@ class ResponseImage(Model):
def from_database(cls, image: Image):
image_model = cls()
image_model.id = image.id
image_model.project_id = image.project_id
image_model.name = image.name

image_model.file_name = image.file_name
image_model.region_id = image.region_id
image_model.visibility = image.visibility

image_model.state = image.state
if image.error_message != "":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from deli.http.schematics.types import KubeString, EnumType, ArrowType, KubeName
from deli.kubernetes.resources.model import ResourceState
from deli.kubernetes.resources.v1alpha1.image.model import ImageVisibility
from deli.kubernetes.resources.v1alpha1.instance.model import Instance, VMPowerState, VMTask


Expand Down Expand Up @@ -98,6 +99,7 @@ class ParamsListInstance(Model):

class RequestInstanceImage(Model):
name = KubeName(required=True)
visibility = EnumType(ImageVisibility, default=ImageVisibility.PRIVATE)


class RequestInstancePowerOffRestart(Model):
Expand Down
3 changes: 3 additions & 0 deletions deli/kubernetes/resources/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@

ID_LABEL = GROUP + '/id'
NAME_LABEL = GROUP + '/name'
PROJECT_LABEL = GROUP + '/project'
REGION_LABEL = GROUP + '/region'
ZONE_LABEL = GROUP + '/zone'
IMAGE_LABEL = GROUP + '/image'
NETWORK_LABEL = GROUP + '/network'
NETWORK_PORT_LABEL = GROUP + '/network_port'
SERVICE_ACCOUNT_LABEL = GROUP + '/service_account'
TAG_LABEL = 'tag.' + GROUP
IMAGE_VISIBILITY_LABEL = GROUP + '/visibility'
IMAGE_MEMBER_LABEL = 'image-member.' + GROUP
Loading

0 comments on commit 2ef6296

Please sign in to comment.