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

cyclades: Create '/volume/types' API

Create the API endpoint to list and show information about existing
volume types. Also, update the view to create a new volume to use the
'volume_type' attribute.
parent 3a6a2c8e
......@@ -100,6 +100,7 @@ def create(userid, name, password, flavor, image_id, metadata={},
if volumes[0]["source_type"] == "blank":
raise faults.BadRequest("Root volume cannot be blank")
server_vtype = flavor.volume_type
server_volumes = []
for index, vol_info in enumerate(volumes):
if vol_info["source_type"] == "volume":
......@@ -114,6 +115,7 @@ def create(userid, name, password, flavor, image_id, metadata={},
v.save()
else:
v = _create_volume(server=vm, user_id=userid,
volume_type=server_vtype,
index=index, **vol_info)
server_volumes.append(v)
......
#from .api_tests import *
from .volumes import *
from .volume_types import *
# Copyright (C) 2010-2014 GRNET S.A.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import json
from snf_django.utils.testing import BaseAPITest
from synnefo.db.models_factory import VolumeTypeFactory
from synnefo.lib.services import get_service_path
from synnefo.cyclades_settings import cyclades_services
from synnefo.lib import join_urls
VOLUME_URL = get_service_path(cyclades_services, 'volume',
version='v2.0')
VOLUME_TYPES_URL = join_urls(VOLUME_URL, "types/")
class VolumeTypeAPITest(BaseAPITest):
def test_list(self):
VolumeTypeFactory(disk_template="drbd", name="drbd1")
VolumeTypeFactory(disk_template="file", name="file1")
VolumeTypeFactory(disk_template="plain", name="deleted",
deleted=True)
response = self.get(VOLUME_TYPES_URL)
self.assertSuccess(response)
api_vtypes = json.loads(response.content)["volume_types"]
self.assertEqual(len(api_vtypes), 2)
self.assertEqual(api_vtypes[0]["SNF:disk_template"], "drbd")
self.assertEqual(api_vtypes[0]["name"], "drbd1")
self.assertEqual(api_vtypes[1]["SNF:disk_template"], "file")
self.assertEqual(api_vtypes[1]["name"], "file1")
def test_get(self):
vtype1 = VolumeTypeFactory(disk_template="drbd", name="drbd1")
vtype2 = VolumeTypeFactory(disk_template="drbd", name="drbd2")
response = self.get(join_urls(VOLUME_TYPES_URL, str(vtype1.id)))
self.assertSuccess(response)
api_vtype = json.loads(response.content)["volume_type"]
self.assertEqual(api_vtype["SNF:disk_template"], "drbd")
self.assertEqual(api_vtype["name"], "drbd1")
self.assertEqual(api_vtype["deleted"], False)
vtype2.deleted = True
vtype2.save()
response = self.get(join_urls(VOLUME_TYPES_URL, str(vtype2.id)))
self.assertSuccess(response)
api_vtype = json.loads(response.content)["volume_type"]
self.assertEqual(api_vtype["SNF:disk_template"], "drbd")
self.assertEqual(api_vtype["name"], "drbd1")
self.assertEqual(api_vtype["deleted"], True)
......@@ -64,6 +64,7 @@ class VolumesTest(BaseAPITest):
self.assertEqual(vol.source_volume_id, None)
self.assertEqual(vol.source_image_id, None)
self.assertEqual(vol.machine, self.vm)
self.assertEqual(vol.volume_type, self.vm.flavor.volume_type)
name, args, kwargs = mrapi().ModifyInstance.mock_calls[0]
self.assertEqual(kwargs["instance"], self.vm.backend_vm_id)
......@@ -74,18 +75,21 @@ class VolumesTest(BaseAPITest):
def test_create_from_volume(self, mrapi):
# Check permissions
svol = mf.VolumeFactory(userid="other_user")
svol = mf.VolumeFactory(userid="other_user",
volume_type=self.vm.flavor.volume_type)
self.assertRaises(faults.BadRequest,
volumes.create,
source_volume_id=svol.id,
**self.kwargs)
# Invalid volume status
svol = mf.VolumeFactory(userid=self.userid, status="CREATING")
svol = mf.VolumeFactory(userid=self.userid, status="CREATING",
volume_type=self.vm.flavor.volume_type)
self.assertRaises(faults.BadRequest,
volumes.create,
source_volume_id=svol.id,
**self.kwargs)
svol = mf.VolumeFactory(userid=self.userid, status="AVAILABLE")
svol = mf.VolumeFactory(userid=self.userid, status="AVAILABLE",
volume_type=self.vm.flavor.volume_type)
self.assertRaises(faults.BadRequest,
volumes.create,
source_volume_id=svol.id,
......@@ -105,6 +109,7 @@ class VolumesTest(BaseAPITest):
self.assertEqual(vol.description, None)
self.assertEqual(vol.source, "volume:%s" % svol.id)
self.assertEqual(vol.origin, svol.backend_volume_uuid)
self.assertEqual(vol.volume_type, svol.volume_type)
name, args, kwargs = mrapi().ModifyInstance.mock_calls[0]
self.assertEqual(kwargs["instance"], self.vm.backend_vm_id)
......
......@@ -66,6 +66,8 @@ volume_v2_patterns = patterns(
(r'^snapshots/$', snapshot_demux),
(r'^snapshots/detail$', views.list_snapshots, {'detail': True}),
(r'^snapshots/(\d+)(?:.json)?$', snapshot_item_demux),
(r'^types/$', views.list_volume_types),
(r'^types/(\d+)(?:.json)?$', views.get_volume_type),
)
urlpatterns = patterns(
......
......@@ -37,6 +37,23 @@ def get_volume(user_id, volume_id, for_update=False,
raise exception("Volume %s not found" % volume_id)
def get_volume_type(volume_type_id, for_update=False, include_deleted=False,
exception=faults.ItemNotFound):
vtypes = models.VolumeType.objects
if not include_deleted:
vtypes = vtypes.filter(deleted=False)
if for_update:
vtypes = vtypes.select_for_update()
try:
vtype_id = int(volume_type_id)
except (TypeError, ValueError):
raise faults.BadRequest("Invalid volume id: %s" % volume_type_id)
try:
return vtypes.get(id=vtype_id)
except models.VolumeType.DoesNotExist:
raise exception("Volume type %s not found" % vtype_id)
def get_snapshot(user_id, snapshot_id, exception=faults.ItemNotFound):
try:
with backend.PlanktonBackend(user_id) as b:
......
......@@ -18,14 +18,13 @@ from logging import getLogger
from django.http import HttpResponse
from django.utils import simplejson as json
import datetime
from dateutil.parser import parse as date_parse
from snf_django.lib import api
from snf_django.lib.api import faults, utils
from synnefo.volume import volumes, snapshots, util
from synnefo.db.models import Volume
from synnefo.db.models import Volume, VolumeType
from synnefo.plankton.backend import PlanktonBackend
log = getLogger('synnefo.volume')
......@@ -55,8 +54,7 @@ def volume_to_dict(volume, detail=True):
"source_volid": display_null_field(volume.source_volume_id),
"image_id": display_null_field(volume.source_image_id),
"attachments": get_volume_attachments(volume),
# TODO:
"volume_type": None,
"volume_type": volume.volume_type_id,
"delete_on_termination": volume.delete_on_termination,
#"availabilit_zone": None,
#"bootable": None,
......@@ -107,8 +105,7 @@ def create_volume(request):
raise faults.BadRequest("Volume 'size' needs to be a positive integer"
" value. '%s' cannot be accepted." % size)
# TODO: Fix volume type, validate, etc..
volume_type = new_volume.get("volume_type", None)
volume_type_id = new_volume.get("volume_type", None)
# Optional parameters
description = new_volume.get("display_description", "")
......@@ -135,7 +132,8 @@ def create_volume(request):
source_volume_id=source_volume_id,
source_snapshot_id=source_snapshot_id,
source_image_id=source_image_id,
volume_type=volume_type, description=description,
volume_type_id=volume_type_id,
description=description,
metadata=metadata, server_id=server_id)
# Render response
......@@ -146,7 +144,9 @@ def create_volume(request):
@api.api_method(http_method="GET", user_required=True, logger=log)
def list_volumes(request, detail=False):
log.debug('list_volumes detail=%s', detail)
volumes = Volume.objects.filter(userid=request.user_uniq).order_by("id")
volumes = Volume.objects.filter(userid=request.user_uniq)\
.prefetch_related("metadata")\
.order_by("id")
volumes = utils.filter_modified_since(request, objects=volumes)
......@@ -323,3 +323,29 @@ def update_snapshot(request, snapshot_id):
data = json.dumps({'snapshot': snapshot_to_dict(snapshot, detail=True)})
return HttpResponse(data, content_type="application/json", status=200)
def volume_type_to_dict(volume_type):
vtype_info = {
"id": volume_type.id,
"name": volume_type.name,
"deleted": volume_type.deleted,
"SNF:disk_template": volume_type.disk_template}
return vtype_info
@api.api_method(http_method="GET", user_required=True, logger=log)
def list_volume_types(request):
log.debug('list_volumes')
vtypes = VolumeType.objects.filter(deleted=False).order_by("id")
vtypes = [volume_type_to_dict(vtype) for vtype in vtypes]
data = json.dumps({'volume_types': vtypes})
return HttpResponse(data, content_type="application/json", status=200)
@api.api_method(http_method="GET", user_required=True, logger=log)
def get_volume_type(request, volume_type_id):
log.debug('get_volume_type volume_type_id: %s', volume_type_id)
volume_type = util.get_volume_type(volume_type_id, include_deleted=True)
data = json.dumps({'volume_type': volume_type_to_dict(volume_type)})
return HttpResponse(data, content_type="application/json", status=200)
......@@ -28,7 +28,7 @@ log = logging.getLogger(__name__)
@transaction.commit_on_success
def create(user_id, size, server_id, name=None, description=None,
source_volume_id=None, source_snapshot_id=None,
source_image_id=None, volume_type=None, metadata=None):
source_image_id=None, volume_type_id=None, metadata=None):
# Currently we cannot create volumes without being attached to a server
if server_id is None:
......@@ -36,6 +36,18 @@ def create(user_id, size, server_id, name=None, description=None,
server = util.get_server(user_id, server_id, for_update=True,
exception=faults.BadRequest)
server_vtype = server.flavor.volume_type
if volume_type_id is not None:
volume_type = util.get_volume_type(volume_type_id,
include_deleted=False,
exception=faults.BadRequest)
if volume_type != server_vtype:
raise faults.BadRequest("Cannot create a volume with type '%s' to"
" a server with volume type '%s'."
% (volume_type.id, server_vtype.id))
else:
volume_type = server_vtype
# Assert that not more than one source are used
sources = filter(lambda x: x is not None,
[source_volume_id, source_snapshot_id, source_image_id])
......@@ -56,7 +68,8 @@ def create(user_id, size, server_id, name=None, description=None,
source_uuid = None
volume = _create_volume(server, user_id, size, source_type, source_uuid,
name, description, index=None)
volume_type=volume_type, name=name,
description=description, index=None)
if metadata is not None:
for meta_key, meta_val in metadata.items():
......@@ -72,16 +85,16 @@ def create(user_id, size, server_id, name=None, description=None,
def _create_volume(server, user_id, size, source_type, source_uuid,
name=None, description=None, index=None,
volume_type, name=None, description=None, index=None,
delete_on_termination=True):
utils.check_name_length(name, Volume.NAME_LENGTH,
"Volume name is too long")
utils.check_name_length(description, Volume.DESCRIPTION_LENGTH,
"Volume name is too long")
# Only ext_ disk template supports cloning from another source. Otherwise
# is must be the root volume so that 'snf-image' fill the volume
volume_type = server.flavor.volume_type
can_have_source = (index == 0 or
volume_type.provider in settings.GANETI_CLONE_PROVIDERS)
if not can_have_source and source_type != "blank":
......
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