Commit dc4d4e55 by Karsa Zoltán István

Merge branch 'iac-api'

parents 796da6b9 42092a44
...@@ -379,6 +379,8 @@ THIRD_PARTY_APPS = ( ...@@ -379,6 +379,8 @@ THIRD_PARTY_APPS = (
'statici18n', 'statici18n',
'simplesshkey', 'simplesshkey',
'pipeline', 'pipeline',
'rest_framework',
'rest_framework.authtoken',
) )
...@@ -401,6 +403,15 @@ INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS ...@@ -401,6 +403,15 @@ INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
########## END APP CONFIGURATION ########## END APP CONFIGURATION
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAdminUser'
),
}
########## LOGGING CONFIGURATION ########## LOGGING CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#logging # See: https://docs.djangoproject.com/en/dev/ref/settings/#logging
# A sample logging configuration. The only tangible logging # A sample logging configuration. The only tangible logging
......
...@@ -20,7 +20,7 @@ from django.views.generic import TemplateView ...@@ -20,7 +20,7 @@ from django.views.generic import TemplateView
from django.conf import settings from django.conf import settings
from django.contrib import admin from django.contrib import admin
from django.urls import reverse from django.urls import reverse, path
from django.shortcuts import redirect from django.shortcuts import redirect
from django.contrib.auth.views import ( from django.contrib.auth.views import (
PasswordResetView, PasswordResetConfirmView PasswordResetView, PasswordResetConfirmView
...@@ -33,6 +33,8 @@ from dashboard.views import ( ...@@ -33,6 +33,8 @@ from dashboard.views import (
from dashboard.forms import CirclePasswordResetForm, CircleSetPasswordForm from dashboard.forms import CirclePasswordResetForm, CircleSetPasswordForm
from firewall.views import add_blacklist_item from firewall.views import add_blacklist_item
from rest_framework.authtoken.views import obtain_auth_token
admin.autodiscover() admin.autodiscover()
urlpatterns = [ urlpatterns = [
...@@ -71,6 +73,9 @@ urlpatterns = [ ...@@ -71,6 +73,9 @@ urlpatterns = [
name="info.support"), name="info.support"),
url(r'^info/resize-how-to/$', ResizeHelpView.as_view(), url(r'^info/resize-how-to/$', ResizeHelpView.as_view(),
name="info.resize"), name="info.resize"),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
path('api-token-auth/', obtain_auth_token)
] ]
if 'rosetta' in settings.INSTALLED_APPS: if 'rosetta' in settings.INSTALLED_APPS:
......
...@@ -250,6 +250,7 @@ class ActivityModel(TimeStampedModel): ...@@ -250,6 +250,7 @@ class ActivityModel(TimeStampedModel):
return 'failed' return 'failed'
@celery.task() @celery.task()
def compute_cached(method, instance, memcached_seconds, def compute_cached(method, instance, memcached_seconds,
key, start, *args, **kwargs): key, start, *args, **kwargs):
......
...@@ -61,6 +61,15 @@ except NameError: ...@@ -61,6 +61,15 @@ except NameError:
logger = getLogger(__name__) logger = getLogger(__name__)
from django.conf import settings
from django.db.models.signals import post_save
from django.dispatch import receiver
from rest_framework.authtoken.models import Token
@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_auth_token(sender, instance=None, created=False, **kwargs):
if created:
Token.objects.create(user=instance)
def pwgen(): def pwgen():
return User.objects.make_random_password() return User.objects.make_random_password()
......
from rest_framework.renderers import JSONRenderer
from rest_framework import serializers
from django.contrib.auth.models import Group, User
from vm.models import Instance, InstanceTemplate, Lease, Interface, Node, InstanceActivity
from firewall.models import Vlan, Rule
from storage.models import Disk, StorageActivity
class RuleSerializer(serializers.ModelSerializer):
class Meta:
model = Rule
fields = '__all__'
class InstanceActivitySerializer(serializers.ModelSerializer):
get_percentage = serializers.IntegerField()
result_data = serializers.JSONField()
class Meta:
model = InstanceActivity
fields = ('id', 'instance', 'resultant_state', 'interruptible', 'activity_code', 'parent',
'task_uuid', 'user', 'started', 'finished', 'succeeded', 'result_data', 'created', 'modified', 'get_percentage')
class StorageActivitySerializer(serializers.ModelSerializer):
class Meta:
model = StorageActivity
fields = ('id', 'parent', 'task_uuid', 'user', 'started', 'finished', 'succeeded', 'result_data', 'created', 'modified', 'disk')
class GroupSerializer(serializers.ModelSerializer):
class Meta:
model = Group
fields = ('id', 'name', 'user_set')
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'email', 'is_staff', 'groups', 'is_superuser', 'first_name', 'last_name')
class NodeSerializer(serializers.ModelSerializer):
class Meta:
model = Node
fields = [ 'id', 'name', 'priority', 'host', 'enabled', 'schedule_enabled',
'traits', 'overcommit', 'ram_weight', 'cpu_weight', 'time_stamp' ]
class InstanceTemplateSerializer(serializers.ModelSerializer):
class Meta:
model = InstanceTemplate
fields = [ 'id', 'name', 'description', 'parent', 'owner', 'access_method', 'boot_menu',
'lease', 'raw_data', 'cloud_init', 'ci_meta_data', 'ci_user_data', 'system',
'has_agent', 'num_cores', 'ram_size', 'max_ram_size', 'arch', 'priority', 'disks']
class LeaseSerializer(serializers.ModelSerializer):
class Meta:
model = Lease
fields = [ 'id', 'name', 'suspend_interval_seconds', 'delete_interval_seconds']
class DiskSerializer(serializers.ModelSerializer):
class Meta:
model = Disk
fields = ['id', 'name', 'filename', 'datastore', 'type', 'bus', 'size', 'base',
'dev_num', 'destroyed', 'ci_disk', 'is_ready']
class InstanceSerializer(serializers.ModelSerializer):
ipv4addr = serializers.SerializerMethodField('get_ipv4')
ipv6addr = serializers.SerializerMethodField('get_ipv6')
vlans = serializers.SerializerMethodField('get_vlans')
#interfaces = serializers.SerializerMethodField('get_interfaces')
def get_ipv4(self, i):
return str(i.ipv4)
def get_ipv6(self, i):
return str(i.ipv6)
def get_vlans(self, i):
return list(net.vlan.id for net in i.interface_set.all() if net.host)
def get_interfaces(self, i):
return i.interface_set.all()
class Meta:
model = Instance
fields = ['id', 'name', 'description', 'status', 'owner', 'access_method', 'boot_menu', 'pw', 'is_base',
'lease', 'raw_data', 'cloud_init', 'ci_meta_data', 'ci_user_data', 'system', 'req_traits', 'interface_set',
'has_agent', 'num_cores', 'ram_size', 'max_ram_size', 'arch', 'priority', 'disks', 'node', 'ipv4addr', 'ipv6addr', 'vlans']
extra_kwargs = {
'disks': {'required': False, 'allow_empty': True,},
'req_traits': {'required': False, 'allow_empty': True,}
}
class InterfaceSerializer(serializers.ModelSerializer):
mac = serializers.SerializerMethodField('get_mac')
ipv4 = serializers.SerializerMethodField('get_ipv4')
ipv6 = serializers.SerializerMethodField('get_ipv6')
def get_mac(self, i):
return str(i.mac)
def get_ipv4(self, i):
return str(i.ipv4)
def get_ipv6(self, i):
return str(i.ipv6)
class Meta:
model = Interface
fields = ['id', 'vlan', 'host', 'instance', 'model', 'host', 'mac', 'ipv4', 'ipv6']
class VlanSerializer(serializers.ModelSerializer):
class Meta:
model = Vlan
fields = ['id', 'vid', 'name', 'description', 'comment', 'domain']
class CreateDiskSerializer(serializers.Serializer):
size = serializers.CharField(max_length=50)
name = serializers.CharField(max_length=100)
class ResizeDiskSerializer(serializers.Serializer):
size = serializers.CharField(max_length=50)
disk = serializers.IntegerField()
class DownloadDiskSerializer(serializers.Serializer):
url = serializers.CharField(max_length=500)
name = serializers.CharField(max_length=100)
class CreateTemplateSerializer(serializers.Serializer):
name = serializers.CharField(max_length=100)
class DestroyDiskSerializer(serializers.Serializer):
disk = serializers.IntegerField()
instance = serializers.IntegerField(required=False)
class Meta:
extra_kwargs = {'instance': {'required': False, 'allow_null': True}}
class AddPortSerializer(serializers.Serializer):
type = serializers.CharField(max_length=5)
port_destination = serializers.IntegerField()
port_source = serializers.IntegerField(required=False)
forwarding = serializers.BooleanField(required=False)
class VMDeploySerializer(serializers.Serializer):
node = serializers.IntegerField(required=False)
class Meta:
extra_kwargs = {'node': {'required': False, 'allow_null': True}}
\ No newline at end of file
...@@ -42,6 +42,14 @@ ...@@ -42,6 +42,14 @@
{% trans "Email address" %}: {{ profile.email }} {% trans "Email address" %}: {{ profile.email }}
{% endif %} {% endif %}
</p> </p>
<p>
IAC-TOKEN:
{% if profile.is_superuser %}
<i>{{ iac_token }}</i>
{% else %}
for admin users only
{% endif %}
</p>
<p>{% trans "Last login" %}: <span title="{{ profile.last_login }}">{{ profile.last_login|arrowfilter:LANGUAGE_CODE}}</span></p> <p>{% trans "Last login" %}: <span title="{{ profile.last_login }}">{{ profile.last_login|arrowfilter:LANGUAGE_CODE}}</span></p>
{% if request.user == profile %} {% if request.user == profile %}
<p> <p>
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
from django.conf.urls import url from django.conf.urls import url
from django.urls import path
from vm.models import Instance from vm.models import Instance
from .views import ( from .views import (
...@@ -53,17 +54,62 @@ from .views import ( ...@@ -53,17 +54,62 @@ from .views import (
TransferTemplateOwnershipView, TransferTemplateOwnershipConfirmView, TransferTemplateOwnershipView, TransferTemplateOwnershipConfirmView,
OpenSearchDescriptionView, OpenSearchDescriptionView,
NodeActivityView, NodeActivityView,
UserList, UserList, TemplateREST, LeaseREST, DiskRest, InstanceREST,
StorageDetail, DiskDetail, InterfaceREST, InstanceFromTemplateREST, InstanceFTforUsersREST,
DownloadDiskREST, GetInstanceREST, GetInterfaceREST, ShutdownInstanceREST,
GetLeaseREST, GetDiskRest, DeployInstanceREST, CreateDiskREST,
VlanREST, ResizeDiskREST, GetVlanREST, DestroyDiskREST, InstanceFTforUsersIdREST,
StorageDetail, DiskDetail, UserREST, GroupREST, CreateTemplateREST,
InstanceActivityREST, GetInstanceActivityREST, GetGroupREST, GetUserREST,
SleepInstanceREST, WakeUpInstanceREST, DownloadPersistentDiskREST,
CreatePersistentDiskREST, GetStorageActivityREST, GetTemplateREST,
MessageList, MessageDetail, MessageCreate, MessageDelete, MessageList, MessageDetail, MessageCreate, MessageDelete,
SetupPortREST, RulesREST,
EnableTwoFactorView, DisableTwoFactorView, EnableTwoFactorView, DisableTwoFactorView,
AclUserGroupAutocomplete, AclUserAutocomplete, AclUserGroupAutocomplete, AclUserAutocomplete,
RescheduleView, GroupImportView, GroupExportView RescheduleView, GroupImportView, GroupExportView,
) )
from .views.node import node_ops from .views.node import node_ops, NodeREST, GetNodeREST
from .views.vm import vm_ops, vm_mass_ops from .views.vm import vm_ops, vm_mass_ops
urlpatterns = [ urlpatterns = [
path('acpi/stact/<int:pk>/', GetStorageActivityREST.as_view()),
path('acpi/pddisk/', DownloadPersistentDiskREST.as_view()),
path('acpi/pcdisk/', CreatePersistentDiskREST.as_view()),
path('acpi/vmact/', InstanceActivityREST.as_view()),
path('acpi/vmact/<int:pk>/', GetInstanceActivityREST.as_view()),
path('acpi/user/', UserREST.as_view()),
path('acpi/user/<int:pk>/', GetUserREST.as_view()),
path('acpi/group/', GroupREST.as_view()),
path('acpi/group/<int:pk>/', GetGroupREST.as_view()),
path('acpi/vm/', InstanceREST.as_view()),
path('acpi/node/', NodeREST.as_view()),
path('acpi/node/<int:pk>/', GetNodeREST.as_view()),
path('acpi/vm/<int:pk>/', GetInstanceREST.as_view()),
path('acpi/template/<int:pk>/', GetTemplateREST.as_view()),
path('acpi/template/', TemplateREST.as_view()),
path('acpi/ft/', InstanceFromTemplateREST.as_view()),
path('acpi/lease/', LeaseREST.as_view()),
path('acpi/lease/<int:pk>/', GetLeaseREST.as_view()),
path('acpi/disk/', DiskRest.as_view()),
path('acpi/disk/<int:pk>/', GetDiskRest.as_view()),
path('acpi/interface/', InterfaceREST.as_view()),
path('acpi/vlan/', VlanREST.as_view()),
path('acpi/vlan/<int:pk>/', GetVlanREST.as_view()),
path('acpi/interface/<int:pk>/', GetInterfaceREST.as_view()),
path('acpi/ftusers/', InstanceFTforUsersREST.as_view()),
path('acpi/ftusersid/', InstanceFTforUsersIdREST.as_view()),
path('acpi/vm/<int:pk>/downloaddisk/', DownloadDiskREST.as_view()),
path('acpi/vm/<int:vm_id>/port/<int:vlan_id>/', SetupPortREST.as_view()),
path('acpi/vm/<int:vm_id>/rules/<int:vlan_id>/', RulesREST.as_view()),
path('acpi/vm/<int:pk>/createdisk/', CreateDiskREST.as_view()),
path('acpi/vm/<int:pk>/deploy/', DeployInstanceREST.as_view()),
path('acpi/vm/<int:pk>/shutdown/', ShutdownInstanceREST.as_view()),
path('acpi/vm/<int:pk>/sleep/', SleepInstanceREST.as_view()),
path('acpi/vm/<int:pk>/wakeup/', WakeUpInstanceREST.as_view()),
path('acpi/vm/<int:pk>/resizedisk/', ResizeDiskREST.as_view()),
path('acpi/vm/<int:pk>/destroydisk/', DestroyDiskREST.as_view()),
path('acpi/vm/<int:pk>/saveastemplate/', CreateTemplateREST.as_view()),
url(r'^$', IndexView.as_view(), name="dashboard.index"), url(r'^$', IndexView.as_view(), name="dashboard.index"),
url(r"^profile/list/$", UserList.as_view(), url(r"^profile/list/$", UserList.as_view(),
name="dashboard.views.user-list"), name="dashboard.views.user-list"),
......
...@@ -27,7 +27,7 @@ from django.contrib.auth.models import User, Group ...@@ -27,7 +27,7 @@ from django.contrib.auth.models import User, Group
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.core.exceptions import PermissionDenied, SuspiciousOperation from django.core.exceptions import PermissionDenied, SuspiciousOperation
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy
from django.http import HttpResponse, Http404 from django.http import HttpResponse, Http404, JsonResponse
from django.shortcuts import redirect from django.shortcuts import redirect
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.views.generic import UpdateView, TemplateView from django.views.generic import UpdateView, TemplateView
...@@ -35,6 +35,12 @@ from django.views.generic.detail import SingleObjectMixin ...@@ -35,6 +35,12 @@ from django.views.generic.detail import SingleObjectMixin
from django_tables2 import SingleTableView from django_tables2 import SingleTableView
from itertools import chain from itertools import chain
from rest_framework import status
from rest_framework.views import APIView
from rest_framework.parsers import JSONParser
from rest_framework.authentication import TokenAuthentication, BasicAuthentication
from rest_framework.permissions import IsAdminUser
from vm.models import Instance, InstanceTemplate from vm.models import Instance, InstanceTemplate
from .util import (CheckedDetailView, AclUpdateView, search_user, from .util import (CheckedDetailView, AclUpdateView, search_user,
saml_available, DeleteViewBase) saml_available, DeleteViewBase)
...@@ -46,6 +52,8 @@ from ..models import FutureMember, GroupProfile ...@@ -46,6 +52,8 @@ from ..models import FutureMember, GroupProfile
from ..store_api import Store, NoStoreException from ..store_api import Store, NoStoreException
from ..tables import GroupListTable from ..tables import GroupListTable
from dashboard.serializers import GroupSerializer
try: try:
# Python 2: "unicode" is built-in # Python 2: "unicode" is built-in
unicode unicode
...@@ -55,6 +63,33 @@ except NameError: ...@@ -55,6 +63,33 @@ except NameError:
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class GroupREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def get(self, request, format=None):
if request.query_params.get('name'):
try:
group = Group.objects.filter(name__istartswith=request.query_params.get('name')).get()
serializer = GroupSerializer(group, many=False)
return JsonResponse(serializer.data, safe=False)
except:
return JsonResponse({}, status=404)
groups = Group.objects.all()
serializer = GroupSerializer(groups, many=True)
return JsonResponse(serializer.data, safe=False)
class GetGroupREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def get(self, request, pk, format=None):
groups = Group.objects.get(pk=pk)
serializer = GroupSerializer(groups, many=False)
return JsonResponse(serializer.data, safe=False)
class GroupCodeMixin(object): class GroupCodeMixin(object):
@classmethod @classmethod
......
...@@ -31,6 +31,12 @@ from django.template.loader import render_to_string ...@@ -31,6 +31,12 @@ from django.template.loader import render_to_string
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.views.generic import DetailView, TemplateView, View from django.views.generic import DetailView, TemplateView, View
from rest_framework import status
from rest_framework.views import APIView
from rest_framework.parsers import JSONParser
from rest_framework.authentication import TokenAuthentication, BasicAuthentication
from rest_framework.permissions import IsAdminUser
from braces.views import LoginRequiredMixin, SuperuserRequiredMixin from braces.views import LoginRequiredMixin, SuperuserRequiredMixin
from django_tables2 import SingleTableView from django_tables2 import SingleTableView
...@@ -45,6 +51,35 @@ from .util import AjaxOperationMixin, OperationView, GraphMixin, DeleteViewBase ...@@ -45,6 +51,35 @@ from .util import AjaxOperationMixin, OperationView, GraphMixin, DeleteViewBase
from manager.mancelery import crontab_parser from manager.mancelery import crontab_parser
from dashboard.serializers import NodeSerializer
class NodeREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def get(self, request, format=None):
if request.query_params.get('name'):
try:
template = Node.objects.filter(name__istartswith=request.query_params.get('name')).get()
serializer = NodeSerializer(template, many=False)
return JsonResponse(serializer.data, safe=False)
except:
return JsonResponse({}, status=404)
templates = Node.objects.all()
serializer = NodeSerializer(templates, many=True)
return JsonResponse(serializer.data, safe=False)
class GetNodeREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def get(self, request, pk, format=None):
interface = Node.objects.get(pk=pk)
serializer = NodeSerializer(interface, many=False)
return JsonResponse(serializer.data, safe=False)
def get_operations(instance, user): def get_operations(instance, user):
ops = [] ops = []
......
...@@ -21,14 +21,20 @@ from django.urls import reverse ...@@ -21,14 +21,20 @@ from django.urls import reverse
from django.db.models import Q from django.db.models import Q
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.views.generic import UpdateView from django.views.generic import UpdateView
from django.http import JsonResponse
from braces.views import SuperuserRequiredMixin from braces.views import SuperuserRequiredMixin
from sizefield.utils import filesizeformat from sizefield.utils import filesizeformat
from rest_framework.authentication import TokenAuthentication, BasicAuthentication
from rest_framework.permissions import IsAdminUser
from rest_framework.views import APIView
from common.models import WorkerNotFound from common.models import WorkerNotFound
from storage.models import DataStore, Disk from storage.models import DataStore, Disk
from ..tables import DiskListTable from ..tables import DiskListTable
from ..forms import DataStoreForm, DiskForm from ..forms import DataStoreForm, DiskForm
from dashboard.serializers import DiskSerializer
class StorageDetail(SuperuserRequiredMixin, UpdateView): class StorageDetail(SuperuserRequiredMixin, UpdateView):
...@@ -138,3 +144,27 @@ class DiskDetail(SuperuserRequiredMixin, UpdateView): ...@@ -138,3 +144,27 @@ class DiskDetail(SuperuserRequiredMixin, UpdateView):
def form_valid(self, form): def form_valid(self, form):
pass pass
class DiskRest(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def get(self, request, format=None):
templates = Disk.objects.all()
serializer = DiskSerializer(templates, many=True)
return JsonResponse(serializer.data, safe=False)
class GetDiskRest(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def get(self, request, pk, format=None):
disk = Disk.objects.get(pk=pk)
serializer = DiskSerializer(disk, many=False)
return JsonResponse(serializer.data, safe=False)
def delete(self, request, pk, format=None):
return JsonResponse(status=204)
...@@ -19,6 +19,8 @@ ...@@ -19,6 +19,8 @@
from datetime import timedelta from datetime import timedelta
import json import json
import logging import logging
from string import Template
from xml.dom import NotFoundErr
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.models import User from django.contrib.auth.models import User
...@@ -26,7 +28,7 @@ from django.contrib.messages.views import SuccessMessageMixin ...@@ -26,7 +28,7 @@ from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy
from django.core.exceptions import PermissionDenied, SuspiciousOperation from django.core.exceptions import PermissionDenied, SuspiciousOperation
from django.db.models import Count from django.db.models import Count
from django.http import HttpResponse, HttpResponseRedirect from django.http import HttpResponse, HttpResponseRedirect, JsonResponse
from django.shortcuts import redirect, get_object_or_404 from django.shortcuts import redirect, get_object_or_404
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext as _, ugettext_noop from django.utils.translation import ugettext as _, ugettext_noop
...@@ -56,6 +58,13 @@ from .util import ( ...@@ -56,6 +58,13 @@ from .util import (
GraphMixin GraphMixin
) )
from dashboard.serializers import InstanceTemplateSerializer, LeaseSerializer, InstanceSerializer
from rest_framework.authentication import TokenAuthentication, BasicAuthentication
from rest_framework.permissions import IsAdminUser
from rest_framework.views import APIView
from rest_framework.parsers import JSONParser
try: try:
# Python 2: "unicode" is built-in # Python 2: "unicode" is built-in
unicode unicode
...@@ -184,6 +193,178 @@ class TemplateCreate(SuccessMessageMixin, CreateView): ...@@ -184,6 +193,178 @@ class TemplateCreate(SuccessMessageMixin, CreateView):
return reverse_lazy("dashboard.views.template-list") return reverse_lazy("dashboard.views.template-list")
class TemplateREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def get(self, request, format=None):
if request.query_params.get("name"):
try:
template = InstanceTemplate.objects.filter(name__istartswith=request.query_params.get('name')).get()
serializer = InstanceTemplateSerializer(template, many=False)
return JsonResponse(serializer.data, safe=False)
except:
return JsonResponse(state=411)
templates = InstanceTemplate.objects.all()
serializer = InstanceTemplateSerializer(templates, many=True)
return JsonResponse(serializer.data, safe=False)
def post(self, request, format=None):
data = JSONParser().parse(request)
serializer = InstanceTemplateSerializer(data=data)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data, status=201)
return JsonResponse(serializer.errors, status=400)
class GetTemplateREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def get(self, request, pk, format=None):
templates = InstanceTemplate.objects.get(pk=pk)
serializer = InstanceTemplateSerializer(templates, many=False)
return JsonResponse(serializer.data, safe=False)
def delete(self, request, pk, format=None):
return JsonResponse(status=400)
class InstanceFromTemplateREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def post(self, request, format=None):
data = JSONParser().parse(request)
user = User.objects.get(pk=request.user.pk)
template = InstanceTemplate.objects.get(pk=data['template'])
ikwargs = {
'name': data['name'],
'template': template,
'owner': user,
}
amount = data.get("amount", 1)
if 'num_cores' in data:
ikwargs.update({'num_cores':data['num_cores']})
if 'ram_size' in data:
ikwargs.update({'ram_size':data['ram_size']})
if 'priority' in data:
ikwargs.update({'priority':data['priority']})
if 'max_ram_size' in data:
ikwargs.update({'max_ram_size':data['ram_size']})
instances = Instance.mass_create_from_template(amount=amount,
**ikwargs)
for i in instances:
i.deploy._async(user=user)
if amount == 1:
serializer = InstanceSerializer(instances, many=True)
return JsonResponse(serializer.data, status=201, safe=False)
serializer = InstanceSerializer(instances, many=True)
return JsonResponse(serializer.data, status=201, safe=False)
class InstanceFTforUsersREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def post(self, request, format=None):
data = JSONParser().parse(request)
template = InstanceTemplate.objects.get(pk=data['template'])
ikwargs = {
'name': data['name'],
'users': data['users'],
'template': template,
'operator': data.get('operator', None),
'admin': data.get('admin', None)
}
if 'num_cores' in data:
ikwargs.update({'num_cores':data['num_cores']})
if 'ram_size' in data:
ikwargs.update({'ram_size':data['ram_size']})
if 'priority' in data:
ikwargs.update({'priority':data['priority']})
if 'max_ram_size' in data:
ikwargs.update({'max_ram_size':data['ram_size']})
missing_users, instances = Instance.mass_create_for_users(**ikwargs)
serializer = InstanceSerializer(instances, many=True)
return JsonResponse(serializer.data, status=201)
class InstanceFTforUsersIdREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def post(self, request, format=None):
data = JSONParser().parse(request)
template = InstanceTemplate.objects.get(pk=int(data['template']))
ikwargs = {
'name': data['name'],
'users': data['users'],
'template': template,
'operator': data.get('operator', None),
'admin': data.get('admin', None)
}
if 'num_cores' in data:
ikwargs.update({'num_cores':data['num_cores']})
if 'ram_size' in data:
ikwargs.update({'ram_size':data['ram_size']})
if 'priority' in data:
ikwargs.update({'priority':data['priority']})
if 'max_ram_size' in data:
ikwargs.update({'max_ram_size':data['ram_size']})
missing_users, instances = Instance.mass_create_for_users_id(**ikwargs)
serializer = InstanceSerializer(instances, many=True)
return JsonResponse(serializer.data, status=201, safe=False)
class LeaseREST(APIView):
authentication_classes = [TokenAuthentication, BasicAuthentication]
permission_classes = [IsAdminUser]
def get(self, request, format=None):
if request.query_params.get('name'):
try:
template = Lease.objects.filter(name__istartswith=request.query_params.get('name')).get()
serializer = LeaseSerializer(template, many=False)
return JsonResponse(serializer.data, safe=False)
except:
return JsonResponse({}, status=404)
templates = Lease.objects.all()
serializer = LeaseSerializer(templates, many=True)
return JsonResponse(serializer.data, safe=False)
def post(self, request, format=None):
data = JSONParser().parse(request)
serializer = LeaseSerializer(data=data)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data, status=201)
return JsonResponse(serializer.errors, status=400)
class GetLeaseREST(APIView):
authentication_classes = [TokenAuthentication, BasicAuthentication]
permission_classes = [IsAdminUser]
def get(self, request, pk, format=None):
lease = Lease.objects.get(pk=pk)
serializer = LeaseSerializer(lease, many=False)
return JsonResponse(serializer.data, safe=False)
class TemplateAclUpdateView(AclUpdateView): class TemplateAclUpdateView(AclUpdateView):
model = InstanceTemplate model = InstanceTemplate
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
import json import json
import logging import logging
from multiprocessing import context
import pyotp import pyotp
...@@ -31,7 +32,7 @@ from django.core.exceptions import PermissionDenied, SuspiciousOperation ...@@ -31,7 +32,7 @@ from django.core.exceptions import PermissionDenied, SuspiciousOperation
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy
from django.core.paginator import Paginator, InvalidPage from django.core.paginator import Paginator, InvalidPage
from django.db.models import Q from django.db.models import Q
from django.http import HttpResponse, HttpResponseRedirect, Http404 from django.http import HttpResponse, HttpResponseRedirect, Http404, JsonResponse
from django.shortcuts import redirect, get_object_or_404 from django.shortcuts import redirect, get_object_or_404
from django.templatetags.static import static from django.templatetags.static import static
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
...@@ -41,6 +42,12 @@ from django.views.generic import ( ...@@ -41,6 +42,12 @@ from django.views.generic import (
) )
from simplesshkey.models import UserKey from simplesshkey.models import UserKey
from rest_framework import status
from rest_framework.views import APIView
from rest_framework.parsers import JSONParser
from rest_framework.authentication import TokenAuthentication, BasicAuthentication
from rest_framework.permissions import IsAdminUser
from braces.views import LoginRequiredMixin, PermissionRequiredMixin from braces.views import LoginRequiredMixin, PermissionRequiredMixin
from django_tables2 import SingleTableView, LazyPaginator from django_tables2 import SingleTableView, LazyPaginator
...@@ -56,8 +63,9 @@ from ..models import Profile, GroupProfile, ConnectCommand ...@@ -56,8 +63,9 @@ from ..models import Profile, GroupProfile, ConnectCommand
from ..tables import ( from ..tables import (
UserKeyListTable, ConnectCommandListTable, UserListTable, UserKeyListTable, ConnectCommandListTable, UserListTable,
) )
from rest_framework.authtoken.models import Token
from .util import saml_available, DeleteViewBase, LoginView from .util import saml_available, DeleteViewBase, LoginView
from dashboard.serializers import UserSerializer
try: try:
# Python 2: "unicode" is built-in # Python 2: "unicode" is built-in
...@@ -67,6 +75,30 @@ except NameError: ...@@ -67,6 +75,30 @@ except NameError:
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class UserREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def get(self, request, format=None):
if request.query_params.get('username'):
try:
user = User.objects.filter(username__istartswith=request.query_params.get('username')).get()
serializer = UserSerializer(user, many=False)
return JsonResponse(serializer.data, safe=False)
except:
return JsonResponse({}, status=404)
users = User.objects.all()
serializer = UserSerializer(users, many=True)
return JsonResponse(serializer.data, safe=False)
class GetUserREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def get(self, request, pk, format=None):
users = User.objects.get(pk=pk)
serializer = UserSerializer(users, many=False)
return JsonResponse(serializer.data, safe=False)
def set_session_expiry(request, user): def set_session_expiry(request, user):
if user.is_superuser: if user.is_superuser:
...@@ -387,6 +419,8 @@ class ProfileView(LoginRequiredMixin, SuccessMessageMixin, UpdateView): ...@@ -387,6 +419,8 @@ class ProfileView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
context['perm_email'] = ( context['perm_email'] = (
context['perm_group_list'] or self.request.user == user) context['perm_group_list'] or self.request.user == user)
context['iac_token'] = Token.objects.filter(user=user).get()
# filter the virtual machine list # filter the virtual machine list
# if the logged in user is not superuser or not the user itself # if the logged in user is not superuser or not the user itself
# filter the list so only those virtual machines are shown that are # filter the list so only those virtual machines are shown that are
......
...@@ -51,8 +51,18 @@ from braces.views import LoginRequiredMixin ...@@ -51,8 +51,18 @@ from braces.views import LoginRequiredMixin
from braces.views._access import AccessMixin from braces.views._access import AccessMixin
from celery.exceptions import TimeoutError from celery.exceptions import TimeoutError
from rest_framework import status
from rest_framework.views import APIView
from rest_framework.parsers import JSONParser
from rest_framework.authentication import TokenAuthentication, BasicAuthentication
from rest_framework.permissions import IsAdminUser
from storage.models import StorageActivity
from dashboard.serializers import InstanceActivitySerializer, StorageActivitySerializer
from common.models import HumanReadableException, HumanReadableObject from common.models import HumanReadableException, HumanReadableObject
from ..models import GroupProfile, Profile from ..models import GroupProfile, Profile
from vm.models import InstanceActivity
from ..forms import TransferOwnershipForm from ..forms import TransferOwnershipForm
...@@ -67,6 +77,36 @@ logger = logging.getLogger(__name__) ...@@ -67,6 +77,36 @@ logger = logging.getLogger(__name__)
saml_available = hasattr(settings, "SAML_CONFIG") saml_available = hasattr(settings, "SAML_CONFIG")
class InstanceActivityREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def get(self, request, format=None):
templates = InstanceActivity.objects.all()
serializer = InstanceActivitySerializer(templates, many=True)
return JsonResponse(serializer.data, safe=False)
class GetInstanceActivityREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def get(self, request, pk, format=None):
interface = InstanceActivity.objects.get(pk=pk)
serializer = InstanceActivitySerializer(interface, many=False)
return JsonResponse(serializer.data, safe=False)
class GetStorageActivityREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def get(self, request, pk, format=None):
act = StorageActivity.objects.get(pk=pk)
serializer = StorageActivitySerializer(act, many=False)
return JsonResponse(serializer.data, safe=False)
class RedirectToLoginMixin(AccessMixin): class RedirectToLoginMixin(AccessMixin):
redirect_exception_classes = (PermissionDenied, ) redirect_exception_classes = (PermissionDenied, )
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
from django import contrib from django import contrib
# from django.utils.translation import ugettext_lazy as _ # from django.utils.translation import ugettext_lazy as _
from .models import Disk, DataStore from .models import Disk, DataStore, StorageActivity
class DiskAdmin(contrib.admin.ModelAdmin): class DiskAdmin(contrib.admin.ModelAdmin):
...@@ -32,3 +32,4 @@ class DataStoreAdmin(contrib.admin.ModelAdmin): ...@@ -32,3 +32,4 @@ class DataStoreAdmin(contrib.admin.ModelAdmin):
contrib.admin.site.register(Disk, DiskAdmin) contrib.admin.site.register(Disk, DiskAdmin)
contrib.admin.site.register(DataStore, DataStoreAdmin) contrib.admin.site.register(DataStore, DataStoreAdmin)
contrib.admin.site.register(StorageActivity)
\ No newline at end of file
# Generated by Django 3.2.3 on 2022-09-14 15:32
import common.models
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import jsonfield.fields
import model_utils.fields
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('storage', '0004_disk_ci_disk'),
]
operations = [
migrations.CreateModel(
name='StorageActivity',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
('activity_code', models.CharField(max_length=100, verbose_name='activity code')),
('readable_name_data', jsonfield.fields.JSONField(blank=True, dump_kwargs={'cls': common.models.Encoder}, help_text='Human readable name of activity.', null=True, verbose_name='human readable name')),
('task_uuid', models.CharField(blank=True, help_text='Celery task unique identifier.', max_length=50, null=True, unique=True, verbose_name='task_uuid')),
('started', models.DateTimeField(blank=True, help_text='Time of activity initiation.', null=True, verbose_name='started at')),
('finished', models.DateTimeField(blank=True, help_text='Time of activity finalization.', null=True, verbose_name='finished at')),
('succeeded', models.BooleanField(blank=True, help_text='True, if the activity has finished successfully.', null=True)),
('result_data', jsonfield.fields.JSONField(blank=True, dump_kwargs={'cls': common.models.Encoder}, help_text='Human readable result of activity.', null=True, verbose_name='result')),
('disk', models.ForeignKey(blank=True, help_text='Disks which are to be mounted.', null=True, on_delete=django.db.models.deletion.CASCADE, to='storage.disk', verbose_name='disk')),
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='storage.storageactivity')),
('user', models.ForeignKey(blank=True, help_text='The person who started this activity.', null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='user')),
],
options={
'db_table': 'st_act',
'ordering': ['-finished', '-started', '-id'],
},
),
]
...@@ -39,7 +39,7 @@ from os.path import join ...@@ -39,7 +39,7 @@ from os.path import join
from sizefield.models import FileSizeField from sizefield.models import FileSizeField
from common.models import ( from common.models import (
WorkerNotFound, HumanReadableException, humanize_exception, method_cache WorkerNotFound, HumanReadableException, humanize_exception, join_activity_code, method_cache
) )
from .tasks import local_tasks, storage_tasks from .tasks import local_tasks, storage_tasks
...@@ -622,3 +622,23 @@ class Disk(TimeStampedModel): ...@@ -622,3 +622,23 @@ class Disk(TimeStampedModel):
@property @property
def is_exportable(self): def is_exportable(self):
return self.type in ('qcow2-norm', 'qcow2-snap', 'raw-rw', 'raw-ro') return self.type in ('qcow2-norm', 'qcow2-snap', 'raw-rw', 'raw-ro')
from common.models import ActivityModel
class StorageActivity(ActivityModel):
disk = ForeignKey('storage.Disk', blank=True, null=True, verbose_name=_('disk'), on_delete=models.CASCADE,
help_text=_('Disks which are to be mounted.'))
ACTIVITY_CODE_BASE = join_activity_code('st', 'Storage')
class Meta:
db_table = 'st_act'
ordering = ['-finished', '-started', '-id']
@classmethod
def create(cls, code_suffix, task_uuid=None, user=None):
activity_code = cls.construct_activity_code(code_suffix)
act = cls(activity_code=activity_code, parent=None,
started=timezone.now(), task_uuid=task_uuid, user=user)
act.save()
return act
\ No newline at end of file
# Generated by Django 3.2.3 on 2022-09-14 15:32
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('vm', '0009_auto_20220721_1118'),
]
operations = [
migrations.AlterField(
model_name='instance',
name='ci_meta_data',
field=models.TextField(blank=True, default='instance-id: {{ hostname }} \nlocal-hostname: {{ hostname }} \ncloud-name: circle3\nplatform: circle3', help_text='When cloud-init is active, set meta-data (YAML format)', verbose_name='CI Meta Data'),
),
migrations.AlterField(
model_name='instance',
name='ci_user_data',
field=models.TextField(blank=True, default='#cloud-config\n\nusers:\n - name: {{ sysuser }} \n sudo: [\'ALL=(ALL) NOPASSWD:ALL\']\n groups: sudo\n shell: /bin/bash\n ssh_pwauth: True\n chpasswd: { expire: False }\n lock-passwd: false\n passwd: "{{ password | hash }}"', help_text='When cloud-init is active, set user-data (YAML format)', verbose_name='CI User Data'),
),
migrations.AlterField(
model_name='instance',
name='has_agent',
field=models.BooleanField(default=False, help_text='If the machine has agent installed, and the manager should wait for its start.', verbose_name='has agent'),
),
migrations.AlterField(
model_name='instancetemplate',
name='ci_meta_data',
field=models.TextField(blank=True, default='instance-id: {{ hostname }} \nlocal-hostname: {{ hostname }} \ncloud-name: circle3\nplatform: circle3', help_text='When cloud-init is active, set meta-data (YAML format)', verbose_name='CI Meta Data'),
),
migrations.AlterField(
model_name='instancetemplate',
name='ci_user_data',
field=models.TextField(blank=True, default='#cloud-config\n\nusers:\n - name: {{ sysuser }} \n sudo: [\'ALL=(ALL) NOPASSWD:ALL\']\n groups: sudo\n shell: /bin/bash\n ssh_pwauth: True\n chpasswd: { expire: False }\n lock-passwd: false\n passwd: "{{ password | hash }}"', help_text='When cloud-init is active, set user-data (YAML format)', verbose_name='CI User Data'),
),
migrations.AlterField(
model_name='instancetemplate',
name='has_agent',
field=models.BooleanField(default=False, help_text='If the machine has agent installed, and the manager should wait for its start.', verbose_name='has agent'),
),
]
...@@ -580,10 +580,14 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin, ...@@ -580,10 +580,14 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
""" """
user_instances = [] user_instances = []
missing_users = [] missing_users = []
instances = []
for user_id in users: for user_id in users:
try: try:
user_instances.append(User.objects.get(profile__org_id=user_id)) user_instances.append(User.objects.get(profile__org_id=user_id))
except User.DoesNotExist: except User.DoesNotExist:
try:
user_instances.append(User.objects.get(username=user_id))
except User.DoesNotExist:
missing_users.append(user_id) missing_users.append(user_id)
for user in user_instances: for user in user_instances:
...@@ -592,9 +596,36 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin, ...@@ -592,9 +596,36 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
instance.set_level(User.objects.get(username=admin), 'owner') instance.set_level(User.objects.get(username=admin), 'owner')
if operator: if operator:
instance.set_level(User.objects.get(username=operator), 'operator') instance.set_level(User.objects.get(username=operator), 'operator')
instance.deploy(user=user) instance.deploy._async(user=user)
instances.append(instance)
return missing_users, instances
@classmethod
def mass_create_for_users_id(cls, template, users, admin=None, operator=None, **kwargs):
"""
Create and deploy an instance of a template for each user
in a list of users. Returns the user IDs of missing users.
"""
user_instances = []
missing_users = []
instances = []
for user_id in users:
try:
user_instances.append(User.objects.get(pk=user_id))
except User.DoesNotExist:
missing_users.append(user_id)
for user in user_instances:
instance = cls.create_from_template(template, user, **kwargs)
if admin:
instance.set_level(User.objects.get(pk=admin), 'owner')
if operator:
instance.set_level(User.objects.get(pk=operator), 'operator')
instance.deploy._async(user=user)
instances.append(instance)
return missing_users return missing_users, instances
def clean(self, *args, **kwargs): def clean(self, *args, **kwargs):
self.time_of_suspend, self.time_of_delete = self.get_renew_times() self.time_of_suspend, self.time_of_delete = self.get_renew_times()
......
...@@ -87,6 +87,14 @@ class Interface(Model): ...@@ -87,6 +87,14 @@ class Interface(Model):
except: except:
return Interface.generate_mac(self.instance, self.vlan) return Interface.generate_mac(self.instance, self.vlan)
@property
def ipv4(self):
return self.host.ipv4
@property
def ipv6(self):
return self.host.ipv6
@classmethod @classmethod
def generate_mac(cls, instance, vlan): def generate_mac(cls, instance, vlan):
"""Generate MAC address for a VM instance on a VLAN. """Generate MAC address for a VM instance on a VLAN.
......
...@@ -36,11 +36,11 @@ from django.urls import reverse ...@@ -36,11 +36,11 @@ from django.urls import reverse
from django.db.models import Q from django.db.models import Q
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext as _, ugettext_noop from django.utils.translation import ugettext as _, ugettext_noop
from re import search from re import search, template
from sizefield.utils import filesizeformat from sizefield.utils import filesizeformat
from common.models import ( from common.models import (
create_readable, humanize_exception, HumanReadableException ActivityModel, create_readable, humanize_exception, HumanReadableException
) )
from common.operations import Operation, register_operation, SubOperationMixin from common.operations import Operation, register_operation, SubOperationMixin
from dashboard.store_api import Store, NoStoreException from dashboard.store_api import Store, NoStoreException
...@@ -172,6 +172,38 @@ class InstanceOperation(Operation): ...@@ -172,6 +172,38 @@ class InstanceOperation(Operation):
return False return False
class StorageOperation(Operation):
acl_level = 'owner'
async_operation = abortable_async_instance_operation
host_cls = Disk
concurrency_check = False
accept_states = None
deny_states = None
resultant_state = None
def __init__(self):
super(InstanceOperation, self).__init__(subject=None)
def check_precond(self):
pass
def check_auth(self, user):
if not user.is_superuser:
raise Exception()
def create_activity(self, parent, user, kwargs):
name = self.get_activity_name(kwargs)
return ActivityModel.create(
readable_name=name, user=user,
concurrency_check=self.concurrency_check,
resultant_state=self.resultant_state)
def is_preferred(self):
"""If this is the recommended op in the current state of the instance.
"""
return False
class RemoteInstanceOperation(RemoteOperationMixin, InstanceOperation): class RemoteInstanceOperation(RemoteOperationMixin, InstanceOperation):
remote_queue = ('vm', 'fast') remote_queue = ('vm', 'fast')
...@@ -258,6 +290,7 @@ class CreateDiskOperation(InstanceOperation): ...@@ -258,6 +290,7 @@ class CreateDiskOperation(InstanceOperation):
description = _("Create and attach empty disk to the virtual machine.") description = _("Create and attach empty disk to the virtual machine.")
required_perms = ('storage.create_empty_disk',) required_perms = ('storage.create_empty_disk',)
accept_states = ('STOPPED', 'PENDING', 'RUNNING') accept_states = ('STOPPED', 'PENDING', 'RUNNING')
concurrency_check = False
def _operation(self, user, size, activity, name=None): def _operation(self, user, size, activity, name=None):
from storage.models import Disk from storage.models import Disk
...@@ -330,6 +363,7 @@ class DownloadDiskOperation(InstanceOperation): ...@@ -330,6 +363,7 @@ class DownloadDiskOperation(InstanceOperation):
required_perms = ('storage.download_disk',) required_perms = ('storage.download_disk',)
accept_states = ('STOPPED', 'PENDING', 'RUNNING') accept_states = ('STOPPED', 'PENDING', 'RUNNING')
async_queue = "localhost.man.slow" async_queue = "localhost.man.slow"
concurrency_check = False # warning!!!
def _operation(self, user, url, task, activity, name=None): def _operation(self, user, url, task, activity, name=None):
disk = Disk.download(url=url, name=name, task=task) disk = Disk.download(url=url, name=name, task=task)
...@@ -341,15 +375,19 @@ class DownloadDiskOperation(InstanceOperation): ...@@ -341,15 +375,19 @@ class DownloadDiskOperation(InstanceOperation):
disk.save() disk.save()
self.instance.disks.add(disk) self.instance.disks.add(disk)
activity.readable_name = create_readable( activity.readable_name = create_readable(
ugettext_noop("download %(name)s"), name=disk.name) ugettext_noop("download %(name)s (id: %(disk_id)d)"), name=disk.name, disk_id=disk.id)
activity.result = create_readable(ugettext_noop( activity.result = create_readable(ugettext_noop(
"Downloading %(url)s is finished. The file md5sum " "Downloading %(url)s is finished. The file md5sum "
"is: '%(checksum)s'."), "is: '%(checksum)s' (id: %(disk_id)d)."),
url=url, checksum=disk.checksum) url=url, checksum=disk.checksum, disk_id=disk.id, disk_size=disk.size)
# TODO iso (cd) hot-plug is not supported by kvm/guests # TODO iso (cd) hot-plug is not supported by kvm/guests
if self.instance.is_running and disk.type not in ["iso"]: if self.instance.is_running and disk.type not in ["iso"]:
self.instance._attach_disk(parent_activity=activity, disk=disk) self.instance._attach_disk(parent_activity=activity, disk=disk)
return create_readable(ugettext_noop("Downloading %(url)s is finished. The file md5sum "
"is: '%(checksum)s' (id: %(disk_id)s)."), url=url, checksum=disk.checksum, disk_id=str(disk.id))
@register_operation @register_operation
...@@ -891,8 +929,8 @@ class SaveAsTemplateOperation(InstanceOperation): ...@@ -891,8 +929,8 @@ class SaveAsTemplateOperation(InstanceOperation):
raise raise
else: else:
return create_readable( return create_readable(
ugettext_noop("New template: %(template)s"), ugettext_noop("New template: %(template)s (%(template_id)s)"),
template=reverse('dashboard.views.template-detail', template_id=tmpl.id, template=reverse('dashboard.views.template-detail',
kwargs={'pk': tmpl.pk})) kwargs={'pk': tmpl.pk}))
...@@ -913,7 +951,7 @@ class ShutdownOperation(AbortableRemoteOperationMixin, ...@@ -913,7 +951,7 @@ class ShutdownOperation(AbortableRemoteOperationMixin,
remote_queue = ("vm", "slow") remote_queue = ("vm", "slow")
remote_timeout = 180 remote_timeout = 180
def _operation(self, task): def _operation(self, task=vm_tasks.shutdown):
super(ShutdownOperation, self)._operation(task=task) super(ShutdownOperation, self)._operation(task=task)
self.instance.yield_node() self.instance.yield_node()
......
...@@ -15,7 +15,10 @@ ...@@ -15,7 +15,10 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>. # with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from datetime import timezone
from celery.contrib.abortable import AbortableTask from celery.contrib.abortable import AbortableTask
from common.models import ActivityModel
from storage.models import Disk, StorageActivity
from manager.mancelery import celery from manager.mancelery import celery
...@@ -53,3 +56,21 @@ def abortable_async_node_operation(task, operation_id, node_pk, activity_pk, ...@@ -53,3 +56,21 @@ def abortable_async_node_operation(task, operation_id, node_pk, activity_pk,
allargs['task'] = task allargs['task'] = task
return operation._exec_op(allargs, auxargs) return operation._exec_op(allargs, auxargs)
@celery.task(base=AbortableTask, bind=True)
def abortable_async_downloaddisk_operation(task, activity_pk, url, name):
activity = StorageActivity.objects.get(pk=activity_pk)
activity.task_uuid = task.request.id
activity.save()
disk = Disk.download(url=url, name=name, task=task)
disk.dev_num = 'g'
disk.full_clean()
disk.save()
activity.disk = disk
activity.succeeded = True
#activity.finished = timezone.now()
activity.save()
return
\ No newline at end of file
...@@ -55,3 +55,4 @@ django-nose==1.4.7 ...@@ -55,3 +55,4 @@ django-nose==1.4.7
nose-exclude==0.5.0 nose-exclude==0.5.0
factory_boy==3.2.1 factory_boy==3.2.1
passlib==1.7.4 passlib==1.7.4
djangorestframework==3.13.1
\ No newline at end of file
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