Commit 890071ed authored by Christos Stavrakakis's avatar Christos Stavrakakis
Browse files

Merge branch 'hotfix-0.14.2'

parents e735638e 9ececcf9
......@@ -6,6 +6,18 @@ Unified Changelog file for Synnefo versions >= 0.13
Since v0.13 most of the Synnefo components have been merged into a single
repository and have aligned versions.
.. _Changelog-0.14.2:
v0.14.2
=======
Released: Fri Jul 12 13:13:32 EEST 2013
Cyclades
--------
* Add new setting PITHOS_BACKEND_POOL_SIZE, which configures the size
of the pool of Pithos backends that are used by plankton.
.. _Changelog-0.14:
......
......@@ -6,6 +6,13 @@ Unified NEWS file for Synnefo versions >= 0.13
Since v0.13 all Synnefo components have been merged into a single repository.
.. _NEWS-0.14.2:
v0.14.2
=======
Released: Fri Jul 12 13:13:32 EEST 2013
.. _NEWS-0.14:
v0.14
......
......@@ -49,9 +49,12 @@ def retry(func):
is_last_attempt = attemps == self.retry
if is_last_attempt:
raise err
if err.status == 401 or err.status == 404:
if err.status == 401 or \
err.status == 404 or \
err.status == 413:
# In case of Unauthorized response
# or Not Found return immediately
# or Not Found or Request Entity Too Large
# return immediately
raise err
self.logger.warning("AstakosClient request failed..retrying")
attemps += 1
......
......@@ -1802,9 +1802,13 @@ Upgrade Notes
v0.12 -> v0.13 <upgrade/upgrade-0.13>
v0.13 -> v0.14 <upgrade/upgrade-0.14>
v0.14 -> v0.14.2 <upgrade/upgrade-0.14.2>
Changelog, NEWS
===============
* v0.14.2 :ref:`Changelog <Changelog-0.14.2>`, :ref:`NEWS <NEWS-0.14.2>`
* v0.14 :ref:`Changelog <Changelog-0.14>`, :ref:`NEWS <NEWS-0.14>`
* v0.13 :ref:`Changelog <Changelog-0.13>`, :ref:`NEWS <NEWS-0.13>`
......@@ -599,6 +599,7 @@ to point at node1 which is where we have installed Astakos.
If you are an advanced user and want to use the Shibboleth Authentication
method, read the relative :ref:`section <shibboleth-auth>`.
.. _email-configuration:
Email delivery configuration
----------------------------
......@@ -1759,8 +1760,6 @@ It can be retrieved by running on the Astakos node (node1 in our case):
The token has been generated automatically during the :ref:`Cyclades service
registration <services-reg>`.
TODO: Document the Network Options here
Edit ``/etc/synnefo/20-snf-cyclades-app-cloudbar.conf``:
.. code-block:: console
......@@ -1799,19 +1798,6 @@ The above settings denote the Message Queue. Those settings should have the same
values as in ``/etc/synnefo/10-snf-cyclades-gtools-backend.conf`` file, and
reflect our :ref:`Message Queue setup <rabbitmq-setup>`.
Edit ``/etc/synnefo/20-snf-cyclades-app-ui.conf``:
.. code-block:: console
UI_LOGIN_URL = "https://node1.example.com/ui/login"
UI_LOGOUT_URL = "https://node1.example.com/ui/logout"
The ``UI_LOGIN_URL`` option tells the Cyclades Web UI where to redirect users,
if they are not logged in. We point that to Astakos.
The ``UI_LOGOUT_URL`` option tells the Cyclades Web UI where to redirect the
user when he/she logs out. We point that to Astakos, too.
Edit ``/etc/synnefo/20-snf-cyclades-app-vmapi.conf``:
.. code-block:: console
......@@ -2106,13 +2092,12 @@ installation. We do this by running:
.. code-block:: console
$ kamaki config set user.url "https://node1.example.com"
$ kamaki config set compute.url "https://node1.example.com/api/v1.1"
$ kamaki config set image.url "https://node1.example.com/image"
$ kamaki config set file.url "https://node2.example.com/v1"
$ kamaki config set token USER_TOKEN
$ kamaki config set cloud.default.url \
"https://node1.example.com/astakos/identity/v2.0"
$ kamaki config set cloud.default.token USER_TOKEN
The USER_TOKEN appears on the user's `Profile` web page on the Astakos Web UI.
Both the Authentication URL and the USER_TOKEN appear on the user's
`API access` web page on the Astakos Web UI.
You can see that the new configuration options have been applied correctly,
either by checking the editable file ``~/.kamakirc`` or by running:
......
......@@ -33,12 +33,13 @@ following line in your ``/etc/apt/sources.list`` file:
.. code-block:: console
deb http://apt.dev.grnet.gr unstable/
deb http://apt.dev.grnet.gr stable/
Then run:
.. code-block:: console
# curl https://dev.grnet.gr/files/apt-grnetdev.pub | apt-key add -
# apt-get update
# apt-get install snf-deploy
......@@ -105,6 +106,8 @@ installed Synnefo on a single node.
Caveats
=======
Certificates
------------
To be able to view all web pages make sure you have accepted all certificates
for domains:
......@@ -115,6 +118,38 @@ for domains:
* cms.synnefo.live
Spawning VMs
------------
By default, snf-deploy can't spawn VMs. To be able to do so, edit
``/etc/synnefo/cyclades.conf`` and change line 29 from:
.. code-block:: console
'no_install': True,
to:
.. code-block:: console
'no_install': False,
Networks
--------
In order to create private networks, you have to edit
``/etc/synnefo/cyclades.conf`` and change line 3 from:
.. code-block:: console
PRIVATE_MAC_FILTERED_BRIDGE = 'br0'
to:
.. code-block:: console
DEFAULT_MAC_FILTERED_BRIDGE = 'br0'
Using the installation
======================
......
......@@ -72,8 +72,6 @@ In `/etc/synnefo/cyclades.conf` add:
FEEDBACK_CONTACTS = (
('feedback@example.com', 'feedback@example.com'),
)
UI_LOGIN_URL = "https://accounts.example.com/im/login"
UI_LOGOUT_URL = "https://accounts.example.com/im/logout"
UI_FLAVORS_DISK_TEMPLATES_INFO = {
'rbd': {'name': 'Rbd',
'description': 'Volumes residing inside a RADOS cluster'},
......
Upgrade to Synnefo v0.14.2
^^^^^^^^^^^^^^^^^^^^^^^^^^
The upgrade from v0.14 to v0.14.2 consists in three steps:
1. Bring down services and backup databases.
2. Upgrade packages and migrate Pithos database.
3. Bring up all services.
1. Bring web services down, backup databases
============================================
1. All web services must be brought down so that the database maintains a
predictable and consistent state during the migration process::
$ service gunicorn stop
$ service snf-dispatcher stop
$ service snf-ganeti-eventd stop
2. Backup databases for recovery to a pre-migration state.
3. Keep the database servers running during the migration process
2. Upgrade Synnefo and configure settings
=========================================
2.1 Install the new versions of packages
----------------------------------------
::
astakos.host$ apt-get install \
python-objpool \
snf-common \
python-astakosclient \
snf-django-lib \
snf-webproject \
snf-branding \
snf-astakos-app
cyclades.host$ apt-get install \
python-objpool \
snf-common \
python-astakosclient \
snf-django-lib \
snf-webproject \
snf-branding \
snf-pithos-backend \
snf-cyclades-app
pithos.host$ apt-get install \
python-objpool \
snf-common \
python-astakosclient \
snf-django-lib \
snf-webproject \
snf-branding \
snf-pithos-backend \
snf-pithos-app \
snf-pithos-webclient
ganeti.node$ apt-get install \
python-objpool \
snf-common \
snf-cyclades-gtools \
snf-pithos-backend
.. note::
Make sure `snf-webproject' has the same version with snf-common
2.2 Sync and migrate the database
---------------------------------
::
pithos-host$ pithos-migrate upgrade head
5. Bring all services up
========================
After the upgrade is finished, we bring up all services:
.. code-block:: console
astakos.host # service gunicorn start
cyclades.host # service gunicorn start
pithos.host # service gunicorn start
cyclades.host # service snf-dispatcher start
......@@ -209,8 +209,14 @@ the deprecated user setting.
You are now done migrating from Synnefo v0.13 to v0.14. Please test your
installation to make sure everything works as expected.
4. Update astakos email notification settings
=============================================
4. Bring all services up
Make sure to update your configuration to include settings refered in the
updated :ref:`Email delivery configuration <email-configuration>` section
of the quick installation admin guide.
5. Bring all services up
========================
After the upgrade is finished, we bring up all services:
......
......@@ -107,6 +107,7 @@ def authenticate(request):
for l in [e.data.values('key', 'value') for e in s.endpoints.all()]:
endpoint = dict((d['key'], d['value']) for d in l)
endpoint["SNF:uiURL"] = s.component.url
endpoint["region"] = "default"
if s.name == 'astakos_weblogin':
endpoint["SNF:webloginURL"] = endpoint["publicURL"]
endpoints.append(endpoint)
......
......@@ -46,6 +46,8 @@ from django.conf import settings
from astakos.im import settings as astakos_settings
from astakos.im import messages as astakos_messages
from synnefo_branding import utils as branding_utils
import logging
logger = logging.getLogger(__name__)
......@@ -99,7 +101,10 @@ class AuthProvider(object):
('add_prompt', 'Allows you to login using {title}'),
('login_extra', ''),
('username', '{username}'),
('disabled_for_create', '{title} is not available for signup.'),
('disabled_for_create', 'It seems this is the first time you\'re '
'trying to access {service_name}. '
'Unfortunately, we are not accepting new '
'users at this point.'),
('switch_success', 'Account changed successfully.'),
('cannot_login', '{title} is not available for login. '
'Please use one of your other available methods '
......@@ -286,7 +291,7 @@ class AuthProvider(object):
self.provider_details['info'] = \
json.loads(self.provider_details['info'])
for key, val in self.provider_details['info'].iteritems():
params['provider_info_%s' % key.lower()] = val
params['provider_info_%s' % key.lower()] = val
# resolve username, handle unexisting defined username key
if self.user and self.username_key in params:
......@@ -294,6 +299,10 @@ class AuthProvider(object):
else:
params['username'] = self.identifier
branding_params = dict(map(lambda k: (k[0].lower(), k[1]),
branding_utils.get_branding_dict().iteritems()))
params.update(branding_params)
if not self.message_tpls_compiled:
for key, message_tpl in self.message_tpls.iteritems():
msg = self.messages.get(key, self.message_tpls.get(key))
......@@ -440,8 +449,8 @@ class AuthProvider(object):
})
if self.identifier and self._instance:
urls.update({
'switch': reverse(self.login_view) + '?switch_from=%d' % \
self._instance.pk,
'switch': reverse(self.login_view) + '?switch_from=%d' %
self._instance.pk,
'remove': reverse('remove_auth_provider',
kwargs={'pk': self._instance.pk})
})
......@@ -587,7 +596,7 @@ class LocalAuthProvider(AuthProvider):
class ShibbolethAuthProvider(AuthProvider):
module = 'shibboleth'
login_view = 'astakos.im.views.target.shibboleth.login'
username_key = 'identifier'
username_key = 'provider_info_eppn'
policies = {
'switch': False
......@@ -597,9 +606,13 @@ class ShibbolethAuthProvider(AuthProvider):
'title': _('Academic'),
'login_description': _('If you are a student, professor or researcher'
' you can login using your academic account.'),
'add_prompt': _('Allows you to login using your Academic '
'account'),
'method_details': 'Account: {username}',
'logout_extra': _('Please close all browser windows to complete '
'logout from your Academic account, too.')
'logout_success_extra': _('You may still be logged in at your Academic'
' account though. Consider logging out '
'from there too by closing all browser '
'windows')
}
......
......@@ -1080,7 +1080,8 @@ class ExtendedProfileForm(ProfileForm):
password, email = True, True
profile = super(ExtendedProfileForm, self).is_valid()
if profile and self.cleaned_data.get('change_password', None):
self.password_change_form.fields['new_password1'].required = True
self.password_change_form.fields['new_password2'].required = True
password = self.password_change_form.is_valid()
self.save_extra_forms.append('password')
if profile and self.cleaned_data.get('change_email'):
......
......@@ -512,15 +512,14 @@ def leave_project_checks(project):
def can_leave_request(project, user):
leave_policy = project.application.member_leave_policy
if leave_policy == CLOSED_POLICY:
try:
leave_project_checks(project)
except PermissionDenied:
return False
m = user.get_membership(project)
if m is None:
return False
if m.state != ProjectMembership.ACCEPTED:
return False
return True
return m.can_leave()
def leave_project(project_id, request_user):
......@@ -560,13 +559,13 @@ def join_project_checks(project):
def can_join_request(project, user):
join_policy = project.application.member_join_policy
if join_policy == CLOSED_POLICY:
try:
join_project_checks(project)
except PermissionDenied:
return False
m = user.get_membership(project)
if m:
return False
return True
return not(m)
def join_project(project_id, request_user):
......@@ -767,7 +766,7 @@ def check_expiration(execute=False):
expired = objects.expired_projects()
if execute:
for project in expired:
terminate(project.id)
terminate(project.pk)
return [project.expiration_info() for project in expired]
......
......@@ -213,7 +213,7 @@ PENDING_APPLICATION_LIMIT_MODIFY = \
# Auth providers messages
AUTH_PROVIDER_LOGIN_SUCCESS = "Logged in successfully, using {method_prompt}."
AUTH_PROVIDER_LOGOUT_SUCCESS = "Logged out successfully."
AUTH_PROVIDER_LOGOUT_SUCCESS = "Logged out successfully from {service_name}."
AUTH_PROVIDER_LOGOUT_SUCCESS_EXTRA = "You may still be logged in at {title} though. Consider logging out from there too."
AUTH_PROVIDER_NOT_ACTIVE = "'{method_prompt}' is disabled."
AUTH_PROVIDER_ADD_DISABLED = "{method_prompt} is disabled for your account."
......
......@@ -1906,15 +1906,7 @@ class Project(models.Model):
### Other
def count_pending_memberships(self):
memb_set = self.projectmembership_set
memb_count = memb_set.filter(state=ProjectMembership.REQUESTED).count()
return memb_count
def count_actually_accepted_memberships(self):
memb_set = self.projectmembership_set
memb_count = memb_set.filter(state=ProjectMembership.LEAVE_REQUESTED)
memb_count = memb_set.filter(state=ProjectMembership.ACCEPTED).count()
return memb_count
return self.projectmembership_set.requested().count()
def members_count(self):
return self.approved_memberships.count()
......@@ -1958,7 +1950,7 @@ CHAIN_STATE = {
class ProjectMembershipManager(ForUpdateManager):
def any_accepted(self):
q = self.model.Q_ACTUALLY_ACCEPTED
q = self.model.Q_ACCEPTED_STATES
return self.filter(q)
def actually_accepted(self):
......@@ -2006,7 +1998,7 @@ class ProjectMembership(models.Model):
objects = ProjectMembershipManager()
# Compiled queries
Q_ACCEPTED_STATES = ~Q(state=REQUESTED) & ~Q(state=REMOVED)
Q_ACCEPTED_STATES = Q(state__in=ACCEPTED_STATES)
Q_ACTUALLY_ACCEPTED = Q(state=ACCEPTED) | Q(state=LEAVE_REQUESTED)
MEMBERSHIP_STATE_DISPLAY = {
......
......@@ -344,7 +344,7 @@ def member_action_extra_context(membership, table, col):
_('Are you sure you want to accept this member?')]
confirms = [True, True]
if membership.state in ProjectMembership.ACTUALLY_ACCEPTED:
if membership.state in ProjectMembership.ACCEPTED_STATES:
urls = ['astakos.im.views.project_remove_member']
actions = [_('Remove')]
prompts = [_('Are you sure you want to remove this member?')]
......
......@@ -133,7 +133,7 @@
<dl class="alt-style">
<dt>Max participants</dt>
<dd>
{% if object.limit_on_members_number %}
{% if object.limit_on_members_number != None %}
{{object.limit_on_members_number}}
{% else %}Not set{% endif %}
</dd>
......@@ -150,9 +150,10 @@
<dt><a href="{% url project_approved_members object.chain %}" title="view approved members">Approved members</a></dt>
<dd>{{ approved_members_count }}
<span class="faint">
{% if object.limit_on_members_number %}
({% substract object.limit_on_members_number approved_members_count%} memberships remain)
{% if remaining_memberships_count != None %}
({{ remaining_memberships_count }}
membership{{ remaining_memberships_count|pluralize }}
remain{{ remaining_memberships_count|pluralize:"s," }})
{% else %}&nbsp;{% endif %}
</span>
</dd>
......
......@@ -131,13 +131,14 @@ class ShibbolethTests(TestCase):
# provider info stored
provider = AstakosUserAuthProvider.objects.get(module="shibboleth")
self.assertEqual(provider.affiliation, 'Test Affiliation')
self.assertEqual(provider.info, {u'email': u'kpap@synnefo.org',
u'eppn': u'kpapeppn',
u'name': u'Kostas Papadimitriou'})
self.assertEqual(provider.info['email'], u'kpap@synnefo.org')
self.assertEqual(provider.info['eppn'], u'kpapeppn')
self.assertEqual(provider.info['name'], u'Kostas Papadimitriou')
self.assertTrue('headers' in provider.info)
# login (not activated yet)
client.set_tokens(mail="kpap@synnefo.org", eppn="kpapeppn",
cn="Kostas Papadimitriou", )
cn="Kostas Papadimitriou")
r = client.get(ui_url("login/shibboleth?"), follow=True)
self.assertContains(r, 'is pending moderation')
......@@ -320,6 +321,17 @@ class ShibbolethTests(TestCase):
self.assertTrue(user.has_auth_provider('shibboleth'))
self.assertTrue(user.check_password('111'))
self.assertTrue(user.has_usable_password())
# change password via profile form
r = client.post(ui_url("profile"), {
'old_password': '111',
'new_password': '',
'new_password2': '',
'change_password': 'on',
}, follow=False)
self.assertEqual(r.status_code, 200)
self.assertFalse(r.context['profile_form'].is_valid())
self.client.logout()
# now we can login
......@@ -1339,12 +1351,14 @@ class TestWebloginRedirect(TestCase):
# scheme preserved
self.assertTrue(url.startswith('pithos://localhost/'))
# redirect contains token param
params = urlparse.urlparse(urlparse.urlparse(url).path, 'https').query
params = urlparse.urlparse(url.replace('pithos', 'https'),
scheme='https').query
params = urlparse.parse_qs(params)
self.assertEqual(params['token'][0],
AstakosUser.objects.get().auth_token)
# does not contain uuid
self.assertFalse('uuid' in params)
# reverted for 0.14.2 to support old pithos desktop clients
#self.assertFalse('uuid' in params)
# invalid cases
r = self.client.get(invalid_scheme, follow=True)
......
......@@ -275,13 +275,19 @@ def addmembers(request, chain_id, addmembers_form):
messages.error(request, e)
MEMBERSHIP_STATUS_FILTER = {
0: lambda x: x.requested(),
1: lambda x: x.any_accepted(),
}
def common_detail(request, chain_or_app_id, project_view=True,
template_name='im/projects/project_detail.html',
members_status_filter=None):
project = None
approved_members_count = 0
pending_members_count = 0
remaining_memberships_count = 0
remaining_memberships_count = None
if project_view:
chain_id = chain_or_app_id
if request.method == 'POST':
......@@ -297,18 +303,19 @@ def common_detail(request, chain_or_app_id, project_view=True,
else:
addmembers_form = AddProjectMembersForm() # initialize form
approved_members_count = 0
pending_members_count = 0
remaining_memberships_count = 0
project, application = get_by_chain_or_404(chain_id)
if project:
members = project.projectmembership_set.select_related()
approved_members_count = \
project.count_actually_accepted_memberships()
members = project.projectmembership_set
approved_members_count = project.members_count()
pending_members_count = project.count_pending_memberships()
if members_status_filter in (ProjectMembership.REQUESTED,
ProjectMembership.ACCEPTED):
members = members.filter(state=members_status_filter)
_limit = application.limit_on_members_number
if _limit is not None:
remaining_memberships_count = \