Commit b4fee3ab by Szabolcs Gelencser

Add automatic lease creation, renew operation

parent 895f7d98
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true"> <inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredPackages"> <option name="ignoredPackages">
<value> <value>
<list size="9"> <list size="11">
<item index="0" class="java.lang.String" itemvalue="salt" /> <item index="0" class="java.lang.String" itemvalue="salt" />
<item index="1" class="java.lang.String" itemvalue="six" /> <item index="1" class="java.lang.String" itemvalue="six" />
<item index="2" class="java.lang.String" itemvalue="netaddr" /> <item index="2" class="java.lang.String" itemvalue="netaddr" />
...@@ -24,6 +24,8 @@ ...@@ -24,6 +24,8 @@
<item index="6" class="java.lang.String" itemvalue="lxml" /> <item index="6" class="java.lang.String" itemvalue="lxml" />
<item index="7" class="java.lang.String" itemvalue="MarkupSafe" /> <item index="7" class="java.lang.String" itemvalue="MarkupSafe" />
<item index="8" class="java.lang.String" itemvalue="pylibmc" /> <item index="8" class="java.lang.String" itemvalue="pylibmc" />
<item index="9" class="java.lang.String" itemvalue="pytz" />
<item index="10" class="java.lang.String" itemvalue="simplejson" />
</list> </list>
</value> </value>
</option> </option>
......
No preview for this file type
...@@ -2,8 +2,11 @@ ...@@ -2,8 +2,11 @@
"admin": "is_admin:True or (role:admin and is_admin_project:True)", "admin": "is_admin:True or (role:admin and is_admin_project:True)",
"owner": "project_id:%(project_id)s", "owner": "project_id:%(project_id)s",
"circle_admin": "role:circle_admin and project_id:%(project_id)s", "circle_admin": "role:circle_admin and project_id:%(project_id)s",
"default": "rule:admin",
"template:create": "rule:admin or rule:owner or rule:circle_admin", "template:create": "rule:admin or rule:owner or rule:circle_admin",
"request:decide": "rule:admin or rule:circle_admin" "request:decide": "rule:admin or rule:circle_admin",
"lease:manage": "rule:admin or rule:circle_admin"
} }
\ No newline at end of file
...@@ -593,7 +593,17 @@ DEFAULT_PUBLIC_ROUTER_NAME_FOR_USER = "default_public" ...@@ -593,7 +593,17 @@ DEFAULT_PUBLIC_ROUTER_NAME_FOR_USER = "default_public"
DEFAULT_PUBLIC_ROUTED_NET_NAME_FOR_USER = "default_public_routed" DEFAULT_PUBLIC_ROUTED_NET_NAME_FOR_USER = "default_public_routed"
OPENSTACK_KEYSTONE_DEFAULT_DOMAIN="bme" OPENSTACK_KEYSTONE_DEFAULT_DOMAIN="bme"
OPENSTACK_KEYSTONE_URL="https://oscircle.guest.ik.bme.hu:5000" OPENSTACK_KEYSTONE_URL="https://oscircle.guest.ik.bme.hu:5000/v3"
WEBSSO_ENABLED = True #TODO: it is always enabled, refactor openstack_auth WEBSSO_ENABLED = True #TODO: it is always enabled, refactor openstack_auth
OPENSTACK_SSL_NO_VERIFY = True OPENSTACK_SSL_NO_VERIFY = True
\ No newline at end of file
OPENSTACK_CIRCLE_DOMAIN_ID = "0cfb6010abcc405ebd19eb5b8f72949d"
OPENSTACK_CIRCLE_USERID = "97980e2747994acba7982d3262c1f3e2"
OPENSTACK_CIRCLE_PASSWORD = "a"
OPENSTACK_INTERFACE = "public"
DEFAULT_LEASE_NAME = "default"
DEFAULT_LEASE_SUSPEND_SECONDS = 3600
DEFAULT_LEASE_DELETE_SECONDS = 7200
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
</a> </a>
{% if object.active and lease_types and not request.token_user %} {% if object.active and lease_types and not request.token_user %}
<a class="btn btn-primary" id="vm-renew-request-lease-button" <a class="btn btn-primary" id="vm-renew-request-lease-button"
href="{% url "request.views.request-lease" vm_pk=object.pk %}"> href="{% url "request.views.request-lease" vm_pk=object.id %}">
<i class="fa fa-forward"></i> <i class="fa fa-forward"></i>
{% trans "Request longer lease" %} {% trans "Request longer lease" %}
</a> </a>
......
...@@ -6,97 +6,16 @@ ...@@ -6,97 +6,16 @@
{% block content %} {% block content %}
<div class="row"> <div class="row">
<div class="col-md-7"> <div class="panel panel-default">
<div class="panel panel-default"> <div class="panel-heading">
<div class="panel-heading"> <a class="pull-right btn btn-default btn-xs" href="{% url "dashboard.views.template-list" %}">{% trans "Back" %}</a>
<a class="pull-right btn btn-default btn-xs" href="{% url "dashboard.views.template-list" %}">{% trans "Back" %}</a> <h3 class="no-margin"><i class="fa fa-clock-o"></i> {% trans "Edit lease" %}</h3>
<h3 class="no-margin"><i class="fa fa-clock-o"></i> {% trans "Edit lease" %}</h3>
</div>
<div class="panel-body">
{% with form=form %}
{% include "display-form-errors.html" %}
{% endwith %}
{% crispy form %}
</div>
</div> </div>
</div> <div class="panel-body">
{% with form=form %}
<div class="col-md-5"> {% include "display-form-errors.html" %}
<div class="panel panel-default"> {% endwith %}
<div class="panel-heading"> {% crispy form %}
<h4 class="no-margin"><i class="icon-group"></i> {% trans "Manage access" %}</h4>
</div>
<div class="panel-body">
<form action="{% url "dashboard.views.lease-acl" pk=object.pk %}" method="post">{% csrf_token %}
<table class="table table-striped table-with-form-fields" id="template-access-table">
<thead>
<tr>
<th></th>
<th>{% trans "Who" %}</th>
<th>{% trans "What" %}</th>
<th><i class="icon-remove"></i></th>
</tr>
</thead>
<tbody>
{% for i in acl.users %}
<tr>
<td>
<i class="icon-user"></i>
</td>
<td>
<a href="{% url "dashboard.views.profile" username=i.user.username %}"
title="{{ i.user.username }}">
{% include "dashboard/_display-name.html" with user=i.user show_org=True %}
</a>
</td>
<td>
<select class="form-control" name="perm-u-{{i.user.id}}">
{% for id, name in acl.levels %}
<option{%if id == i.level%} selected="selected"{%endif%} value="{{id}}">{{name}}</option>
{% endfor %}
</select>
</td>
<td>
<input type="checkbox" name="remove-u-{{i.user.id}}" title="{% trans "Remove" %}"/>
</td>
</tr>
{% endfor %}
{% for i in acl.groups %}
<tr>
<td><i class="icon-group"></i></td>
<td>
<a href="{% url "dashboard.views.group-detail" pk=i.group.pk %}">
{{i.group}}
</a>
</td>
<td>
<select class="form-control" name="perm-g-{{i.group.id}}">
{% for id, name in acl.levels %}
<option{%if id == i.level%} selected="selected"{%endif%} value="{{id}}">{{name}}</option>
{% endfor %}
</select>
</td>
<td>
<input type="checkbox" name="remove-g-{{i.group.id}}" title="{% trans "Remove" %}"/>
</td>
</tr>
{% endfor %}
<tr><td><i class="icon-plus"></i></td>
<td><input type="text" class="form-control" name="name"
placeholder="{% trans "Name of group or user" %}"></td>
<td><select class="form-control" name="level">
{% for id, name in acl.levels %}
<option value="{{id}}">{{name}}</option>
{% endfor %}
</select></td><td></td>
</tr>
</tbody>
</table>
<div class="form-actions">
<button type="submit" class="btn btn-success">{% trans "Save" %}</button>
</div>
</form>
</div>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -47,12 +47,10 @@ ...@@ -47,12 +47,10 @@
<div class="col-md-6"> <div class="col-md-6">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
{% if perms.vm.create_leases %}
<a href="{% url "dashboard.views.lease-create" %}" <a href="{% url "dashboard.views.lease-create" %}"
class="pull-right btn btn-success btn-xs" style="margin-right: 10px;"> class="pull-right btn btn-success btn-xs" style="margin-right: 10px;">
<i class="fa fa-plus"></i> {% trans "new lease" %} <i class="fa fa-plus"></i> {% trans "new lease" %}
</a> </a>
{% endif %}
<h3 class="no-margin"><i class="fa fa-clock-o"></i> {% trans "Leases" %}</h3> <h3 class="no-margin"><i class="fa fa-clock-o"></i> {% trans "Leases" %}</h3>
</div> </div>
<div class="panel-body"> <div class="panel-body">
......
...@@ -56,7 +56,7 @@ ...@@ -56,7 +56,7 @@
<div id="home_expiration_and_lease"> <div id="home_expiration_and_lease">
<h4> <h4>
{% trans "Expiration" %} {% trans "Expiration" %}
{# {% if instance.is_expiring %}<i class="fa fa-warning-sign text-danger"></i>{% endif %}#} {% if is_expiring %}<i class="fa fa-warning-sign text-danger"></i>{% endif %}
<span id="vm-details-renew-op"> <span id="vm-details-renew-op">
{% with op=op.renew %}{% if op %} {% with op=op.renew %}{% if op %}
<a href="{{op.get_url}}" class="btn btn-xs operation operation-{{ op.op }} <a href="{{op.get_url}}" class="btn btn-xs operation operation-{{ op.op }}
...@@ -70,14 +70,14 @@ ...@@ -70,14 +70,14 @@
<dl> <dl>
<dt>{% trans "Suspended at:" %}</dt> <dt>{% trans "Suspended at:" %}</dt>
<dd> <dd>
<span title="{{ instance.time_of_suspend }}"> <span title="{{ time_of_suspend }}">
<i class="fa fa-moon-o"></i> {{ instance.time_of_suspend|arrowfilter:LANGUAGE_CODE }} <i class="fa fa-moon-o"></i> {{ time_of_suspend|arrowfilter:LANGUAGE_CODE }}
</span> </span>
</dd> </dd>
<dt>{% trans "Destroyed at:" %}</dt> <dt>{% trans "Destroyed at:" %}</dt>
<dd> <dd>
<span title="{{ instance.time_of_delete }}"> <span title="{{ time_of_delete }}">
<i class="fa fa-times"></i> {{ instance.time_of_delete|arrowfilter:LANGUAGE_CODE }} <i class="fa fa-times"></i> {{ time_of_delete|arrowfilter:LANGUAGE_CODE }}
</span> </span>
</dd> </dd>
</dl> </dl>
......
...@@ -19,7 +19,7 @@ from __future__ import absolute_import ...@@ -19,7 +19,7 @@ from __future__ import absolute_import
from dashboard.views.autocomplete import AclUserGroupAutocomplete, AclUserAutocomplete from dashboard.views.autocomplete import AclUserGroupAutocomplete, AclUserAutocomplete
from dashboard.views.template import TemplateList, TemplateChoose, TemplateDetail, TemplateDelete, \ from dashboard.views.template import TemplateList, TemplateChoose, TemplateDetail, TemplateDelete, \
TransferTemplateOwnershipConfirmView, TransferTemplateOwnershipView TransferTemplateOwnershipConfirmView, TransferTemplateOwnershipView, LeaseCreate, LeaseDetail, LeaseDelete
from dashboard.views.vm import VmDetailView, VmList, VmCreate, vm_activity, vm_ops, FavouriteView, VmPlainImageCreate from dashboard.views.vm import VmDetailView, VmList, VmCreate, vm_activity, vm_ops, FavouriteView, VmPlainImageCreate
from django.conf.urls import url from django.conf.urls import url
...@@ -34,12 +34,12 @@ urlpatterns = [ ...@@ -34,12 +34,12 @@ urlpatterns = [
# url(r'^profile/create/$', # url(r'^profile/create/$',
# UserCreationView.as_view(), # UserCreationView.as_view(),
# name="dashboard.views.user-create"), # name="dashboard.views.user-create"),
# url(r'^lease/(?P<pk>\d+)/$', LeaseDetail.as_view(), url(r'^lease/(?P<pk>\d+)/$', LeaseDetail.as_view(),
# name="dashboard.views.lease-detail"), name="dashboard.views.lease-detail"),
# url(r'^lease/create/$', LeaseCreate.as_view(), url(r'^lease/create/$', LeaseCreate.as_view(),
# name="dashboard.views.lease-create"), name="dashboard.views.lease-create"),
# url(r'^lease/delete/(?P<pk>\d+)/$', LeaseDelete.as_view(), url(r'^lease/delete/(?P<pk>\d+)/$', LeaseDelete.as_view(),
# name="dashboard.views.lease-delete"), name="dashboard.views.lease-delete"),
# url(r'^lease/(?P<pk>\d+)/acl/$', LeaseAclUpdateView.as_view(), # url(r'^lease/(?P<pk>\d+)/acl/$', LeaseAclUpdateView.as_view(),
# name="dashboard.views.lease-acl"), # name="dashboard.views.lease-acl"),
# #
......
...@@ -21,6 +21,7 @@ import json ...@@ -21,6 +21,7 @@ import json
import logging import logging
import openstack_api import openstack_api
from braces.views._access import AccessMixin
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
...@@ -197,16 +198,19 @@ class TemplateList(LoginRequiredMixin, FilterMixin, SingleTableView): ...@@ -197,16 +198,19 @@ class TemplateList(LoginRequiredMixin, FilterMixin, SingleTableView):
def get_context_data(self, *args, **kwargs): def get_context_data(self, *args, **kwargs):
context = super(TemplateList, self).get_context_data(*args, **kwargs) context = super(TemplateList, self).get_context_data(*args, **kwargs)
user = self.request.user user = self.request.user
# leases_w_operator = Lease.get_objects_with_level("operator", user)
# context['lease_table'] = LeaseListTable( from django.utils.module_loading import import_string
# leases_w_operator, request=self.request, check = import_string("openstack_auth.policy.check")
# template="django_tables2/table_no_page.html",
# ) os_policy_actions = (("circle", "lease:manage"),)
context['show_lease_table'] = ( has_lease_rights = check(os_policy_actions, self.request)
# leases_w_operator.count() > 0 or
# user.has_perm("vm.create_leases") leases = Lease.objects.all()
False context['lease_table'] = LeaseListTable(
leases, request=self.request,
template="django_tables2/table_no_page.html",
) )
context['show_lease_table'] = has_lease_rights
context['search_form'] = self.search_form context['search_form'] = self.search_form
...@@ -382,86 +386,80 @@ class TemplateDetail(LoginRequiredMixin, GraphMixin, SuccessMessageMixin, Update ...@@ -382,86 +386,80 @@ class TemplateDetail(LoginRequiredMixin, GraphMixin, SuccessMessageMixin, Update
# def get_success_url(self): # def get_success_url(self):
# return self.request.POST.get("next") or "/" # return self.request.POST.get("next") or "/"
class PolicyMixin(AccessMixin):
os_policy_actions = None
def has_permission(self):
if self.os_policy_actions:
from django.utils.module_loading import import_string
check = import_string("openstack_auth.policy.check")
return check(self.os_policy_actions, self.request)
return False
class LeaseCreate(LoginRequiredMixin, PermissionRequiredMixin, def dispatch(self, request, *args, **kwargs):
if not self.has_permission():
return self.handle_no_permission(request)
return super(PolicyMixin, self).dispatch(request, *args, **kwargs)
class LeaseCreate(LoginRequiredMixin, PolicyMixin,
SuccessMessageMixin, CreateView): SuccessMessageMixin, CreateView):
model = Lease model = Lease
form_class = LeaseForm form_class = LeaseForm
permission_required = 'vm.create_leases'
template_name = "dashboard/lease-create.html" template_name = "dashboard/lease-create.html"
success_message = _("Successfully created a new lease.") success_message = _("Successfully created a new lease.")
os_policy_actions = (("circle", "lease:manage"),)
def get_success_url(self): def get_success_url(self):
return reverse_lazy("dashboard.views.template-list") return reverse_lazy("dashboard.views.template-list")
def form_valid(self, form):
retval = super(LeaseCreate, self).form_valid(form)
self.object.set_level(self.request.user, "owner")
return retval
# class LeaseAclUpdateView(AclUpdateView):
class LeaseAclUpdateView(AclUpdateView): # model = Lease
model = Lease
class LeaseDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView): class LeaseDetail(LoginRequiredMixin, PolicyMixin,
SuccessMessageMixin, UpdateView):
model = Lease model = Lease
form_class = LeaseForm form_class = LeaseForm
template_name = "dashboard/lease-edit.html" template_name = "dashboard/lease-edit.html"
success_message = _("Successfully modified lease.") success_message = _("Successfully modified lease.")
os_policy_actions = (("circle", "lease:manage"),)
def get_context_data(self, *args, **kwargs):
obj = self.get_object()
context = super(LeaseDetail, self).get_context_data(*args, **kwargs)
context['acl'] = AclUpdateView.get_acl_data(
obj, self.request.user, 'dashboard.views.lease-acl')
return context
def get_success_url(self): def get_success_url(self):
return reverse_lazy("dashboard.views.lease-detail", kwargs=self.kwargs) return reverse_lazy("dashboard.views.lease-detail", kwargs=self.kwargs)
def get(self, request, *args, **kwargs):
if not self.get_object().has_level(request.user, "owner"):
message = _("Only the owners can modify the selected lease.")
messages.warning(request, message)
return redirect(reverse_lazy("dashboard.views.template-list"))
return super(LeaseDetail, self).get(request, *args, **kwargs)
def post(self, request, *args, **kwargs): class LeaseDelete(DeleteViewBase):
if not self.get_object().has_level(request.user, "owner"): model = Lease
raise PermissionDenied() success_message = _("Lease successfully deleted.")
os_policy_actions = (("circle", "lease:manage"),)
def get_success_url(self):
return reverse("dashboard.views.template-list")
def get_context_data(self, *args, **kwargs):
c = super(LeaseDelete, self).get_context_data(*args, **kwargs)
lease = self.get_object()
templates = lease.instancetemplate_set
if templates.count() > 0:
text = _("You can't delete this lease because some templates "
"are still using it, modify these to proceed: ")
c['text'] = text + ", ".join("<strong>%s (#%d)</strong>"
"" % (o.name, o.pk)
for o in templates.all())
c['disable_submit'] = True
return c
def delete_obj(self, request, *args, **kwargs):
object = self.get_object()
if object.instancetemplate_set.count() > 0:
raise SuspiciousOperation()
object.delete()
return super(LeaseDetail, self).post(request, *args, **kwargs)
#
# class LeaseDelete(DeleteViewBase):
# model = Lease
# success_message = _("Lease successfully deleted.")
#
# def get_success_url(self):
# return reverse("dashboard.views.template-list")
#
# def get_context_data(self, *args, **kwargs):
# c = super(LeaseDelete, self).get_context_data(*args, **kwargs)
# lease = self.get_object()
# templates = lease.instancetemplate_set
# if templates.count() > 0:
# text = _("You can't delete this lease because some templates "
# "are still using it, modify these to proceed: ")
#
# c['text'] = text + ", ".join("<strong>%s (#%d)</strong>"
# "" % (o.name, o.pk)
# for o in templates.all())
# c['disable_submit'] = True
# return c
#
# def delete_obj(self, request, *args, **kwargs):
# object = self.get_object()
# if object.instancetemplate_set.count() > 0:
# raise SuspiciousOperation()
# object.delete()
#
#
class TransferTemplateOwnershipConfirmView(TransferOwnershipConfirmView): class TransferTemplateOwnershipConfirmView(TransferOwnershipConfirmView):
template = "dashboard/confirm/transfer-template-ownership.html" template = "dashboard/confirm/transfer-template-ownership.html"
model = InstanceTemplate model = InstanceTemplate
......
...@@ -725,7 +725,7 @@ class TransferOwnershipConfirmView(LoginRequiredMixin, View): ...@@ -725,7 +725,7 @@ class TransferOwnershipConfirmView(LoginRequiredMixin, View):
class DeleteViewBase(LoginRequiredMixin, DeleteView): class DeleteViewBase(LoginRequiredMixin, DeleteView):
level = 'owner' os_policy_actions = None
def get_template_names(self): def get_template_names(self):
if self.request.is_ajax(): if self.request.is_ajax():
...@@ -734,7 +734,12 @@ class DeleteViewBase(LoginRequiredMixin, DeleteView): ...@@ -734,7 +734,12 @@ class DeleteViewBase(LoginRequiredMixin, DeleteView):
return ['dashboard/confirm/base-delete.html'] return ['dashboard/confirm/base-delete.html']
def check_auth(self): def check_auth(self):
if not self.get_object().has_level(self.request.user, self.level): from django.utils.module_loading import import_string
check = import_string("openstack_auth.policy.check")
has_rights = check(self.os_policy_actions, self.request,
{'project_id': self.request.user.tenant_id})
if not has_rights:
raise PermissionDenied() raise PermissionDenied()
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
......
...@@ -50,12 +50,13 @@ from firewall.models import Vlan, Host, Rule ...@@ -50,12 +50,13 @@ from firewall.models import Vlan, Host, Rule
# from manager.scheduler import SchedulerError # from manager.scheduler import SchedulerError
from network.models import DefaultPublicRouter, DefaultPublicRoutedNet from network.models import DefaultPublicRouter, DefaultPublicRoutedNet
from openstack_api.nova import Server from openstack_api.nova import Server
from request.forms import TemplateRequestForm from request.forms import TemplateRequestForm, LeaseRequestForm
from request.models import TemplateAccessType from request.models import TemplateAccessType, LeaseType
from storage.models import Disk from storage.models import Disk
from vm.models import ( from vm.models import (
Instance, InstanceActivity, Interface, Instance, InstanceActivity, Interface,
InstanceTemplate) InstanceTemplate, Lease)
from vm.models.vm_lease import VmLease
from .util import ( from .util import (
CheckedDetailView, AjaxOperationMixin, OperationView, AclUpdateView, CheckedDetailView, AjaxOperationMixin, OperationView, AclUpdateView,
...@@ -63,7 +64,7 @@ from .util import ( ...@@ -63,7 +64,7 @@ from .util import (
) )
from ..forms import ( from ..forms import (
AclUserOrGroupAddForm, VmResourcesForm, VmCustomizeForm, VmDeployForm, VmFromPlainImageForm, VmRemoveInterfaceForm, AclUserOrGroupAddForm, VmResourcesForm, VmCustomizeForm, VmDeployForm, VmFromPlainImageForm, VmRemoveInterfaceForm,
VmAddInterfaceForm, VmSaveForm, VmPortAddForm, VmPublicIpAddForm, VmPublicIpRemoveForm) VmAddInterfaceForm, VmSaveForm, VmPortAddForm, VmPublicIpAddForm, VmPublicIpRemoveForm, VmRenewForm)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
...@@ -141,6 +142,15 @@ class VmDetailView(LoginRequiredMixin, GraphMixin, DetailView): ...@@ -141,6 +142,15 @@ class VmDetailView(LoginRequiredMixin, GraphMixin, DetailView):
'instance': self.object 'instance': self.object
}) })
vm_lease = VmLease.get_or_create_lease(instance.id)
context.update({
'time_of_suspend': vm_lease.time_of_suspend,
'time_of_delete': vm_lease.time_of_delete,
'is_expiring': vm_lease.is_delete_expiring() or
vm_lease.is_suspend_expiring() and instance.status is "ACTIVE",
})
# activity data # activity data
# activities = instance.get_merged_activities(user) # activities = instance.get_merged_activities(user)
# show_show_all = len(activities) > 10 # show_show_all = len(activities) > 10
...@@ -705,40 +715,39 @@ class VmSaveView(FormOperationMixin, VmOperationView): ...@@ -705,40 +715,39 @@ class VmSaveView(FormOperationMixin, VmOperationView):
# return user # return user
# #
# #
# class VmRenewView(FormOperationMixin, TokenOperationView, VmOperationView): class VmRenewView(FormOperationMixin, VmOperationView):
# op = 'renew'
# op = 'renew' icon = 'calendar'
# icon = 'calendar' effect = 'success'
# effect = 'success' show_in_toolbar = False
# show_in_toolbar = False form_class = VmRenewForm
# form_class = VmRenewForm template_name = 'dashboard/_vm-renew.html'
# wait_for_result = 0.5
# template_name = 'dashboard/_vm-renew.html' def get_form_kwargs(self):
# with_reload = True choices = Lease.objects.all() #TODO: filter on permissions
# instance = self.get_op().instance
# def get_form_kwargs(self): default = VmLease.objects.get(os_server_id=instance.id).lease
# choices = Lease.get_objects_with_level("user", self.request.user) # if default and default not in choices:
# default = self.get_op().instance.lease # choices = (choices.distinct() |
# if default and default not in choices: # Lease.objects.filter(pk=default.pk).distinct())
# choices = (choices.distinct() |
# Lease.objects.filter(pk=default.pk).distinct()) val = super(VmRenewView, self).get_form_kwargs()
# val.update({'choices': choices, 'default': default})
# val = super(VmRenewView, self).get_form_kwargs() return val
# val.update({'choices': choices, 'default': default})
# return val def get_response_data(self, result, done, extra=None, **kwargs):
# extra = super(VmRenewView, self).get_response_data(result, done,
# def get_response_data(self, result, done, extra=None, **kwargs): extra, **kwargs)
# extra = super(VmRenewView, self).get_response_data(result, done, instance = self.get_op().instance
# extra, **kwargs) time_of_suspend = VmLease.objects.get(os_server_id=instance.id).time_of_suspend
# extra["new_suspend_time"] = unicode(self.get_op(). extra["new_suspend_time"] = unicode(time_of_suspend)
# instance.time_of_suspend) return extra
# return extra
# def get_context_data(self, **kwargs):
# def get_context_data(self, **kwargs): context = super(VmRenewView, self).get_context_data(**kwargs)
# context = super(VmRenewView, self).get_context_data(**kwargs) context['lease_request_form'] = LeaseRequestForm(request=self.request)
# context['lease_request_form'] = LeaseRequestForm(request=self.request) context['lease_types'] = LeaseType.objects.exists()
# context['lease_types'] = LeaseType.objects.exists() return context
# return context
# #
# #
# class VmStateChangeView(FormOperationMixin, VmOperationView): # class VmStateChangeView(FormOperationMixin, VmOperationView):
...@@ -844,7 +853,7 @@ vm_ops = OrderedDict([ ...@@ -844,7 +853,7 @@ vm_ops = OrderedDict([
# ('add_port', VmPortAddView), # ('add_port', VmPortAddView),
('add_public_ip', VmPublicIpAddView), ('add_public_ip', VmPublicIpAddView),
('remove_public_ip', VmPublicIpRemoveView), ('remove_public_ip', VmPublicIpRemoveView),
# ('renew', VmRenewView), ('renew', VmRenewView),
# ('resources_change', VmResourcesChangeView), # ('resources_change', VmResourcesChangeView),
# ('password_reset', VmOperationView.factory( # ('password_reset', VmOperationView.factory(
# op='password_reset', icon='unlock', effect='warning', # op='password_reset', icon='unlock', effect='warning',
......
{% load i18n %} {% load i18n %}
{% load crispy_forms_tags %} {% load crispy_forms_tags %}
<form action="{% url "request.views.request-lease" vm_pk=vm.pk %}" method="POST"> <form action="{% url "request.views.request-lease" vm_pk=vm.id %}" method="POST">
{% include "display-form-errors.html" %} {% include "display-form-errors.html" %}
{% csrf_token %} {% csrf_token %}
{{ form.lease|as_crispy_field }} {{ form.lease|as_crispy_field }}
......
# noqa default_app_config = 'vm.startup.DefaultLeaseConfig'
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2018-05-15 09:51
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('vm', '0011_auto_20180420_1054'),
]
operations = [
migrations.CreateModel(
name='VmLease',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('os_server_id', models.CharField(max_length=100, unique=True)),
('time_of_suspend', models.DateTimeField(blank=True, default=None, help_text='Proposed time of automatic suspension.', null=True, verbose_name='time of suspend')),
('time_of_delete', models.DateTimeField(blank=True, default=None, help_text='Proposed time of automatic deletion.', null=True, verbose_name='time of delete')),
('lease', models.ForeignKey(help_text='Preferred expiration periods.', on_delete=django.db.models.deletion.CASCADE, to='vm.Lease', verbose_name='Lease')),
],
),
migrations.AddField(
model_name='instancetemplate',
name='lease',
field=models.ForeignKey(default=0, help_text='Preferred expiration periods.', on_delete=django.db.models.deletion.CASCADE, to='vm.Lease', verbose_name='Lease'),
preserve_default=False,
),
]
...@@ -163,3 +163,4 @@ class Trait(Model): ...@@ -163,3 +163,4 @@ class Trait(Model):
self.instance_set.exists() or self.node_set.exists() or self.instance_set.exists() or self.node_set.exists() or
self.instancetemplate_set.exists() self.instancetemplate_set.exists()
) )
...@@ -29,7 +29,7 @@ from django.core import signing ...@@ -29,7 +29,7 @@ from django.core import signing
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.db.models import (BooleanField, CharField, DateTimeField, from django.db.models import (BooleanField, CharField, DateTimeField,
IntegerField, ForeignKey, Manager, IntegerField, ForeignKey, Manager,
ManyToManyField, permalink, SET_NULL, TextField) ManyToManyField, permalink, SET_NULL, TextField, Model)
from django.db import IntegrityError from django.db import IntegrityError
from django.dispatch import Signal from django.dispatch import Signal
from django.utils import timezone from django.utils import timezone
...@@ -53,7 +53,6 @@ from .common import BaseResourceConfigModel, Lease ...@@ -53,7 +53,6 @@ from .common import BaseResourceConfigModel, Lease
from .network import Interface from .network import Interface
from network.models import EditorElement from network.models import EditorElement
from openstack_auth.user import User from openstack_auth.user import User
logger = getLogger(__name__) logger = getLogger(__name__)
...@@ -95,7 +94,6 @@ def find_unused_vnc_port(): ...@@ -95,7 +94,6 @@ def find_unused_vnc_port():
class VirtualMachineDescModel(BaseResourceConfigModel): class VirtualMachineDescModel(BaseResourceConfigModel):
"""Abstract base for virtual machine describing models. """Abstract base for virtual machine describing models.
""" """
access_method = CharField(max_length=10, choices=ACCESS_METHODS, access_method = CharField(max_length=10, choices=ACCESS_METHODS,
...@@ -134,8 +132,8 @@ class InstanceTemplate(TimeStampedModel): ...@@ -134,8 +132,8 @@ class InstanceTemplate(TimeStampedModel):
# disks = ManyToManyField('storage.Disk', verbose_name=_('disks'), # disks = ManyToManyField('storage.Disk', verbose_name=_('disks'),
# related_name='template_set', # related_name='template_set',
# help_text=_('Disks which are to be mounted.')) # help_text=_('Disks which are to be mounted.'))
# lease = ForeignKey(Lease, help_text=_("Preferred expiration periods."), lease = ForeignKey(Lease, help_text=_("Preferred expiration periods."),
# verbose_name=_("Lease")) verbose_name=_("Lease"))
image_id = CharField(blank=False, max_length=100) image_id = CharField(blank=False, max_length=100)
flavor_id = CharField(blank=False, max_length=100) flavor_id = CharField(blank=False, max_length=100)
owner_id = CharField(blank=False, max_length=100) owner_id = CharField(blank=False, max_length=100)
...@@ -143,7 +141,7 @@ class InstanceTemplate(TimeStampedModel): ...@@ -143,7 +141,7 @@ class InstanceTemplate(TimeStampedModel):
class Meta: class Meta:
app_label = 'vm' app_label = 'vm'
db_table = 'vm_instancetemplate' db_table = 'vm_instancetemplate'
ordering = ('name', ) ordering = ('name',)
verbose_name = _('template') verbose_name = _('template')
verbose_name_plural = _('templates') verbose_name_plural = _('templates')
...@@ -152,13 +150,13 @@ class InstanceTemplate(TimeStampedModel): ...@@ -152,13 +150,13 @@ class InstanceTemplate(TimeStampedModel):
@property @property
def running_instances(self): def running_instances(self):
return 0 #TODO: see get_running_instances return 0 # TODO: see get_running_instances
@property @property
def os_type(self): def os_type(self):
"""The type of the template's operating system. """The type of the template's operating system.
""" """
return 'linux' #TODO return 'linux' # TODO
if self.access_method == 'rdp': if self.access_method == 'rdp':
return 'windows' return 'windows'
else: else:
...@@ -169,25 +167,24 @@ class InstanceTemplate(TimeStampedModel): ...@@ -169,25 +167,24 @@ class InstanceTemplate(TimeStampedModel):
return ('dashboard.views.template-detail', None, {'pk': self.pk}) return ('dashboard.views.template-detail', None, {'pk': self.pk})
def remove_disk(self, disk, **kwargs): def remove_disk(self, disk, **kwargs):
#TODO: why? # TODO: why?
self.disks.remove(disk) self.disks.remove(disk)
def destroy_disks(self): def destroy_disks(self):
#TODO: why? # TODO: why?
for disk in self.disks.all(): for disk in self.disks.all():
disk.destroy() disk.destroy()
def get_running_instances(self): def get_running_instances(self):
return () #TODO: do we need this? if so, store created vm ids in template return () # TODO: do we need this? if so, store created vm ids in template
@property @property
def metric_prefix(self): def metric_prefix(self):
#TODO: what is this? # TODO: what is this?
return 'template.%d' % self.pk return 'template.%d' % self.pk
class Instance(OperatedMixin, TimeStampedModel): class Instance(OperatedMixin, TimeStampedModel):
"""Virtual machine instance. """Virtual machine instance.
""" """
STATUS = Choices( STATUS = Choices(
...@@ -237,7 +234,7 @@ class Instance(OperatedMixin, TimeStampedModel): ...@@ -237,7 +234,7 @@ class Instance(OperatedMixin, TimeStampedModel):
class Meta: class Meta:
app_label = 'vm' app_label = 'vm'
db_table = 'vm_instance' db_table = 'vm_instance'
ordering = ('pk', ) ordering = ('pk',)
permissions = ( permissions = (
('access_console', _('Can access the graphical console of a VM.')), ('access_console', _('Can access the graphical console of a VM.')),
('change_resources', _('Can change resources of a running VM.')), ('change_resources', _('Can change resources of a running VM.')),
...@@ -506,7 +503,7 @@ class Instance(OperatedMixin, TimeStampedModel): ...@@ -506,7 +503,7 @@ class Instance(OperatedMixin, TimeStampedModel):
def get_connect_port(self, use_ipv6=False): def get_connect_port(self, use_ipv6=False):
"""Get public port number for default access method. """Get public port number for default access method.
""" """
return None #TODO return None # TODO
port, proto = ACCESS_PROTOCOLS[self.access_method][1:3] port, proto = ACCESS_PROTOCOLS[self.access_method][1:3]
if self.primary_host: if self.primary_host:
...@@ -538,8 +535,8 @@ class Instance(OperatedMixin, TimeStampedModel): ...@@ -538,8 +535,8 @@ class Instance(OperatedMixin, TimeStampedModel):
elif proto == 'ssh': elif proto == 'ssh':
return ('sshpass -p %(pw)s ssh -o StrictHostKeyChecking=no ' return ('sshpass -p %(pw)s ssh -o StrictHostKeyChecking=no '
'cloud@%(host)s -p %(port)d') % { 'cloud@%(host)s -p %(port)d') % {
'port': port, 'proto': proto, 'pw': self.pw, 'port': port, 'proto': proto, 'pw': self.pw,
'host': host} 'host': host}
except: except:
return return
...@@ -674,7 +671,7 @@ class Instance(OperatedMixin, TimeStampedModel): ...@@ -674,7 +671,7 @@ class Instance(OperatedMixin, TimeStampedModel):
if (self.status != "SUSPENDED" and if (self.status != "SUSPENDED" and
self.time_of_suspend is not None and interval is not None): self.time_of_suspend is not None and interval is not None):
limit = timezone.now() + timedelta(seconds=( limit = timezone.now() + timedelta(seconds=(
threshold * self.lease.suspend_interval.total_seconds())) threshold * self.lease.suspend_interval.total_seconds()))
return limit > self.time_of_suspend return limit > self.time_of_suspend
else: else:
return False return False
...@@ -683,7 +680,7 @@ class Instance(OperatedMixin, TimeStampedModel): ...@@ -683,7 +680,7 @@ class Instance(OperatedMixin, TimeStampedModel):
interval = self.lease.delete_interval interval = self.lease.delete_interval
if self.time_of_delete is not None and interval is not None: if self.time_of_delete is not None and interval is not None:
limit = timezone.now() + timedelta(seconds=( limit = timezone.now() + timedelta(seconds=(
threshold * self.lease.delete_interval.total_seconds())) threshold * self.lease.delete_interval.total_seconds()))
return limit > self.time_of_delete return limit > self.time_of_delete
else: else:
return False return False
......
from django.db.models import Model, ForeignKey, CharField, DateTimeField
from django.conf import settings
from django.utils import timezone
from vm.models import Lease
from django.utils.translation import ugettext_lazy as _
from datetime import timedelta
class VmLease(Model):
lease = ForeignKey(Lease, help_text=_("Preferred expiration periods."),
verbose_name=_("Lease"))
os_server_id = CharField(blank=False, max_length=100, unique=True)
time_of_suspend = DateTimeField(blank=True, default=None, null=True,
verbose_name=_('time of suspend'),
help_text=_("Proposed time of automatic "
"suspension."))
time_of_delete = DateTimeField(blank=True, default=None, null=True,
verbose_name=_('time of delete'),
help_text=_("Proposed time of automatic "
"deletion."))
def get_renew_times(self, lease=None):
"""Returns new suspend and delete times if renew would be called.
"""
if lease is None:
lease = self.lease
return (
timezone.now() + lease.suspend_interval,
timezone.now() + lease.delete_interval)
def clean(self, *args, **kwargs):
self.time_of_suspend, self.time_of_delete = self.get_renew_times()
super(VmLease, self).clean(*args, **kwargs)
def is_suspend_expiring(self, threshold=0.1):
limit = timezone.now() + timedelta(seconds=(
threshold * self.lease.suspend_interval.total_seconds()))
return limit > self.time_of_suspend
def is_delete_expiring(self, threshold=0.1):
limit = timezone.now() + timedelta(seconds=(
threshold * self.lease.delete_interval.total_seconds()))
return limit > self.time_of_delete
@classmethod
def get_or_create_lease(cls, server_id):
try:
return VmLease.objects.get(os_server_id=server_id)
except VmLease.DoesNotExist:
lease = VmLease(
os_server_id=server_id,
lease=Lease.objects.get(name=settings.DEFAULT_LEASE_NAME)
)
lease.clean()
lease.save()
return lease
...@@ -46,6 +46,8 @@ from common.models import ( ...@@ -46,6 +46,8 @@ from common.models import (
) )
from common.operations import Operation, register_operation from common.operations import Operation, register_operation
# from manager.scheduler import SchedulerError # from manager.scheduler import SchedulerError
from vm.models.vm_lease import VmLease
from .tasks.local_tasks import ( from .tasks.local_tasks import (
abortable_async_instance_operation, abortable_async_node_operation, abortable_async_instance_operation, abortable_async_node_operation,
) )
...@@ -645,52 +647,44 @@ class RenewOperation(InstanceOperation): ...@@ -645,52 +647,44 @@ class RenewOperation(InstanceOperation):
"expire. This operation renews expiration times according " "expire. This operation renews expiration times according "
"to the lease type. If the machine is close to the " "to the lease type. If the machine is close to the "
"expiration, its owner will be notified.") "expiration, its owner will be notified.")
acl_level = "operator"
required_perms = ()
concurrency_check = False
def set_time_of_suspend(self, activity, suspend, force): def set_time_of_suspend(self, vm_lease, suspend, force):
with activity.sub_activity( if (not force and suspend and vm_lease.time_of_suspend and
'renew_suspend', concurrency_check=False, suspend < vm_lease.time_of_suspend):
readable_name=ugettext_noop('set time of suspend')): raise HumanReadableException.create(ugettext_noop(
if (not force and suspend and self.instance.time_of_suspend and "Renewing the machine with the selected lease would "
suspend < self.instance.time_of_suspend): "result in its suspension time get earlier than before."))
raise HumanReadableException.create(ugettext_noop( vm_lease.time_of_suspend = suspend
"Renewing the machine with the selected lease would "
"result in its suspension time get earlier than before.")) def set_time_of_delete(self, vm_lease, delete, force):
self.instance.time_of_suspend = suspend if (not force and delete and vm_lease.time_of_delete and
delete < vm_lease.time_of_delete):
def set_time_of_delete(self, activity, delete, force): raise HumanReadableException.create(ugettext_noop(
with activity.sub_activity( "Renewing the machine with the selected lease would "
'renew_delete', concurrency_check=False, "result in its delete time get earlier than before."))
readable_name=ugettext_noop('set time of delete')): vm_lease.time_of_delete = delete
if (not force and delete and self.instance.time_of_delete and
delete < self.instance.time_of_delete): def _operation(self, request, lease=None, force=False, save=False):
raise HumanReadableException.create(ugettext_noop( vm_lease = VmLease.get_or_create_lease(self.instance.id)
"Renewing the machine with the selected lease would " suspend, delete = vm_lease.get_renew_times(lease)
"result in its delete time get earlier than before."))
self.instance.time_of_delete = delete
def _operation(self, activity, lease=None, force=False, save=False):
suspend, delete = self.instance.get_renew_times(lease)
try: try:
self.set_time_of_suspend(activity, suspend, force) self.set_time_of_suspend(vm_lease, suspend, force)
except HumanReadableException: except HumanReadableException:
pass pass
try: try:
self.set_time_of_delete(activity, delete, force) self.set_time_of_delete(vm_lease, delete, force)
except HumanReadableException: except HumanReadableException:
pass pass
if save: if save:
self.instance.lease = lease vm_lease.lease = lease
self.instance.save() vm_lease.save()
return create_readable(ugettext_noop( return create_readable(ugettext_noop(
"Renewed to suspend at %(suspend)s and destroy at %(delete)s."), "Renewed to suspend at %(suspend)s and destroy at %(delete)s."),
suspend=self.instance.time_of_suspend, suspend=vm_lease.time_of_suspend,
delete=self.instance.time_of_suspend) delete=vm_lease.time_of_suspend)
@register_operation @register_operation
......
from django.conf import settings
from django.apps import AppConfig
class DefaultLeaseConfig(AppConfig):
name = 'vm'
def ready(self):
from vm.models import Lease
try:
Lease.objects.get(name=settings.DEFAULT_LEASE_NAME)
except Lease.DoesNotExist:
Lease(
name=settings.DEFAULT_LEASE_NAME,
suspend_interval_seconds=settings.DEFAULT_LEASE_SUSPEND_SECONDS,
delete_interval_seconds=settings.DEFAULT_LEASE_DELETE_SECONDS,
).save()
from test import * from lease_tasks import *
\ No newline at end of file \ No newline at end of file
from celery.decorators import periodic_task
from celery.task.schedules import crontab
from django.conf import settings
from openstack_auth.utils import fix_auth_url_version
from keystoneauth1.identity import v3
from keystoneauth1 import session
from vm.models import Lease
from vm.models.vm_lease import VmLease
def get_projects():
from keystoneclient.v3 import client
auth = v3.Password(
auth_url=fix_auth_url_version(settings.OPENSTACK_KEYSTONE_URL),
user_id=settings.OPENSTACK_CIRCLE_USERID,
password=settings.OPENSTACK_CIRCLE_PASSWORD,
)
sess = session.Session(auth=auth, verify=False)
keystone = client.Client(session=sess, interface=settings.OPENSTACK_INTERFACE)
return keystone.projects.list(
domain=settings.OPENSTACK_CIRCLE_DOMAIN_ID,
user=settings.OPENSTACK_CIRCLE_USERID
)
def get_project_client(project):
from novaclient import client
auth = v3.Password(
auth_url=fix_auth_url_version(settings.OPENSTACK_KEYSTONE_URL),
user_id=settings.OPENSTACK_CIRCLE_USERID,
password=settings.OPENSTACK_CIRCLE_PASSWORD,
project_id=project.id,
)
sess = session.Session(auth=auth, verify=False)
return client.Client("2.0", session=sess)
@periodic_task(run_every=crontab(hour="*", minute="*", day_of_week="*"))
def check_lease_expiration():
projects = get_projects()
for project in projects:
client = get_project_client(project)
servers = client.servers.list()
for server in servers:
lease = VmLease.get_or_create_lease(server.id)
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