Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
CIRCLE
/
cloud
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
94
Merge Requests
10
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
d0dc1ed8
authored
Jul 14, 2014
by
Őry Máté
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'master' into issue-139
Conflicts: circle/vm/models/activity.py
parents
06663c9e
2ca7eb56
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
397 additions
and
213 deletions
+397
-213
circle/acl/management/__init__.py
+3
-3
circle/circle/settings/test.py
+3
-2
circle/common/operations.py
+2
-0
circle/dashboard/forms.py
+24
-0
circle/dashboard/templates/dashboard/lease-edit.html
+80
-1
circle/dashboard/templates/dashboard/template-list.html
+2
-0
circle/dashboard/templates/dashboard/vm-detail/home.html
+6
-1
circle/dashboard/tests/test_mockedviews.py
+0
-0
circle/dashboard/tests/test_views.py
+2
-100
circle/dashboard/urls.py
+4
-3
circle/dashboard/views.py
+0
-0
circle/vm/models/activity.py
+2
-0
circle/vm/models/common.py
+16
-2
circle/vm/models/instance.py
+6
-31
circle/vm/operations.py
+23
-4
docs/deploy.rst
+145
-2
docs/install.rst
+78
-63
requirements/base.txt
+1
-1
No files found.
circle/acl/management/__init__.py
View file @
d0dc1ed8
...
...
@@ -59,10 +59,10 @@ def create_levels(app, created_models, verbosity, db=DEFAULT_DB_ALIAS,
]
Level
.
objects
.
using
(
db
)
.
bulk_create
(
levels
)
if
verbosity
>=
2
:
print
(
"Adding levels [
%
s]."
%
", "
.
join
(
levels
))
print
(
"Adding levels [
%
s]."
%
", "
.
join
(
unicode
(
l
)
for
l
in
levels
))
print
(
"Searched: [
%
s]."
%
", "
.
join
(
[
unicode
(
l
)
for
l
in
searched_levels
]
))
print
(
"All: [
%
s]."
%
", "
.
join
(
[
unicode
(
l
)
for
l
in
all_levels
]
))
unicode
(
l
)
for
l
in
searched_levels
))
print
(
"All: [
%
s]."
%
", "
.
join
(
unicode
(
l
)
for
l
in
all_levels
))
# set weights
for
ctype
,
codename
,
weight
in
level_weights
:
...
...
circle/circle/settings/test.py
View file @
d0dc1ed8
...
...
@@ -46,8 +46,9 @@ CACHES = {
LOGGING
[
'loggers'
][
'djangosaml2'
]
=
{
'handlers'
:
[
'console'
],
'level'
:
'CRITICAL'
}
LOGGING
[
'handlers'
][
'console'
]
=
{
'level'
:
'WARNING'
,
level
=
environ
.
get
(
'LOGLEVEL'
,
'CRITICAL'
)
LOGGING
[
'handlers'
][
'console'
]
=
{
'level'
:
level
,
'class'
:
'logging.StreamHandler'
,
'formatter'
:
'simple'
}
for
i
in
LOCAL_APPS
:
LOGGING
[
'loggers'
][
i
]
=
{
'handlers'
:
[
'console'
],
'level'
:
'CRITICAL'
}
LOGGING
[
'loggers'
][
i
]
=
{
'handlers'
:
[
'console'
],
'level'
:
level
}
circle/common/operations.py
View file @
d0dc1ed8
...
...
@@ -59,6 +59,8 @@ class Operation(object):
skip_auth_check
=
auxargs
.
pop
(
'system'
)
user
=
auxargs
.
pop
(
'user'
)
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
argspec
=
getargspec
(
self
.
_operation
)
...
...
circle/dashboard/forms.py
View file @
d0dc1ed8
...
...
@@ -612,6 +612,9 @@ class TemplateForm(forms.ModelForm):
self
.
instance
.
ram_size
=
512
self
.
instance
.
num_cores
=
2
self
.
fields
[
"lease"
]
.
queryset
=
Lease
.
get_objects_with_level
(
"operator"
,
self
.
user
)
def
clean_owner
(
self
):
if
self
.
instance
.
pk
is
not
None
:
return
User
.
objects
.
get
(
pk
=
self
.
instance
.
owner
.
pk
)
...
...
@@ -888,6 +891,27 @@ class LeaseForm(forms.ModelForm):
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
):
name
=
forms
.
CharField
(
max_length
=
100
,
label
=
_
(
"Name"
))
size
=
forms
.
CharField
(
...
...
circle/dashboard/templates/dashboard/lease-edit.html
View file @
d0dc1ed8
...
...
@@ -6,7 +6,7 @@
{% block content %}
<div
class=
"row"
>
<div
class=
"col-md-
12
"
>
<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>
...
...
@@ -20,6 +20,85 @@
</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>
{% endblock %}
circle/dashboard/templates/dashboard/template-list.html
View file @
d0dc1ed8
...
...
@@ -26,9 +26,11 @@
<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=
"icon-plus"
></i>
{% trans "new lease" %}
</a>
{% endif %}
<h3
class=
"no-margin"
><i
class=
"icon-time"
></i>
{% trans "Leases" %}
</h3>
</div>
<div
class=
"panel-body"
>
...
...
circle/dashboard/templates/dashboard/vm-detail/home.html
View file @
d0dc1ed8
...
...
@@ -47,7 +47,12 @@
</dl>
<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>
<dl>
<dt>
{% trans "Suspended at:" %}
</dt>
...
...
circle/dashboard/tests/test_mockedviews.py
View file @
d0dc1ed8
This diff is collapsed.
Click to expand it.
circle/dashboard/tests/test_views.py
View file @
d0dc1ed8
...
...
@@ -21,14 +21,12 @@ import json
from
django.test
import
TestCase
from
django.test.client
import
Client
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
import
authenticate
from
vm.models
import
Instance
,
InstanceTemplate
,
Lease
,
Node
,
Trait
from
vm.operations
import
WakeUpOperation
from
..models
import
Profile
from
..views
import
VmRenewView
from
storage.models
import
Disk
from
firewall.models
import
Vlan
,
Host
,
VlanGroup
from
mock
import
Mock
,
patch
...
...
@@ -568,10 +566,8 @@ class VmDetailTest(LoginMixin, TestCase):
inst
=
Instance
.
objects
.
get
(
pk
=
1
)
inst
.
manual_state_change
(
'SUSPENDED'
)
inst
.
set_level
(
self
.
u2
,
'user'
)
with
patch
(
'dashboard.views.messages'
)
as
msg
:
response
=
c
.
post
(
"/dashboard/vm/1/op/wake_up/"
)
assert
msg
.
error
.
called
self
.
assertEqual
(
response
.
status_code
,
302
)
response
=
c
.
post
(
"/dashboard/vm/1/op/wake_up/"
)
self
.
assertEqual
(
response
.
status_code
,
403
)
inst
=
Instance
.
objects
.
get
(
pk
=
1
)
self
.
assertEqual
(
inst
.
status
,
'SUSPENDED'
)
...
...
@@ -1631,100 +1627,6 @@ class TransferOwnershipViewTest(LoginMixin, TestCase):
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
):
fixtures
=
[
'test-vm-fixture.json'
,
'node.json'
]
...
...
circle/dashboard/urls.py
View file @
d0dc1ed8
...
...
@@ -29,7 +29,7 @@ from .views import (
TemplateDelete
,
TemplateDetail
,
TemplateList
,
TransferOwnershipConfirmView
,
TransferOwnershipView
,
vm_activity
,
VmCreate
,
VmDelete
,
VmDetailView
,
VmDetailVncTokenView
,
VmGraphView
,
VmList
,
VmMassDelete
,
VmRenewView
,
DiskRemoveView
,
get_disk_download_status
,
InterfaceDeleteView
,
DiskRemoveView
,
get_disk_download_status
,
InterfaceDeleteView
,
GroupRemoveAclUserView
,
GroupRemoveAclGroupView
,
GroupRemoveUserView
,
GroupRemoveFutureUserView
,
GroupCreate
,
GroupProfileUpdate
,
...
...
@@ -40,6 +40,7 @@ from .views import (
UserKeyDelete
,
UserKeyDetail
,
UserKeyCreate
,
VmTraitsUpdate
,
VmRawDataUpdate
,
GroupPermissionsView
,
LeaseAclUpdateView
,
)
urlpatterns
=
patterns
(
...
...
@@ -51,6 +52,8 @@ urlpatterns = patterns(
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"
),
url
(
r'^template/create/$'
,
TemplateCreate
.
as_view
(),
name
=
"dashboard.views.template-create"
),
...
...
@@ -84,8 +87,6 @@ urlpatterns = patterns(
url
(
r'^vm/mass-delete/'
,
VmMassDelete
.
as_view
(),
name
=
'dashboard.view.mass-delete-vm'
),
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
(),
name
=
'dashboard.views.vm-activity'
),
url
(
r'^vm/(?P<pk>\d+)/screenshot/$'
,
get_vm_screenshot
,
...
...
circle/dashboard/views.py
View file @
d0dc1ed8
This diff is collapsed.
Click to expand it.
circle/vm/models/activity.py
View file @
d0dc1ed8
...
...
@@ -225,6 +225,8 @@ def node_activity(code_suffix, node, task_uuid=None, user=None):
@worker_ready.connect
()
def
cleanup
(
conf
=
None
,
**
kwargs
):
# 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. "
"You can try again now."
)
message
=
create_readable
(
msg_txt
,
msg_txt
)
...
...
circle/vm/models/common.py
View file @
d0dc1ed8
...
...
@@ -18,12 +18,14 @@
from
__future__
import
absolute_import
,
unicode_literals
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.timesince
import
timeuntil
from
model_utils.models
import
TimeStampedModel
from
acl.models
import
AclBase
ARCHITECTURES
=
((
'x86_64'
,
'x86-64 (64 bit)'
),
(
'i686'
,
'x86 (32 bit)'
))
...
...
@@ -66,13 +68,18 @@ class NamedBaseResourceConfig(BaseResourceConfigModel, TimeStampedModel):
return
self
.
name
class
Lease
(
Model
):
class
Lease
(
AclBase
):
"""Lease times for VM instances.
Specifies a time duration until suspension and deletion of a VM
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
,
verbose_name
=
_
(
'name'
))
suspend_interval_seconds
=
IntegerField
(
...
...
@@ -88,6 +95,9 @@ class Lease(Model):
app_label
=
'vm'
db_table
=
'vm_lease'
ordering
=
[
'name'
,
]
permissions
=
(
(
'create_leases'
,
_
(
'Can create new leases.'
)),
)
@property
def
suspend_interval
(
self
):
...
...
@@ -141,6 +151,10 @@ class Lease(Model):
's'
:
self
.
get_readable_suspend_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
):
name
=
CharField
(
max_length
=
50
,
verbose_name
=
_
(
'name'
))
...
...
circle/vm/models/instance.py
View file @
d0dc1ed8
...
...
@@ -439,10 +439,7 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
for
cps
in
customized_params
]
def
clean
(
self
,
*
args
,
**
kwargs
):
if
self
.
time_of_suspend
is
None
:
self
.
_do_renew
(
which
=
'suspend'
)
if
self
.
time_of_delete
is
None
:
self
.
_do_renew
(
which
=
'delete'
)
self
.
time_of_suspend
,
self
.
time_of_delete
=
self
.
get_renew_times
()
super
(
Instance
,
self
)
.
clean
(
*
args
,
**
kwargs
)
def
manual_state_change
(
self
,
new_state
=
"NOSTATE"
,
reason
=
None
,
user
=
None
):
...
...
@@ -715,36 +712,14 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
else
:
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.
"""
if
lease
is
None
:
lease
=
self
.
lease
return
(
timezone
.
now
()
+
self
.
lease
.
suspend_interval
,
timezone
.
now
()
+
self
.
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
()
timezone
.
now
()
+
lease
.
suspend_interval
,
timezone
.
now
()
+
lease
.
delete_interval
)
def
change_password
(
self
,
user
=
None
):
"""Generate new password for the vm
...
...
circle/vm/operations.py
View file @
d0dc1ed8
...
...
@@ -189,8 +189,9 @@ class DeployOperation(InstanceOperation):
self
.
instance
.
deploy_disks
()
# Deploy VM on remote machine
with
activity
.
sub_activity
(
'deploying_vm'
)
as
deploy_act
:
deploy_act
.
result
=
self
.
instance
.
deploy_vm
(
timeout
=
timeout
)
if
self
.
instance
.
state
not
in
[
'PAUSED'
]:
with
activity
.
sub_activity
(
'deploying_vm'
)
as
deploy_act
:
deploy_act
.
result
=
self
.
instance
.
deploy_vm
(
timeout
=
timeout
)
# Establish network connection (vmdriver)
with
activity
.
sub_activity
(
'deploying_net'
):
...
...
@@ -200,7 +201,7 @@ class DeployOperation(InstanceOperation):
with
activity
.
sub_activity
(
'booting'
):
self
.
instance
.
resume_vm
(
timeout
=
timeout
)
self
.
instance
.
renew
(
which
=
'both'
,
base
_activity
=
activity
)
self
.
instance
.
renew
(
parent
_activity
=
activity
)
register_operation
(
DeployOperation
)
...
...
@@ -613,12 +614,30 @@ class WakeUpOperation(InstanceOperation):
self
.
instance
.
deploy_net
()
# Renew vm
self
.
instance
.
renew
(
which
=
'both'
,
base
_activity
=
activity
)
self
.
instance
.
renew
(
parent
_activity
=
activity
)
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
):
async_operation
=
abortable_async_node_operation
host_cls
=
Node
...
...
docs/deploy.rst
View file @
d0dc1ed8
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/ssl-cert-snakeoil.pem;
ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;
location /static {
alias ${PWD}/circle/static_collected; # your Django project's static files
}
location / {
uwsgi_pass unix:///tmp/uwsgi.sock;
include /etc/nginx/uwsgi_params; # or the uwsgi_params you installed manually
}
location /vnc/ {
proxy_pass http://localhost:9999;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header Host \$host;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
# WebSocket support (nginx 1.4)
proxy_http_version 1.1;
proxy_set_header Upgrade \$http_upgrade;
proxy_set_header Connection "upgrade";
}
}
server {
listen 80 default;
rewrite ^ https://\$host/; # permanent;
}
END
sudo /etc/init.d/nginx restart
.. warning::
For a production deployment, you should use certificates issued by a
recognized certificate authority. Until you get it, you can use a
self-signed one automatically generated by the package.
Setting up Circle itself
------------------------
Clone the git repository::
git clone https://git.ik.bme.hu/circle/cloud.git circle
Set up *virtualenvwrapper* and the *virtual Python environment* for the
project::
source /etc/bash_completion.d/virtualenvwrapper
mkvirtualenv circle
Set up default Circle configuration and activate the virtual environment::
cat >>/home/cloud/.virtualenvs/circle/bin/postactivate <<END
export DJANGO_SETTINGS_MODULE=circle.settings.production
export DJANGO_DB_HOST=localhost
export DJANGO_DB_PASSWORD=$(cat pgpw)
export DJANGO_FIREWALL_SETTINGS='{"dns_ip": "152.66.243.60", "dns_hostname":
"localhost", "dns_ttl": "300", "reload_sleep": "10",
"rdns_ip": "152.66.243.60", "default_vlangroup": "publikus"}'
export AMQP_URI='amqp://cloud:$(cat rmqpw)@localhost:5672/circle'
export CACHE_URI='pylibmc://127.0.0.1:11211/'
END
workon circle
cd ~/circle
You should change DJANGO_FIREWALL_SETTINGS to your needs.
Install the required Python libraries to the virtual environment::
pip install -r requirements.txt
Sync the database and create a superuser::
circle/manage.py syncdb --all --noinput
circle/manage.py migrate --fake
circle/manage.py createsuperuser
Copy the init files to Upstart's config directory and start the manager and
the portal application server::
sudo cp miscellaneous/mancelery.conf /etc/init/
sudo start mancelery
sudo cp miscellaneous/portal-uwsgi.conf /etc/init/
sudo start portal-uwsgi
docs/install.rst
View file @
d0dc1ed8
...
...
@@ -3,31 +3,40 @@ Installation of a development machine
.. highlight:: bash
This tutorial describes the installation of a development 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.
Preparation
-----------
To get the project running on a development machine, create a new Ubuntu 12.04
instance, and log in to it over SSH.
To get the project running on a development machine, launch a new Ubuntu
14.04 machine, and log in to it over SSH.
.. info::
To use *git* over *SSH*, we advise enabling SSH *agent forwarding*.
On your terminal computer check if *ssh-agent* is running (the command
should print a process id)::
To use *git* over *SSH*, we advise enabling SSH *agent forwarding*.
On your personal computer check if *ssh-agent* is running (the command should
print a process id)::
$ echo $SSH_AGENT_PID
1234
$ echo $SSH_AGENT_PID
1234
If it is not running, you should set up your login manager or some other
solution to
automatically launch it.
If it is not running, you can configure your dektop environment to
automatically launch it.
Add your private key to the agent (if it is not added by your desktop
environment)::
Add your private key to the agent (if it is not added by your desktop
environment)::
$ ssh-add [~/.ssh/path_to_id_rsa]
ssh-add [~/.ssh/path_to_id_rsa]
You can read and write all repositories over https, but you will have to
provide username and password for every push command.
Log in to the new vm. The :kbd:`-A` switch enables agent forwarding::
$
ssh -A cloud@host
ssh -A cloud@host
You can check agent forwarding on the vm::
...
...
@@ -37,56 +46,56 @@ You can check agent forwarding on the vm::
.. warning::
If the first character of the hostname of the vm is a digit, you have to
change it, because RabbitMQ won't work with it. ::
$
old=$(hostname)
$
new=c-${old}
$
sudo tee /etc/hostname <<<$new
$
sudo hostname $new
$
sudo sed -i /etc/hosts -e "s/$old/$new/g"