Commit d0dc1ed8 by Őry Máté

Merge branch 'master' into issue-139

Conflicts:
	circle/vm/models/activity.py
parents 06663c9e 2ca7eb56
...@@ -59,10 +59,10 @@ def create_levels(app, created_models, verbosity, db=DEFAULT_DB_ALIAS, ...@@ -59,10 +59,10 @@ def create_levels(app, created_models, verbosity, db=DEFAULT_DB_ALIAS,
] ]
Level.objects.using(db).bulk_create(levels) Level.objects.using(db).bulk_create(levels)
if verbosity >= 2: if verbosity >= 2:
print("Adding levels [%s]." % ", ".join(levels)) print("Adding levels [%s]." % ", ".join(unicode(l) for l in levels))
print("Searched: [%s]." % ", ".join( print("Searched: [%s]." % ", ".join(
[unicode(l) for l in searched_levels])) unicode(l) for l in searched_levels))
print("All: [%s]." % ", ".join([unicode(l) for l in all_levels])) print("All: [%s]." % ", ".join(unicode(l) for l in all_levels))
# set weights # set weights
for ctype, codename, weight in level_weights: for ctype, codename, weight in level_weights:
......
...@@ -46,8 +46,9 @@ CACHES = { ...@@ -46,8 +46,9 @@ CACHES = {
LOGGING['loggers']['djangosaml2'] = {'handlers': ['console'], LOGGING['loggers']['djangosaml2'] = {'handlers': ['console'],
'level': 'CRITICAL'} 'level': 'CRITICAL'}
LOGGING['handlers']['console'] = {'level': 'WARNING', level = environ.get('LOGLEVEL', 'CRITICAL')
LOGGING['handlers']['console'] = {'level': level,
'class': 'logging.StreamHandler', 'class': 'logging.StreamHandler',
'formatter': 'simple'} 'formatter': 'simple'}
for i in LOCAL_APPS: for i in LOCAL_APPS:
LOGGING['loggers'][i] = {'handlers': ['console'], 'level': 'CRITICAL'} LOGGING['loggers'][i] = {'handlers': ['console'], 'level': level}
...@@ -59,6 +59,8 @@ class Operation(object): ...@@ -59,6 +59,8 @@ class Operation(object):
skip_auth_check = auxargs.pop('system') skip_auth_check = auxargs.pop('system')
user = auxargs.pop('user') user = auxargs.pop('user')
parent_activity = auxargs.pop('parent_activity') parent_activity = auxargs.pop('parent_activity')
if parent_activity and user is None and not skip_auth_check:
user = parent_activity.user
# check for unexpected keyword arguments # check for unexpected keyword arguments
argspec = getargspec(self._operation) argspec = getargspec(self._operation)
......
...@@ -612,6 +612,9 @@ class TemplateForm(forms.ModelForm): ...@@ -612,6 +612,9 @@ class TemplateForm(forms.ModelForm):
self.instance.ram_size = 512 self.instance.ram_size = 512
self.instance.num_cores = 2 self.instance.num_cores = 2
self.fields["lease"].queryset = Lease.get_objects_with_level(
"operator", self.user)
def clean_owner(self): def clean_owner(self):
if self.instance.pk is not None: if self.instance.pk is not None:
return User.objects.get(pk=self.instance.owner.pk) return User.objects.get(pk=self.instance.owner.pk)
...@@ -888,6 +891,27 @@ class LeaseForm(forms.ModelForm): ...@@ -888,6 +891,27 @@ class LeaseForm(forms.ModelForm):
model = Lease model = Lease
class VmRenewForm(forms.Form):
def __init__(self, *args, **kwargs):
choices = kwargs.pop('choices')
default = kwargs.pop('default')
super(VmRenewForm, self).__init__(*args, **kwargs)
self.fields['lease'] = forms.ModelChoiceField(queryset=choices,
initial=default,
required=True,
label=_('Length'))
if len(choices) < 2:
self.fields['lease'].widget = HiddenInput()
@property
def helper(self):
helper = FormHelper(self)
helper.form_tag = False
return helper
class VmCreateDiskForm(forms.Form): class VmCreateDiskForm(forms.Form):
name = forms.CharField(max_length=100, label=_("Name")) name = forms.CharField(max_length=100, label=_("Name"))
size = forms.CharField( size = forms.CharField(
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
{% block content %} {% block content %}
<div class="row"> <div class="row">
<div class="col-md-12"> <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>
...@@ -20,6 +20,85 @@ ...@@ -20,6 +20,85 @@
</div> </div>
</div> </div>
</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="perm-new-name"
placeholder="{% trans "Name of group or user" %}"></td>
<td><select class="form-control" name="perm-new">
{% 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>
{% endblock %} {% endblock %}
...@@ -26,9 +26,11 @@ ...@@ -26,9 +26,11 @@
<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" %}" class="pull-right btn btn-success btn-xs" style="margin-right: 10px;"> <a href="{% url "dashboard.views.lease-create" %}" class="pull-right btn btn-success btn-xs" style="margin-right: 10px;">
<i class="icon-plus"></i> {% trans "new lease" %} <i class="icon-plus"></i> {% trans "new lease" %}
</a> </a>
{% endif %}
<h3 class="no-margin"><i class="icon-time"></i> {% trans "Leases" %}</h3> <h3 class="no-margin"><i class="icon-time"></i> {% trans "Leases" %}</h3>
</div> </div>
<div class="panel-body"> <div class="panel-body">
......
...@@ -47,7 +47,12 @@ ...@@ -47,7 +47,12 @@
</dl> </dl>
<h4>{% trans "Expiration" %} {% if instance.is_expiring %}<i class="icon-warning-sign text-danger"></i>{% endif %} <h4>{% trans "Expiration" %} {% if instance.is_expiring %}<i class="icon-warning-sign text-danger"></i>{% endif %}
<a href="{% url "dashboard.views.vm-renew" instance.pk "" %}" class="btn btn-success btn-xs pull-right">{% trans "renew" %}</a> {% with op=op.renew %}
<a href="{{op.get_url}}" class="btn btn-success btn-xs
operation operation-{{op.op}} btn btn-default">
<i class="icon-{{op.icon}}"></i>
{{op.name}} </a>
{% endwith %}
</h4> </h4>
<dl> <dl>
<dt>{% trans "Suspended at:" %}</dt> <dt>{% trans "Suspended at:" %}</dt>
......
...@@ -21,14 +21,12 @@ import json ...@@ -21,14 +21,12 @@ import json
from django.test import TestCase from django.test import TestCase
from django.test.client import Client from django.test.client import Client
from django.contrib.auth.models import User, Group from django.contrib.auth.models import User, Group
from django.core.urlresolvers import reverse
from django.contrib.auth.models import Permission from django.contrib.auth.models import Permission
from django.contrib.auth import authenticate from django.contrib.auth import authenticate
from vm.models import Instance, InstanceTemplate, Lease, Node, Trait from vm.models import Instance, InstanceTemplate, Lease, Node, Trait
from vm.operations import WakeUpOperation from vm.operations import WakeUpOperation
from ..models import Profile from ..models import Profile
from ..views import VmRenewView
from storage.models import Disk from storage.models import Disk
from firewall.models import Vlan, Host, VlanGroup from firewall.models import Vlan, Host, VlanGroup
from mock import Mock, patch from mock import Mock, patch
...@@ -568,10 +566,8 @@ class VmDetailTest(LoginMixin, TestCase): ...@@ -568,10 +566,8 @@ class VmDetailTest(LoginMixin, TestCase):
inst = Instance.objects.get(pk=1) inst = Instance.objects.get(pk=1)
inst.manual_state_change('SUSPENDED') inst.manual_state_change('SUSPENDED')
inst.set_level(self.u2, 'user') inst.set_level(self.u2, 'user')
with patch('dashboard.views.messages') as msg: response = c.post("/dashboard/vm/1/op/wake_up/")
response = c.post("/dashboard/vm/1/op/wake_up/") self.assertEqual(response.status_code, 403)
assert msg.error.called
self.assertEqual(response.status_code, 302)
inst = Instance.objects.get(pk=1) inst = Instance.objects.get(pk=1)
self.assertEqual(inst.status, 'SUSPENDED') self.assertEqual(inst.status, 'SUSPENDED')
...@@ -1631,100 +1627,6 @@ class TransferOwnershipViewTest(LoginMixin, TestCase): ...@@ -1631,100 +1627,6 @@ class TransferOwnershipViewTest(LoginMixin, TestCase):
self.assertEquals(Instance.objects.get(pk=1).owner.pk, self.u2.pk) self.assertEquals(Instance.objects.get(pk=1).owner.pk, self.u2.pk)
class RenewViewTest(LoginMixin, TestCase):
fixtures = ['test-vm-fixture.json']
def setUp(self):
self.u1 = User.objects.create(username='user1')
self.u1.set_password('password')
self.u1.save()
Profile.objects.create(user=self.u1)
self.u2 = User.objects.create(username='user2', is_staff=True)
self.u2.set_password('password')
self.u2.save()
Profile.objects.create(user=self.u2)
self.us = User.objects.create(username='superuser', is_superuser=True)
self.us.set_password('password')
self.us.save()
Profile.objects.create(user=self.us)
inst = Instance.objects.get(pk=1)
inst.owner = self.u1
inst.save()
def test_renew_by_owner(self):
c = Client()
ct = Instance.objects.get(pk=1).activity_log.\
filter(activity_code__endswith='renew').count()
self.login(c, 'user1')
response = c.get('/dashboard/vm/1/renew/')
self.assertEquals(response.status_code, 200)
response = c.post('/dashboard/vm/1/renew/')
self.assertEquals(response.status_code, 302)
ct2 = Instance.objects.get(pk=1).activity_log.\
filter(activity_code__endswith='renew').count()
self.assertEquals(ct + 1, ct2)
def test_renew_get_by_nonowner_wo_key(self):
c = Client()
self.login(c, 'user2')
response = c.get('/dashboard/vm/1/renew/')
self.assertEquals(response.status_code, 403)
def test_renew_post_by_nonowner_wo_key(self):
c = Client()
self.login(c, 'user2')
response = c.post('/dashboard/vm/1/renew/')
self.assertEquals(response.status_code, 403)
def test_renew_get_by_nonowner_w_key(self):
key = VmRenewView.get_token_url(Instance.objects.get(pk=1), self.u2)
c = Client()
response = c.get(key)
self.assertEquals(response.status_code, 200)
def test_renew_post_by_anon_w_key(self):
key = VmRenewView.get_token_url(Instance.objects.get(pk=1), self.u2)
ct = Instance.objects.get(pk=1).activity_log.\
filter(activity_code__endswith='renew').count()
c = Client()
response = c.post(key)
self.assertEquals(response.status_code, 302)
ct2 = Instance.objects.get(pk=1).activity_log.\
filter(activity_code__endswith='renew').count()
self.assertEquals(ct + 1, ct2)
def test_renew_post_by_anon_w_invalid_key(self):
class Mockinst(object):
pk = 2
key = VmRenewView.get_token_url(Mockinst(), self.u2)
ct = Instance.objects.get(pk=1).activity_log.\
filter(activity_code__endswith='renew').count()
c = Client()
self.login(c, 'user2')
response = c.get(key)
self.assertEquals(response.status_code, 404)
response = c.post(key)
self.assertEquals(response.status_code, 404)
ct2 = Instance.objects.get(pk=1).activity_log.\
filter(activity_code__endswith='renew').count()
self.assertEquals(ct, ct2)
def test_renew_post_by_anon_w_expired_key(self):
key = reverse(VmRenewView.url_name, args=(
12, 'WzEyLDFd:1WLbSi:2zIb8SUNAIRIOMTmSmKSSit2gpY'))
ct = Instance.objects.get(pk=12).activity_log.\
filter(activity_code__endswith='renew').count()
c = Client()
self.login(c, 'user2')
response = c.get(key)
self.assertEquals(response.status_code, 302)
response = c.post(key)
self.assertEquals(response.status_code, 403)
ct2 = Instance.objects.get(pk=12).activity_log.\
filter(activity_code__endswith='renew').count()
self.assertEquals(ct, ct2)
class IndexViewTest(LoginMixin, TestCase): class IndexViewTest(LoginMixin, TestCase):
fixtures = ['test-vm-fixture.json', 'node.json'] fixtures = ['test-vm-fixture.json', 'node.json']
......
...@@ -29,7 +29,7 @@ from .views import ( ...@@ -29,7 +29,7 @@ from .views import (
TemplateDelete, TemplateDetail, TemplateList, TransferOwnershipConfirmView, TemplateDelete, TemplateDetail, TemplateList, TransferOwnershipConfirmView,
TransferOwnershipView, vm_activity, VmCreate, VmDelete, VmDetailView, TransferOwnershipView, vm_activity, VmCreate, VmDelete, VmDetailView,
VmDetailVncTokenView, VmGraphView, VmList, VmMassDelete, VmDetailVncTokenView, VmGraphView, VmList, VmMassDelete,
VmRenewView, DiskRemoveView, get_disk_download_status, InterfaceDeleteView, DiskRemoveView, get_disk_download_status, InterfaceDeleteView,
GroupRemoveAclUserView, GroupRemoveAclGroupView, GroupRemoveUserView, GroupRemoveAclUserView, GroupRemoveAclGroupView, GroupRemoveUserView,
GroupRemoveFutureUserView, GroupRemoveFutureUserView,
GroupCreate, GroupProfileUpdate, GroupCreate, GroupProfileUpdate,
...@@ -40,6 +40,7 @@ from .views import ( ...@@ -40,6 +40,7 @@ from .views import (
UserKeyDelete, UserKeyDetail, UserKeyCreate, UserKeyDelete, UserKeyDetail, UserKeyCreate,
VmTraitsUpdate, VmRawDataUpdate, VmTraitsUpdate, VmRawDataUpdate,
GroupPermissionsView, GroupPermissionsView,
LeaseAclUpdateView,
) )
urlpatterns = patterns( urlpatterns = patterns(
...@@ -51,6 +52,8 @@ urlpatterns = patterns( ...@@ -51,6 +52,8 @@ urlpatterns = patterns(
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(),
name="dashboard.views.lease-acl"),
url(r'^template/create/$', TemplateCreate.as_view(), url(r'^template/create/$', TemplateCreate.as_view(),
name="dashboard.views.template-create"), name="dashboard.views.template-create"),
...@@ -84,8 +87,6 @@ urlpatterns = patterns( ...@@ -84,8 +87,6 @@ urlpatterns = patterns(
url(r'^vm/mass-delete/', VmMassDelete.as_view(), url(r'^vm/mass-delete/', VmMassDelete.as_view(),
name='dashboard.view.mass-delete-vm'), name='dashboard.view.mass-delete-vm'),
url(r'^vm/(?P<pk>\d+)/activity/$', vm_activity), url(r'^vm/(?P<pk>\d+)/activity/$', vm_activity),
url(r'^vm/(?P<pk>\d+)/renew/((?P<key>.*)/?)$', VmRenewView.as_view(),
name='dashboard.views.vm-renew'),
url(r'^vm/activity/(?P<pk>\d+)/$', InstanceActivityDetail.as_view(), url(r'^vm/activity/(?P<pk>\d+)/$', InstanceActivityDetail.as_view(),
name='dashboard.views.vm-activity'), name='dashboard.views.vm-activity'),
url(r'^vm/(?P<pk>\d+)/screenshot/$', get_vm_screenshot, url(r'^vm/(?P<pk>\d+)/screenshot/$', get_vm_screenshot,
......
...@@ -225,6 +225,8 @@ def node_activity(code_suffix, node, task_uuid=None, user=None): ...@@ -225,6 +225,8 @@ def node_activity(code_suffix, node, task_uuid=None, user=None):
@worker_ready.connect() @worker_ready.connect()
def cleanup(conf=None, **kwargs): def cleanup(conf=None, **kwargs):
# TODO check if other manager workers are running # TODO check if other manager workers are running
from celery.task.control import discard_all
discard_all()
msg_txt = ugettext_noop("Manager is restarted, activity is cleaned up. " msg_txt = ugettext_noop("Manager is restarted, activity is cleaned up. "
"You can try again now.") "You can try again now.")
message = create_readable(msg_txt, msg_txt) message = create_readable(msg_txt, msg_txt)
......
...@@ -18,12 +18,14 @@ ...@@ -18,12 +18,14 @@
from __future__ import absolute_import, unicode_literals from __future__ import absolute_import, unicode_literals
from datetime import timedelta, datetime from datetime import timedelta, datetime
from django.db.models import Model, CharField, IntegerField from django.db.models import Model, CharField, IntegerField, permalink
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils.timesince import timeuntil from django.utils.timesince import timeuntil
from model_utils.models import TimeStampedModel from model_utils.models import TimeStampedModel
from acl.models import AclBase
ARCHITECTURES = (('x86_64', 'x86-64 (64 bit)'), ARCHITECTURES = (('x86_64', 'x86-64 (64 bit)'),
('i686', 'x86 (32 bit)')) ('i686', 'x86 (32 bit)'))
...@@ -66,13 +68,18 @@ class NamedBaseResourceConfig(BaseResourceConfigModel, TimeStampedModel): ...@@ -66,13 +68,18 @@ class NamedBaseResourceConfig(BaseResourceConfigModel, TimeStampedModel):
return self.name return self.name
class Lease(Model): class Lease(AclBase):
"""Lease times for VM instances. """Lease times for VM instances.
Specifies a time duration until suspension and deletion of a VM Specifies a time duration until suspension and deletion of a VM
instance. instance.
""" """
ACL_LEVELS = (
('user', _('user')), # use this lease
('operator', _('operator')), # share this lease
('owner', _('owner')), # change this lease
)
name = CharField(max_length=100, unique=True, name = CharField(max_length=100, unique=True,
verbose_name=_('name')) verbose_name=_('name'))
suspend_interval_seconds = IntegerField( suspend_interval_seconds = IntegerField(
...@@ -88,6 +95,9 @@ class Lease(Model): ...@@ -88,6 +95,9 @@ class Lease(Model):
app_label = 'vm' app_label = 'vm'
db_table = 'vm_lease' db_table = 'vm_lease'
ordering = ['name', ] ordering = ['name', ]
permissions = (
('create_leases', _('Can create new leases.')),
)
@property @property
def suspend_interval(self): def suspend_interval(self):
...@@ -141,6 +151,10 @@ class Lease(Model): ...@@ -141,6 +151,10 @@ class Lease(Model):
's': self.get_readable_suspend_time(), 's': self.get_readable_suspend_time(),
'r': self.get_readable_delete_time()} 'r': self.get_readable_delete_time()}
@permalink
def get_absolute_url(self):
return ('dashboard.views.lease-detail', None, {'pk': self.pk})
class Trait(Model): class Trait(Model):
name = CharField(max_length=50, verbose_name=_('name')) name = CharField(max_length=50, verbose_name=_('name'))
......
...@@ -439,10 +439,7 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin, ...@@ -439,10 +439,7 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
for cps in customized_params] for cps in customized_params]
def clean(self, *args, **kwargs): def clean(self, *args, **kwargs):
if self.time_of_suspend is None: self.time_of_suspend, self.time_of_delete = self.get_renew_times()
self._do_renew(which='suspend')
if self.time_of_delete is None:
self._do_renew(which='delete')
super(Instance, self).clean(*args, **kwargs) super(Instance, self).clean(*args, **kwargs)
def manual_state_change(self, new_state="NOSTATE", reason=None, user=None): def manual_state_change(self, new_state="NOSTATE", reason=None, user=None):
...@@ -715,36 +712,14 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin, ...@@ -715,36 +712,14 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
else: else:
return False return False
def get_renew_times(self): def get_renew_times(self, lease=None):
"""Returns new suspend and delete times if renew would be called. """Returns new suspend and delete times if renew would be called.
""" """
if lease is None:
lease = self.lease
return ( return (
timezone.now() + self.lease.suspend_interval, timezone.now() + lease.suspend_interval,
timezone.now() + self.lease.delete_interval) timezone.now() + lease.delete_interval)
def _do_renew(self, which='both'):
"""Set expiration times to renewed values.
"""
time_of_suspend, time_of_delete = self.get_renew_times()
if which in ('suspend', 'both'):
self.time_of_suspend = time_of_suspend
if which in ('delete', 'both'):
self.time_of_delete = time_of_delete
def renew(self, which='both', base_activity=None, user=None):
"""Renew virtual machine instance leases.
"""
if base_activity is None:
act_ctx = instance_activity(code_suffix='renew', instance=self,
user=user)
else:
act_ctx = base_activity.sub_activity('renew')
with act_ctx:
if which not in ('suspend', 'delete', 'both'):
raise ValueError('No such expiration type.')
self._do_renew(which)
self.save()
def change_password(self, user=None): def change_password(self, user=None):
"""Generate new password for the vm """Generate new password for the vm
......
...@@ -189,8 +189,9 @@ class DeployOperation(InstanceOperation): ...@@ -189,8 +189,9 @@ class DeployOperation(InstanceOperation):
self.instance.deploy_disks() self.instance.deploy_disks()
# Deploy VM on remote machine # Deploy VM on remote machine
with activity.sub_activity('deploying_vm') as deploy_act: if self.instance.state not in ['PAUSED']:
deploy_act.result = self.instance.deploy_vm(timeout=timeout) with activity.sub_activity('deploying_vm') as deploy_act:
deploy_act.result = self.instance.deploy_vm(timeout=timeout)
# Establish network connection (vmdriver) # Establish network connection (vmdriver)
with activity.sub_activity('deploying_net'): with activity.sub_activity('deploying_net'):
...@@ -200,7 +201,7 @@ class DeployOperation(InstanceOperation): ...@@ -200,7 +201,7 @@ class DeployOperation(InstanceOperation):
with activity.sub_activity('booting'): with activity.sub_activity('booting'):
self.instance.resume_vm(timeout=timeout) self.instance.resume_vm(timeout=timeout)
self.instance.renew(which='both', base_activity=activity) self.instance.renew(parent_activity=activity)
register_operation(DeployOperation) register_operation(DeployOperation)
...@@ -613,12 +614,30 @@ class WakeUpOperation(InstanceOperation): ...@@ -613,12 +614,30 @@ class WakeUpOperation(InstanceOperation):
self.instance.deploy_net() self.instance.deploy_net()
# Renew vm # Renew vm
self.instance.renew(which='both', base_activity=activity) self.instance.renew(parent_activity=activity)
register_operation(WakeUpOperation) register_operation(WakeUpOperation)
class RenewOperation(InstanceOperation):
activity_code_suffix = 'renew'
id = 'renew'
name = _("renew")
description = _("Renew expiration times")
acl_level = "operator"
required_perms = ()
concurrency_check = False
def _operation(self, lease=None):
(self.instance.time_of_suspend,
self.instance.time_of_delete) = self.instance.get_renew_times(lease)
self.instance.save()
register_operation(RenewOperation)
class NodeOperation(Operation): class NodeOperation(Operation):
async_operation = abortable_async_node_operation async_operation = abortable_async_node_operation
host_cls = Node host_cls = Node
......
Deploy Deploy
======== ======
This is where you describe how the project is deployed in production. This tutorial describes the installation of a production environment. To
have a fully working environment, you have to set up the other components
as well. The full procedure is included in the :doc:`Puppet recipes
<puppet>` available for CIRCLE Cloud.
This component should normally deployed to a single head node.
This is the web-based entry point to the end users, and also the manager of
the compute and storage nodes.
Preparation
-----------
To get the project running, launch a new Ubuntu 14.04 machine, and
log in to it over SSH.
.. warning::
If the first character of the hostname should not be a digit, because
RabbitMQ won't work with it.
The machine should have an :abbr:`fqdn (fully qualified domain name)`,
which shoud be correctly printed by :kbd:`hostname -f`. You can achieve
this with an IP address (e.g. 127.0.1.1) in :file:`/etc/hosts` having the
short hostname as first, and the fqdn as second alias).
Setting up required software
----------------------------
Update the package lists, and install the required system software::
sudo apt-get update
sudo apt-get install --yes virtualenvwrapper postgresql git \
python-pip rabbitmq-server libpq-dev python-dev ntp memcached \
libmemcached-dev gettext wget pwgen nginx
Set up *PostgreSQL* to listen on localhost and restart it::
sudo sed -i /etc/postgresql/9.1/main/postgresql.conf -e '/#listen_addresses/ s/^#//'
sudo /etc/init.d/postgresql restart
Also, create a new database and user::
pwgen 12 >pgpw
sudo -u postgres createuser -S -D -R circle
sudo -u postgres psql <<<"ALTER USER circle WITH PASSWORD '$(cat pgpw)';"
sudo -u postgres createdb circle -O circle
Configure RabbitMQ: remove the guest user, add virtual host and user with
proper permissions::
pwgen 12 >rmqpw
sudo rabbitmqctl delete_user guest
sudo rabbitmqctl add_vhost circle
sudo rabbitmqctl add_user cloud $(cat rmqpw)
sudo rabbitmqctl set_permissions -p circle cloud '.*' '.*' '.*'
Set up nginx to serve the CIRCLE portal. ::
sudo tee /etc/nginx/conf.d/default.conf <<END
ignore_invalid_headers on;
server {
listen 443 ssl default;
ssl on;
ssl_certificate /etc/ssl/certs/s