Commit d4935afb by Chif Gergő

Resolve conflicts

# Conflicts:
#   recircle/image/models.py
parents 3421dbd8 799c0577
Pipeline #902 passed with stage
in 1 minute 22 seconds
......@@ -17,6 +17,7 @@ django-cors-headers = "*"
openstacksdk = "*"
python-novaclient = "*"
keystoneauth1 = "*"
django-guardian = "*"
djoser = "*"
[requires]
......
{
"_meta": {
"hash": {
"sha256": "8cb2efb1cfa79de96297c90953faf757011ca1f1ff4784ac05226a249001e1f5"
"sha256": "dd7bfbb33d07cbcc96d1f3b1f838538dba41f3eb0bf5279381714b4b9abf90d7"
},
"pipfile-spec": 6,
"requires": {
......@@ -135,6 +135,14 @@
"index": "pypi",
"version": "==3.0.2"
},
"django-guardian": {
"hashes": [
"sha256:965d3a1e20fb3639e0ab16b0e768611f694c02d762916f80d9f3f7520c16aa7b",
"sha256:e8c4556c4e145028a5dcfd7b2611d52e1ac104af562017ce17c3f67e47a62693"
],
"index": "pypi",
"version": "==2.0.0"
},
"django-templated-mail": {
"hashes": [
"sha256:8db807effebb42a532622e2d142dfd453dafcd0d7794c4c3332acb90656315f9",
......
from django.contrib.auth.models import Permission
from django.contrib import admin
admin.site.register(Permission)
from django.apps import AppConfig
class AuthorizationConfig(AppConfig):
name = 'authorization'
from guardian.shortcuts import get_objects_for_user
import logging
logger = logging.getLogger(__name__)
class AuthorizationMixin():
authorization = {}
def get_objects_with_perms(self, user, method, instance):
auth_params = self.authorization[method]
if auth_params:
return get_objects_for_user(user, auth_params["filter"], instance)
else:
logger.error(f"Invalid method for authorization: {method}")
return False
def has_perms_for_object(self, user, method, instance):
auth_params = self.authorization[method]
if auth_params:
for perm in auth_params["object"]:
if not user.has_perm(perm, instance):
return False
return True
else:
logger.error(f"Invalid method for authorization: {method}")
return False
def has_perms_for_model(self, user, method):
auth_params = self.authorization[method]
if auth_params:
for perm in auth_params["model"]:
if not user.has_perm(perm):
return False
return True
else:
logger.error(f"Invalid method for authorization: {method}")
return False
from django.contrib import admin
from image.models import Image
from image.models import Disk
from image.models import Image, Disk
admin.site.register(Image)
admin.site.register(Disk)
......@@ -7,7 +7,6 @@ from interface_openstack.implementation.image.openstack_image_manager import Ope
class Disk(models.Model):
"""A virtual disk.
"""
......@@ -26,7 +25,6 @@ class Disk(models.Model):
class Image(models.Model):
"""A virtual image.
"""
......@@ -79,16 +77,12 @@ class Image(models.Model):
remote_image = interface.create_image(instance.remote_id)
remote_id = remote_image.id
name = remote_image.name
new_image = cls.create(
name=name,
remote_id=remote_id,
created_by=user,
uploaded_by_user=False,
description=description
)
for attr, value in new_image.__dict__.items():
setattr(remote_image, attr, value)
return remote_image
new_image = Image.objects.create(name=name,
remote_id=remote_id,
created_by=user,
uploaded_by_user=False,
description=description)
return new_image
@classmethod
def create_from_user(cls, description,
......@@ -97,18 +91,16 @@ class Image(models.Model):
remote_image = interface.upload_file(name=name,
path=image_file.temporary_file_path(),
format=file_format)
new_image = cls.create(
name=name,
remote_id=remote_image.id,
created_by=user,
uploaded_by_user=True,
description=description
)
new_image = Image.objects.create(name=name,
remote_id=remote_image.id,
created_by=user,
uploaded_by_user=True,
description=description)
for attr, value in new_image.__dict__.items():
setattr(remote_image, attr, value)
return remote_image
def delete(self):
def delete(self, **kwargs):
interface = OpenstackImageManager(settings.CONNECTION)
if interface.delete(self.remote_id):
super().delete()
......
......@@ -11,7 +11,7 @@ class ImageUpdateSerializer(serializers.Serializer):
class ImageSerializer(serializers.ModelSerializer):
image_file = serializers.FileField(write_only=True)
file_format = serializers.CharField(max_length=10)
size = serializers.IntegerField()
size = serializers.IntegerField(read_only=True)
class Meta:
model = Image
......
from rest_framework import routers
from image import views
router = routers.DefaultRouter()
......
# from django.shortcuts import render
from rest_framework.viewsets import ViewSet
from rest_framework.response import Response
from django.shortcuts import get_object_or_404
......@@ -6,8 +5,7 @@ from rest_framework import status
from django.core.exceptions import ObjectDoesNotExist
from image.models import Image
from image.serializers import ImageSerializer
from image.serializers import ImageUpdateSerializer
from image.serializers import ImageSerializer, ImageUpdateSerializer
class ImageViewSet(ViewSet):
......@@ -37,7 +35,6 @@ class ImageViewSet(ViewSet):
def retrieve(self, request, pk=None):
queryset = Image.objects.all()
image = get_object_or_404(queryset, pk=pk)
serializer = ImageSerializer(instance=image)
image = image.get()
serializer = ImageSerializer(instance=image)
return Response(serializer.data)
......
......@@ -2,6 +2,13 @@ from django.contrib import admin
from instance.models import Instance, Flavor, Lease
admin.site.register(Instance)
from guardian.admin import GuardedModelAdmin
class InstanceAdmin(GuardedModelAdmin):
pass
admin.site.register(Instance, InstanceAdmin)
admin.site.register(Flavor)
admin.site.register(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.'))},
),
]
......@@ -5,8 +5,8 @@ from django.utils import timezone
from datetime import timedelta
from image.models import Disk
from interface_openstack.implementation.vm.instance import (
OSVirtualMachineManager
)
OSVirtualMachineManager
)
import logging
logger = logging.getLogger(__name__)
......@@ -45,8 +45,8 @@ class Flavor(models.Model):
name = models.CharField(blank=True, 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"
)
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)
......@@ -82,6 +82,20 @@ class Instance(models.Model):
"""
from template.models import ImageTemplate
class Meta:
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.'),
)
name = models.CharField(max_length=100,
help_text="Human readable name of instance")
......@@ -168,10 +182,20 @@ class Instance(models.Model):
lease = self.lease
return (
timezone.now() + timedelta(
seconds=lease.suspend_interval_in_sec),
seconds=lease.suspend_interval_in_sec),
timezone.now() + timedelta(
seconds=lease.delete_interval_in_sec)
)
seconds=lease.delete_interval_in_sec)
)
def renew(self, lease=None):
"""Renew virtual machine, if a new lease is provided it changes it as well.
"""
if lease is None:
lease = self.lease
else:
self.lease = lease
self.time_of_suspend, self.time_of_delete = self.get_renew_times(lease)
self.save()
def delete(self):
try:
......@@ -193,5 +217,13 @@ class Instance(models.Model):
@classmethod
def generate_password(self):
return User.objects.make_random_password(
allowed_chars='abcdefghijklmnopqrstuvwx'
'ABCDEFGHIJKLMNOPQRSTUVWX123456789')
allowed_chars='abcdefghijklmnopqrstuvwx'
'ABCDEFGHIJKLMNOPQRSTUVWX123456789')
def change_name(self, new_name):
self.name = new_name
self.save()
def change_description(self, new_description):
self.description = new_description
self.save()
......@@ -9,6 +9,7 @@ class InstanceSerializer(serializers.ModelSerializer):
class Meta:
model = Instance
fields = (
"id",
"name",
"description",
"system",
......@@ -18,7 +19,7 @@ class InstanceSerializer(serializers.ModelSerializer):
"template",
"time_of_suspend",
"time_of_delete")
read_only_fields = ("password", "template", "time_of_suspend", "time_of_delete")
read_only_fields = ("id", "password", "template", "time_of_suspend", "time_of_delete")
class FlavorSerializer(serializers.ModelSerializer):
......
......@@ -9,9 +9,46 @@ from template.serializers import InstanceFromTemplateSerializer
from instance.models import Instance, Flavor, Lease
from template.models import ImageTemplate
from template.serializers import ImageTemplateModelSerializer
class InstanceViewSet(ViewSet):
from authorization.mixins import AuthorizationMixin
authorization = {
"list": {"filter": ["use_instance"]},
"create": {"model": ["instance.create_instance"]},
"retrieve": {"object": ["use_instance"]},
"update": {"object": ["use_instance"]},
"destroy": {"object": ["administer_instance"]},
"template": {"model": ["create_template_from_instance"],
"object": ["use_instance"]},
"start": {"object": ["operate_instance"]},
"stop": {"object": ["operate_instance"]},
"suspend": {"object": ["operate_instance"]},
"wake_up": {"object": ["operate_instance"]},
"reset": {"object": ["operate_instance"]},
"reboot": {"object": ["operate_instance"]},
}
update_actions = [
"change_name",
"change_description",
"renew",
"change_lease",
"change_flavor",
"attach_disk",
"resize_disk",
"add_permission",
"remove_permission",
"open_port",
"close_port",
"add_network",
"remove_network",
"new_password",
]
class InstanceViewSet(AuthorizationMixin, ViewSet):
authorization = authorization
def get_object(self, pk):
try:
......@@ -20,10 +57,14 @@ class InstanceViewSet(ViewSet):
raise Http404
def list(self, request):
instances = Instance.objects.all()
instances = self.get_objects_with_perms(request.user, "list", Instance)
return Response(InstanceSerializer(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)
data = request.data
template = ImageTemplate.objects.get(pk=data["template"])
......@@ -50,6 +91,10 @@ class InstanceViewSet(ViewSet):
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)
instanceDict = InstanceSerializer(instance).data
remoteInstance = instance.get_remote_instance()
remoteInstanceDict = remoteInstance.__dict__
......@@ -59,21 +104,67 @@ class InstanceViewSet(ViewSet):
return Response(merged_dict)
def update(self, request, pk, format=None):
instance = self.get_object(pk)
serializer = InstanceSerializer(instance, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
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":
instance.change_name(request.data["name"])
elif action == "change_description":
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
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):
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()
return Response(status=status.HTTP_204_NO_CONTENT)
@action(detail=True, methods=["post"])
def template(self, request, pk):
instance = self.get_object(pk)
if not self.has_perms_for_model(request.user, 'template'):
return Response({"error": "No permission to create template from instance."},
status=status.HTTP_401_UNAUTHORIZED)
if not self.has_perms_for_object(request.user, 'template', instance):
return Response({"error": "No permission to access the Virtual Machine."},
status=status.HTTP_401_UNAUTHORIZED)
serializer = InstanceFromTemplateSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
data = serializer.validated_data
......@@ -87,8 +178,10 @@ class InstanceViewSet(ViewSet):
@action(detail=True, methods=["POST"])
def actions(self, request, pk):
instance = self.get_object(pk)
if not self.has_perms_for_object(request.user, request.data["action"], instance):
return Response({"error": "No permission to use this action on the VM."},
status=status.HTTP_401_UNAUTHORIZED)
success = instance.execute_common_action(action=request.data["action"])
return Response(success)
......
......@@ -41,6 +41,7 @@ INSTALLED_APPS = [
"djoser",
"rest_framework_swagger",
"corsheaders",
"guardian",
"django_nose",
]
......@@ -49,6 +50,7 @@ LOCAL_APPS = [
"instance",
"storage",
"template",
"authorization",
]
INSTALLED_APPS += LOCAL_APPS
......@@ -112,6 +114,7 @@ REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
),
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema' # needed for swagger
}
# Internationalization
......@@ -119,7 +122,7 @@ REST_FRAMEWORK = {
LANGUAGE_CODE = "en-us"
TIME_ZONE = "UTC"
TIME_ZONE = "Europe/Budapest"
USE_I18N = True
......@@ -230,3 +233,10 @@ LOGGING = {
for i in LOCAL_APPS:
LOGGING['loggers'][i] = {'handlers': ['console'], 'level': 'DEBUG'}
# Configure django-guardian for the authorization
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend', # this is default
'guardian.backends.ObjectPermissionBackend',
)
......@@ -12,5 +12,8 @@ ADMIN_ENABLED = True
ALLOWED_HOSTS = ['*']
AUTH_PASSWORD_VALIDATORS = []
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
NOSE_ARGS = LOCAL_APPS
\ No newline at end of file
NOSE_ARGS = LOCAL_APPS
......@@ -8,71 +8,53 @@ from interface_openstack.implementation.storage.openstack_snapshot_manager impor
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."
)
# remote_id = models.CharField(
# max_length=40,
# unique=True,
# verbose_name="remote_ID",
# help_text="ID, which helps access 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="Reasources given to the vm",
verbose_name="flavor", on_delete="CASCADE",
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",
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
)
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."
)
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 = cls.create(
name=name,
description=description,
disk=disk,
remote_id=remote_id,
created_by=user
)
new_template = DiskTemplate.objects.create(name=name,
description=description,
disk=disk,
remote_id=remote_id,
created_by=user)
return new_template
......@@ -82,33 +64,22 @@ class ImageTemplate(BaseTemplate):
('I', 'Template created from instance'),
('P', '"Pure" template'),
)
image = models.ForeignKey(
Image,
related_name="templates",
on_delete=models.CASCADE,
help_text=""
)
type = models.CharField(max_length=10, choices=TYPES, default="U")
@classmethod
def create(cls, name, description, image, lease, flavor, created_by, type='U'):
inst = cls(name=name, description=description, image=image, lease=lease,
flavor=flavor, created_by=created_by, type=type)
inst.full_clean()
inst.save()
return inst
image = models.ForeignKey(Image,
related_name="templates",
on_delete=models.CASCADE,
help_text="")
type = models.CharField(max_length=10,
choices=TYPES,
default="U")
@classmethod
def create_from_instance(cls, name, description, instance, user):
image = Image.create_from_instance(user, instance, description)
new_template = cls.create(
name=name,
description=description,
created_by=user,
image=image,
lease=instance.lease,
flavor=instance.flavor,
type="I"
)
new_template = ImageTemplate.objects.create(name=name,
description=description,
created_by=user,
image=image,
lease=instance.lease,
flavor=instance.flavor,
type="I")
return new_template
from rest_framework import routers
from template import views
router = routers.DefaultRouter()
......
# from django.shortcuts import render
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from template.serializers import ImageTemplateModelSerializer
from template.models import ImageTemplate
......
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