commit eb76521332eb93f5d60db3af1b3f3888d9960e1f
Author: Giorgos Korfiatis <gkorf@grnet.gr>
Date:   Tue Sep 24 18:42:33 2013 +0300

    astakos: Store base URL in component
    
    Deployer should provide it when adding a new component. Service endpoints
    originating from a component are expected to match its base URL; otherwise,
    a warning is issued during service registration. Re-registration with
    `snf-component-register' affects both the base and the ui URLs.
    
    `component-list' now lists by default the base URL.

diff --git a/Changelog b/Changelog
index 2555ee0..99cffbb 100644
--- a/Changelog
+++ b/Changelog
@@ -30,6 +30,16 @@ Astakos
 
 * Implement API calls for projects.
 
+* Store the base URL of a component. Deployer should provide it when adding
+  a new component. Service endpoints originating from a component are
+  expected to match its base URL; otherwise, a warning is issued.
+  Re-registration with `snf-component-register' affects both the base and
+  the ui URL.
+
+* Management commands:
+  * Changed commands:
+     * component-add got options --base-url and --ui-url
+
 Cyclades
 --------
 
diff --git a/docs/quick-install-admin-guide.rst b/docs/quick-install-admin-guide.rst
index 5420402..e8c6fc5 100644
--- a/docs/quick-install-admin-guide.rst
+++ b/docs/quick-install-admin-guide.rst
@@ -917,9 +917,9 @@ offered by the services.
 
     .. code-block:: console
 
-       astakos-host$ snf-manage component-add astakos astakos_ui_url
-       astakos-host$ snf-manage component-add cyclades cyclades_ui_url
-       astakos-host$ snf-manage component-add pithos pithos_ui_url
+       astakos-host$ snf-manage component-add astakos --base-url astakos_base_url --ui-url astakos_ui_url
+       astakos-host$ snf-manage component-add cyclades --base-url cyclades_base_url --ui-url cyclades_ui_url
+       astakos-host$ snf-manage component-add pithos --base-url pithos_base_url --ui-url pithos_ui_url
        astakos-host$ snf-manage service-export-astakos > astakos.json
        astakos-host$ snf-manage service-import --json astakos.json
        cyclades-host$ snf-manage service-export-cyclades > cyclades.json
diff --git a/docs/upgrade/upgrade-0.15.rst b/docs/upgrade/upgrade-0.15.rst
index c203a77..5050a03 100644
--- a/docs/upgrade/upgrade-0.15.rst
+++ b/docs/upgrade/upgrade-0.15.rst
@@ -7,7 +7,9 @@ The upgrade to v0.15 consists in the following steps:
 
 2. Upgrade packages, migrate the databases and configure settings.
 
-3. Bring up all services.
+3. Re-register components and services in astakos.
+
+4. Bring up all services.
 
 .. warning::
 
@@ -100,7 +102,19 @@ The upgrade to v0.15 consists in the following steps:
 
     pithos-host$ pithos-migrate upgrade head
 
-3. Bring all services up
+3. Re-register components and services in astakos
+=================================================
+
+Component registration has changed; you will thus need to repeat the
+process. On the astakos node, run::
+
+    astakos-host$ snf-component-register
+
+This will detect that the Synnefo components are already registered and ask
+to re-register. Answer positively. You need to enter the base URL and the UI
+URL for each component, just like during the initial registration.
+
+4. Bring all services up
 ========================
 
 After the upgrade is finished, we bring up all services:
diff --git a/snf-astakos-app/astakos/im/management/commands/component-add.py b/snf-astakos-app/astakos/im/management/commands/component-add.py
index 535aecf..538234f 100644
--- a/snf-astakos-app/astakos/im/management/commands/component-add.py
+++ b/snf-astakos-app/astakos/im/management/commands/component-add.py
@@ -31,34 +31,44 @@
 # interpreted as representing official policies, either expressed
 # or implied, of GRNET S.A.
 
+from optparse import make_option
 from django.core.management.base import BaseCommand, CommandError
 from astakos.im.models import Component
 
 
 class Command(BaseCommand):
-    args = "<name> <component URL>"
+    args = "<name>"
     help = "Register a component"
 
+    option_list = BaseCommand.option_list + (
+        make_option('--ui-url',
+                    dest='ui_url',
+                    default=None,
+                    help="Set UI URL"),
+        make_option('--base-url',
+                    dest='base_url',
+                    default=None,
+                    help="Set base URL"),
+    )
+
     def handle(self, *args, **options):
-        if len(args) < 2:
+        if len(args) != 1:
             raise CommandError("Invalid number of arguments")
 
         name = args[0]
-        url = args[1]
+        base_url = options['base_url']
+        ui_url = options['ui_url']
+
         try:
-            s = Component.objects.get(name=name)
+            Component.objects.get(name=name)
             m = "There already exists a component named '%s'." % name
             raise CommandError(m)
         except Component.DoesNotExist:
             pass
 
-        components = list(Component.objects.filter(url=url))
-        if components:
-            m = "Component URL '%s' is registered for another service." % url
-            raise CommandError(m)
-
         try:
-            c = Component.objects.create(name=name, url=url)
+            c = Component.objects.create(
+                name=name, url=ui_url, base_url=base_url)
         except BaseException:
             raise CommandError("Failed to register component.")
         else:
diff --git a/snf-astakos-app/astakos/im/management/commands/component-list.py b/snf-astakos-app/astakos/im/management/commands/component-list.py
index 45e1429..ac8bb8c 100644
--- a/snf-astakos-app/astakos/im/management/commands/component-list.py
+++ b/snf-astakos-app/astakos/im/management/commands/component-list.py
@@ -42,9 +42,10 @@ class Command(ListCommand):
     FIELDS = {
         "id": ("id", "Component ID"),
         "name": ("name", "Component Name"),
-        "url": ("url", "Component URL"),
+        "base url": ("base_url", "Component base URL"),
+        "ui url": ("url", "Component UI URL"),
         "token": ("auth_token", "Authentication token"),
         "token created": ("auth_token_created", "Token creation date"),
     }
 
-    fields = ["id", "name", "url", "token"]
+    fields = ["id", "name", "base url"]
diff --git a/snf-astakos-app/astakos/im/management/commands/component-modify.py b/snf-astakos-app/astakos/im/management/commands/component-modify.py
index 0b85337..6443fb4 100644
--- a/snf-astakos-app/astakos/im/management/commands/component-modify.py
+++ b/snf-astakos-app/astakos/im/management/commands/component-modify.py
@@ -1,4 +1,4 @@
-# Copyright 2012 GRNET S.A. All rights reserved.
+# Copyright 2012, 2013 GRNET S.A. All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or
 # without modification, are permitted provided that the following
@@ -43,10 +43,13 @@ class Command(BaseCommand):
     help = "Modify component attributes"
 
     option_list = BaseCommand.option_list + (
-        make_option('--url',
-                    dest='url',
+        make_option('--ui-url',
+                    dest='ui_url',
                     default=None,
-                    help="Set component url"),
+                    help="Set UI URL"),
+        make_option('--base-url',
+                    dest='base_url',
+                    help="Set base URL"),
         make_option('--auth-token',
                     dest='auth_token',
                     default=None,
@@ -79,16 +82,21 @@ class Command(BaseCommand):
                 "Component does not exist. You may run snf-manage "
                 "component-list for available component IDs.")
 
-        url = options.get('url')
+        ui_url = options.get('ui_url')
+        base_url = options.get('base_url')
         auth_token = options.get('auth_token')
         renew_token = options.get('renew_token')
         purge_services = options.get('purge_services')
 
-        if not any([url, auth_token, renew_token, purge_services]):
+        if not any([ui_url, base_url, auth_token, renew_token,
+                    purge_services]):
             raise CommandError("No option specified.")
 
-        if url:
-            component.url = url
+        if ui_url:
+            component.url = ui_url
+
+        if base_url:
+            component.base_url = base_url
 
         if auth_token:
             component.auth_token = auth_token
diff --git a/snf-astakos-app/astakos/im/management/commands/service-import.py b/snf-astakos-app/astakos/im/management/commands/service-import.py
index 262dd9d..2dd2e1d 100644
--- a/snf-astakos-app/astakos/im/management/commands/service-import.py
+++ b/snf-astakos-app/astakos/im/management/commands/service-import.py
@@ -90,7 +90,8 @@ class Command(BaseCommand):
                 raise CommandError(m)
 
             try:
-                existed = add_service(component, name, service_type, endpoints)
+                existed = add_service(component, name, service_type, endpoints,
+                                      out=self.stdout)
             except RegisterException as e:
                 raise CommandError(e.message)
 
diff --git a/snf-astakos-app/astakos/im/migrations/0053_auto__add_field_component_base_url.py b/snf-astakos-app/astakos/im/migrations/0053_auto__add_field_component_base_url.py
new file mode 100644
index 0000000..363eda6
--- /dev/null
+++ b/snf-astakos-app/astakos/im/migrations/0053_auto__add_field_component_base_url.py
@@ -0,0 +1,306 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        
+        # Adding field 'Component.base_url'
+        db.add_column('im_component', 'base_url', self.gf('django.db.models.fields.CharField')(max_length=1024, null=True), keep_default=False)
+
+
+    def backwards(self, orm):
+        
+        # Deleting field 'Component.base_url'
+        db.delete_column('im_component', 'base_url')
+
+
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        'im.additionalmail': {
+            'Meta': {'object_name': 'AdditionalMail'},
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"})
+        },
+        'im.approvalterms': {
+            'Meta': {'object_name': 'ApprovalTerms'},
+            'date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'location': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'im.astakosuser': {
+            'Meta': {'object_name': 'AstakosUser', '_ormbases': ['auth.User']},
+            'accepted_email': ('django.db.models.fields.EmailField', [], {'default': 'None', 'max_length': '75', 'null': 'True', 'blank': 'True'}),
+            'accepted_policy': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'activation_sent': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'auth_token': ('django.db.models.fields.CharField', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+            'auth_token_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'auth_token_expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'date_signed_terms': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'deactivated_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'deactivated_reason': ('django.db.models.fields.TextField', [], {'default': 'None', 'null': 'True'}),
+            'disturbed_quota': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
+            'email_verified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'has_credits': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'has_signed_terms': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'invitations': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'is_rejected': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_verified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'level': ('django.db.models.fields.IntegerField', [], {'default': '4'}),
+            'moderated': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'moderated_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'moderated_data': ('django.db.models.fields.TextField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'policy': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['im.Resource']", 'null': 'True', 'through': "orm['im.AstakosUserQuota']", 'symmetrical': 'False'}),
+            'rejected_reason': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'updated': ('django.db.models.fields.DateTimeField', [], {}),
+            'user_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': 'True'}),
+            'uuid': ('django.db.models.fields.CharField', [], {'max_length': '255', 'unique': 'True', 'null': 'True'}),
+            'verification_code': ('django.db.models.fields.CharField', [], {'max_length': '255', 'unique': 'True', 'null': 'True'}),
+            'verified_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'im.astakosuserauthprovider': {
+            'Meta': {'ordering': "('module', 'created')", 'unique_together': "(('identifier', 'module', 'user'),)", 'object_name': 'AstakosUserAuthProvider'},
+            'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'affiliation': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'auth_backend': ('django.db.models.fields.CharField', [], {'default': "'astakos'", 'max_length': '255'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'identifier': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'info_data': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}),
+            'module': ('django.db.models.fields.CharField', [], {'default': "'local'", 'max_length': '255'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'auth_providers'", 'to': "orm['im.AstakosUser']"})
+        },
+        'im.astakosuserquota': {
+            'Meta': {'unique_together': "(('resource', 'user'),)", 'object_name': 'AstakosUserQuota'},
+            'capacity': ('snf_django.lib.db.fields.IntDecimalField', [], {'max_digits': '38', 'decimal_places': '0'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'resource': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Resource']"}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"})
+        },
+        'im.authproviderpolicyprofile': {
+            'Meta': {'ordering': "['priority']", 'object_name': 'AuthProviderPolicyProfile'},
+            'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'authpolicy_profiles'", 'symmetrical': 'False', 'to': "orm['auth.Group']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_exclusive': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+            'policy_add': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'policy_automoderate': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'policy_create': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'policy_limit': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True'}),
+            'policy_login': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'policy_remove': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'policy_required': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'policy_switch': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'priority': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
+            'provider': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'authpolicy_profiles'", 'symmetrical': 'False', 'to': "orm['im.AstakosUser']"})
+        },
+        'im.chain': {
+            'Meta': {'object_name': 'Chain'},
+            'chain': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'im.component': {
+            'Meta': {'object_name': 'Component'},
+            'auth_token': ('django.db.models.fields.CharField', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+            'auth_token_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'auth_token_expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'base_url': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'}),
+            'url': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True'})
+        },
+        'im.emailchange': {
+            'Meta': {'object_name': 'EmailChange'},
+            'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40', 'db_index': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'new_email_address': ('django.db.models.fields.EmailField', [], {'max_length': '75'}),
+            'requested_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'emailchanges'", 'unique': 'True', 'to': "orm['im.AstakosUser']"})
+        },
+        'im.endpoint': {
+            'Meta': {'object_name': 'Endpoint'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'service': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'endpoints'", 'to': "orm['im.Service']"})
+        },
+        'im.endpointdata': {
+            'Meta': {'unique_together': "(('endpoint', 'key'),)", 'object_name': 'EndpointData'},
+            'endpoint': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'data'", 'to': "orm['im.Endpoint']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'value': ('django.db.models.fields.CharField', [], {'max_length': '1024'})
+        },
+        'im.invitation': {
+            'Meta': {'object_name': 'Invitation'},
+            'code': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+            'consumed': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'inviter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'invitations_sent'", 'null': 'True', 'to': "orm['im.AstakosUser']"}),
+            'is_consumed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'realname': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
+        },
+        'im.pendingthirdpartyuser': {
+            'Meta': {'unique_together': "(('provider', 'third_party_identifier'),)", 'object_name': 'PendingThirdPartyUser'},
+            'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'info': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}),
+            'provider': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'third_party_identifier': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'token': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'im.project': {
+            'Meta': {'object_name': 'Project'},
+            'application': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'project'", 'unique': 'True', 'to': "orm['im.ProjectApplication']"}),
+            'creation_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.BigIntegerField', [], {'primary_key': 'True', 'db_column': "'id'"}),
+            'members': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['im.AstakosUser']", 'through': "orm['im.ProjectMembership']", 'symmetrical': 'False'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'unique': 'True', 'null': 'True', 'db_index': 'True'}),
+            'state': ('django.db.models.fields.IntegerField', [], {'default': '1', 'db_index': 'True'})
+        },
+        'im.projectapplication': {
+            'Meta': {'unique_together': "(('chain', 'id'),)", 'object_name': 'ProjectApplication'},
+            'applicant': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projects_applied'", 'to': "orm['im.AstakosUser']"}),
+            'chain': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'chained_apps'", 'db_column': "'chain'", 'to': "orm['im.Project']"}),
+            'comments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'end_date': ('django.db.models.fields.DateTimeField', [], {}),
+            'homepage': ('django.db.models.fields.URLField', [], {'max_length': '255', 'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'issue_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'limit_on_members_number': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}),
+            'member_join_policy': ('django.db.models.fields.IntegerField', [], {}),
+            'member_leave_policy': ('django.db.models.fields.IntegerField', [], {}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
+            'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projects_owned'", 'to': "orm['im.AstakosUser']"}),
+            'resource_grants': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['im.Resource']", 'null': 'True', 'through': "orm['im.ProjectResourceGrant']", 'blank': 'True'}),
+            'response': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'response_actor': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'responded_apps'", 'null': 'True', 'to': "orm['im.AstakosUser']"}),
+            'response_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'start_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'state': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'}),
+            'waive_actor': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'waived_apps'", 'null': 'True', 'to': "orm['im.AstakosUser']"}),
+            'waive_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'waive_reason': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'im.projectlock': {
+            'Meta': {'object_name': 'ProjectLock'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'im.projectlog': {
+            'Meta': {'object_name': 'ProjectLog'},
+            'actor': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']", 'null': 'True'}),
+            'comments': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'date': ('django.db.models.fields.DateTimeField', [], {}),
+            'from_state': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'log'", 'to': "orm['im.Project']"}),
+            'reason': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'to_state': ('django.db.models.fields.IntegerField', [], {})
+        },
+        'im.projectmembership': {
+            'Meta': {'unique_together': "(('person', 'project'),)", 'object_name': 'ProjectMembership'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"}),
+            'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Project']"}),
+            'state': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'})
+        },
+        'im.projectmembershiplog': {
+            'Meta': {'object_name': 'ProjectMembershipLog'},
+            'actor': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']", 'null': 'True'}),
+            'comments': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'date': ('django.db.models.fields.DateTimeField', [], {}),
+            'from_state': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'membership': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'log'", 'to': "orm['im.ProjectMembership']"}),
+            'reason': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'to_state': ('django.db.models.fields.IntegerField', [], {})
+        },
+        'im.projectresourcegrant': {
+            'Meta': {'unique_together': "(('resource', 'project_application'),)", 'object_name': 'ProjectResourceGrant'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'member_capacity': ('snf_django.lib.db.fields.IntDecimalField', [], {'default': '0', 'max_digits': '38', 'decimal_places': '0'}),
+            'project_application': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.ProjectApplication']", 'null': 'True'}),
+            'project_capacity': ('snf_django.lib.db.fields.IntDecimalField', [], {'null': 'True', 'max_digits': '38', 'decimal_places': '0'}),
+            'resource': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Resource']"})
+        },
+        'im.resource': {
+            'Meta': {'object_name': 'Resource'},
+            'allow_in_projects': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'desc': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+            'service_origin': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+            'service_type': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'unit': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
+            'uplimit': ('snf_django.lib.db.fields.IntDecimalField', [], {'default': '0', 'max_digits': '38', 'decimal_places': '0'})
+        },
+        'im.service': {
+            'Meta': {'object_name': 'Service'},
+            'component': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Component']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'im.sessioncatalog': {
+            'Meta': {'object_name': 'SessionCatalog'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'session_key': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'sessions'", 'null': 'True', 'to': "orm['im.AstakosUser']"})
+        },
+        'im.usersetting': {
+            'Meta': {'unique_together': "(('user', 'setting'),)", 'object_name': 'UserSetting'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'setting': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"}),
+            'value': ('django.db.models.fields.IntegerField', [], {})
+        }
+    }
+
+    complete_apps = ['im']
diff --git a/snf-astakos-app/astakos/im/models.py b/snf-astakos-app/astakos/im/models.py
index 3800155..cc577ee 100644
--- a/snf-astakos-app/astakos/im/models.py
+++ b/snf-astakos-app/astakos/im/models.py
@@ -124,6 +124,7 @@ class Component(models.Model):
                             db_index=True)
     url = models.CharField(_('Component url'), max_length=1024, null=True,
                            help_text=_("URL the component is accessible from"))
+    base_url = models.CharField(max_length=1024, null=True)
     auth_token = models.CharField(_('Authentication Token'), max_length=64,
                                   null=True, blank=True, unique=True)
     auth_token_created = models.DateTimeField(_('Token creation date'),
diff --git a/snf-astakos-app/astakos/im/register.py b/snf-astakos-app/astakos/im/register.py
index 9c92b06..c67b2ce 100644
--- a/snf-astakos-app/astakos/im/register.py
+++ b/snf-astakos-app/astakos/im/register.py
@@ -121,14 +121,19 @@ def get_resources(resources=None, services=None):
     return resource_dict
 
 
-def add_endpoint(service, endpoint_dict):
+def add_endpoint(component, service, endpoint_dict, out=None):
     endpoint = Endpoint.objects.create(service=service)
     for key, value in endpoint_dict.iteritems():
+        base_url = component.base_url
+        if key == "publicURL" and not value.startswith(base_url):
+            warn = out.write if out is not None else logger.warning
+            warn("Warning: Endpoint URL '%s' does not start with "
+                 "assumed component base URL '%s'.\n" % (value, base_url))
         EndpointData.objects.create(
             endpoint=endpoint, key=key, value=value)
 
 
-def add_service(component, name, service_type, endpoints):
+def add_service(component, name, service_type, endpoints, out=None):
     defaults = {'component': component,
                 'type': service_type,
                 }
@@ -146,6 +151,6 @@ def add_service(component, name, service_type, endpoints):
         service.save()
 
     for endpoint in endpoints:
-        add_endpoint(service, endpoint)
+        add_endpoint(component, service, endpoint, out=out)
 
     return not created
diff --git a/snf-astakos-app/astakos/scripts/snf-component-register b/snf-astakos-app/astakos/scripts/snf-component-register
index 016a13d..8c233b5 100755
--- a/snf-astakos-app/astakos/scripts/snf-component-register
+++ b/snf-astakos-app/astakos/scripts/snf-component-register
@@ -42,6 +42,7 @@ decide () {
 
 register_component () {
     component=$1
+    exists=$2
     component_desc=${desc[$component]}
     component_ex_url=${ex_url[$component]}
     echo "Registering the $component_desc ($component):"
@@ -55,29 +56,18 @@ register_component () {
     read ui_url
     decide "Register $component with the given URLs (y/n)? "
     if [ $? -eq 0 ]; then
-        snf-manage component-add $component $ui_url
-        if [ $? -eq 0 ]; then
-            read -p "Please write down the token and press Enter to continue. "
-            register_services $component $base_url
-            changed=1
-            echo
+        if [ $exists -eq 0 ]; then
+            snf-manage component-add $component --base-url $base_url \
+                --ui-url $ui_url
+            if [ $? -eq 0 ]; then
+                read -p "Please write down the token and press Enter to continue. "
+                changed=1
+            fi
+        else
+            snf-manage component-modify $component --base-url $base_url \
+                --ui-url $ui_url --purge-services
         fi
-    fi
-}
-
-register_comp_serv () {
-    component=$1
-    component_desc=${desc[$component]}
-    component_ex_url=${ex_url[$component]}
-    echo "Registering services for $component:"
-    echo "Give the URL of $component base installation" \
-        "(e.g. $component_ex_url)"
-    echo -n 'Base URL: '
-    read base_url
-    decide "Register ${component}'s services with the given URL (y/n)? "
-    if [ $? -eq 0 ]; then
         register_services $component $base_url
-        echo
     fi
 }
 
@@ -91,13 +81,13 @@ register_one () {
     if [ $? -ne 0 ]; then
         decide "Register the ${desc[$component]} ($component) (y/n)? "
         if [ $? -eq 0 ]; then
-            register_component $component
+            register_component $component 0
         fi
     else
         echo "The ${desc[$component]} ($component) is registered."
-        decide "Update its registered services (y/n)? "
+        decide "Re-register (y/n)? "
         if [ $? -eq 0 ]; then
-            register_comp_serv $component
+            register_component $component 1
         fi
     fi
 }
diff --git a/snf-deploy/fabfile.py b/snf-deploy/fabfile.py
index ac56911..d9b2e1f 100644
--- a/snf-deploy/fabfile.py
+++ b/snf-deploy/fabfile.py
@@ -600,15 +600,15 @@ def astakos_loaddata():
 def astakos_register_components():
     debug(env.host, " * Register services in astakos...")
 
-    cyclades_base_url = "https://%s/cyclades/" % env.env.cyclades.fqdn
-    pithos_base_url = "https://%s/pithos/" % env.env.pithos.fqdn
-    astakos_base_url = "https://%s/astakos/" % env.env.accounts.fqdn
+    cyclades_base_url = "https://%s/cyclades" % env.env.cyclades.fqdn
+    pithos_base_url = "https://%s/pithos" % env.env.pithos.fqdn
+    astakos_base_url = "https://%s/astakos" % env.env.accounts.fqdn
 
     cmd = """
-    snf-manage component-add "home" https://{0} home-icon.png
-    snf-manage component-add "cyclades" {1}ui/
-    snf-manage component-add "pithos" {2}ui/
-    snf-manage component-add "astakos" {3}ui/
+    snf-manage component-add "home" --ui-url https://{0}
+    snf-manage component-add "cyclades" --base-url {1} --ui-url {1}/ui
+    snf-manage component-add "pithos" --base-url {2} --ui-url {2}/ui
+    snf-manage component-add "astakos" --base-url {3} --ui-url {3}/ui
     """.format(env.env.cms.fqdn, cyclades_base_url,
                pithos_base_url, astakos_base_url)
     try_run(cmd)
