Commit dc4d4e55 by Karsa Zoltán István

Merge branch 'iac-api'

parents 796da6b9 42092a44
......@@ -379,6 +379,8 @@ THIRD_PARTY_APPS = (
'statici18n',
'simplesshkey',
'pipeline',
'rest_framework',
'rest_framework.authtoken',
)
......@@ -401,6 +403,15 @@ INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
########## END APP CONFIGURATION
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAdminUser'
),
}
########## LOGGING CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#logging
# A sample logging configuration. The only tangible logging
......
......@@ -20,7 +20,7 @@ from django.views.generic import TemplateView
from django.conf import settings
from django.contrib import admin
from django.urls import reverse
from django.urls import reverse, path
from django.shortcuts import redirect
from django.contrib.auth.views import (
PasswordResetView, PasswordResetConfirmView
......@@ -33,6 +33,8 @@ from dashboard.views import (
from dashboard.forms import CirclePasswordResetForm, CircleSetPasswordForm
from firewall.views import add_blacklist_item
from rest_framework.authtoken.views import obtain_auth_token
admin.autodiscover()
urlpatterns = [
......@@ -71,6 +73,9 @@ urlpatterns = [
name="info.support"),
url(r'^info/resize-how-to/$', ResizeHelpView.as_view(),
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:
......
......@@ -250,6 +250,7 @@ class ActivityModel(TimeStampedModel):
return 'failed'
@celery.task()
def compute_cached(method, instance, memcached_seconds,
key, start, *args, **kwargs):
......
......@@ -61,6 +61,15 @@ except NameError:
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():
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 @@
{% trans "Email address" %}: {{ profile.email }}
{% endif %}
</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>
{% if request.user == profile %}
<p>
......
......@@ -18,6 +18,7 @@
from django.conf.urls import url
from django.urls import path
from vm.models import Instance
from .views import (
......@@ -52,18 +53,63 @@ from .views import (
TransferInstanceOwnershipView, TransferInstanceOwnershipConfirmView,
TransferTemplateOwnershipView, TransferTemplateOwnershipConfirmView,
OpenSearchDescriptionView,
NodeActivityView,
UserList,
StorageDetail, DiskDetail,
NodeActivityView,
UserList, TemplateREST, LeaseREST, DiskRest, InstanceREST,
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,
SetupPortREST, RulesREST,
EnableTwoFactorView, DisableTwoFactorView,
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
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"^profile/list/$", UserList.as_view(),
name="dashboard.views.user-list"),
......
......@@ -27,7 +27,7 @@ from django.contrib.auth.models import User, Group
from django.contrib.messages.views import SuccessMessageMixin
from django.core.exceptions import PermissionDenied, SuspiciousOperation
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.utils.translation import ugettext as _
from django.views.generic import UpdateView, TemplateView
......@@ -35,6 +35,12 @@ from django.views.generic.detail import SingleObjectMixin
from django_tables2 import SingleTableView
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 .util import (CheckedDetailView, AclUpdateView, search_user,
saml_available, DeleteViewBase)
......@@ -46,6 +52,8 @@ from ..models import FutureMember, GroupProfile
from ..store_api import Store, NoStoreException
from ..tables import GroupListTable
from dashboard.serializers import GroupSerializer
try:
# Python 2: "unicode" is built-in
unicode
......@@ -55,6 +63,33 @@ except NameError:
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):
@classmethod
......
......@@ -31,6 +31,12 @@ from django.template.loader import render_to_string
from django.utils.translation import ugettext as _
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 django_tables2 import SingleTableView
......@@ -45,6 +51,35 @@ from .util import AjaxOperationMixin, OperationView, GraphMixin, DeleteViewBase
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):
ops = []
......
......@@ -21,14 +21,20 @@ from django.urls import reverse
from django.db.models import Q
from django.utils.translation import ugettext_lazy as _
from django.views.generic import UpdateView
from django.http import JsonResponse
from braces.views import SuperuserRequiredMixin
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 storage.models import DataStore, Disk
from ..tables import DiskListTable
from ..forms import DataStoreForm, DiskForm
from dashboard.serializers import DiskSerializer
class StorageDetail(SuperuserRequiredMixin, UpdateView):
......@@ -138,3 +144,27 @@ class DiskDetail(SuperuserRequiredMixin, UpdateView):
def form_valid(self, form):
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 @@
from datetime import timedelta
import json
import logging
from string import Template
from xml.dom import NotFoundErr
from django.contrib import messages
from django.contrib.auth.models import User
......@@ -26,7 +28,7 @@ from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse, reverse_lazy
from django.core.exceptions import PermissionDenied, SuspiciousOperation
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.utils import timezone
from django.utils.translation import ugettext as _, ugettext_noop
......@@ -56,6 +58,13 @@ from .util import (
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:
# Python 2: "unicode" is built-in
unicode
......@@ -184,6 +193,178 @@ class TemplateCreate(SuccessMessageMixin, CreateView):
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):
model = InstanceTemplate
......
......@@ -18,6 +18,7 @@
import json
import logging
from multiprocessing import context
import pyotp
......@@ -31,7 +32,7 @@ from django.core.exceptions import PermissionDenied, SuspiciousOperation
from django.urls import reverse, reverse_lazy
from django.core.paginator import Paginator, InvalidPage
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.templatetags.static import static
from django.utils.translation import ugettext as _
......@@ -41,6 +42,12 @@ from django.views.generic import (
)
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 django_tables2 import SingleTableView, LazyPaginator
......@@ -56,8 +63,9 @@ from ..models import Profile, GroupProfile, ConnectCommand
from ..tables import (
UserKeyListTable, ConnectCommandListTable, UserListTable,
)
from rest_framework.authtoken.models import Token
from .util import saml_available, DeleteViewBase, LoginView
from dashboard.serializers import UserSerializer
try:
# Python 2: "unicode" is built-in
......@@ -67,6 +75,30 @@ except NameError:
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):
if user.is_superuser:
......@@ -387,6 +419,8 @@ class ProfileView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
context['perm_email'] = (
context['perm_group_list'] or self.request.user == user)
context['iac_token'] = Token.objects.filter(user=user).get()
# filter the virtual machine list
# 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
......
......@@ -51,8 +51,18 @@ from braces.views import LoginRequiredMixin
from braces.views._access import AccessMixin
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 ..models import GroupProfile, Profile
from vm.models import InstanceActivity
from ..forms import TransferOwnershipForm
......@@ -67,6 +77,36 @@ logger = logging.getLogger(__name__)
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):
redirect_exception_classes = (PermissionDenied, )
......
......@@ -18,7 +18,7 @@
from django import contrib
# from django.utils.translation import ugettext_lazy as _
from .models import Disk, DataStore
from .models import Disk, DataStore, StorageActivity
class DiskAdmin(contrib.admin.ModelAdmin):
......@@ -32,3 +32,4 @@ class DataStoreAdmin(contrib.admin.ModelAdmin):
contrib.admin.site.register(Disk, DiskAdmin)
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
from sizefield.models import FileSizeField
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
......@@ -622,3 +622,23 @@ class Disk(TimeStampedModel):
@property
def is_exportable(self):
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,11 +580,15 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
"""
user_instances = []
missing_users = []
instances = []
for user_id in users:
try:
user_instances.append(User.objects.get(profile__org_id=user_id))
except User.DoesNotExist:
missing_users.append(user_id)
try:
user_instances.append(User.objects.get(username=user_id))
except User.DoesNotExist:
missing_users.append(user_id)
for user in user_instances:
instance = cls.create_from_template(template, user, **kwargs)
......@@ -592,9 +596,36 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
instance.set_level(User.objects.get(username=admin), 'owner')
if 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):
self.time_of_suspend, self.time_of_delete = self.get_renew_times()
......
......@@ -86,6 +86,14 @@ class Interface(Model):
return self.host.mac
except:
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
def generate_mac(cls, instance, vlan):
......
......@@ -36,11 +36,11 @@ from django.urls import reverse
from django.db.models import Q
from django.utils import timezone
from django.utils.translation import ugettext as _, ugettext_noop
from re import search
from re import search, template
from sizefield.utils import filesizeformat
from common.models import (
create_readable, humanize_exception, HumanReadableException
ActivityModel, create_readable, humanize_exception, HumanReadableException
)
from common.operations import Operation, register_operation, SubOperationMixin
from dashboard.store_api import Store, NoStoreException
......@@ -172,6 +172,38 @@ class InstanceOperation(Operation):
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):
remote_queue = ('vm', 'fast')
......@@ -258,6 +290,7 @@ class CreateDiskOperation(InstanceOperation):
description = _("Create and attach empty disk to the virtual machine.")
required_perms = ('storage.create_empty_disk',)
accept_states = ('STOPPED', 'PENDING', 'RUNNING')
concurrency_check = False
def _operation(self, user, size, activity, name=None):
from storage.models import Disk
......@@ -330,6 +363,7 @@ class DownloadDiskOperation(InstanceOperation):
required_perms = ('storage.download_disk',)
accept_states = ('STOPPED', 'PENDING', 'RUNNING')
async_queue = "localhost.man.slow"
concurrency_check = False # warning!!!
def _operation(self, user, url, task, activity, name=None):
disk = Disk.download(url=url, name=name, task=task)
......@@ -341,15 +375,19 @@ class DownloadDiskOperation(InstanceOperation):
disk.save()
self.instance.disks.add(disk)
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(
"Downloading %(url)s is finished. The file md5sum "
"is: '%(checksum)s'."),
url=url, checksum=disk.checksum)
"is: '%(checksum)s' (id: %(disk_id)d)."),
url=url, checksum=disk.checksum, disk_id=disk.id, disk_size=disk.size)
# TODO iso (cd) hot-plug is not supported by kvm/guests
if self.instance.is_running and disk.type not in ["iso"]:
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
......@@ -891,8 +929,8 @@ class SaveAsTemplateOperation(InstanceOperation):
raise
else:
return create_readable(
ugettext_noop("New template: %(template)s"),
template=reverse('dashboard.views.template-detail',
ugettext_noop("New template: %(template)s (%(template_id)s)"),
template_id=tmpl.id, template=reverse('dashboard.views.template-detail',
kwargs={'pk': tmpl.pk}))
......@@ -913,7 +951,7 @@ class ShutdownOperation(AbortableRemoteOperationMixin,
remote_queue = ("vm", "slow")
remote_timeout = 180
def _operation(self, task):
def _operation(self, task=vm_tasks.shutdown):
super(ShutdownOperation, self)._operation(task=task)
self.instance.yield_node()
......
......@@ -15,7 +15,10 @@
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from datetime import timezone
from celery.contrib.abortable import AbortableTask
from common.models import ActivityModel
from storage.models import Disk, StorageActivity
from manager.mancelery import celery
......@@ -53,3 +56,21 @@ def abortable_async_node_operation(task, operation_id, node_pk, activity_pk,
allargs['task'] = task
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
nose-exclude==0.5.0
factory_boy==3.2.1
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