Commit 5a2a706e authored by Christos Stavrakakis's avatar Christos Stavrakakis
Browse files

cyclades: Replace disk template with volume types

Replace usage of the stale 'disk_template' attribute of flavors and
volumes with the newly introduced volume type.
parent 1f79d0e3
......@@ -81,7 +81,10 @@ def get_resources_stats(backend=None):
for res in ["cpu", "ram", "disk", "disk_template"]:
server_count[res] = {}
allocated[res] = 0
val = "flavor__%s" % res
if res == "disk_template":
val = "flavor__volume_type__%s" % res
else:
val = "flavor__%s" % res
results = active_servers.values(val).annotate(count=Count(val))
for result in results:
server_count[res][result[val]] = result["count"]
......
......@@ -43,7 +43,8 @@ def flavor_to_dict(flavor, detail=True):
d['ram'] = flavor.ram
d['disk'] = flavor.disk
d['vcpus'] = flavor.cpu
d['SNF:disk_template'] = flavor.disk_template
d['SNF:disk_template'] = flavor.volume_type.disk_template
d['SNF:volume_type'] = flavor.volume_type_id
d['SNF:allow_create'] = flavor.allow_create
return d
......@@ -58,7 +59,8 @@ def list_flavors(request, detail=False):
# overLimit (413)
log.debug('list_flavors detail=%s', detail)
active_flavors = Flavor.objects.exclude(deleted=True)
active_flavors = Flavor.objects.select_related("volume_type")\
.exclude(deleted=True)
flavors = [flavor_to_dict(flavor, detail)
for flavor in active_flavors.order_by('id')]
......
......@@ -68,7 +68,7 @@ class FlavorAPITest(BaseAPITest):
self.assertEqual(api_flavor['name'], db_flavor.name)
self.assertEqual(api_flavor['ram'], db_flavor.ram)
self.assertEqual(api_flavor['SNF:disk_template'],
db_flavor.disk_template)
db_flavor.volume_type.disk_template)
def test_flavor_details(self):
"""Test if the expected flavor is returned."""
......@@ -85,7 +85,7 @@ class FlavorAPITest(BaseAPITest):
self.assertEqual(api_flavor['name'], db_flavor.name)
self.assertEqual(api_flavor['ram'], db_flavor.ram)
self.assertEqual(api_flavor['SNF:disk_template'],
db_flavor.disk_template)
db_flavor.volume_type.disk_template)
def test_deleted_flavor_details(self):
"""Test that API returns details for deleted flavors"""
......
......@@ -591,7 +591,7 @@ class ServerCreateAPITest(ComputeAPITest):
self.assertEqual(response.status_code, 202, msg=response.content)
vm_id = json.loads(response.content)["server"]["id"]
volume = Volume.objects.get(machine_id=vm_id)
self.assertEqual(volume.disk_template, self.flavor.disk_template)
self.assertEqual(volume.volume_type, self.flavor.volume_type)
self.assertEqual(volume.size, self.flavor.disk)
self.assertEqual(volume.source, "image:%s" % fixed_image()["id"])
self.assertEqual(volume.delete_on_termination, True)
......@@ -610,7 +610,7 @@ class ServerCreateAPITest(ComputeAPITest):
self.assertEqual(response.status_code, 202, msg=response.content)
vm_id = json.loads(response.content)["server"]["id"]
volume = Volume.objects.get(machine_id=vm_id)
self.assertEqual(volume.disk_template, self.flavor.disk_template)
self.assertEqual(volume.volume_type, self.flavor.volume_type)
self.assertEqual(volume.size, 10)
self.assertEqual(volume.source, "image:%s" % fixed_image()["id"])
self.assertEqual(volume.delete_on_termination, False)
......@@ -630,7 +630,7 @@ class ServerCreateAPITest(ComputeAPITest):
self.assertEqual(response.status_code, 202, msg=response.content)
vm_id = json.loads(response.content)["server"]["id"]
volume = Volume.objects.get(machine_id=vm_id)
self.assertEqual(volume.disk_template, self.flavor.disk_template)
self.assertEqual(volume.volume_type, self.flavor.volume_type)
self.assertEqual(volume.size, 10)
self.assertEqual(volume.source, "snapshot:%s" % fixed_image()["id"])
self.assertEqual(volume.origin, fixed_image()["mapfile"])
......@@ -855,8 +855,10 @@ class ServerActionAPITest(ComputeAPITest):
response = self.mypost('servers/%d/action' % vm.id,
vm.userid, json.dumps(request), 'json')
self.assertBadRequest(response)
flavor2 = mfactory.FlavorFactory(disk_template="foo")
flavor3 = mfactory.FlavorFactory(disk_template="baz")
# Check flavor with different volume type
flavor2 = mfactory.FlavorFactory(volume_type__disk_template="foo")
flavor3 = mfactory.FlavorFactory(volume_type__disk_template="baz")
vm = self.get_vm(flavor=flavor2, operstate="STOPPED")
request = {'resize': {'flavorRef': flavor3.id}}
response = self.mypost('servers/%d/action' % vm.id,
......@@ -864,7 +866,7 @@ class ServerActionAPITest(ComputeAPITest):
self.assertBadRequest(response)
# Check success
vm = self.get_vm(flavor=flavor, operstate="STOPPED")
flavor4 = mfactory.FlavorFactory(disk_template=flavor.disk_template,
flavor4 = mfactory.FlavorFactory(volume_type=vm.flavor.volume_type,
disk=flavor.disk,
cpu=4, ram=2048)
request = {'resize': {'flavorRef': flavor4.id}}
......@@ -981,7 +983,7 @@ class ServerAttachments(ComputeAPITest):
def test_attach_detach_volume(self, mrapi):
vol = mfactory.VolumeFactory(status="AVAILABLE")
vm = vol.machine
disk_template = vm.flavor.disk_template
volume_type = vm.flavor.volume_type
# Test that we cannot detach the root volume
response = self.mydelete("servers/%d/os-volume_attachments/%d" %
(vm.id, vol.id), vm.userid)
......@@ -989,7 +991,7 @@ class ServerAttachments(ComputeAPITest):
# Test that we cannot attach a used volume
vol1 = mfactory.VolumeFactory(status="IN_USE",
disk_template=disk_template,
volume_type=volume_type,
userid=vm.userid)
request = json.dumps({"volumeAttachment": {"volumeId": vol1.id}})
response = self.mypost("servers/%d/os-volume_attachments" %
......@@ -997,16 +999,17 @@ class ServerAttachments(ComputeAPITest):
request, "json")
self.assertBadRequest(response)
# We cannot attach a volume of different disk template
vol1.status = "AVAILABLE"
vol1.disk_template = "lalalal"
# We cannot attach a volume of different disk template
volume_type_2 = mfactory.VolumeTypeFactory(disk_template="lalalal")
vol1.volume_type = volume_type_2
vol1.save()
response = self.mypost("servers/%d/os-volume_attachments/" %
vm.id, vm.userid,
request, "json")
self.assertBadRequest(response)
vol1.disk_template = disk_template
vol1.volume_type = volume_type
vol1.save()
mrapi().ModifyInstance.return_value = 43
response = self.mypost("servers/%d/os-volume_attachments" %
......
......@@ -187,10 +187,10 @@ def get_flavor(flavor_id, include_deleted=False):
try:
flavor_id = int(flavor_id)
if include_deleted:
return Flavor.objects.get(id=flavor_id)
else:
return Flavor.objects.get(id=flavor_id, deleted=include_deleted)
flavors = Flavor.objects.select_related("volume_type")
if not include_deleted:
flavors = flavors.filter(deleted=False)
return flavors.get(id=flavor_id)
except (ValueError, TypeError):
raise faults.BadRequest("Invalid flavor ID '%s'" % flavor_id)
except Flavor.DoesNotExist:
......
......@@ -48,6 +48,17 @@ class VolumeType(models.Model):
return u"<VolumeType %s(disk_template:%s)>" % \
(self.name, self.disk_template)
@property
def template(self):
return self.disk_template.split("_")[0]
@property
def provider(self):
if "_" in self.disk_template:
return self.disk_template.split("_", 1)[1]
else:
return None
class Flavor(models.Model):
cpu = models.IntegerField('Number of CPUs', default=0)
......@@ -1118,17 +1129,6 @@ class Volume(models.Model):
else:
return None
@property
def template(self):
return self.disk_template.split("_")[0]
@property
def provider(self):
if "_" in self.disk_template:
return self.disk_template.split("_", 1)[1]
else:
return None
@staticmethod
def prefix_source(source_id, source_type):
if source_type == "volume":
......
......@@ -19,11 +19,10 @@
from django.test import TestCase
from django.conf import settings
# Import pool tests
from synnefo.db.pools.tests import *
from synnefo.db.models import *
from synnefo.db import models_factory as mfact
from synnefo.db.pools import IPPool, EmptyPool
......@@ -37,7 +36,7 @@ class FlavorTest(TestCase):
def test_flavor_name(self):
"""Test a flavor object name method."""
flavor = mfact.FlavorFactory(cpu=1, ram=1024, disk=40,
disk_template="temp")
volume_type__disk_template="temp")
self.assertEqual(
flavor.name, "C1R1024D40temp", "flavor.name is not"
" generated correctly. Name is %s instead of C1R1024D40temp" %
......
......@@ -35,12 +35,12 @@
<dt>Flavor</dt><dd>{{ vm.flavor.cpu }},
{{ vm.flavor.disk }},
{{ vm.flavor.ram }},
{{ vm.flavor.disk_template }}</dd>
{{ vm.flavor.volume_type.disk_template }}</dd>
</dl>
</div>
<div class="tab-pane" id="metadata{{ vm.pk }}">
<dl class="dl-horizontal well">
{% for meta in vm.metadata.all %}
{% for meta in vm.metadata.all %}
<dt>{{ meta.meta_key }}</dt><dd>{{ meta.meta_value }}</dd>
{% empty %}
<dt>No metadata</dt>
......
......@@ -231,14 +231,14 @@ def find_new_flavor(vm, cpu=None, ram=None):
return None
try:
new_flavor = Flavor.objects.get(cpu=cpu, ram=ram,
disk=old_flavor.disk,
disk_template=old_flavor.disk_template)
new_flavor = Flavor.objects.get(
cpu=cpu, ram=ram, disk=old_flavor.disk,
volume_type_id=old_flavor.volume_type_id)
except Flavor.DoesNotExist:
raise Exception("There is no flavor to match the instance specs!"
" Instance: %s CPU: %s RAM %s: Disk: %s Template: %s"
" Instance: %s CPU: %s RAM %s: Disk: %s VolumeType: %s"
% (vm.backend_vm_id, cpu, ram, old_flavor.disk,
old_flavor.disk_template))
old_flavor.volume_type_id))
log.info("Flavor of VM '%s' changed from '%s' to '%s'", vm,
old_flavor.name, new_flavor.name)
return new_flavor
......@@ -739,12 +739,12 @@ def create_instance(vm, nics, volumes, flavor, image):
kw['name'] = vm.backend_vm_id
# Defined in settings.GANETI_CREATEINSTANCE_KWARGS
kw['disk_template'] = volumes[0].template
kw['disk_template'] = volumes[0].volume_type.template
disks = []
for volume in volumes:
disk = {"name": volume.backend_volume_uuid,
"size": volume.size * 1024}
provider = volume.provider
provider = volume.volume_type.provider
if provider is not None:
disk["provider"] = provider
disk["origin"] = volume.origin
......@@ -1145,7 +1145,7 @@ def attach_volume(vm, volume, depends=[]):
disk = {"size": int(volume.size) << 10,
"name": volume.backend_volume_uuid}
disk_provider = volume.provider
disk_provider = volume.volume_type.provider
if disk_provider is not None:
disk["provider"] = disk_provider
......
......@@ -84,7 +84,7 @@ def get_available_backends(flavor):
excluded.
"""
disk_template = flavor.disk_template
disk_template = flavor.volume_type.disk_template
# Ganeti knows only the 'ext' disk template, but the flavors disk template
# includes the provider.
if disk_template.startswith("ext_"):
......@@ -108,7 +108,7 @@ def flavor_disk(flavor):
""" Get flavor's 'real' disk size
"""
if flavor.disk_template == 'drbd':
if flavor.volume_type.disk_template == 'drbd':
return flavor.disk * 1024 * 2
else:
return flavor.disk * 1024
......
......@@ -19,11 +19,27 @@ from optparse import make_option
from django.core.management.base import CommandError
from snf_django.management.commands import SynnefoCommand
from synnefo.db.models import Flavor
from synnefo.db.models import Flavor, VolumeType
HELP_MSG = """Create one or more flavors.
Create one or more flavors (virtual hardware templates) that define the
compute, memory and storage capacity of virtual servers. The flavors that will
be created are those belonging to the cartesian product of the arguments.
To create a flavor you must specify the following arguments:
* cpu: Number of virtual CPUs.
* ram: Size of virtual RAM (MB).
* disk: Size of virtual disk (GB).
* volume_type_id: ID of the volyme type defining the volume's disk
template.
"""
class Command(SynnefoCommand):
output_transaction = True
help = HELP_MSG
option_list = SynnefoCommand.option_list + (
make_option("-n", "--dry-run", dest="dry_run", action="store_true"),
......@@ -31,9 +47,7 @@ class Command(SynnefoCommand):
args = "<cpu>[,<cpu>,...] " \
"<ram>[,<ram>,...] " \
"<disk>[,<disk>,...] " \
"<disk template>[,<disk template>,...]"
help = "Create one or more flavors.\n\nThe flavors that will be created"\
" are those belonging to the cartesian product of the arguments"
"<volume_type_id>[,<volume_type_id>,...]"
def handle(self, *args, **options):
if len(args) != 4:
......@@ -42,24 +56,35 @@ class Command(SynnefoCommand):
cpus = args[0].split(',')
rams = args[1].split(',')
disks = args[2].split(',')
templates = args[3].split(',')
volume_types = []
volume_type_ids = args[3].split(',')
for vol_t_id in volume_type_ids:
try:
volume_types.append(VolumeType.objects.get(id=vol_t_id,
deleted=False))
except VolumeType.DoesNotExist:
raise CommandError("Volume type with ID '%s' does not exist."
" Use 'snf-manage volume-type-list' to find"
" out available volume types." % vol_t_id)
flavors = []
for cpu, ram, disk, template in product(cpus, rams, disks, templates):
for cpu, ram, disk, volume_type in product(cpus, rams, disks,
volume_types):
try:
flavors.append((int(cpu), int(ram), int(disk), template))
flavors.append((int(cpu), int(ram), int(disk), volume_type))
except ValueError:
raise CommandError("Invalid values")
for cpu, ram, disk, template in flavors:
for cpu, ram, disk, volume_type in flavors:
if options["dry_run"]:
flavor = Flavor(cpu=cpu, ram=ram, disk=disk,
disk_template=template)
volume_type=volume_type)
self.stdout.write("Creating flavor '%s'\n" % (flavor.name,))
else:
flavor, created = \
Flavor.objects.get_or_create(cpu=cpu, ram=ram, disk=disk,
disk_template=template)
volume_type=volume_type)
if created:
self.stdout.write("Created flavor '%s'\n" % (flavor.name,))
else:
......
......@@ -22,6 +22,7 @@ class Command(ListCommand):
object_class = Flavor
deleted_field = "deleted"
select_related = ("volume_type", )
def get_vms(flavor):
return VirtualMachine.objects.filter(flavor=flavor, deleted=False)\
......@@ -33,11 +34,12 @@ class Command(ListCommand):
"cpu": ("cpu", "Number of CPUs"),
"ram": ("ram", "Size(MB) of RAM"),
"disk": ("disk", "Size(GB) of disk"),
"template": ("disk_template", "Disk template"),
"volume_type": ("volume_type_id", "Volume Type ID"),
"template": ("volume_type.disk_template", "Disk template"),
"allow_create": ("allow_create", "Whether servers can be created from"
" this flavor"),
"vms": (get_vms, "Number of active servers using this flavor")
}
fields = ["id", "name", "cpu", "ram", "disk", "template", "allow_create",
"vms"]
fields = ["id", "name", "cpu", "ram", "disk", "template", "volume_type",
"allow_create", "vms"]
......@@ -18,7 +18,7 @@ from optparse import make_option
from django.core.management.base import CommandError
from synnefo.management import common
from synnefo.db.models import VirtualMachine, Network, Flavor
from synnefo.db.models import VirtualMachine, Network, Flavor, VolumeType
from synnefo.logic.utils import id_from_network_name, id_from_instance_name
from synnefo.logic.backend import wait_for_job, connect_to_network
from snf_django.management.commands import SynnefoCommand
......@@ -172,7 +172,13 @@ def flavor_from_instance(instance, flavor, stream):
cpu = beparams['vcpus']
ram = beparams['memory']
return Flavor.objects.get_or_create(disk=disk, disk_template=disk_template,
try:
volume_type = VolumeType.objects.get(disk_template=disk_template)
except VolumeType.DoesNotExist:
raise CommandError("Cannot find volume type with '%s' disk template."
% disk_template)
return Flavor.objects.get_or_create(disk=disk,
volume_type=volume_type,
cpu=cpu, ram=ram)
......
......@@ -251,7 +251,7 @@ class BackendReconciler(object):
ram=gnt_flavor["ram"],
cpu=gnt_flavor["vcpus"],
disk=db_flavor.disk,
disk_template=db_flavor.disk_template)
volume_type_id=db_flavor.volume_type_id)
except Flavor.DoesNotExist:
self.log.warning("Server '%s' has unknown flavor.", server_id)
return
......
......@@ -38,10 +38,10 @@ def attach_volume(vm, volume):
" '%s' status." % volume.status)
# Check that disk templates are the same
if volume.disk_template != vm.flavor.disk_template:
msg = ("Volume and server must have the same disk template. Volume has"
" disk template '%s' while server has '%s'"
% (volume.disk_template, vm.flavor.disk_template))
if volume.volume_type_id != vm.flavor.volume_type_id:
msg = ("Volume and server must have the same volume type. Volume has"
" volume type '%s' while server has '%s'"
% (volume.volume_type_id, vm.flavor.volume_type_id))
raise faults.BadRequest(msg)
# Check maximum disk per instance hard limit
......
......@@ -164,7 +164,7 @@ def create_server(vm, nics, volumes, flavor, image, personality, password):
# the volume with data
image_id = image["backend_id"]
root_volume = volumes[0]
if root_volume.provider is not None:
if root_volume.volume_type.provider is not None:
image_id = "null"
server_created.send(sender=vm, created_vm_params={
......@@ -247,9 +247,9 @@ def _resize(vm, flavor):
% (vm, flavor))
# Check that resize can be performed
if old_flavor.disk != flavor.disk:
raise faults.BadRequest("Cannot resize instance disk.")
if old_flavor.disk_template != flavor.disk_template:
raise faults.BadRequest("Cannot change instance disk template.")
raise faults.BadRequest("Cannot change instance's disk size.")
if old_flavor.volume_type_id != flavor.volume_type_id:
raise faults.BadRequest("Cannot change instance's volume type.")
log.info("Resizing VM from flavor '%s' to '%s", old_flavor, flavor)
return backend.resize_instance(vm, vcpus=flavor.cpu, memory=flavor.ram)
......
......@@ -271,11 +271,13 @@ class UpdateDBTest(TestCase):
db_vm = VirtualMachine.objects.get(id=vm.id)
self.assertEqual(db_vm.operstate, "STOPPED")
# Test success
f1 = mfactory.FlavorFactory(cpu=4, ram=1024, disk_template="drbd",
f1 = mfactory.FlavorFactory(cpu=4, ram=1024,
volume_type__disk_template="drbd",
disk=1024)
vm.flavor = f1
vm.save()
f2 = mfactory.FlavorFactory(cpu=8, ram=2048, disk_template="drbd",
f2 = mfactory.FlavorFactory(cpu=8, ram=2048,
volume_type__disk_template="drbd",
disk=1024)
beparams = {"vcpus": 8, "minmem": 2048, "maxmem": 2048}
msg = self.create_msg(operation='OP_INSTANCE_SET_PARAMS',
......
......@@ -147,9 +147,9 @@ class ServerReconciliationTest(TestCase):
def test_unsynced_flavor(self, mrapi):
flavor1 = mfactory.FlavorFactory(cpu=2, ram=1024, disk=1,
disk_template="drbd")
volume_type__disk_template="drbd")
flavor2 = mfactory.FlavorFactory(cpu=4, ram=2048, disk=1,
disk_template="drbd")
volume_type__disk_template="drbd")
vm1 = mfactory.VirtualMachineFactory(backend=self.backend,
deleted=False,
flavor=flavor1,
......
......@@ -80,8 +80,9 @@ class ServerCreationTest(TransactionTestCase):
# test ext settings:
req = deepcopy(kwargs)
ext_flavor = mfactory.FlavorFactory(disk_template="ext_archipelago",
disk=1)
ext_flavor = mfactory.FlavorFactory(
volume_type__disk_template="ext_archipelago",
disk=1)
req["flavor"] = ext_flavor
mrapi().CreateInstance.return_value = 42
backend.disk_templates = ["ext"]
......
......@@ -322,8 +322,10 @@ def pprint_server_volumes(server, stdout=None, title=None):
vols = []
for vol in server.volumes.filter(deleted=False):
vols.append((vol.id, vol.name, vol.index, vol.size, vol.template,
vol.provider, vol.status, vol.source))
volume_type = vol.volume_type
vols.append((vol.id, vol.name, vol.index, vol.size,
volume_type.template, volume_type.provider,
vol.status, vol.source))
headers = ["ID", "Name", "Index", "Size", "Template", "Provider",
"Status", "Source"]
......@@ -400,11 +402,12 @@ def pprint_volume(volume, display_mails=False, stdout=None, title=None):
ucache = UserCache(ASTAKOS_AUTH_URL, ASTAKOS_TOKEN)
userid = volume.userid
volume_type = volume.volume_type
volume_dict = OrderedDict([
("id", volume.id),
("size", volume.size),
("disk_template", volume.template),
("disk_provider", volume.provider),
("disk_template", volume_type.template),
("disk_provider", volume_type.provider),
("server_id", volume.machine_id),
("userid", volume.userid),
("username", ucache.get_name(userid) if display_mails else None),
......
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