Commit bf9d6a8f authored by Christos Stavrakakis's avatar Christos Stavrakakis
Browse files

Merge branch 'hotfix-0.14.8' into develop

Merge hotfix-0.14.8 which ports Synnefo to wheezy and Django 1.4.5

Conflicts:
	ci/ci_squeeze.conf
	ci/ci_wheezy.conf
	ci/schemas/one_node_squeeze/packages.conf
	ci/schemas/one_node_squeeze/squeeze.conf
	ci/schemas/one_node_squeeze/wheezy.conf
	ci/schemas/one_node_wheezy/packages.conf
	ci/schemas/one_node_wheezy/squeeze.conf
	ci/schemas/one_node_wheezy/wheezy.conf
	ci/tests.sh
	ci/utils.py
	snf-astakos-app/astakos/im/forms.py
	snf-astakos-app/astakos/im/tests/projects.py
	snf-astakos-app/astakos/im/views/im.py
	snf-astakos-app/astakos/im/weblogin_urls.py
	snf-astakos-app/astakos/urls.py
	snf-astakos-app/setup.py
	snf-cyclades-app/synnefo/api/networks.py
	snf-cyclades-app/synnefo/api/tests/flavors.py
	snf-cyclades-app/synnefo/app_settings/urls.py
	snf-cyclades-app/synnefo/logic/management/commands/reconcile-networks.py
	snf-cyclades-app/synnefo/logic/tests/callbacks.py
	snf-deploy/conf/packages.conf
	snf-deploy/conf/squeeze.conf
	snf-deploy/conf/wheezy.conf
	snf-deploy/fabfile.py
	snf-deploy/files/etc/apt/sources.list.d/synnefo.squeeze.list
	snf-deploy/files/etc/apt/sources.list.d/synnefo.wheezy.list
	snf-deploy/files/etc/synnefo/webproject.conf
	snf-django-lib/snf_django/lib/api/__init__.py
	snf-django-lib/snf_django/lib/api/urls.py
	snf-django-lib/snf_django/utils/testing.py
	snf-pithos-app/setup.py
	snf-stats-app/synnefo_stats/urls.py
	version
parents fde7c36c ed9a7f37
......@@ -41,7 +41,7 @@ flavors = name:C8R8...D20ext_.*, name:C8R8...D20drbd, id:1
# A list of images (comma seperated) to choose from
# The user can specify an image name (reg expression)
# with "name:" or an image id with "id:".
images = name:^Debian Base$, id:72d9844f-1024-4a07-a3c3-60d650b8f5cd
images = name:SynnefoCIWheezy.*, name:^Debian Base$, id:72d9844f-1024-4a07-a3c3-60d650b8f5cd
# File containing the ssh keys to upload/install to server
# If not set, no ssh keys will be installed
ssh_keys = ~/.ssh/id_rsa.pub
......
[debian]
rabbitmq-server = testing
rabbitmq-server = squeeze-backports
gunicorn = squeeze-backports
qemu-kvm = squeeze-backports
qemu = squeeze-backports
......
[debian]
rabbitmq-server = testing
rabbitmq-server = squeeze-backports
gunicorn = squeeze-backports
qemu-kvm = squeeze-backports
qemu = squeeze-backports
......
[debian]
rabbitmq-server = testing
rabbitmq-server =
gunicorn =
qemu-kvm =
qemu =
......
[debian]
rabbitmq-server = testing
rabbitmq-server =
gunicorn =
qemu-kvm =
qemu =
......
[debian]
rabbitmq-server = testing
rabbitmq-server = squeeze-backports
gunicorn = squeeze-backports
qemu-kvm = squeeze-backports
qemu = squeeze-backports
......
[debian]
rabbitmq-server = testing
rabbitmq-server =
gunicorn =
qemu-kvm =
qemu =
......
......@@ -643,7 +643,7 @@ class SynnefoCI(object):
apt-get update
apt-get install zlib1g-dev dpkg-dev debhelper git-buildpackage \
python-dev python-all python-pip --yes --force-yes
pip install devflow
pip install -U devflow
"""
_run(cmd, False)
......@@ -737,8 +737,8 @@ class SynnefoCI(object):
self.logger.debug("Install needed packages")
cmd = """
pip install mock
pip install factory_boy
pip install -U mock
pip install -U factory_boy
"""
_run(cmd, False)
......
......@@ -30,7 +30,7 @@ There are also the following tools:
kamaki: Command-line client <http://www.synnefo.org/docs/kamaki/latest/index.html>
snf-deploy: Synnefo deployment tool <snf-deploy>
snf-image-creator: Image bundling/uploading/registering tool <http://www.synnefo.org/docs/snf-image-creator/latest/index.html>
snf-image: Secure image deployment tool <snf-image>
snf-image: Secure image deployment tool <http://www.synnefo.org/docs/snf-image/latest/index.html>
snf-burnin: Integration testing tool for a running Synnefo deployment <snf-burnin>
This is an overview of the Synnefo services:
......@@ -132,7 +132,7 @@ They are also available from our apt repository: ``apt.dev.grnet.gr``
* `snf-cyclades-gtools <http://www.synnefo.org/docs/snf-cyclades-gtools/latest/index.html>`_
* `astakosclient <http://www.synnefo.org/docs/astakosclient/latest/index.html>`_
* `snf-vncauthproxy <https://code.grnet.gr/projects/vncauthproxy>`_
* `snf-image <https://code.grnet.gr/projects/snf-image/wiki/>`_
* `snf-image <http://www.synnefo.org/docs/snf-image/latest/index.html/>`_
* `snf-image-creator <http://www.synnefo.org/docs/snf-image-creator/latest/index.html>`_
* `snf-occi <http://www.synnefo.org/docs/snf-occi/latest/index.html>`_
* `snf-cloudcms <http://www.synnefo.org/docs/snf-cloudcms/latest/index.html>`_
......
......@@ -643,7 +643,7 @@ uncomment and edit the ``DATABASES`` block to reflect our database:
DATABASES = {
'default': {
# 'postgresql_psycopg2', 'postgresql','mysql', 'sqlite3' or 'oracle'
'ENGINE': 'postgresql_psycopg2',
'ENGINE': 'django.db.backends.postgresql_psycopg2',
# ATTENTION: This *must* be the absolute path if using sqlite3.
# See: http://docs.djangoproject.com/en/dev/ref/settings/#name
'NAME': 'snf_apps',
......@@ -839,7 +839,7 @@ file that looks like this:
DATABASES = {
'default': {
# 'postgresql_psycopg2', 'postgresql','mysql', 'sqlite3' or 'oracle'
'ENGINE': 'postgresql_psycopg2',
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'OPTIONS': {'synnefo_poolsize': 8},
# ATTENTION: This *must* be the absolute path if using sqlite3.
......@@ -1296,8 +1296,10 @@ snf-image
Installation
~~~~~~~~~~~~
For :ref:`Cyclades <cyclades>` to be able to launch VMs from specified Images,
you need the :ref:`snf-image <snf-image>` OS Definition installed on *all*
VM-capable Ganeti nodes. This means we need :ref:`snf-image <snf-image>` on
you need the :ref:
`snf-image <http://www.synnefo.org/docs/snf-image/latest/index.html>` OS
Definition installed on *all* VM-capable Ganeti nodes. This means we need
:ref:`snf-image <http://www.synnefo.org/docs/snf-image/latest/index.html>` on
node1 and node2. You can do this by running on *both* nodes:
.. code-block:: console
......@@ -1316,17 +1318,6 @@ VM-capable Ganeti nodes.
directions about the certificates,in order to circumvent this you should edit the file
``/etc/default/snf-image``. Change ``#CURL="curl"`` to ``CURL="curl -k"`` on every node.
After `snf-image` has been installed successfully, create the helper VM by
running on *both* nodes:
.. code-block:: console
# snf-image-update-helper
This will create all the needed files under ``/var/lib/snf-image/helper/`` for
snf-image to run successfully, and it may take a few minutes depending on your
Internet connection.
Configuration
~~~~~~~~~~~~~
snf-image supports native access to Images stored on Pithos. This means that
......@@ -1363,7 +1354,7 @@ This should return ``valid`` for snf-image.
If you are interested to learn more about snf-image's internals (and even use
it alongside Ganeti without Synnefo), please see
`here <https://code.grnet.gr/projects/snf-image/wiki>`_ for information
`here <http://www.synnefo.org/docs/snf-image/latest/index.html>`_ for information
concerning installation instructions, documentation on the design and
implementation, and supported Image formats.
......@@ -1373,13 +1364,15 @@ Actual Images for snf-image
---------------------------
Now that snf-image is installed successfully we need to provide it with some
Images. :ref:`snf-image <snf-image>` supports Images stored in ``extdump``,
``ntfsdump`` or ``diskdump`` format. We recommend the use of the ``diskdump``
format. For more information about snf-image Image formats see `here
<https://code.grnet.gr/projects/snf-image/wiki/Image_Format>`_.
Images.
:ref:`snf-image <http://www.synnefo.org/docs/snf-image/latest/index.html>`
supports Images stored in ``extdump``, ``ntfsdump`` or ``diskdump`` format. We
recommend the use of the ``diskdump`` format. For more information about
snf-image Image formats see `here
<http://www.synnefo.org/docs/snf-image/latest/usage.html#image-format>`_.
:ref:`snf-image <snf-image>` also supports three (3) different locations for the
above Images to be stored:
:ref:`snf-image <http://www.synnefo.org/docs/snf-image/latest/index.html>`
also supports three (3) different locations for the above Images to be stored:
* Under a local folder (usually an NFS mount, configurable as ``IMAGE_DIR``
in :file:`/etc/default/snf-image`)
......@@ -1388,8 +1381,8 @@ above Images to be stored:
For the purpose of this guide, we will use the Debian Squeeze Base Image found
on the official `snf-image page
<https://code.grnet.gr/projects/snf-image/wiki#Sample-Images>`_. The image is
of type ``diskdump``. We will store it in our new Pithos installation.
<http://www.synnefo.org/docs/snf-image/latest/usage.html#sample-images>`_. The
image is of type ``diskdump``. We will store it in our new Pithos installation.
To do so, do the following:
......@@ -1405,7 +1398,7 @@ Ganeti, in the next section.
Of course, you can repeat the procedure to upload more Images, available from
the `official snf-image page
<https://code.grnet.gr/projects/snf-image/wiki#Sample-Images>`_.
<http://www.synnefo.org/docs/snf-image/latest/usage.html#sample-images>`_.
.. _ganeti-with-pithos-images:
......@@ -1438,7 +1431,7 @@ In the above command:
* ``filename``: the name of file (visible also from the Web UI)
* ``img_properties``: taken from the metadata file. Used only the two mandatory
properties ``OSFAMILY`` and ``ROOT_PARTITION``. `Learn more
<https://code.grnet.gr/projects/snf-image/wiki/Image_Format#Image-Properties>`_
<http://www.synnefo.org/docs/snf-image/latest/usage.html#image-properties>`_
If the ``gnt-instance add`` command returns successfully, then run:
......
.. _snf-image:
snf-image
^^^^^^^^^
:ref:`snf-image <snf-image>` is the secure image deployment tool used by the
synnefo's Compute Service, :ref:`cyclades <cyclades>`.
Currently, it is implemented as an OS definition for
`Ganeti <http://code.google.com/p/ganeti>`_.
Furthermore :ref:`snf-image <snf-image>` acts as a standalone add-on for Ganeti
and can be used with it independently. Someone who uses Ganeti, (and has
nothing to do with Synnefo) can use snf-image to serurely deploy many kind of
images with Ganeti.
Please refer to the
`snf-image documentation <https://code.grnet.gr/projects/snf-image/wiki>`_ for
all details concerning installation and configuration.
......@@ -31,7 +31,11 @@
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
from django.conf.urls.defaults import patterns, url
try:
from django.conf.urls import patterns, url
except ImportError: # Django==1.2
from django.conf.urls.defaults import patterns, url
from snf_django.lib.api import api_endpoint_not_found
urlpatterns = patterns(
......
......@@ -31,7 +31,11 @@
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
from django.conf.urls.defaults import patterns, url, include
try:
from django.conf.urls import patterns, url, include
except ImportError: # Django==1.2
from django.conf.urls.defaults import patterns, url, include
from snf_django.lib.api import api_endpoint_not_found
......
......@@ -91,8 +91,8 @@ class CookieHandler():
path='/',
domain=settings.COOKIE_DOMAIN, secure=settings.COOKIE_SECURE
)
msg = str(('Cookie [expiring %(auth_token_expires)s]',
'set for %(uuid)s')) % user.__dict__
msg = str(('Cookie [expiring %s]', 'set for %s')) \
% (user.auth_token_expires, user.uuid)
logger._log(settings.LOGGING_LEVEL, msg, [])
def __delete(self):
......
......@@ -185,16 +185,16 @@ class LocalUserCreationForm(UserCreationForm, StoreUserMixin):
user.add_auth_provider('local', auth_backend='astakos')
user.set_password(self.cleaned_data['password1'])
def save(self, commit=True):
def save(self, commit=True, **kwargs):
"""
Saves the email, first_name and last_name properties, after the normal
save behavior is complete.
"""
user = super(LocalUserCreationForm, self).save(commit=False)
user = super(LocalUserCreationForm, self).save(commit=False, **kwargs)
user.date_signed_terms = datetime.now()
user.renew_token()
if commit:
user.save()
user.save(**kwargs)
logger.info('Created user %s', user.log_display)
return user
......@@ -218,12 +218,13 @@ class InvitedLocalUserCreationForm(LocalUserCreationForm):
for f in ro:
self.fields[f].widget.attrs['readonly'] = True
def save(self, commit=True):
user = super(InvitedLocalUserCreationForm, self).save(commit=False)
def save(self, commit=True, **kwargs):
user = super(InvitedLocalUserCreationForm, self).save(commit=False,
**kwargs)
user.set_invitations_level()
user.email_verified = True
if commit:
user.save()
user.save(**kwargs)
return user
......@@ -297,13 +298,14 @@ class ThirdPartyUserCreationForm(forms.ModelForm, StoreUserMixin):
provider.add_to_user()
pending.delete()
def save(self, commit=True):
user = super(ThirdPartyUserCreationForm, self).save(commit=False)
def save(self, commit=True, **kwargs):
user = super(ThirdPartyUserCreationForm, self).save(commit=False,
**kwargs)
user.set_unusable_password()
user.renew_token()
user.date_signed_terms = datetime.now()
if commit:
user.save()
user.save(**kwargs)
logger.info('Created user %s' % user.log_display)
return user
......@@ -324,13 +326,14 @@ class InvitedThirdPartyUserCreationForm(ThirdPartyUserCreationForm):
for f in ro:
self.fields[f].widget.attrs['readonly'] = True
def save(self, commit=True):
user = super(
InvitedThirdPartyUserCreationForm, self).save(commit=False)
def save(self, commit=True, **kwargs):
user = \
super(InvitedThirdPartyUserCreationForm, self).save(commit=False,
**kwargs)
user.set_invitation_level()
user.email_verified = True
if commit:
user.save()
user.save(**kwargs)
return user
......@@ -459,8 +462,8 @@ class ProfileForm(forms.ModelForm):
def clean_email(self):
return self.instance.email
def save(self, commit=True):
user = super(ProfileForm, self).save(commit=False)
def save(self, commit=True, **kwargs):
user = super(ProfileForm, self).save(commit=False, **kwargs)
user.is_verified = True
if self.cleaned_data.get('renew'):
user.renew_token(
......@@ -468,7 +471,7 @@ class ProfileForm(forms.ModelForm):
current_key=self.session_key
)
if commit:
user.save()
user.save(**kwargs)
return user
......@@ -525,10 +528,11 @@ class ExtendedPasswordResetForm(PasswordResetForm):
def save(self, domain_override=None,
email_template_name='registration/password_reset_email.html',
use_https=False, token_generator=default_token_generator,
request=None):
request=None, **kwargs):
"""
Generates a one-use only link for resetting password and sends to the
user.
"""
for user in self.users_cache:
url = user.astakosuser.get_password_reset_url(token_generator)
......@@ -564,8 +568,8 @@ class EmailChangeForm(forms.ModelForm):
def save(self, request,
email_template_name='registration/email_change_email.txt',
commit=True):
ec = super(EmailChangeForm, self).save(commit=False)
commit=True, **kwargs):
ec = super(EmailChangeForm, self).save(commit=False, **kwargs)
ec.user = request.user
# delete pending email changes
request.user.emailchanges.all().delete()
......@@ -574,7 +578,7 @@ class EmailChangeForm(forms.ModelForm):
str(random()) + smart_str(ec.new_email_address))
ec.activation_key = activation_key.hexdigest()
if commit:
ec.save()
ec.save(**kwargs)
send_change_email(ec, request, email_template_name=email_template_name)
......@@ -593,11 +597,11 @@ class SignApprovalTermsForm(forms.ModelForm):
raise forms.ValidationError(_(astakos_messages.SIGN_TERMS))
return has_signed_terms
def save(self, commit=True):
user = super(SignApprovalTermsForm, self).save(commit)
def save(self, commit=True, **kwargs):
user = super(SignApprovalTermsForm, self).save(commit=commit, **kwargs)
user.date_signed_terms = datetime.now()
if commit:
user.save()
user.save(**kwargs)
return user
......@@ -638,7 +642,7 @@ class ExtendedPasswordChangeForm(PasswordChangeForm):
self.session_key = kwargs.pop('session_key', None)
super(ExtendedPasswordChangeForm, self).__init__(user, *args, **kwargs)
def save(self, commit=True):
def save(self, commit=True, **kwargs):
try:
if settings.NEWPASSWD_INVALIDATE_TOKEN or \
self.cleaned_data.get('renew'):
......@@ -647,7 +651,8 @@ class ExtendedPasswordChangeForm(PasswordChangeForm):
except AttributeError:
# if user model does has not such methods
pass
return super(ExtendedPasswordChangeForm, self).save(commit=commit)
return super(ExtendedPasswordChangeForm, self).save(commit=commit,
**kwargs)
class ExtendedSetPasswordForm(SetPasswordForm):
......@@ -666,7 +671,7 @@ class ExtendedSetPasswordForm(SetPasswordForm):
super(ExtendedSetPasswordForm, self).__init__(user, *args, **kwargs)
@transaction.commit_on_success()
def save(self, commit=True):
def save(self, commit=True, **kwargs):
try:
self.user = AstakosUser.objects.get(id=self.user.id)
if settings.NEWPASSWD_INVALIDATE_TOKEN or \
......@@ -679,7 +684,8 @@ class ExtendedSetPasswordForm(SetPasswordForm):
except BaseException, e:
logger.exception(e)
return super(ExtendedSetPasswordForm, self).save(commit=commit)
return super(ExtendedSetPasswordForm, self).save(commit=commit,
**kwargs)
app_name_label = "Project name"
......@@ -915,7 +921,7 @@ class ProjectApplicationForm(forms.ModelForm):
return policies
def save(self, commit=True):
def save(self, commit=True, **kwargs):
data = dict(self.cleaned_data)
is_new = self.instance.id is None
data['project_id'] = self.instance.chain.id if not is_new else None
......
......@@ -1221,7 +1221,7 @@ class PendingThirdPartyUser(models.Model):
else:
self.last_name = parts[0]
def save(self, **kwargs):
def save(self, *args, **kwargs):
if not self.id:
# set username
while not self.username:
......@@ -1230,7 +1230,7 @@ class PendingThirdPartyUser(models.Model):
AstakosUser.objects.get(username=username)
except AstakosUser.DoesNotExist:
self.username = username
super(PendingThirdPartyUser, self).save(**kwargs)
super(PendingThirdPartyUser, self).save(*args, **kwargs)
def generate_token(self):
self.password = self.third_party_identifier
......@@ -1564,7 +1564,7 @@ class ProjectManager(ForUpdateManager):
relevant = model.o_states_q(model.RELEVANT_STATES)
return self.filter(flt, relevant).order_by(
'application__issue_date').select_related(
'application', 'application__owner', 'application__applicant')
'application', 'application__owner', 'application__applicant')
def search_by_name(self, *search_strings):
q = Q()
......
......@@ -767,7 +767,7 @@ class TestProjects(TestCase):
# user rejoins
self.member_client.get(reverse("edit_profile"))
join_url = reverse("project_join", kwargs={'chain_id': app1_id})
join_url = reverse("project_join", kwargs={'chain_id': project1_id})
r = self.member_client.post(join_url, follow=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(ProjectMembership.objects.requested().count(), 1)
......
......@@ -31,7 +31,11 @@
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
from django.conf.urls.defaults import patterns, url
try:
from django.conf.urls import patterns, url
except ImportError: # Django==1.2
from django.conf.urls.defaults import patterns, url
from astakos.im.forms import (
ExtendedPasswordResetForm,
......
......@@ -143,7 +143,7 @@ def update_token(request):
@require_http_methods(["GET", "POST"])
@cookie_fix
@valid_astakos_user_required
@transaction.commit_manually
@transaction.commit_on_success
def invite(request, template_name='im/invitations.html', extra_context=None):
"""
Allows a user to invite somebody else.
......@@ -153,9 +153,8 @@ def invite(request, template_name='im/invitations.html', extra_context=None):
In case of POST checks whether the user has not run out of invitations and
then sends an invitation email to singup to the service.
The view uses commit_manually decorator in order to ensure the number of
the user invitations is going to be updated only if the email has been
successfully sent.
The number of the user invitations is going to be updated only if the email
has been successfully sent.
If the user isn't logged in, redirects to settings.LOGIN_URL.
......@@ -188,17 +187,11 @@ def invite(request, template_name='im/invitations.html', extra_context=None):
form = InvitationForm(request.POST)
if inviter.invitations > 0:
if form.is_valid():
try:
email = form.cleaned_data.get('username')
realname = form.cleaned_data.get('realname')
invite_func(inviter, email, realname)
message = _(astakos_messages.INVITATION_SENT) % locals()
messages.success(request, message)
except Exception, e:
transaction.rollback()
raise
else:
transaction.commit()
email = form.cleaned_data.get('username')
realname = form.cleaned_data.get('realname')
invite_func(inviter, email, realname)
message = _(astakos_messages.INVITATION_SENT) % locals()
messages.success(request, message)
else:
message = _(astakos_messages.MAX_INVITATION_NUMBER_REACHED)
messages.error(request, message)
......@@ -364,7 +357,7 @@ def edit_profile(request, template_name='im/profile.html', extra_context=None):
extra_context))
@transaction.commit_manually
@transaction.commit_on_success
@require_http_methods(["GET", "POST"])
@cookie_fix
def signup(request, template_name='im/signup.html', on_success='index',
......@@ -408,13 +401,11 @@ def signup(request, template_name='im/signup.html', on_success='index',
if request.user.is_authenticated():
logger.info("%s already signed in, redirect to index",
request.user.log_display)
transaction.rollback()
return HttpResponseRedirect(reverse('index'))
provider = get_query(request).get('provider', 'local')
if not auth.get_provider(provider).get_create_policy:
logger.error("%s provider not available for signup", provider)
transaction.rollback()
raise PermissionDenied
instance = None
......@@ -465,47 +456,36 @@ def signup(request, template_name='im/signup.html', on_success='index',
**form_kwargs)
if form.is_valid():
commited = False
try:
user = form.save(commit=False)
# delete previously unverified accounts
if AstakosUser.objects.user_exists(user.email):
AstakosUser.objects.get_by_identifier(user.email).delete()
# store_user so that user auth providers get initialized
form.store_user(user, request)
result = activation_backend.handle_registration(user)
if result.status == \
activation_backend.Result.PENDING_MODERATION:
# user should be warned that his account is not active yet
status = messages.WARNING
else:
status = messages.SUCCESS
message = result.message
activation_backend.send_result_notifications(result, user)
user = form.save(commit=False)
# delete previously unverified accounts
if AstakosUser.objects.user_exists(user.email):
AstakosUser.objects.get_by_identifier(user.email).delete()
# store_user so that user auth providers get initialized
form.store_user(user, request)
result = activation_backend.handle_registration(user)
if result.status == \
activation_backend.Result.PENDING_MODERATION:
# user should be warned that his account is not active yet
status = messages.WARNING
else:
status = messages.SUCCESS
message = result.message