Merge branch 'master' of github.com:open-cloud/xos
diff --git a/xos/core/admin.py b/xos/core/admin.py
index f77345a..a3aea48 100644
--- a/xos/core/admin.py
+++ b/xos/core/admin.py
@@ -1253,6 +1253,9 @@
     password = ReadOnlyPasswordHashField(label='Password',
                    help_text= '<a href=\"password/\">Change Password</a>.')
 
+    PROFILE_CHOICES = ((None, '------'), ('regular', 'Regular user'), ('cp', 'Content Provider'))
+    profile = forms.ChoiceField(choices=PROFILE_CHOICES, required=False, label="Quick Profile")
+
     class Meta:
         model = User
         widgets = { 'public_key': UploadTextareaWidget, }
@@ -1263,6 +1266,12 @@
         # field does not have access to the initial value
         return self.initial["password"]
 
+    def save(self, *args, **kwargs):
+        if self.cleaned_data['profile']:
+             self.instance.apply_profile(self.cleaned_data['profile'])
+
+        return super(UserChangeForm, self).save(*args, **kwargs)
+
 class UserDashboardViewInline(XOSTabularInline):
     model = UserDashboardView
     extra = 0
@@ -1296,7 +1305,7 @@
     list_filter = ('site',)
     inlines = [SlicePrivilegeInline,SitePrivilegeInline]
     admin_inlines = [ControllerUserInline]
-    fieldListLoginDetails = ['backend_status_text', 'email','site','password','is_active','is_readonly','is_admin','is_appuser', 'public_key']
+    fieldListLoginDetails = ['backend_status_text', 'email', 'site','password','is_active','is_readonly','is_admin','is_appuser', 'public_key', 'profile']
     fieldListContactInfo = ['firstname','lastname','phone','timezone']
 
     fieldsets = (
@@ -1350,9 +1359,13 @@
             if 'is_admin' in login_details_fields:
                 login_details_fields.remove('is_admin')
             if 'is_readonly' in login_details_fields:
-                login_details_fields.remove('is_readonly') 
+                login_details_fields.remove('is_readonly')
+            if 'is_appuser' in login_details_fields:
+                login_details_fields.remove('is_admin')
+            if 'profile' in login_details_fields:
+                login_details_fields.remove('profile')
             #if len(request.user.siteprivileges.filter(role__role = 'pi')) > 0:
-                # only admins and pis can change a user's site  
+                # only admins and pis can change a user's site
             #    self.readonly_fields = ('backend_status_text', 'site') 
         self.fieldsets = (
             ('Login Details', {'fields': login_details_fields, 'classes':['suit-tab suit-tab-general']}),
diff --git a/xos/core/dashboard/views/home.py b/xos/core/dashboard/views/home.py
index 045cd34..8c20e80 100644
--- a/xos/core/dashboard/views/home.py
+++ b/xos/core/dashboard/views/home.py
@@ -54,30 +54,26 @@
         head_template = self.head_template
         tail_template = self.tail_template
 
-        body = """
-         <div id="hometabs" >
-         <ul id="suit_form_tabs" class="nav nav-tabs nav-tabs-suit" data-tab-prefix="suit-tab">
-        """
-
         dashboards = request.user.get_dashboards()
 
-        # customize is a special dashboard they always get
-        customize = DashboardView.objects.filter(name="Customize")
-        if customize:
-            dashboards.append(customize[0])
+        if not request.user.is_appuser:
+            # customize is a special dashboard they always get
+            customize = DashboardView.objects.filter(name="Customize")
+            if customize:
+                dashboards.append(customize[0])
 
-        for i,view in enumerate(dashboards):
+        tabs = []
+        bodies = []
+
+        i = 0
+        for view in dashboards:
             # don't display disabled dashboards
             if (not view.enabled):
                 continue
-            body = body + '<li><a href="#dashtab-%d">%s</a></li>\n' % (i, view.name)
 
-        body = body + "</ul>\n"
+            tabs.append( '<li><a href="#dashtab-%d">%s</a></li>\n' % (i, view.name) )
 
-        for i,view in enumerate(dashboards):
-            # don't display disabled dashboards
-            if (not view.enabled):
-                continue
+            body = ""
 
             url = view.url
             body = body + '<div id="dashtab-%d">\n' % i
@@ -109,9 +105,36 @@
                 body = body + self.embedDashboard(url)
             body = body + '</div>\n'
 
-        body=body+"</div>\n"
+            bodies.append(body)
+            i = i + 1
 
-        t = template.Template(head_template + body + self.tail_template)
+        # embed content provider dashboards
+        for cp in ContentProvider.objects.all():
+            if request.user in cp.users.all():
+                tabs.append( '<li><a href="#dashtab-%d">%s</a></li>\n' % (i, cp.name) )
+
+                body = ""
+                body = body + '<div id="dashtab-%d">\n' % i
+                body = body + self.embedDashboard("http:/admin/hpc/contentprovider/%s/%s/embeddedfilteredchange" % (cp.serviceProvider.hpcService.id, cp.id))
+                body = body + '</div>\n'
+
+                bodies.append(body)
+                i = i + 1
+
+        if (len(tabs)==1) and (len(bodies)==1):
+            # there is only one dashboard, so optimize out the tabbing
+            contents = bodies[0]
+        else:
+            contents = """
+             <div id="hometabs" >
+             <ul id="suit_form_tabs" class="nav nav-tabs nav-tabs-suit" data-tab-prefix="suit-tab">
+             %s
+             </ul>
+             %s
+             </div>
+            """ % ("\n".join(tabs), "\n".join(bodies))
+
+        t = template.Template(head_template + contents + self.tail_template)
 
         response_kwargs = {}
         response_kwargs.setdefault('content_type', self.content_type)
diff --git a/xos/core/models/user.py b/xos/core/models/user.py
index 397c4f8..32e3e7d 100644
--- a/xos/core/models/user.py
+++ b/xos/core/models/user.py
@@ -198,7 +198,7 @@
         dashboards = sorted(list(self.userdashboardviews.all()), key=attrgetter('order'))
         dashboards = [x.dashboardView for x in dashboards]
 
-        if not dashboards:
+        if (not dashboards) and (not self.is_appuser):
             for dashboardName in DEFAULT_DASHBOARDS:
                 dbv = DashboardView.objects.filter(name=dashboardName)
                 if dbv:
@@ -353,6 +353,17 @@
             raise PermissionDenied("You do not have permission to delete %s objects" % self.__class__.__name__)
         self.delete(*args, **kwds)
 
+    def apply_profile(self, profile):
+        if profile=="regular":
+            self.is_appuser = False
+            self.is_admin = False
+
+        elif profile=="cp":
+            self.is_appuser = True
+            self.is_admin = False
+            for db in self.userdashboardviews.all():
+                db.delete()
+
 class UserDashboardView(PlCoreBase):
      user = models.ForeignKey(User, related_name='userdashboardviews')
      dashboardView = models.ForeignKey(DashboardView, related_name='userdashboardviews')
diff --git a/xos/core/views/hpc_config.py b/xos/core/views/hpc_config.py
index 8b5bac0..af183bf 100644
--- a/xos/core/views/hpc_config.py
+++ b/xos/core/views/hpc_config.py
@@ -101,6 +101,11 @@
     d["xos_hostname"] = xos.settings.RESTAPI_HOSTNAME
     d["xos_port"] = str(xos.settings.RESTAPI_PORT)
 
+    if hpc.hpc_port80:
+        d["hpc_port80"] = "True"
+    else:
+        d["hpc_port80"] = "False"
+
     return HttpResponse("""# auto-generated by HpcConfig
 ENABLE_PLC=False
 ENABLE_PS=True
@@ -122,5 +127,6 @@
 PUPPET_MASTER_PORT="8140"

 PS_HOSTNAME="{xos_hostname}"

 PS_PORT="{xos_port}"
+COBLITZ_PORT_80={hpc_port80}
 """.format(**d))
 
diff --git a/xos/core/xoslib/methods/hpcview.py b/xos/core/xoslib/methods/hpcview.py
index 41f6051..005d7e1 100644
--- a/xos/core/xoslib/methods/hpcview.py
+++ b/xos/core/xoslib/methods/hpcview.py
@@ -67,6 +67,28 @@
 
     return config_run
 
+# from hpc_watcher.py
+def get_public_ip(service, sliver):
+    network_name = None
+    if "hpc" in sliver.slice.name:
+        network_name = getattr(service, "watcher_hpc_network", None)
+    elif "demux" in sliver.slice.name:
+        network_name = getattr(service, "watcher_dnsdemux_network", None)
+    elif "redir" in sliver.slice.name:
+        network_name = getattr(service, "watcher_dnsredir_network", None)
+
+    if network_name and network_name.lower()=="nat":
+        return None
+
+    if (network_name is None) or (network_name=="") or (network_name.lower()=="public"):
+        return sliver.get_public_ip()
+
+    for ns in sliver.networkslivers.all():
+        if (ns.ip) and (ns.network.name==network_name):
+            return ns.ip
+
+    raise ValueError("Couldn't find network %s" % str(network_name))
+
 def getHpcDict(user, pk):
     hpc = HpcService.objects.get(pk=pk)
     slices = get_service_slices(hpc)
@@ -106,10 +128,10 @@
         print "no dnsdemux slice"
         return
 
-    dnsdemux_has_public_network = False
-    for network in dnsdemux_slice.networks.all():
-        if (network.template) and (network.template.visibility=="public") and (network.template.translation=="none"):
-            dnsdemux_has_public_network = True
+    #dnsdemux_has_public_network = False
+    #for network in dnsdemux_slice.networks.all():
+    #    if (network.template) and (network.template.visibility=="public") and (network.template.translation=="none"):
+    #        dnsdemux_has_public_network = True
 
     nameservers = {}
     for nameserver in NAMESERVERS:
@@ -120,9 +142,12 @@
 
     dnsdemux=[]
     for sliver in dnsdemux_slice.slivers.all():
-        if dnsdemux_has_public_network:
-            ip = sliver.get_public_ip()
-        else:
+        ip=None
+        try:
+            ip = get_public_ip(dnsdemux_service, sliver)
+        except Exception, e:
+            ip = "Exception: " + str(e)
+        if not ip:
             try:
                 ip = socket.gethostbyname(sliver.node.name)
             except:
diff --git a/xos/hpc/admin.py b/xos/hpc/admin.py
index d171beb..89384eb 100644
--- a/xos/hpc/admin.py
+++ b/xos/hpc/admin.py
@@ -191,7 +191,7 @@
     verbose_name_plural = "HPC Service"
     list_display = ("backend_status_icon", "name","enabled")
     list_display_links = ('backend_status_icon', 'name', )
-    fieldsets = [(None, {'fields': ['backend_status_text', 'name','scale','enabled','versionNumber', 'description', "cmi_hostname"], 'classes':['suit-tab suit-tab-general']})]
+    fieldsets = [(None, {'fields': ['backend_status_text', 'name','scale','enabled','versionNumber', 'description', "cmi_hostname", "hpc_port80", "watcher_hpc_network", "watcher_dnsredir_network", "watcher_dnsdemux_network"], 'classes':['suit-tab suit-tab-general']})]
     readonly_fields = ('backend_status_text', )
     inlines = [SliceInline,ServiceAttrAsTabInline,ServicePrivilegeInline]
     form = HpcServiceForm
@@ -251,6 +251,13 @@
     fields = ('backend_status_icon', 'cdn_prefix_id', 'prefix', 'defaultOriginServer', 'enabled')
     readonly_fields = ('backend_status_icon', 'cdn_prefix_id',)
 
+class OriginServerInline(XOSTabularInline):
+    model = OriginServer
+    extra = 0
+    suit_classes = 'suit-tab suit-tab-origins'
+    fields = ('backend_status_icon', 'origin_server_id', 'url')
+    readonly_fields = ('backend_status_icon', 'origin_server_id')
+
 class ContentProviderInline(XOSTabularInline):
     model = ContentProvider
     extra = 0
@@ -267,23 +274,50 @@
     user_readonly_fields = ('url','protocol','redirects','contentProvider','authenticated','enabled','origin_server_id','description')
 
 class ContentProviderForm(forms.ModelForm):
+    users = forms.ModelMultipleChoiceField(
+        queryset=User.objects.all(),
+        required=False,
+        help_text="Select which users can manage this ContentProvider",
+        widget=FilteredSelectMultiple(
+            verbose_name=('Users'), is_stacked=False
+        )
+    )
+
     class Meta:
+        model = ContentProvider
         widgets = {
             'serviceProvider' : LinkedSelect
         }
 
+    def __init__(self, *args, **kwargs):
+      request = kwargs.pop('request', None)
+      super(ContentProviderForm, self).__init__(*args, **kwargs)
+
+      if self.instance and self.instance.pk:
+        self.fields['users'].initial = self.instance.users.all()
+
 class ContentProviderAdmin(HPCAdmin):
     form = ContentProviderForm
     list_display = ('backend_status_icon', 'name','description','enabled' )
     list_display_links = ('backend_status_icon', 'name', )
-    fieldsets = [ (None, {'fields': ['backend_status_text', 'name','enabled','description','serviceProvider','users'], 'classes':['suit-tab suit-tab-general']})]
     readonly_fields = ('backend_status_text', )
+    admin_readonly_fields = ('backend_status_text', )
+    cp_readonly_fields = ('backend_status_text', 'name', 'enabled', 'serviceProvider', 'users')
+    fieldsets = [ (None, {'fields': ['backend_status_text', 'name','enabled','description','serviceProvider','users'], 'classes':['suit-tab suit-tab-general']})]
 
-    inlines = [CDNPrefixInline]
+    inlines = [CDNPrefixInline, OriginServerInline]
 
     user_readonly_fields = ('name','description','enabled','serviceProvider','users')
 
-    suit_form_tabs = (('general','Details'),('prefixes','CDN Prefixes'))
+    suit_form_tabs = (('general','Details'),('prefixes','CDN Prefixes'), ('origins','Origin Servers'))
+
+    def change_view(self,request, *args, **kwargs):
+        if request.user.is_admin:
+            self.readonly_fields = self.admin_readonly_fields
+        else:
+            self.readonly_fields = self.cp_readonly_fields
+
+        return super(ContentProviderAdmin, self).change_view(request, *args, **kwargs)
 
 class ServiceProviderAdmin(HPCAdmin):
     list_display = ('backend_status_icon', 'name', 'description', 'enabled')
diff --git a/xos/hpc/models.py b/xos/hpc/models.py
index 7ba0b34..80f4260 100644
--- a/xos/hpc/models.py
+++ b/xos/hpc/models.py
@@ -17,6 +17,11 @@
 
     cmi_hostname = StrippedCharField(max_length=254, null=True, blank=True)
 
+    hpc_port80 = models.BooleanField(default=True, help_text="Enable port 80 for HPC")
+    watcher_hpc_network = StrippedCharField(max_length=254, null=True, blank=True, help_text="Network for hpc_watcher to contact hpc sliver")
+    watcher_dnsdemux_network = StrippedCharField(max_length=254, null=True, blank=True, help_text="Network for hpc_watcher to contact dnsdemux sliver")
+    watcher_dnsredir_network = StrippedCharField(max_length=254, null=True, blank=True, help_text="Network for hpc_watcher to contact dnsredir sliver")
+
     @property
     def scale(self):
         hpc_slices = [x for x in self.slices.all() if "hpc" in x.name]
@@ -84,6 +89,14 @@
         # filtering of visible objects by user.
         return qs.filter(serviceProvider__hpcService=hpcService)
 
+    def can_update(self, user):
+        if super(ContentProvider, self).can_update(user):
+            return True
+
+        if user in self.users.all():
+            return True
+
+        return False
 
 class OriginServer(PlCoreBase):
     class Meta:
@@ -108,6 +121,15 @@
         # filtering of visible objects by user.
         return qs.filter(contentProvider__serviceProvider__hpcService=hpcService)
 
+    def can_update(self, user):
+        if super(OriginServer, self).can_update(user):
+            return True
+
+        if self.contentProvider and self.contentProvider.can_update(user):
+            return True
+
+        return False
+
 class CDNPrefix(PlCoreBase):
     class Meta:
         app_label = "hpc"
@@ -128,6 +150,15 @@
         # filtering of visible objects by user.
         return qs.filter(contentProvider__serviceProvider__hpcService=hpcService)
 
+    def can_update(self, user):
+        if super(CDNPrefix, self).can_update(user):
+            return True
+
+        if self.contentProvider and self.contentProvider.can_update(user):
+            return True
+
+        return False
+
 class AccessMap(PlCoreBase):
     class Meta:
         app_label = "hpc"
diff --git a/xos/observers/hpc/hpc_watcher.py b/xos/observers/hpc/hpc_watcher.py
index 59d104a..177d17f 100644
--- a/xos/observers/hpc/hpc_watcher.py
+++ b/xos/observers/hpc/hpc_watcher.py
@@ -347,6 +347,27 @@
         Thread.__init__(self)
         self.daemon = True
 
+    def get_public_ip(self, service, sliver):
+        network_name = None
+        if "hpc" in sliver.slice.name:
+            network_name = getattr(service, "watcher_hpc_network", None)
+        elif "demux" in sliver.slice.name:
+            network_name = getattr(service, "watcher_dnsdemux_network", None)
+        elif "redir" in sliver.slice.name:
+            network_name = getattr(service, "watcher_dnsredir_network", None)
+
+        if network_name and network_name.lower()=="nat":
+            return None
+
+        if (network_name is None) or (network_name=="") or (network_name.lower()=="public"):
+            return sliver.get_public_ip()
+
+        for ns in sliver.networkslivers.all():
+            if (ns.ip) and (ns.network.name==network_name):
+                return ns.ip
+
+        raise ValueError("Couldn't find network %s" % str(network_name))
+
     def set_status(self, sliver, service, kind, msg):
         #print sliver.node.name, kind, msg
         sliver.has_error = (msg!="success")
@@ -394,7 +415,11 @@
         for sliver in slivers:
             sliver.has_error = False
 
-            ip = sliver.get_public_ip()
+            try:
+                ip = self.get_public_ip(service, sliver)
+            except Exception, e:
+                self.set_status(sliver, service, "watcher.DNS", "exception: %s" % str(e))
+                continue
             if not ip:
                 try:
                     ip = socket.gethostbyname(sliver.node.name)
@@ -526,16 +551,20 @@
 
     def fetch_watcher(self, service, slivers):
         for sliver in slivers:
-            ip = sliver.get_public_ip()
+            try:
+                ip = self.get_public_ip(service, sliver)
+            except Exception, e:
+                self.set_status(sliver, service, "watcher.watcher", json.dumps({"status": "exception: %s" % str(e)}) )
+                continue
             if not ip:
                 try:
                     ip = socket.gethostbyname(sliver.node.name)
                 except:
-                    self.set_status(sliver, service, "watcher.watcher", "dns resolution failure")
+                    self.set_status(sliver, service, "watcher.watcher", json.dumps({"status": "dns resolution failure"}) )
                     continue
 
             if not ip:
-                self.set_status(sliver, service, "watcher.watcher", "no IP address")
+                self.set_status(sliver, service, "watcher.watcher", json.dumps({"status": "no IP address"}) )
                 continue
 
             port = 8015
diff --git a/xos/observers/hpc/steps/sync_originserver.py b/xos/observers/hpc/steps/sync_originserver.py
index 7efab07..851590a 100644
--- a/xos/observers/hpc/steps/sync_originserver.py
+++ b/xos/observers/hpc/steps/sync_originserver.py
@@ -68,6 +68,8 @@
             url = "http://" + url
 
         ors_dict = {"authenticated_content": ors.authenticated, "zone_redirects": ors.redirects, "content_provider_id": cpid, "url": url, "service_type": "HyperCache", "caching_type": "Optimistic", "description": ors.description}
+        if not ors_dict["description"]:
+            ors_dict["description"] = "blank"
 
         #print os_dict