Commit b4fee3ab by Szabolcs Gelencser

Add automatic lease creation, renew operation

parent 895f7d98
......@@ -14,7 +14,7 @@
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredPackages">
<value>
<list size="9">
<list size="11">
<item index="0" class="java.lang.String" itemvalue="salt" />
<item index="1" class="java.lang.String" itemvalue="six" />
<item index="2" class="java.lang.String" itemvalue="netaddr" />
......@@ -24,6 +24,8 @@
<item index="6" class="java.lang.String" itemvalue="lxml" />
<item index="7" class="java.lang.String" itemvalue="MarkupSafe" />
<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>
</value>
</option>
......
No preview for this file type
......@@ -2,8 +2,11 @@
"admin": "is_admin:True or (role:admin and is_admin_project:True)",
"owner": "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",
"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"
DEFAULT_PUBLIC_ROUTED_NET_NAME_FOR_USER = "default_public_routed"
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
OPENSTACK_SSL_NO_VERIFY = True
\ No newline at end of file
OPENSTACK_SSL_NO_VERIFY = True
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 @@
</a>
{% if object.active and lease_types and not request.token_user %}
<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>
{% trans "Request longer lease" %}
</a>
......
......@@ -6,97 +6,16 @@
{% block content %}
<div class="row">
<div class="col-md-7">
<div class="panel panel-default">
<div class="panel-heading">
<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>
</div>
<div class="panel-body">
{% with form=form %}
{% include "display-form-errors.html" %}
{% endwith %}
{% crispy form %}
</div>
<div class="panel panel-default">
<div class="panel-heading">
<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>
</div>
</div>
<div class="col-md-5">
<div class="panel panel-default">
<div class="panel-heading">
<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 class="panel-body">
{% with form=form %}
{% include "display-form-errors.html" %}
{% endwith %}
{% crispy form %}
</div>
</div>
</div>
......
......@@ -47,12 +47,10 @@
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
{% if perms.vm.create_leases %}
<a href="{% url "dashboard.views.lease-create" %}"
class="pull-right btn btn-success btn-xs" style="margin-right: 10px;">
<i class="fa fa-plus"></i> {% trans "new lease" %}
</a>
{% endif %}
<h3 class="no-margin"><i class="fa fa-clock-o"></i> {% trans "Leases" %}</h3>
</div>
<div class="panel-body">
......
......@@ -56,7 +56,7 @@
<div id="home_expiration_and_lease">
<h4>
{% 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">
{% with op=op.renew %}{% if op %}
<a href="{{op.get_url}}" class="btn btn-xs operation operation-{{ op.op }}
......@@ -70,14 +70,14 @@
<dl>
<dt>{% trans "Suspended at:" %}</dt>
<dd>
<span title="{{ instance.time_of_suspend }}">
<i class="fa fa-moon-o"></i> {{ instance.time_of_suspend|arrowfilter:LANGUAGE_CODE }}
<span title="{{ time_of_suspend }}">
<i class="fa fa-moon-o"></i> {{ time_of_suspend|arrowfilter:LANGUAGE_CODE }}
</span>
</dd>
<dt>{% trans "Destroyed at:" %}</dt>
<dd>
<span title="{{ instance.time_of_delete }}">
<i class="fa fa-times"></i> {{ instance.time_of_delete|arrowfilter:LANGUAGE_CODE }}
<span title="{{ time_of_delete }}">
<i class="fa fa-times"></i> {{ time_of_delete|arrowfilter:LANGUAGE_CODE }}
</span>
</dd>
</dl>
......
......@@ -19,7 +19,7 @@ from __future__ import absolute_import
from dashboard.views.autocomplete import AclUserGroupAutocomplete, AclUserAutocomplete
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 django.conf.urls import url
......@@ -34,12 +34,12 @@ urlpatterns = [
# url(r'^profile/create/$',
# UserCreationView.as_view(),
# name="dashboard.views.user-create"),
# url(r'^lease/(?P<pk>\d+)/$', LeaseDetail.as_view(),
# name="dashboard.views.lease-detail"),
# url(r'^lease/create/$', LeaseCreate.as_view(),
# name="dashboard.views.lease-create"),
# url(r'^lease/delete/(?P<pk>\d+)/$', LeaseDelete.as_view(),
# name="dashboard.views.lease-delete"),
url(r'^lease/(?P<pk>\d+)/$', LeaseDetail.as_view(),
name="dashboard.views.lease-detail"),
url(r'^lease/create/$', LeaseCreate.as_view(),
name="dashboard.views.lease-create"),
url(r'^lease/delete/(?P<pk>\d+)/$', LeaseDelete.as_view(),
name="dashboard.views.lease-delete"),
# url(r'^lease/(?P<pk>\d+)/acl/$', LeaseAclUpdateView.as_view(),
# name="dashboard.views.lease-acl"),
#
......
......@@ -21,6 +21,7 @@ import json
import logging
import openstack_api
from braces.views._access import AccessMixin
from django.contrib import messages
from django.contrib.auth.models import User
from django.contrib.messages.views import SuccessMessageMixin
......@@ -197,16 +198,19 @@ class TemplateList(LoginRequiredMixin, FilterMixin, SingleTableView):
def get_context_data(self, *args, **kwargs):
context = super(TemplateList, self).get_context_data(*args, **kwargs)
user = self.request.user
# leases_w_operator = Lease.get_objects_with_level("operator", user)
# context['lease_table'] = LeaseListTable(
# leases_w_operator, request=self.request,
# template="django_tables2/table_no_page.html",
# )
context['show_lease_table'] = (
# leases_w_operator.count() > 0 or
# user.has_perm("vm.create_leases")
False
from django.utils.module_loading import import_string
check = import_string("openstack_auth.policy.check")
os_policy_actions = (("circle", "lease:manage"),)
has_lease_rights = check(os_policy_actions, self.request)
leases = Lease.objects.all()
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
......@@ -382,86 +386,80 @@ class TemplateDetail(LoginRequiredMixin, GraphMixin, SuccessMessageMixin, Update
# def get_success_url(self):
# 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):
model = Lease
form_class = LeaseForm
permission_required = 'vm.create_leases'
template_name = "dashboard/lease-create.html"
success_message = _("Successfully created a new lease.")
os_policy_actions = (("circle", "lease:manage"),)
def get_success_url(self):
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):
model = Lease
# class LeaseAclUpdateView(AclUpdateView):
# model = Lease
class LeaseDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
class LeaseDetail(LoginRequiredMixin, PolicyMixin,
SuccessMessageMixin, UpdateView):
model = Lease
form_class = LeaseForm
template_name = "dashboard/lease-edit.html"
success_message = _("Successfully modified lease.")
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
os_policy_actions = (("circle", "lease:manage"),)
def get_success_url(self):
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):
if not self.get_object().has_level(request.user, "owner"):
raise PermissionDenied()
class LeaseDelete(DeleteViewBase):
model = Lease
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):
template = "dashboard/confirm/transfer-template-ownership.html"
model = InstanceTemplate
......
......@@ -725,7 +725,7 @@ class TransferOwnershipConfirmView(LoginRequiredMixin, View):
class DeleteViewBase(LoginRequiredMixin, DeleteView):
level = 'owner'
os_policy_actions = None
def get_template_names(self):
if self.request.is_ajax():
......@@ -734,7 +734,12 @@ class DeleteViewBase(LoginRequiredMixin, DeleteView):
return ['dashboard/confirm/base-delete.html']
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()
def get(self, request, *args, **kwargs):
......
......@@ -50,12 +50,13 @@ from firewall.models import Vlan, Host, Rule
# from manager.scheduler import SchedulerError
from network.models import DefaultPublicRouter, DefaultPublicRoutedNet
from openstack_api.nova import Server
from request.forms import TemplateRequestForm
from request.models import TemplateAccessType
from request.forms import TemplateRequestForm, LeaseRequestForm
from request.models import TemplateAccessType, LeaseType
from storage.models import Disk
from vm.models import (
Instance, InstanceActivity, Interface,
InstanceTemplate)
InstanceTemplate, Lease)
from vm.models.vm_lease import VmLease
from .util import (
CheckedDetailView, AjaxOperationMixin, OperationView, AclUpdateView,
......@@ -63,7 +64,7 @@ from .util import (
)
from ..forms import (
AclUserOrGroupAddForm, VmResourcesForm, VmCustomizeForm, VmDeployForm, VmFromPlainImageForm, VmRemoveInterfaceForm,
VmAddInterfaceForm, VmSaveForm, VmPortAddForm, VmPublicIpAddForm, VmPublicIpRemoveForm)
VmAddInterfaceForm, VmSaveForm, VmPortAddForm, VmPublicIpAddForm, VmPublicIpRemoveForm, VmRenewForm)
logger = logging.getLogger(__name__)
......@@ -141,6 +142,15 @@ class VmDetailView(LoginRequiredMixin, GraphMixin, DetailView):
'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
# activities = instance.get_merged_activities(user)
# show_show_all = len(activities) > 10
......@@ -705,40 +715,39 @@ class VmSaveView(FormOperationMixin, VmOperationView):
# return user
#
#
# class VmRenewView(FormOperationMixin, TokenOperationView, VmOperationView):
#
# op = 'renew'
# icon = 'calendar'
# effect = 'success'
# show_in_toolbar = False
# form_class = VmRenewForm
# wait_for_result = 0.5
# template_name = 'dashboard/_vm-renew.html'
# with_reload = True
#
# def get_form_kwargs(self):
# choices = Lease.get_objects_with_level("user", self.request.user)
# default = self.get_op().instance.lease
# if default and default not in choices:
# choices = (choices.distinct() |
# Lease.objects.filter(pk=default.pk).distinct())
#
# val = super(VmRenewView, self).get_form_kwargs()
# 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,
# extra, **kwargs)
# extra["new_suspend_time"] = unicode(self.get_op().
# instance.time_of_suspend)
# return extra
#
# def get_context_data(self, **kwargs):
# context = super(VmRenewView, self).get_context_data(**kwargs)
# context['lease_request_form'] = LeaseRequestForm(request=self.request)
# context['lease_types'] = LeaseType.objects.exists()
# return context
class VmRenewView(FormOperationMixin, VmOperationView):
op = 'renew'
icon = 'calendar'
effect = 'success'
show_in_toolbar = False
form_class = VmRenewForm
template_name = 'dashboard/_vm-renew.html'
def get_form_kwargs(self):
choices = Lease.objects.all() #TODO: filter on permissions
instance = self.get_op().instance
default = VmLease.objects.get(os_server_id=instance.id).lease
# if default and default not in choices:
# choices = (choices.distinct() |
# Lease.objects.filter(pk=default.pk).distinct())
val = super(VmRenewView, self).get_form_kwargs()
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,
extra, **kwargs)
instance = self.get_op().instance
time_of_suspend = VmLease.objects.get(os_server_id=instance.id).time_of_suspend
extra["new_suspend_time"] = unicode(time_of_suspend)
return extra
def get_context_data(self, **kwargs):
context = super(VmRenewView, self).get_context_data(**kwargs)
context['lease_request_form'] = LeaseRequestForm(request=self.request)
context['lease_types'] = LeaseType.objects.exists()
return context
#
#
# class VmStateChangeView(FormOperationMixin, VmOperationView):
......@@ -844,7 +853,7 @@ vm_ops = OrderedDict([
# ('add_port', VmPortAddView),
('add_public_ip', VmPublicIpAddView),
('remove_public_ip', VmPublicIpRemoveView),
# ('renew', VmRenewView),
('renew', VmRenewView),
# ('resources_change', VmResourcesChangeView),
# ('password_reset', VmOperationView.factory(
# op='password_reset', icon='unlock', effect='warning',
......
{% load i18n %}
{% 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" %}
{% csrf_token %}
{{ 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):
self.instance_set.exists() or self.node_set.exists() or
self.instancetemplate_set.exists()
)
......@@ -29,7 +29,7 @@ from django.core import signing
from django.core.exceptions import PermissionDenied
from django.db.models import (BooleanField, CharField, DateTimeField,
IntegerField, ForeignKey, Manager,
ManyToManyField, permalink, SET_NULL, TextField)
ManyToManyField, permalink, SET_NULL, TextField, Model)
from django.db import IntegrityError
from django.dispatch import Signal
from django.utils import timezone
......@@ -53,7 +53,6 @@ from .common import BaseResourceConfigModel, Lease
from .network import Interface
from network.models import EditorElement
from openstack_auth.user import User
logger = getLogger(__name__)
......@@ -95,7 +94,6 @@ def find_unused_vnc_port():
class VirtualMachineDescModel(BaseResourceConfigModel):
"""Abstract base for virtual machine describing models.
"""
access_method = CharField(max_length=10, choices=ACCESS_METHODS,
......@@ -134,8 +132,8 @@ class InstanceTemplate(TimeStampedModel):
# disks = ManyToManyField('storage.Disk', verbose_name=_('disks'),
# related_name='template_set',
# help_text=_('Disks which are to be mounted.'))
# lease = ForeignKey(Lease, help_text=_("Preferred expiration periods."),
# verbose_name=_("Lease"))
lease = ForeignKey(Lease, help_text=_("Preferred expiration periods."),
verbose_name=_("Lease"))
image_id = CharField(blank=False, max_length=100)
flavor_id = CharField(blank=False, max_length=100)
owner_id = CharField(blank=False, max_length=100)
......@@ -143,7 +141,7 @@ class InstanceTemplate(TimeStampedModel):
class Meta:
app_label = 'vm'
db_table = 'vm_instancetemplate'
ordering = ('name', )
ordering = ('name',)
verbose_name = _('template')
verbose_name_plural = _('templates')
......@@ -152,13 +150,13 @@ class InstanceTemplate(TimeStampedModel):
@property
def running_instances(self):
return 0 #TODO: see get_running_instances
return 0 # TODO: see get_running_instances
@property
def os_type(self):
"""The type of the template's operating system.
"""
return 'linux' #TODO
return 'linux' # TODO
if self.access_method == 'rdp':
return 'windows'
else:
......@@ -169,25 +167,24 @@ class InstanceTemplate(TimeStampedModel):
return ('dashboard.views.template-detail', None, {'pk': self.pk})
def remove_disk(self, disk, **kwargs):
#TODO: why?
# TODO: why?
self.disks.remove(disk)
def destroy_disks(self):
#TODO: why?
# TODO: why?
for disk in self.disks.all():
disk.destroy()
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
def metric_prefix(self):
#TODO: what is this?
# TODO: what is this?
return 'template.%d' % self.pk
class Instance(OperatedMixin, TimeStampedModel):
"""Virtual machine instance.
"""
STATUS = Choices(
......@@ -237,7 +234,7 @@ class Instance(OperatedMixin, TimeStampedModel):
class Meta:
app_label = 'vm'
db_table = 'vm_instance'
ordering = ('pk', )
ordering = ('pk',)
permissions = (
('access_console', _('Can access the graphical console of a VM.')),
('change_resources', _('Can change resources of a running VM.')),
......@@ -506,7 +503,7 @@ class Instance(OperatedMixin, TimeStampedModel):
def get_connect_port(self, use_ipv6=False):
"""Get public port number for default access method.
"""
return None #TODO
return None # TODO
port, proto = ACCESS_PROTOCOLS[self.access_method][1:3]
if self.primary_host:
......@@ -538,8 +535,8 @@ class Instance(OperatedMixin, TimeStampedModel):
elif proto == 'ssh':
return ('sshpass -p %(pw)s ssh -o StrictHostKeyChecking=no '
'cloud@%(host)s -p %(port)d') % {
'port': port, 'proto': proto, 'pw': self.pw,
'host': host}
'port': port, 'proto': proto, 'pw': self.pw,
'host': host}
except:
return
......@@ -674,7 +671,7 @@ class Instance(OperatedMixin, TimeStampedModel):
if (self.status != "SUSPENDED" and
self.time_of_suspend is not None and interval is not None):
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
else:
return False
......@@ -683,7 +680,7 @@ class Instance(OperatedMixin, TimeStampedModel):
interval = self.lease.delete_interval
if self.time_of_delete is not None and interval is not None:
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
else:
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 (
)
from common.operations import Operation, register_operation
# from manager.scheduler import SchedulerError
from vm.models.vm_lease import VmLease
from .tasks.local_tasks import (
abortable_async_instance_operation, abortable_async_node_operation,
)
......@@ -645,52 +647,44 @@ class RenewOperation(InstanceOperation):
"expire. This operation renews expiration times according "
"to the lease type. If the machine is close to the "
"expiration, its owner will be notified.")
acl_level = "operator"
required_perms = ()
concurrency_check = False
def set_time_of_suspend(self, activity, suspend, force):
with activity.sub_activity(
'renew_suspend', concurrency_check=False,
readable_name=ugettext_noop('set time of suspend')):
if (not force and suspend and self.instance.time_of_suspend and
suspend < self.instance.time_of_suspend):
raise HumanReadableException.create(ugettext_noop(
"Renewing the machine with the selected lease would "
"result in its suspension time get earlier than before."))
self.instance.time_of_suspend = suspend
def set_time_of_delete(self, activity, delete, force):
with activity.sub_activity(
'renew_delete', concurrency_check=False,
readable_name=ugettext_noop('set time of delete')):
if (not force and delete and self.instance.time_of_delete and
delete < self.instance.time_of_delete):
raise HumanReadableException.create(ugettext_noop(
"Renewing the machine with the selected lease would "
"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)
def set_time_of_suspend(self, vm_lease, suspend, force):
if (not force and suspend and vm_lease.time_of_suspend and
suspend < vm_lease.time_of_suspend):
raise HumanReadableException.create(ugettext_noop(
"Renewing the machine with the selected lease would "
"result in its suspension time get earlier than before."))
vm_lease.time_of_suspend = suspend
def set_time_of_delete(self, vm_lease, delete, force):
if (not force and delete and vm_lease.time_of_delete and
delete < vm_lease.time_of_delete):
raise HumanReadableException.create(ugettext_noop(
"Renewing the machine with the selected lease would "
"result in its delete time get earlier than before."))
vm_lease.time_of_delete = delete
def _operation(self, request, lease=None, force=False, save=False):
vm_lease = VmLease.get_or_create_lease(self.instance.id)
suspend, delete = vm_lease.get_renew_times(lease)
try:
self.set_time_of_suspend(activity, suspend, force)
self.set_time_of_suspend(vm_lease, suspend, force)
except HumanReadableException:
pass
try:
self.set_time_of_delete(activity, delete, force)
self.set_time_of_delete(vm_lease, delete, force)
except HumanReadableException:
pass
if save:
self.instance.lease = lease
vm_lease.lease = lease
self.instance.save()
vm_lease.save()
return create_readable(ugettext_noop(
"Renewed to suspend at %(suspend)s and destroy at %(delete)s."),
suspend=self.instance.time_of_suspend,
delete=self.instance.time_of_suspend)
suspend=vm_lease.time_of_suspend,
delete=vm_lease.time_of_suspend)
@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 *
\ No newline at end of file
from lease_tasks import *
\ 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