Commit eb627644 by Chif Gergő

Merge branch 'instance-fixes' into 'DEV'

Instance fixes

See merge request !22
parents 70bd9c63 720ab1e4
Pipeline #1095 passed with stage
in 1 minute 31 seconds
......@@ -9,6 +9,7 @@ flake8 = "*"
django-rest-swagger = "*"
coverage = "*"
django-nose = "*"
v = {editable = true,version = "*"}
[packages]
django = "*"
......
# Generated by Django 2.2.1 on 2019-05-13 12:02
# Generated by Django 3.0.4 on 2020-04-06 12:09
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
......@@ -8,6 +10,7 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
......@@ -16,7 +19,19 @@ class Migration(migrations.Migration):
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(blank=True, help_text='Name of the disk', max_length=100, verbose_name='name')),
('remote_ID', models.CharField(help_text='ID, which helps access the disk', max_length=40, unique=True, verbose_name='remote_ID')),
('remote_id', models.CharField(help_text='ID, which helps access the disk', max_length=40, unique=True, verbose_name='remote_ID')),
],
),
migrations.CreateModel(
name='Image',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(help_text='Human readable name of image.', max_length=100, verbose_name='name')),
('description', models.TextField(blank=True, help_text='Description of the image.', verbose_name='description')),
('remote_id', models.CharField(help_text='ID, which helps access the image.', max_length=40, unique=True, verbose_name='remote_ID')),
('created_at', models.DateTimeField(auto_now_add=True, help_text='Date, when the image created.')),
('uploaded_by_user', models.BooleanField(default=True, editable=False, help_text='The field is false if the image created from instance')),
('created_by', models.ForeignKey(help_text='The user, who create the image', on_delete=django.db.models.deletion.DO_NOTHING, related_name='created_images', to=settings.AUTH_USER_MODEL)),
],
),
]
# Generated by Django 2.2.3 on 2019-07-15 13:54
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('image', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Image',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(help_text='Human readable name of image.', max_length=100, verbose_name='name')),
('description', models.TextField(blank=True, help_text='Description of the image.', verbose_name='description')),
('remote_ID', models.CharField(help_text='ID, which helps access the image.', max_length=40, unique=True, verbose_name='remote_ID')),
('created_at', models.DateTimeField(auto_now_add=True, help_text='Date, when the image created.')),
],
),
]
# Generated by Django 2.2.3 on 2019-07-16 11:51
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('image', '0002_image'),
]
operations = [
migrations.RenameField(
model_name='disk',
old_name='remote_ID',
new_name='remote_id',
),
migrations.RenameField(
model_name='image',
old_name='remote_ID',
new_name='remote_id',
),
migrations.AddField(
model_name='image',
name='created_by',
field=models.ForeignKey(default=0, help_text='The user, who create the image', on_delete=django.db.models.deletion.DO_NOTHING, related_name='created_images', to=settings.AUTH_USER_MODEL),
preserve_default=False,
),
migrations.AddField(
model_name='image',
name='uploaded_by_user',
field=models.BooleanField(default=True, editable=False, help_text='The field is false if the image created from instance'),
),
]
from django.test import TestCase
"""
from django.contrib.auth.models import User
from unittest.mock import Mock
from unittest.mock import patch
......@@ -11,10 +12,16 @@ from instance.models import Lease
from interface_openstack.interface.image.image import Image as RemoteImage
from interface_openstack.interface.vm.resources import Flavor as RemoteFlavor
from template.models import ImageTemplate
"""
class ImageModelsTest(TestCase):
@staticmethod
# Need review and rewrite because multiple changes in other modules
# (Btw unit tests should not depend on other modules)
pass
""" @staticmethod
def create_user(name="test_user", email="test_email", password="test_password"):
return User.objects.create_user(username=name, email=email, password=password)
......@@ -29,11 +36,11 @@ class ImageModelsTest(TestCase):
def create_instance(self):
params = {"name": "test_instance",
"description": "test instance description",
"access_method": "ssh",
"system": "test_system",
"access_protocol": "SSH",
"system": "LINUX",
"distro": "test_distro"
"time_of_suspend": "2019-05-28 23:59",
"time_of_delete": "2019-05-28 23:59",
}
flavor = self.create_flavor()
lease = Lease.objects.create(name="test_lease", description="test lease description",
......@@ -91,3 +98,4 @@ class ImageModelsTest(TestCase):
self.assertEqual(image.created_by_id, user.id)
self.assertEqual(image.uploaded_by_user, True)
self.assertEqual(image.description, description)
"""
# Generated by Django 2.2 on 2019-04-17 14:01
# Generated by Django 3.0.4 on 2020-04-06 12:09
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
......@@ -8,15 +10,67 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('image', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Instance',
name='Flavor',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)),
('description', models.CharField(blank=True, max_length=200)),
('remote_id', models.CharField(help_text='ID of the instance on the backend', max_length=100)),
('ram', models.PositiveIntegerField(default=0)),
('vcpu', models.PositiveIntegerField(default=0)),
('initial_disk', models.PositiveIntegerField(default=0)),
('priority', models.PositiveIntegerField(blank=True, null=True)),
],
),
migrations.CreateModel(
name='Lease',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)),
('description', models.CharField(blank=True, max_length=100)),
('suspend_interval_in_sec', models.IntegerField(default=3600)),
('delete_interval_in_sec', models.IntegerField(default=7200)),
],
),
migrations.CreateModel(
name='BaseMachineDescriptor',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(blank=True, help_text='Human readable name of instance.', max_length=100, verbose_name='name')),
('description', models.TextField(blank=True, help_text='The description of the instance.', verbose_name='description')),
('name', models.CharField(help_text='Human readable name of template.', max_length=100, verbose_name='name')),
('description', models.TextField(blank=True, help_text='Description of the template.', verbose_name='description')),
('system_type', models.CharField(choices=[('LINUX', 'The system based on Linux'), ('WINDOWS', 'Windows based system')], help_text='The base name of the operating system', max_length=50, verbose_name='operating_system')),
('distro', models.CharField(blank=True, help_text='The specific name and version of the installed OSe. g. Win 7, Ubuntu 18.04', max_length=100, verbose_name='system distribution')),
('access_protocol', models.CharField(choices=[('rdp', 'Remote Desktop Protocol'), ('ssh', 'Secure Shell')], help_text='The protocol which used to connect to the machinethat created from this template', max_length=50, verbose_name='remote_access_protocol')),
('created_at', models.DateTimeField(auto_now_add=True, help_text='Date, when the template created.')),
('network_id', models.CharField(blank=True, help_text='The new instance will be in this network.', max_length=100, null=True, verbose_name='network_id')),
('created_by', models.ForeignKey(help_text='The user, who create the template', on_delete=django.db.models.deletion.DO_NOTHING, related_name='created_templates', to=settings.AUTH_USER_MODEL)),
('flavor', models.ForeignKey(help_text='Resources given to the vm', on_delete=django.db.models.deletion.CASCADE, related_name='templates', to='instance.Flavor', verbose_name='flavor')),
('lease', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='templates', to='instance.Lease')),
],
),
migrations.CreateModel(
name='Instance',
fields=[
('basemachinedescriptor_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='instance.BaseMachineDescriptor')),
('remote_id', models.CharField(help_text='ID of the instance on the backend', max_length=100)),
('password', models.CharField(help_text='Original password of the instance', max_length=50)),
('time_of_suspend', models.DateTimeField(blank=True, help_text='After this point in time, the instance will be suspended')),
('time_of_delete', models.DateTimeField(blank=True, help_text='After this point in time, the instance will be deleted!')),
('status', models.CharField(default='NO_STATE', max_length=50, verbose_name='instance_status')),
('deleted', models.BooleanField(default=False, help_text='Indicates if the instance is ready for garbage collection')),
('disks', models.ManyToManyField(help_text='Disks attached to instance', related_name='instance', to='image.Disk', verbose_name='disks')),
('owner', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'permissions': (('create_instance', 'Can create a new VM.'), ('create_template_from_instance', 'Can create template from instance.'), ('use_instance', 'Can access the VM connection info.'), ('operate_instance', 'Can use basic lifecycle methods of the VM.'), ('administer_instance', 'Can delete VM.'), ('access_console', 'Can access the graphical console of a VM.'), ('change_resources', 'Can change resources of a VM.'), ('manage_access', 'Can manage access rights for the VM.'), ('config_ports', 'Can configure port forwards.')),
'default_permissions': (),
},
bases=('instance.basemachinedescriptor',),
),
]
# Generated by Django 2.2 on 2019-05-02 13:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('instance', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='instance',
name='access_method',
field=models.CharField(choices=[('rdp', 'Remote Desktop Protocol'), ('ssh', 'Secure Shell')], default='rdp', help_text='Primary remote access method.', max_length=10),
preserve_default=False,
),
migrations.AddField(
model_name='instance',
name='lease',
field=models.CharField(choices=[('project', 'Project'), ('server', 'Server'), ('infinite', 'Infinite lease')], default='project', help_text='Expiration method', max_length=50),
preserve_default=False,
),
migrations.AddField(
model_name='instance',
name='password',
field=models.CharField(default=12345678, help_text='Original password of the instance.', max_length=50),
preserve_default=False,
),
migrations.AddField(
model_name='instance',
name='system',
field=models.CharField(default='windows', help_text='Operating system type', max_length=50),
preserve_default=False,
),
migrations.AlterField(
model_name='instance',
name='description',
field=models.TextField(blank=True, help_text='The description of the instance'),
),
migrations.AlterField(
model_name='instance',
name='name',
field=models.CharField(help_text='Human readable name of instance', max_length=100),
),
]
# Generated by Django 2.2.4 on 2019-08-08 10:41
# Generated by Django 3.0.4 on 2020-04-06 12:09
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('template', '0007_basetemplate_network_id'),
('instance', '0009_auto_20190715_1354'),
('template', '0001_initial'),
('instance', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='instance',
name='template',
field=models.ForeignKey(help_text='The base image of the vm', null=True, on_delete='DO_NOTHING', related_name='vm', to='template.ImageTemplate'),
field=models.ForeignKey(help_text='The base image of the vm', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='vm', to='template.ImageTemplate'),
),
]
# Generated by Django 2.2.1 on 2019-05-10 13:07
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('instance', '0002_auto_20190502_1341'),
]
operations = [
migrations.AddField(
model_name='instance',
name='deleted',
field=models.BooleanField(default=False, help_text='Indicates if the instance is ready for garbage collection'),
),
migrations.AddField(
model_name='instance',
name='time_of_delete',
field=models.DateTimeField(default=django.utils.timezone.now, help_text='After this point in time, the instance will be deleted!'),
preserve_default=False,
),
migrations.AddField(
model_name='instance',
name='time_of_suspend',
field=models.DateTimeField(default=django.utils.timezone.now, help_text='After this point in time, the instance will be suspended'),
preserve_default=False,
),
migrations.AlterField(
model_name='instance',
name='access_method',
field=models.CharField(choices=[('rdp', 'Remote Desktop Protocol'), ('ssh', 'Secure Shell')], help_text='Primary remote access method', max_length=10),
),
migrations.AlterField(
model_name='instance',
name='lease',
field=models.CharField(choices=[('shortlab', 'Short laboratory'), ('lab', 'Laboratory'), ('project', 'Project')], help_text='Expiration method', max_length=50),
),
migrations.AlterField(
model_name='instance',
name='password',
field=models.CharField(help_text='Original password of the instance', max_length=50),
),
]
# Generated by Django 2.2.1 on 2019-07-04 12:56
# Generated by Django 3.0.4 on 2020-04-08 13:03
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('instance', '0006_auto_20190704_1154'),
('image', '0001_initial'),
('instance', '0002_instance_template'),
]
operations = [
migrations.AlterField(
migrations.RemoveField(
model_name='instance',
name='owner',
field=models.ForeignKey(null=True, on_delete='CASCADE', to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='instance',
name='disks',
field=models.ManyToManyField(blank=True, help_text='Disks attached to instance', related_name='instance', to='image.Disk', verbose_name='disks'),
),
]
# Generated by Django 2.2.1 on 2019-05-13 12:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('instance', '0003_auto_20190510_1307'),
]
operations = [
migrations.AddField(
model_name='instance',
name='remoteId',
field=models.CharField(default=0, help_text='ID of the instance on the backend', max_length=100),
preserve_default=False,
),
]
# Generated by Django 2.2.1 on 2019-05-13 12:03
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('instance', '0004_instance_remoteid'),
]
operations = [
migrations.RenameField(
model_name='instance',
old_name='remoteId',
new_name='remote_id',
),
]
# Generated by Django 2.2.1 on 2019-07-04 11:54
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('image', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('instance', '0005_auto_20190513_1203'),
]
operations = [
migrations.CreateModel(
name='Flavor',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(blank=True, max_length=100)),
('description', models.CharField(blank=True, max_length=200)),
('remote_id', models.CharField(help_text='ID of the instance on the backend', max_length=100)),
('ram', models.IntegerField(blank=True, null=True)),
('vcpu', models.IntegerField(blank=True, null=True)),
('initial_disk', models.IntegerField(blank=True, null=True)),
('priority', models.IntegerField(blank=True, null=True)),
],
),
migrations.CreateModel(
name='Lease',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(blank=True, max_length=100)),
('description', models.CharField(blank=True, max_length=100)),
('suspend_interval_in_sec', models.IntegerField(blank=True, null=True)),
('delete_interval_in_sec', models.IntegerField(blank=True, null=True)),
],
),
migrations.AddField(
model_name='instance',
name='disks',
field=models.ManyToManyField(help_text='Disks attached to instance', related_name='instance', to='image.Disk', verbose_name='disks'),
),
migrations.AddField(
model_name='instance',
name='owner',
field=models.ForeignKey(default=None, on_delete='CASCADE', to=settings.AUTH_USER_MODEL),
preserve_default=False,
),
migrations.AlterField(
model_name='instance',
name='lease',
field=models.ForeignKey(on_delete='CASCADE', to='instance.Lease'),
),
migrations.AddField(
model_name='instance',
name='flavor',
field=models.ForeignKey(default=None, help_text='Reasources given to the vm', on_delete='CASCADE', to='instance.Flavor', verbose_name='flavor'),
preserve_default=False,
),
]
# Generated by Django 2.2.1 on 2019-07-04 13:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('instance', '0007_auto_20190704_1256'),
]
operations = [
migrations.AlterField(
model_name='instance',
name='time_of_delete',
field=models.DateTimeField(blank=True, help_text='After this point in time, the instance will be deleted!'),
),
migrations.AlterField(
model_name='instance',
name='time_of_suspend',
field=models.DateTimeField(blank=True, help_text='After this point in time, the instance will be suspended'),
),
]
# Generated by Django 2.2.3 on 2019-07-15 13:54
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('instance', '0008_auto_20190704_1310'),
]
operations = [
migrations.AlterField(
model_name='instance',
name='flavor',
field=models.ForeignKey(help_text='Reasources given to the vm', on_delete='CASCADE', related_name='instances', to='instance.Flavor', verbose_name='flavor'),
),
migrations.AlterField(
model_name='instance',
name='lease',
field=models.ForeignKey(on_delete='CASCADE', related_name='instances', to='instance.Lease'),
),
]
# Generated by Django 2.2.4 on 2019-08-08 11:37
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('instance', '0010_instance_template'),
]
operations = [
migrations.AlterModelOptions(
name='instance',
options={'permissions': (('create_instance', 'Can create a new VM.'), ('use_instance', 'Can access the VM connection info.'), ('operate_instance', 'Can use basic lifecycle methods of the VM.'), ('administer_instance', 'Can delete VM.'), ('access_console', 'Can access the graphical console of a VM.'), ('change_resources', 'Can change resources of a VM.'), ('manage_access', 'Can manage access rights for the VM.'), ('config_ports', 'Can configure port forwards.'))},
),
]
# Generated by Django 2.2.4 on 2019-08-29 07:54
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('instance', '0011_auto_20190808_1137'),
]
operations = [
migrations.AlterModelOptions(
name='instance',
options={'default_permissions': (), 'permissions': (('create_instance', 'Can create a new VM.'), ('use_instance', 'Can access the VM connection info.'), ('operate_instance', 'Can use basic lifecycle methods of the VM.'), ('administer_instance', 'Can delete VM.'), ('access_console', 'Can access the graphical console of a VM.'), ('change_resources', 'Can change resources of a VM.'), ('manage_access', 'Can manage access rights for the VM.'), ('config_ports', 'Can configure port forwards.'))},
),
]
# Generated by Django 2.2.4 on 2019-08-30 12:27
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('instance', '0012_auto_20190829_0754'),
]
operations = [
migrations.AlterModelOptions(
name='instance',
options={'default_permissions': (), 'permissions': (('create_instance', 'Can create a new VM.'), ('create_template_from_instance', 'Can create template from instance.'), ('use_instance', 'Can access the VM connection info.'), ('operate_instance', 'Can use basic lifecycle methods of the VM.'), ('administer_instance', 'Can delete VM.'), ('access_console', 'Can access the graphical console of a VM.'), ('change_resources', 'Can change resources of a VM.'), ('manage_access', 'Can manage access rights for the VM.'), ('config_ports', 'Can configure port forwards.'))},
),
]
......@@ -11,10 +11,6 @@ import logging
logger = logging.getLogger(__name__)
ACCESS_METHODS = tuple(
[(key, val[0]) for key, val in settings.VM_ACCESS_PROTOCOLS.items()]
)
interface = OSVirtualMachineManager(settings.CONNECTION)
ACTIONS = {
......@@ -32,25 +28,25 @@ class Lease(models.Model):
After suspend interval the vm suspends and after delete it will be
destroyed
"""
name = models.CharField(blank=True, max_length=100)
name = models.CharField(max_length=100)
description = models.CharField(blank=True, max_length=100)
suspend_interval_in_sec = models.IntegerField(blank=True, null=True)
delete_interval_in_sec = models.IntegerField(blank=True, null=True)
suspend_interval_in_sec = models.IntegerField(default=3600)
delete_interval_in_sec = models.IntegerField(default=7200)
class Flavor(models.Model):
""" Flavors are reasource packages, contains all information about
resources accociated with the virtual machine
"""
name = models.CharField(blank=True, max_length=100)
name = models.CharField(blank=False, max_length=100)
description = models.CharField(blank=True, max_length=200)
remote_id = models.CharField(
max_length=100, help_text="ID of the instance on the backend"
)
ram = models.IntegerField(blank=True, null=True)
vcpu = models.IntegerField(blank=True, null=True)
initial_disk = models.IntegerField(blank=True, null=True)
priority = models.IntegerField(blank=True, null=True)
ram = models.PositiveIntegerField(default=0)
vcpu = models.PositiveIntegerField(default=0)
initial_disk = models.PositiveIntegerField(default=0)
priority = models.PositiveIntegerField(blank=True, null=True)
@classmethod
def create(cls, name, description, ram=0, vcpu=0,
......@@ -68,7 +64,6 @@ class Flavor(models.Model):
raise ValueError("Can't create Flavor in remote cloud.")
def delete(self):
interface = OSVirtualMachineManager(settings.CONNECTION)
try:
interface.delete_flavor(self.remote_id)
super(Flavor, self).delete()
......@@ -77,7 +72,63 @@ class Flavor(models.Model):
logger.error("Can not delete the flavor in remote cloud")
class Instance(models.Model):
class BaseMachineDescriptor(models.Model):
"""Virtual machine template.
"""
OSTypes = (
('LINUX', 'The system based on Linux'),
('WINDOWS', 'Windows based system'),
)
CONN_PROTOCOL = tuple(
[(key, val[0]) for key, val in settings.VM_ACCESS_PROTOCOLS.items()]
)
name = models.CharField(max_length=100,
verbose_name="name",
help_text="Human readable name of template.")
description = models.TextField(verbose_name="description",
blank=True,
help_text="Description of the template.")
system_type = models.CharField(max_length=50,
choices=OSTypes,
verbose_name="operating_system",
help_text="The base name of the operating system"
)
distro = models.CharField(max_length=100,
blank=True,
verbose_name='system distribution',
help_text="The specific name and version of the installed OS"
"e. g. Win 7, Ubuntu 18.04"
)
access_protocol = models.CharField(max_length=50,
choices=CONN_PROTOCOL,
verbose_name="remote_access_protocol",
help_text="The protocol which used to connect to the machine"
"that created from this template"
)
created_at = models.DateTimeField(auto_now_add=True,
editable=False,
help_text="Date, when the template created.")
created_by = models.ForeignKey(User,
on_delete=models.DO_NOTHING,
related_name="created_templates",
help_text="The user, who create the template")
flavor = models.ForeignKey(Flavor,
help_text="Resources given to the vm",
verbose_name="flavor",
on_delete=models.CASCADE,
related_name='templates')
lease = models.ForeignKey(Lease,
on_delete=models.CASCADE,
related_name='templates')
network_id = models.CharField(max_length=100,
verbose_name="network_id",
help_text="The new instance will be in this network.",
null=True,
blank=True)
class Instance(BaseMachineDescriptor):
"""Virtual machine instance.
"""
from template.models import ImageTemplate
......@@ -95,21 +146,9 @@ class Instance(models.Model):
('manage_access', 'Can manage access rights for the VM.'),
('config_ports', 'Can configure port forwards.'),
)
name = models.CharField(max_length=100,
help_text="Human readable name of instance")
remote_id = models.CharField(
max_length=100, help_text="ID of the instance on the backend"
)
description = models.TextField(
blank=True, help_text="The description of the instance"
)
access_method = models.CharField(
max_length=10, choices=ACCESS_METHODS,
help_text="Primary remote access method"
)
system = models.CharField(max_length=50, help_text="Operating system type")
password = models.CharField(
max_length=50, help_text="Original password of the instance"
)
......@@ -121,6 +160,7 @@ class Instance(models.Model):
blank=True,
help_text="After this point in time, the instance will be deleted!"
)
status = models.CharField(max_length=50, verbose_name="instance_status", default="NO_STATE")
deleted = models.BooleanField(
help_text="Indicates if the instance is ready for garbage collection",
default=False,
......@@ -128,24 +168,20 @@ class Instance(models.Model):
template = models.ForeignKey(ImageTemplate, related_name="vm", null=True,
help_text="The base image of the vm",
on_delete="DO_NOTHING")
on_delete=models.DO_NOTHING)
disks = models.ManyToManyField(Disk, related_name="instance",
blank=True,
help_text="Disks attached to instance",
verbose_name="disks")
owner = models.ForeignKey(User, on_delete="CASCADE", null=True)
flavor = models.ForeignKey(Flavor, help_text="Reasources given to the vm",
verbose_name="flavor", on_delete="CASCADE",
related_name='instances')
lease = models.ForeignKey(Lease, on_delete="CASCADE",
related_name='instances')
@classmethod
def create(cls, lease, owner, flavor, template, remote_id, params):
params["password"] = cls.generate_password()
inst = cls(lease=lease, flavor=flavor, owner=owner,
remote_id=remote_id, template=template, **params)
inst = cls(lease=lease, flavor=flavor, created_by=owner, system_type=template.system_type,
distro=template.distro, access_protocol=template.access_protocol,
remote_id=remote_id, template=template, status="CREATING",
**params)
inst.full_clean()
inst.save()
......@@ -155,8 +191,7 @@ class Instance(models.Model):
@classmethod
def create_instance_from_template(cls, params, template, owner, lease,
disks, networks, flavor):
# TODO: attach disks when the remote instance created
networks, flavor):
try:
remote_id = interface.create_vm_from_template(params["name"],
template.image.remote_id,
......@@ -214,12 +249,21 @@ class Instance(models.Model):
def get_remote_instance(self):
return interface.get_vm(self.remote_id)
def update_status(self):
remote = self.get_remote_instance()
self.status = remote.status
self.save()
@classmethod
def generate_password(self):
return User.objects.make_random_password(
allowed_chars='abcdefghijklmnopqrstuvwx'
'ABCDEFGHIJKLMNOPQRSTUVWX123456789')
def reset_password(self):
self.password = self.generate_password()
self.save()
def change_name(self, new_name):
self.name = new_name
self.save()
......@@ -227,3 +271,7 @@ class Instance(models.Model):
def change_description(self, new_description):
self.description = new_description
self.save()
def destroy(self):
self.deleted = True
self.save()
......@@ -2,26 +2,6 @@ from rest_framework import serializers
from .models import Flavor, Instance, Lease
class InstanceSerializer(serializers.ModelSerializer):
lease = serializers.PrimaryKeyRelatedField(read_only=True)
flavor = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Instance
fields = (
"id",
"name",
"description",
"system",
"lease",
"flavor",
"password",
"template",
"time_of_suspend",
"time_of_delete")
read_only_fields = ("id", "password", "template", "time_of_suspend", "time_of_delete")
class FlavorSerializer(serializers.ModelSerializer):
class Meta:
model = Flavor
......@@ -31,4 +11,23 @@ class FlavorSerializer(serializers.ModelSerializer):
class LeaseSerializer(serializers.ModelSerializer):
class Meta:
model = Lease
fields = '__all__'
fields = "__all__"
class InstanceListItemSerializer(serializers.ModelSerializer):
lease = LeaseSerializer(read_only=True)
class Meta:
model = Instance
fields = ["id", "name", "status", "system_type", "distro", "status", "lease"]
class InstanceSerializer(serializers.ModelSerializer):
lease = serializers.PrimaryKeyRelatedField(read_only=True)
flavor = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Instance
fields = "__all__"
read_only_fields = ("id", "status" "password", "template",
"time_of_suspend", "time_of_delete")
from instance.serializers import InstanceSerializer, FlavorSerializer, LeaseSerializer
from instance.serializers import (InstanceSerializer, InstanceListItemSerializer, FlavorSerializer,
LeaseSerializer)
from django.http import Http404
from django.db.models import Q
from rest_framework.viewsets import ViewSet, ModelViewSet
from rest_framework.response import Response
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from template.serializers import InstanceFromTemplateSerializer
from instance.models import Instance, Flavor, Lease
from template.models import ImageTemplate
from template.serializers import ImageTemplateModelSerializer
from authorization.mixins import AuthorizationMixin
from guardian.shortcuts import get_perms_for_model, assign_perm
authorization = {
......@@ -49,6 +53,7 @@ update_actions = [
class InstanceViewSet(AuthorizationMixin, ViewSet):
authorization = authorization
permission_classes = [IsAuthenticated]
def get_object(self, pk):
try:
......@@ -56,104 +61,65 @@ class InstanceViewSet(AuthorizationMixin, ViewSet):
except Instance.DoesNotExist:
raise Http404
def get_merged_object(self, pk):
instance = self.get_object(pk)
instanceDict = InstanceSerializer(instance).data
remoteInstance = instance.get_remote_instance()
remoteInstanceDict = {
"remote_id": remoteInstance.id,
"status": remoteInstance.status,
"disks": remoteInstance.disks,
"addresses": remoteInstance.addresses,
}
return({**instanceDict, **remoteInstanceDict})
def list(self, request):
instances = self.get_objects_with_perms(request.user, "list", Instance)
return Response(InstanceSerializer(instances, many=True).data)
# instances = self.get_objects_with_perms(request.user, "list", Instance)
instances = Instance.objects.filter(Q(created_by=request.user) & Q(deleted=False))
return Response(InstanceListItemSerializer(instances, many=True).data)
def create(self, request):
if not self.has_perms_for_model(request.user, 'create'):
return Response({"error": "No permission to create Virtual Machine."},
status=status.HTTP_401_UNAUTHORIZED)
params = request.data
template = ImageTemplate.objects.get(pk=params["template"])
template = ImageTemplate.objects.get(Q(pk=params["template"]) &
(Q(created_by=request.user) | Q(type="SYSTEM")))
if not template:
return Response({"error": "No permission to use this template."},
status=status.HTTP_401_UNAUTHORIZED)
flavor = Flavor.objects.get(pk=params["flavor"]) if "flavor" in params else template.flavor
lease = Lease.objects.get(pk=params["lease"]) if "lease" in params else template.lease
newInstance = Instance.create_instance_from_template(
params={"name": params["name"],
"description": params["description"],
"access_method": params["access"],
"system": params["system"],
},
flavor=flavor,
lease=lease,
networks=[{"uuid": template.network_id}],
template=template,
flavor=flavor,
networks=[{"uuid": template.network_id}],
owner=request.user,
disks=None
)
return Response(InstanceSerializer(newInstance).data)
for perm in get_perms_for_model(Instance):
print(perm)
assign_perm(perm, request.user, newInstance)
return Response(InstanceSerializer(newInstance).data, status=status.HTTP_201_CREATED)
def retrieve(self, request, pk):
instance = self.get_object(pk)
if not self.has_perms_for_object(request.user, 'retrieve', instance):
return Response({"error": "No permission to access the Virtual Machine."},
status=status.HTTP_401_UNAUTHORIZED)
mergedInstance = self.get_merged_object(pk)
return Response(mergedInstance)
instance.update_status()
return Response(InstanceSerializer(instance).data)
# PUT method for updating the instance own properties BUT not the related fields
def update(self, request, pk, format=None):
if request.data["action"] in update_actions:
instance = self.get_object(pk)
if not self.has_perms_for_object(request.user, 'update', instance):
return Response({"error": "No permission to access the Virtual Machine."},
status=status.HTTP_401_UNAUTHORIZED)
action = request.data["action"]
if action == "change_name":
if "name" in request.data:
instance.change_name(request.data["name"])
elif action == "change_description":
if "description" in request.data:
instance.change_description(request.data["description"])
elif action == "renew":
instance.renew()
elif action == "change_lease":
lease = Lease.objects.get(pk=request.data["lease"])
instance.renew(lease)
elif action == "change_flavor":
pass
elif action == "attach_disk":
pass
elif action == "resize_disk":
pass
elif action == "add_permission":
pass
elif action == "remove_permission":
pass
elif action == "open_port":
pass
elif action == "close_port":
pass
elif action == "add_network":
pass
elif action == "remove_network":
pass
elif action == "new_password":
pass
return Response(InstanceSerializer(instance).data)
instanceDict = InstanceSerializer(instance).data
remoteInstance = instance.get_remote_instance()
remoteInstanceDict = remoteInstance.__dict__
merged_dict = {"db": instanceDict, "openstack": remoteInstanceDict}
return Response(merged_dict)
else:
return Response({"error": "Unknown update action."}, status=status.HTTP_400_BAD_REQUEST)
def destroy(self, request, pk, format=None):
def destroy(self, request, pk):
instance = self.get_object(pk)
if not self.has_perms_for_object(request.user, 'destroy', instance):
return Response({"error": "No permission to destroy the Virtual Machine."},
status=status.HTTP_401_UNAUTHORIZED)
instance.delete()
if not instance:
return Response(status=status.HTTP_204_NO_CONTENT)
instance.destroy()
return Response(status=status.HTTP_204_NO_CONTENT)
@action(detail=True, methods=["post"])
......@@ -184,6 +150,50 @@ class InstanceViewSet(AuthorizationMixin, ViewSet):
success = instance.execute_common_action(action=request.data["action"])
return Response(success)
@action(detail=True, methods=["POST"])
def renew_lease(self, request, pk):
instance = self.get_object(pk)
instance.renew()
return Response(status=status.HTTP_200_OK)
@action(detail=True, methods=["POST"])
def change_lease(self, request, pk):
instance = self.get_object(pk)
if "lease" in request.data:
lease = Lease.objects.get(pk=request.data["lease"])
instance.renew(lease)
return Response(status=status.HTTP_200_OK)
else:
return Response(data="No new lease provided", status=status.HTTP_400_BAD_REQUEST)
@action(detail=True, methods=["POST"])
def resize(self, request, pk):
pass
# TODO: resize not implemented in model
# instance = self.get_object(pk)
# if "flavor" in request.data:
# flavor = Flavor.objects.get(pk=request.data["flavor"])
# instance.resize(flavor)
# return Response(status=status.HTTP_200_OK)
# else:
# return Response(data="No new flavor provided", status=status.HTTP_400_BAD_REQUEST)
@action(detail=True, methods=["POST"])
def attach_disk(self, request, pk):
# TODO: attach not implemented in model
pass
@action(detail=True, methods=["POST"])
def detach_disk(self, request, pk):
# TODO: attach not implemented in model
pass
@action(detail=True, methods=["POST"])
def reset_password(self, request, pk):
instance = self.get_object(pk)
instance.reset_password()
return Response(status=status.HTTP_200_OK)
class FlavorViewSet(ViewSet):
"""
......@@ -202,12 +212,15 @@ class FlavorViewSet(ViewSet):
def create(self, request, format=None):
data = request.data
prior = 1
if "priority" in data:
prior = data["priority"]
new_flavor = Flavor.create(name=data["name"],
description=data["description"],
ram=data["ram"],
vcpu=data["vcpu"],
initial_disk=data["initial_disk"],
priority=data["priority"])
priority=prior)
return Response(new_flavor.pk)
def update(self, request, pk):
......
......@@ -143,8 +143,8 @@ STATIC_URL = "/static/"
# VM ACCESS PROTOCOLS
VM_ACCESS_PROTOCOLS = {
"rdp": ["Remote Desktop Protocol", 3389, "tcp"],
"ssh": ["Secure Shell", 22, "tcp"],
"RDP": ["Remote Desktop Protocol", 3389, "tcp"],
"SSH": ["Secure Shell", 22, "tcp"],
}
# LEASE TYPES
......
# Generated by Django 2.2.3 on 2019-07-03 12:09
# Generated by Django 3.0.4 on 2020-04-06 12:09
from django.db import migrations, models
import django.db.models.deletion
......@@ -9,26 +9,18 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('instance', '0001_initial'),
('image', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='BaseTemplate',
name='ImageTemplate',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(help_text='Human readable name of template.', max_length=100, verbose_name='name')),
('description', models.TextField(blank=True, help_text='Description of the template.', verbose_name='description')),
('remote_ID', models.CharField(help_text='ID, which helps access the template.', max_length=40, unique=True, verbose_name='remote_ID')),
('created_at', models.DateTimeField(auto_now_add=True, help_text='Date, when the template created.')),
('basemachinedescriptor_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='instance.BaseMachineDescriptor')),
('type', models.CharField(choices=[('SYSTEM', 'Common templates provided by the system.'), ('IMAGE', 'Template created from image by a user'), ('INSTANCE', 'Template created from instance by a user')], default='SYSTEM', max_length=10)),
('image', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='templates', to='image.Image')),
],
),
migrations.CreateModel(
name='Template',
fields=[
('basetemplate_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='template.BaseTemplate')),
('disk', models.ForeignKey(help_text='The disk where the template is located.', on_delete=django.db.models.deletion.CASCADE, related_name='templates', to='image.Disk')),
],
bases=('template.basetemplate',),
bases=('instance.basemachinedescriptor',),
),
]
# Generated by Django 2.2.3 on 2019-07-15 13:54
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('image', '0002_image'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('template', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='basetemplate',
name='created_by',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.DO_NOTHING, related_name='created_templates', to=settings.AUTH_USER_MODEL),
preserve_default=False,
),
migrations.CreateModel(
name='PureTemplate',
fields=[
('basetemplate_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='template.BaseTemplate')),
('images', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='templates', to='image.Image')),
],
bases=('template.basetemplate',),
),
]
# Generated by Django 2.2.3 on 2019-07-16 11:51
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('image', '0003_auto_20190716_1151'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('template', '0002_auto_20190715_1354'),
]
operations = [
migrations.RenameModel(
old_name='Template',
new_name='DiskTemplate',
),
migrations.RenameModel(
old_name='PureTemplate',
new_name='ImageTemplate',
),
migrations.RemoveField(
model_name='basetemplate',
name='remote_ID',
),
migrations.AlterField(
model_name='basetemplate',
name='created_by',
field=models.ForeignKey(help_text='The user, who create the template', on_delete=django.db.models.deletion.DO_NOTHING, related_name='created_templates', to=settings.AUTH_USER_MODEL),
),
]
# Generated by Django 2.2.3 on 2019-07-16 12:27
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('template', '0003_auto_20190716_1151'),
]
operations = [
migrations.RenameField(
model_name='imagetemplate',
old_name='images',
new_name='image',
),
]
# Generated by Django 2.2.3 on 2019-07-17 09:48
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('instance', '0009_auto_20190715_1354'),
('template', '0004_auto_20190716_1227'),
]
operations = [
migrations.AddField(
model_name='basetemplate',
name='flavor',
field=models.ForeignKey(default=1, help_text='Reasources given to the vm', on_delete='CASCADE', related_name='templates', to='instance.Flavor', verbose_name='flavor'),
preserve_default=False,
),
migrations.AddField(
model_name='basetemplate',
name='lease',
field=models.ForeignKey(default=1, on_delete='CASCADE', related_name='templates', to='instance.Lease'),
preserve_default=False,
),
migrations.AddField(
model_name='imagetemplate',
name='type',
field=models.CharField(choices=[('U', 'User create the template from image'), ('I', 'Template created from instance'), ('D', 'Default "Pure" template')], default='I', max_length=10),
preserve_default=False,
),
]
# Generated by Django 2.2.3 on 2019-07-19 14:16
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('template', '0005_auto_20190717_0948'),
]
operations = [
migrations.AlterField(
model_name='imagetemplate',
name='type',
field=models.CharField(choices=[('U', 'User create the template from image'), ('I', 'Template created from instance'), ('P', '"Pure" template')], default='U', max_length=10),
),
]
# Generated by Django 2.2.3 on 2019-08-07 12:19
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('template', '0006_auto_20190719_1416'),
]
operations = [
migrations.AddField(
model_name='basetemplate',
name='network_id',
field=models.CharField(blank=True, help_text='The new instance will be in this network.', max_length=100, null=True, verbose_name='network_id'),
),
]
from django.db import models
from django.contrib.auth.models import User
from django.conf import settings
from image.models import Image
from instance.models import BaseMachineDescriptor
from image.models import Disk, Image
from instance.models import Lease, Flavor
from interface_openstack.implementation.storage.openstack_snapshot_manager import SnapshotManager
class BaseTemplate(models.Model):
"""Virtual machine template.
"""
name = models.CharField(max_length=100,
verbose_name="name",
help_text="Human readable name of template.")
description = models.TextField(verbose_name="description",
blank=True,
help_text="Description of the template.")
created_at = models.DateTimeField(auto_now_add=True,
editable=False,
help_text="Date, when the template created.")
created_by = models.ForeignKey(User,
on_delete=models.DO_NOTHING,
related_name="created_templates",
help_text="The user, who create the template")
flavor = models.ForeignKey(Flavor,
help_text="Resources given to the vm",
verbose_name="flavor",
on_delete="CASCADE",
related_name='templates')
lease = models.ForeignKey(Lease,
on_delete="CASCADE",
related_name='templates')
network_id = models.CharField(max_length=100,
verbose_name="network_id",
help_text="The new instance will be in this network.",
null=True,
blank=True)
class DiskTemplate(BaseTemplate):
disk = models.ForeignKey(Disk,
related_name="templates",
on_delete=models.CASCADE,
help_text="The disk where the template is located.")
@classmethod
def create_from_volume(cls, name, description, disk, user):
interface = SnapshotManager(settings.CONNECTION)
remote_template = interface.create_from_volume(disk.remote_id)
remote_id = remote_template.id
new_template = DiskTemplate.objects.create(name=name,
description=description,
disk=disk,
remote_id=remote_id,
created_by=user)
return new_template
class ImageTemplate(BaseTemplate):
class ImageTemplate(BaseMachineDescriptor):
TYPES = (
('U', 'User create the template from image'),
('I', 'Template created from instance'),
('P', '"Pure" template'),
('SYSTEM', 'Common templates provided by the system.'),
('IMAGE', 'Template created from image by a user'),
('INSTANCE', 'Template created from instance by a user'),
)
image = models.ForeignKey(Image,
related_name="templates",
......@@ -70,7 +15,7 @@ class ImageTemplate(BaseTemplate):
help_text="")
type = models.CharField(max_length=10,
choices=TYPES,
default="U")
default="SYSTEM")
@classmethod
def create_from_instance(cls, name, description, instance, user):
......@@ -79,7 +24,10 @@ class ImageTemplate(BaseTemplate):
description=description,
created_by=user,
image=image,
system_type=instance.system_type,
distro=instance.distro,
access_protocol=instance.access_protocol,
lease=instance.lease,
flavor=instance.flavor,
type="I")
type="INSTANCE")
return new_template
from rest_framework import serializers
from template.models import ImageTemplate
from instance.serializers import FlavorSerializer, LeaseSerializer
class ImageTemplateListItemSerializer(serializers.ModelSerializer):
flavor = FlavorSerializer(read_only=True)
lease = LeaseSerializer(read_only=True)
class Meta:
model = ImageTemplate
fields = ["id", "name", "distro", "flavor", "lease", "system_type"]
read_only_fields = ("id", "name", "distro", "flavor", "lease", "system_type")
class InstanceFromTemplateSerializer(serializers.Serializer):
......@@ -11,17 +22,7 @@ class InstanceFromTemplateSerializer(serializers.Serializer):
class ImageTemplateModelSerializer(serializers.ModelSerializer):
class Meta:
model = ImageTemplate
fields = (
"id",
"name",
"description",
"created_at",
"created_by",
"image",
"flavor",
"lease",
"type",
)
fields = "__all__"
read_only_fields = (
"created_at",
"created_by",
......
......@@ -3,6 +3,6 @@ from rest_framework import routers
from template import views
router = routers.DefaultRouter()
router.register(r"templates/image-templates", views.ImageTemplateViewSet, basename="image-template")
router.register(r"templates", views.ImageTemplateViewSet, basename="image-template")
urlpatterns = router.urls
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from template.serializers import ImageTemplateModelSerializer
from template.serializers import ImageTemplateModelSerializer, ImageTemplateListItemSerializer
from template.models import ImageTemplate
from django.db.models import Q
from rest_framework.permissions import IsAuthenticated
class ImageTemplateViewSet(ModelViewSet):
serializer_class = ImageTemplateModelSerializer
queryset = ImageTemplate.objects.all()
permission_classes = [IsAuthenticated]
def create(self, request):
# TODO: Check the permissions to create template and using the flavors and leases
serializer = ImageTemplateModelSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
data = serializer.validated_data
new_template = ImageTemplate.create(
new_template = ImageTemplate(
name=data["name"],
description=data["description"],
access_protocol=data["access_protocol"],
system_type=data["system_type"],
distro=data["distro"],
created_by=request.user,
image=data["image"],
lease=data["lease"],
flavor=data["flavor"],
type="U"
type="IMAGE"
)
new_template.save()
serializer = ImageTemplateModelSerializer(instance=new_template)
return Response(serializer.data)
def update(self, request, *args, **kwargs):
# only the name, description, lease, flavor can be updated
allowed_keys = ["name", "description", "lease", "flavor"]
allowed_keys = ["name", "description", "access_prtotocol", "distro", "lease", "flavor"]
# delete not allowed key
for key in request.data.keys():
......@@ -35,3 +43,9 @@ class ImageTemplateViewSet(ModelViewSet):
request.data.pop(key, None)
return super(ImageTemplateViewSet, self).update(request, partial=True)
def list(self, request, *args, **kwargs):
# TODO: Filter for shared tempaltes
query = ImageTemplate.objects.filter(Q(created_by=request.user.id) | Q(type="SYSTEM"))
serializer = ImageTemplateListItemSerializer(query, many=True)
return Response(serializer.data)
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 sign in to comment