initial code migration from xos repo

Change-Id: I8c848929ec4583a7a18ba9da44095f8f688f96c0
diff --git a/xos/admin.py b/xos/admin.py
new file mode 100644
index 0000000..19f5190
--- /dev/null
+++ b/xos/admin.py
@@ -0,0 +1,229 @@
+from django.contrib import admin
+
+from services.hpc.models import *
+from django import forms
+from django.utils.safestring import mark_safe
+from django.contrib.auth.admin import UserAdmin
+from django.contrib.admin.widgets import FilteredSelectMultiple
+from django.contrib.auth.forms import ReadOnlyPasswordHashField
+from django.contrib.auth.signals import user_logged_in
+from django.utils import timezone
+from suit.widgets import LinkedSelect
+from core.admin import ServiceAppAdmin,SliceInline,ServiceAttrAsTabInline, ReadOnlyAwareAdmin, XOSTabularInline, SliderWidget, ServicePrivilegeInline
+from core.middleware import get_request
+
+from functools import update_wrapper
+from django.contrib.admin.views.main import ChangeList
+from django.core.urlresolvers import reverse, NoReverseMatch
+from django.contrib.admin.utils import quote
+
+from filteredadmin import FilteredChangeList, FilteredAdmin, FilteredInline
+
+class HpcServiceForm(forms.ModelForm):
+    scale = forms.IntegerField(widget = SliderWidget, required=False)
+
+    def __init__(self, *args, **kwargs):
+        super(HpcServiceForm, self).__init__(*args, **kwargs)
+        if ("instance" in kwargs) and (hasattr(kwargs["instance"], "scale")):
+            self.fields['scale'].initial = kwargs["instance"].scale
+
+    def save(self, *args, **kwargs):
+        if self.cleaned_data['scale']:
+             self.instance.scale = self.cleaned_data['scale']
+
+        return super(HpcServiceForm, self).save(*args, **kwargs)
+
+class HpcServiceAdmin(ReadOnlyAwareAdmin):
+    model = HpcService
+    verbose_name = "HPC Service"
+    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", "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
+
+    extracontext_registered_admins = True
+
+    user_readonly_fields = ["name", "enabled", "versionNumber", "description"]
+
+    suit_form_tabs =(('general', 'HPC Service Details'),
+        ('administration', 'Administration'),
+        ('tools', 'Tools'),
+        ('slices','Slices'),
+        ('serviceattrs','Additional Attributes'),
+                ('serviceprivileges','Privileges'),
+    )
+
+    suit_form_includes = (('hpcadmin.html', 'top', 'administration'),
+                          ('hpctools.html', 'top', 'tools') )
+
+    def url_for_model_changelist(self, request, model):
+       if not request.resolver_match.args:
+           return reverse('admin:%s_%s_changelist' % (model._meta.app_label, model._meta.model_name), current_app=model._meta.app_label)
+       else:
+           obj_id = request.resolver_match.args[0]
+           changelist_url = reverse('admin:%s_%s_filteredchangelist' % (model._meta.app_label, model._meta.model_name), args=(obj_id,), current_app=model._meta.app_label)
+           return changelist_url
+
+class HPCAdmin(FilteredAdmin):
+   # Change the application breadcrumb to point to an HPC Service if one is
+   # defined
+
+   custom_app_breadcrumb_name = "Hpc"
+   @property
+   def custom_app_breadcrumb_url(self):
+       services = HpcService.objects.all()
+       if len(services)==1:
+           return "/admin/hpc/hpcservice/%s/" % services[0].id
+       else:
+           return "/admin/hpc/hpcservice/"
+
+class CDNPrefixInline(FilteredInline):
+    model = CDNPrefix
+    extra = 0
+    suit_classes = 'suit-tab suit-tab-prefixes'
+    fields = ('backend_status_icon', 'cdn_prefix_id', 'prefix', 'defaultOriginServer', 'enabled')
+    readonly_fields = ('backend_status_icon', 'cdn_prefix_id',)
+
+class OriginServerInline(FilteredInline):
+    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(FilteredInline):
+    model = ContentProvider
+    extra = 0
+    suit_classes = 'suit-tab suit-tab-cps'
+    fields = ('backend_status_icon', 'content_provider_id', 'name', 'enabled')
+    readonly_fields = ('backend_status_icon', 'content_provider_id',)
+
+class OriginServerAdmin(HPCAdmin):
+    list_display = ('backend_status_icon', 'url','protocol','redirects','contentProvider','authenticated','enabled' )
+    list_display_links = ('backend_status_icon', 'url', )
+
+    fields = ('backend_status_text', 'url','protocol','redirects','contentProvider','authenticated','enabled','origin_server_id','description' )
+    readonly_fields = ('backend_status_text', 'origin_server_id',)
+    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
+        }
+        fields = '__all__'
+
+    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', )
+    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, OriginServerInline]
+
+    user_readonly_fields = ('name','description','enabled','serviceProvider','users')
+
+    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)
+
+    def has_add_permission(self, request):
+        return request.user.is_admin
+
+    def has_delete_permission(self, request, obj=None):
+        return request.user.is_admin
+
+class ServiceProviderAdmin(HPCAdmin):
+    list_display = ('backend_status_icon', 'name', 'description', 'enabled')
+    list_display_links = ('backend_status_icon', 'name', )
+    fieldsets = [
+        (None, {'fields': ['backend_status_text', 'name','description','enabled', 'hpcService'], 'classes':['suit-tab suit-tab-general']})]
+#, ('Content Providers', {'fields':['contentProviders'],'classes':['suit-tab suit-tab-cps']})]
+
+    readonly_fields = ('backend_status_text', )
+    user_readonly_fields = ('name', 'description', 'enabled')
+
+    suit_form_tabs = (('general','Details'),('cps','Content Providers'))
+    inlines = [ContentProviderInline]
+
+class CDNPrefixForm(forms.ModelForm):
+    class Meta:
+        widgets = {
+            'contentProvider' : LinkedSelect
+        }
+        fields = '__all__'
+
+class CDNPrefixAdmin(HPCAdmin):
+    form = CDNPrefixForm
+    list_display = ['backend_status_icon', 'prefix','contentProvider']
+    list_display_links = ('backend_status_icon', 'prefix', )
+    fields = ['backend_status_text', 'prefix', 'contentProvider', 'cdn_prefix_id', 'description', 'defaultOriginServer', 'enabled']
+    readonly_fields = ('backend_status_text', )
+    user_readonly_fields = ['prefix','contentProvider', "cdn_prefix_id", "description", "defaultOriginServer", "enabled"]
+
+class SiteMapAdmin(HPCAdmin):
+    model = SiteMap
+    verbose_name = "Site Map"
+    verbose_name_plural = "Site Map"
+    list_display = ("backend_status_icon", "name", "contentProvider", "serviceProvider")
+    list_display_links = ('backend_status_icon', 'name', )
+    fields = ['backend_status_text', 'name', 'hpcService', 'cdnPrefix', 'contentProvider', 'serviceProvider', 'map', 'map_id']
+    user_readonly_fields = ('backend_status_text', "name", "hpcService", "cdnPrefix", "contentProvider", "serviceProvider", "description", "map")
+    readonly_fields = ('backend_status_text', )
+
+class AccessMapAdmin(HPCAdmin):
+    model = AccessMap
+    verbose_name = "Access Map"
+    verbose_name_plural = "Access Map"
+    list_display = ("backend_status_icon", "name", "contentProvider")
+    list_display_links = ('backend_status_icon', 'name', )
+    user_readonly_fields = ('backend_status_text', "name", "contentProvider", "description", "map")
+    readonly_fields = ('backend_status_text', )
+
+class HpcHealthCheckAdmin(HPCAdmin):
+    model = HpcHealthCheck
+    verbose_name = "Health Check"
+    verbose_name = "Health Checks"
+    list_display = ["backend_status_icon", "resource_name", "kind"]
+    list_display_links = ["backend_status_icon", "resource_name"]
+    fields = ["backend_status_text", "hpcService", "resource_name", "kind", "result_contains", "result_min_size", "result_max_size"]
+    readonly_fields = ["backend_status_text",]
+
+admin.site.register(ServiceProvider, ServiceProviderAdmin)
+admin.site.register(ContentProvider, ContentProviderAdmin)
+admin.site.register(CDNPrefix, CDNPrefixAdmin)
+admin.site.register(OriginServer,OriginServerAdmin)
+admin.site.register(HpcService, HpcServiceAdmin)
+admin.site.register(SiteMap, SiteMapAdmin)
+admin.site.register(AccessMap, AccessMapAdmin)
+admin.site.register(HpcHealthCheck, HpcHealthCheckAdmin)
+
diff --git a/xos/api/service/hpc/hpc_config.py b/xos/api/service/hpc/hpc_config.py
new file mode 100644
index 0000000..a181a1c
--- /dev/null
+++ b/xos/api/service/hpc/hpc_config.py
@@ -0,0 +1,131 @@
+from django.http import HttpResponse, HttpResponseServerError
+from core.models import *
+from services.hpc.models import *
+from services.requestrouter.models import *
+import xos.settings
+import json
+import os
+import time
+
+def get_service_slices(service):
+    try:
+       return service.slices.all()
+    except:
+       # this field used to be improperly named, and makemigrations won't fix it
+       return service.service.all()
+
+def HpcConfig(request):
+    hpcSlice=None
+    cmiSlice=None
+    redirSlice=None
+    demuxSlice=None
+
+    node_slicename = request.GET.get("slicename", None)
+    if not node_slicename:
+        return HttpResponseServerError("Error: no slicename passed in request")
+
+    # search for an HPC Service that owns the slicename that was passed
+    # to us.
+    hpc=None
+    for candidate in HpcService.objects.all():
+        if candidate.cmi_hostname == node_slicename:
+            # A hack for standalone CMIs that aren't managed by XOS. Set
+            # /etc/slicename to cmi_hostname that's configured in the
+            # HPCService object.
+            hpc = candidate
+
+        for slice in get_service_slices(candidate):
+            if slice.name == node_slicename:
+                hpc = candidate
+
+    if (not hpc):
+        return HttpResponseServerError("Error: no HPC service")
+
+    for slice in get_service_slices(hpc):
+        if "cmi" in slice.name:
+            cmiSlice = slice
+        elif ("hpc" in slice.name) or ("vcoblitz" in slice.name):
+            hpcSlice = slice
+        elif "redir" in slice.name:
+            redirSlice = slice
+        elif "demux" in slice.name:
+            demuxSlice = slice
+
+    if (hpc.cmi_hostname):
+        cmi_hostname = hpc.cmi_hostname
+    else:
+        if not cmiSlice:
+            return HttpResponseServerError("Error: no CMI slice")
+
+        if len(cmiSlice.instances.all())==0:
+            return HttpResponseServerError("Error: CMI slice has no instances")
+
+        # for now, assuming using NAT
+        cmi_hostname = cmiSlice.instances.all()[0].node.name
+
+    if not hpcSlice:
+        return HttpResponseServerError("Error: no HPC slice")
+
+    if (redirSlice==None) or (demuxSlice==None):
+        # The HPC Service didn't have a dnsredir or a dnsdemux, so try looking
+        # in the RequestRouterService for one.
+
+        rr = RequestRouterService.objects.all()
+        if not (rr):
+            return HttpResponseServerError("Error: no RR service")
+
+        rr = rr[0]
+        try:
+           slices = rr.slices.all()
+        except:
+           # this field used to be improperly named, and makemigrations won't fix it
+           slices = rr.service.all()
+        for slice in slices:
+            if "redir" in slice.name:
+                redirSlice = slice
+            elif "demux" in slice.name:
+                demuxSlice = slice
+
+    if not redirSlice:
+        return HttpResponseServerError("Error: no dnsredir slice")
+
+    if not demuxSlice:
+        return HttpResponseServerError("Error: no dnsdemux slice")
+
+    d = {}
+    d["hpc_slicename"] = hpcSlice.name
+    d["redir_slicename"] = redirSlice.name
+    d["demux_slicename"] = demuxSlice.name
+    d["cmi_hostname"] = cmi_hostname
+    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
+BASE_HRN="princeton"
+RELEVANT_SERVICE_NAMES=['vcoblitz', 'coredirect', 'codnsdemux', "syndicate_comon_server"]
+COBLITZ_SLICE_NAME=BASE_HRN+"_vcoblitz"
+COBLITZ_SLICE_ID=70
+COBLITZ_PS_SLICE_NAME="{hpc_slicename}"
+DNSREDIR_SLICE_NAME=BASE_HRN+"_coredirect"
+DNSREDIR_SLICE_ID=71
+DNSREDIR_PS_SLICE_NAME="{redir_slicename}"
+DNSDEMUX_SLICE_NAME=BASE_HRN+"_codnsdemux"
+DNSDEMUX_SLICE_ID=69
+DNSDEMUX_PS_SLICE_NAME="{demux_slicename}"
+CMI_URL="http://{cmi_hostname}/"
+CMI_HTTP_PORT="8004"
+CMI_HTTPS_PORT="8003"
+PUPPET_MASTER_HOSTNAME="{cmi_hostname}"
+PUPPET_MASTER_PORT="8140"
+PS_HOSTNAME="{xos_hostname}"
+PS_PORT="{xos_port}"
+COBLITZ_PORT_80={hpc_port80}
+""".format(**d))
+
diff --git a/xos/api/service/hpc/legacyapi.py b/xos/api/service/hpc/legacyapi.py
new file mode 100644
index 0000000..b5592c0
--- /dev/null
+++ b/xos/api/service/hpc/legacyapi.py
@@ -0,0 +1,353 @@
+import os
+import json
+import socket
+import sys
+import time
+import traceback
+import xmlrpclib
+
+from core.models import Slice, Instance, ServiceClass, Reservation, Tag, Network, User, Node, Image, Deployment, Site, NetworkTemplate, NetworkSlice
+
+from django.http import HttpResponse
+from django.views.decorators.csrf import csrf_exempt
+
+def ps_id_to_pl_id(x):
+    # Since we don't want the XOS object IDs to conflict with existing
+    # PlanetLab object IDs in the CMI, just add 100000 to the XOS object
+    # IDs.
+    return 100000 + x
+
+def pl_id_to_ps_id(x):
+    return x - 100000
+
+# slice_remap is a dict of ps_slice_name -> (pl_slice_name, pl_slice_id)
+
+def pl_slice_id(slice, slice_remap={}):
+    if slice.name in slice_remap:
+        return int(slice_remap[slice.name][1])
+    else:
+        return ps_id_to_pl_id(slice.id)
+
+def pl_slicename(slice, slice_remap={}):
+    if slice.name in slice_remap:
+        return slice_remap[slice.name][0]
+    else:
+        return slice.name
+
+def filter_fields(src, fields):
+    dest = {}
+    for (key,value) in src.items():
+        if (not fields) or (key in fields):
+            dest[key] = value
+    return dest
+
+def GetSlices(filter={}, slice_remap={}):
+    #ps_slices = Slice.objects.filter(**filter)
+    ps_slices = Slice.objects.all()
+    slices = []
+    for ps_slice in ps_slices:
+        if (filter) and ("name" in filter):
+            remapped_name = slice_remap.get(ps_slice.name, (ps_slice.name,))[0]
+            if (remapped_name != filter["name"]):
+                continue
+
+        node_ids=[]
+        for ps_instance in ps_slice.instances.all():
+            node_ids.append(ps_id_to_pl_id(ps_instance.node.id))
+
+        slice = {"instantiation": "plc-instantiated",
+                 "description": "XOS slice",
+                 "slice_id": pl_slice_id(ps_slice, slice_remap),
+                 "node_ids": node_ids,
+                 "url": "xos",
+                 "max_nodes": 1000,
+                 "peer_slice_id": None,
+                 "slice_tag_ids": [],
+                 "peer_id": None,
+                 "site_id": ps_id_to_pl_id(ps_slice.site_id),
+                 "name": pl_slicename(ps_slice, slice_remap),
+                 "planetstack_name": ps_slice.name}     # keeping planetstack_name for now, to match the modified config.py
+
+                 # creator_person_id, person_ids, expires, created
+
+        slices.append(slice)
+    return slices
+
+def GetNodes(node_ids=None, fields=None, slice_remap={}):
+    if node_ids:
+        ps_nodes = Node.objects.filter(id__in=[pl_id_to_ps_id(nid) for nid in node_ids])
+    else:
+        ps_nodes = Node.objects.all()
+    nodes = []
+    for ps_node in ps_nodes:
+        slice_ids=[]
+        for ps_instance in ps_node.instances.all():
+            slice_ids.append(pl_slice_id(ps_instance.slice, slice_remap))
+
+        node = {"node_id": ps_id_to_pl_id(ps_node.id),
+                "site_id": ps_id_to_pl_id(ps_node.site_id),
+                "node_type": "regular",
+                "peer_node_id": None,
+                "hostname": ps_node.name.lower(),
+                "conf_file_ids": [],
+                "slice_ids": slice_ids,
+                "model": "xos",
+                "peer_id": None,
+                "node_tag_ids": []}
+
+                # last_updated, key, boot_state, pcu_ids, node_type, session, last_boot,
+                # interface_ids, slice_ids_whitelist, run_level, ssh_rsa_key, last_pcu_reboot,
+                # nodegroup_ids, verified, last_contact, boot_nonce, version,
+                # last_pcu_configuration, last_download, date_created, ports
+
+        nodes.append(node)
+
+    nodes = [filter_fields(node, fields) for node in nodes]
+
+    return nodes
+
+def GetTags(slicename,node_id):
+    return {}
+
+def GetSites(slice_remap={}):
+    ps_sites = Site.objects.all()
+    sites = []
+    for ps_site in ps_sites:
+        slice_ids=[]
+        for ps_slice in ps_site.slices.all():
+            slice_ids.append(pl_slice_id(ps_slice, slice_remap))
+
+        node_ids=[]
+        for ps_node in ps_site.nodes.all():
+            node_ids.append(ps_id_to_pl_id(ps_node.id))
+
+        if ps_site.location:
+            longitude = ps_site.location.longitude
+            latitude = ps_site.location.latitude
+        else:
+            longitude = 0
+            latitude = 0
+
+        site = {"site_id": ps_id_to_pl_id(ps_site.id),
+                "node_ids": node_ids,
+                "pcu_ids": [],
+                "max_slices": 100,
+                "max_instances": 1000,
+                "is_public": False,
+                "peer_site_id": None,
+                "abbrebiated_name": ps_site.abbreviated_name,
+                "address_ids": [],
+                "name": ps_site.name,
+                "url": None,
+                "site_tag_ids": [],
+                "enabled": True,
+                "longitude": float(longitude),
+                "latitude": float(latitude),
+                "slice_ids": slice_ids,
+                "login_base": ps_site.login_base,
+                "peer_id": None}
+
+                # last_updated, ext_consortium_id, person_ids, date_created
+
+        sites.append(site)
+
+    return sites
+
+def GetInterfaces(slicename, node_ids, return_nat=False, return_private=False):
+    interfaces = []
+    ps_slices = Slice.objects.filter(name=slicename)
+    for ps_slice in ps_slices:
+        for ps_instance in ps_slice.instances.all():
+            node_id = ps_id_to_pl_id(ps_instance.node_id)
+            if node_id in node_ids:
+                ps_node = ps_instance.node
+
+                ip = socket.gethostbyname(ps_node.name.strip())
+
+                # If the slice has a network that's labeled for hpc_client, then
+                # return that network.
+                found_labeled_network = False
+                for port in ps_instance.ports.all():
+                    if (not port.ip):
+                        continue
+                    if (port.network.owner != ps_slice):
+                        continue
+                    if port.network.labels and ("hpc_client" in port.network.labels):
+                        ip=port.ip
+                        found_labeled_network = True
+
+                if not found_labeled_network:
+                    # search for a dedicated public IP address
+                    for port in ps_instance.ports.all():
+                        if (not port.ip):
+                            continue
+                        template = port.network.template
+                        if (template.visibility=="public") and (template.translation=="none"):
+                            ip=port.ip
+
+                if return_nat:
+                    ip = None
+                    for port in ps_instance.ports.all():
+                        if (not port.ip):
+                            continue
+                        template = port.network.template
+                        if (template.visibility=="private") and (template.translation=="NAT"):
+                            ip=port.ip
+                    if not ip:
+                        continue
+
+                if return_private:
+                    ip = None
+                    for port in ps_instance.ports.all():
+                        if (not port.ip):
+                            continue
+                        template = port.network.template
+                        if (template.visibility=="private") and (template.translation=="none"):
+                            ip=port.ip
+                    if not ip:
+                        continue
+
+                interface = {"node_id": node_id,
+                             "ip": ip,
+                             "broadcast": None,
+                             "mac": "11:22:33:44:55:66",
+                             "bwlimit": None,
+                             "network": None,
+                             "is_primary": True,
+                             "dns1": None,
+                             "hostname": None,
+                             "netmask": None,
+                             "interface_tag_ids": [],
+                             "interface_id": node_id,     # assume each node has only one interface
+                             "gateway": None,
+                             "dns2": None,
+                             "type": "ipv4",
+                             "method": "dhcp"}
+                interfaces.append(interface)
+    return interfaces
+
+def GetConfiguration(name, slice_remap={}):
+    slicename = name["name"]
+    if "node_id" in name:
+        node_id = name["node_id"]
+    else:
+        node_id = 0
+
+    node_instance_tags = GetTags(slicename, node_id)
+
+    slices = GetSlices({"name": slicename}, slice_remap=slice_remap)
+    perhost = {}
+    allinterfaces = {}
+    hostprivmap = {}
+    hostipmap = {}
+    hostnatmap = {}
+    nodes = []
+    if len(slices)==1:
+        slice = slices[0]
+        node_ids = slice['node_ids']
+        nodes = GetNodes(node_ids, ['hostname', 'node_id', 'site_id'], slice_remap=slice_remap)
+        nodemap = {}
+        for node in nodes:
+            nodemap[node['node_id']]=node['hostname']
+
+        interfaces = GetInterfaces(slice["planetstack_name"], node_ids)
+        hostipmap = {}
+        for interface in interfaces:
+            if nodemap[interface['node_id']] not in allinterfaces:
+                allinterfaces[nodemap[interface['node_id']]] = []
+            interface['interface_tags'] = []
+            allinterfaces[nodemap[interface['node_id']]].append(interface)
+            if interface['is_primary']:
+                hostipmap[nodemap[interface['node_id']]] = interface['ip']
+
+        hostnatmap = {}
+        interfaces = GetInterfaces(slice["planetstack_name"], node_ids, return_nat=True)
+        for interface in interfaces:
+            interface['interface_tags'] = []
+            hostnatmap[nodemap[interface['node_id']]] = interface['ip']
+
+        hostprivmap = {}
+        interfaces = GetInterfaces(slice["planetstack_name"], node_ids, return_private=True)
+        for interface in interfaces:
+            interface['interface_tags'] = []
+            hostprivmap[nodemap[interface['node_id']]] = interface['ip']
+
+        for nid in node_ids:
+            instance_tags = GetTags(slicename,nid)
+            perhost[nodemap[nid]] = instance_tags
+
+    instances = GetSlices(slice_remap=slice_remap)
+    if node_id != 0:
+        instances = [slice for slice in instances if (node_id in slice.node_ids)]
+
+    sites = GetSites(slice_remap=slice_remap)
+    for site in sites:
+        site["site_tags"] = []
+
+    timestamp = int(time.time())
+    return {'version': 3,
+            'timestamp': timestamp,
+            'configuration': node_instance_tags,
+            'allconfigurations':perhost,
+            'hostipmap':hostipmap,
+            'hostnatmap':hostnatmap,
+            'hostprivmap':hostprivmap,
+            'slivers': instances,
+            'interfaces': allinterfaces,
+            'sites': sites,
+            'nodes': nodes}
+
+DEFAULT_REMAP = {"princeton_vcoblitz2": ["princeton_vcoblitz", 70]}
+
+def HandleGetConfiguration1():
+    configs={}
+    for slicename in ["princeton_vcoblitz"]:
+        configs[slicename] = GetConfiguration({"name": slicename}, DEFAULT_REMAP)
+    return configs
+
+def HandleGetNodes1():
+    return GetNodes(slice_remap=DEFAULT_REMAP)
+
+def HandleGetSlices1():
+    return GetSlices(slice_remap=DEFAULT_REMAP)
+
+def HandleGetConfiguration2(name, slice_remap):
+    return GetConfiguration(name, slice_remap=slice_remap)
+
+def HandleGetNodes2(slice_remap):
+    return GetNodes(slice_remap=slice_remap)
+
+def HandleGetSlices2(slice_remap):
+    return GetSlices(slice_remap=slice_remap)
+
+FUNCS = {"GetConfiguration": HandleGetConfiguration1,
+         "GetNodes": HandleGetNodes1,
+         "GetSlices": HandleGetSlices1,
+         "GetConfiguration2": HandleGetConfiguration2,
+         "GetNodes2": HandleGetNodes2,
+         "GetSlices2": HandleGetSlices2}
+
+@csrf_exempt
+def LegacyXMLRPC(request):
+    if request.method == "POST":
+        try:
+            (args, method) = xmlrpclib.loads(request.body)
+            result = None
+            if method in FUNCS:
+                result = FUNCS[method](*args)
+            return HttpResponse(xmlrpclib.dumps((result,), methodresponse=True, allow_none=1))
+        except:
+            traceback.print_exc()
+            return HttpResponseServerError()
+    else:
+        return HttpResponse("Not Implemented")
+
+if __name__ == '__main__':
+    slices = GetSlices(slice_remap = DEFAULT_REMAP)
+    nodes = GetNodes(slice_remap = DEFAULT_REMAP)
+
+    config = GetConfiguration({"name": "princeton_vcoblitz"}, slice_remap = DEFAULT_REMAP)
+    print config
+    print slices
+    print nodes
+
diff --git a/xos/filteredadmin.py b/xos/filteredadmin.py
new file mode 100644
index 0000000..b677b54
--- /dev/null
+++ b/xos/filteredadmin.py
@@ -0,0 +1,191 @@
+from django.contrib import admin
+
+from django import forms
+from services.hpc.models import HpcService
+from core.admin import ServiceAppAdmin,SliceInline,ServiceAttrAsTabInline, ReadOnlyAwareAdmin, XOSTabularInline, SliderWidget, ServicePrivilegeInline
+from core.middleware import get_request
+
+from functools import update_wrapper
+from django.contrib.admin.views.main import ChangeList
+from django.core.urlresolvers import reverse, NoReverseMatch
+from django.contrib.admin.utils import quote
+
+import threading
+_thread_locals = threading.local()
+
+class FilteredChangeList(ChangeList):
+    """ A special ChangeList with a doctored url_for_result function that
+        points to the filteredchange view instead of the default change
+        view.
+    """
+
+    def __init__(self, request, *args, **kwargs):
+        self.service = getattr(request, "hpcService", None)
+        self.embedded = getattr(request, "embedded", False)
+        super(FilteredChangeList, self).__init__(request, *args, **kwargs)
+
+    def url_for_result(self, result):
+        if (self.service is None):
+             return super(FilteredChangeList, self).url_for_result(result)
+
+        pk = getattr(result, self.pk_attname)
+        if self.embedded:
+            return reverse('admin:%s_%s_embeddedfilteredchange' % (self.opts.app_label,
+                                                           self.opts.model_name),
+                           args=(quote(self.service.id), quote(pk),),
+                           current_app=self.model_admin.admin_site.name)
+
+        else:
+            return reverse('admin:%s_%s_filteredchange' % (self.opts.app_label,
+                                                           self.opts.model_name),
+                           args=(quote(self.service.id), quote(pk),),
+                           current_app=self.model_admin.admin_site.name)
+
+class FilteredAdmin(ReadOnlyAwareAdmin):
+   """
+      One approach to filtering the HPC Admin views by HPCService. Encode
+      the HPCService into the URL for the changelist view. Then we could do our
+      custom filtering in self.filtered_changelist_view.
+
+      To make this work, a few changes needed to be made to the change and
+      change_list templates.
+
+      1) "custom_changelist_breadcrumb_url" is used to replace the breadcrumb
+         in change and add views with one that will point back to the filtered
+         list.
+
+      2) "custom_add_url" is used to replace the Add button's URL with one
+         that points to the filtered add view.
+
+      TODO: Save & Add Another,
+            the add link when the changelist is empty
+   """
+
+   @property
+   def change_list_template(self):
+       return _thread_locals.change_list_template
+
+   @property
+   def change_form_template(self):
+       return _thread_locals.change_form_template
+
+   def get_urls(self):
+       from django.conf.urls import patterns, url
+
+       def wrap(view):
+            def wrapper(*args, **kwargs):
+                return self.admin_site.admin_view(view)(*args, **kwargs)
+            return update_wrapper(wrapper, view)
+
+       urls = super(FilteredAdmin, self).get_urls()
+       info = self.model._meta.app_label, self.model._meta.model_name
+       my_urls = [
+           url(r'^(.+)/filteredlist/$', wrap(self.filtered_changelist_view), name="%s_%s_filteredchangelist" % info),
+           url(r'^(.+)/embeddedfilteredlist/$', wrap(self.embedded_filtered_changelist_view), name="%s_%s_embeddedfilteredchangelist" % info),
+           url(r'^(.+)/(.+)/filteredchange$', wrap(self.filtered_change_view), name='%s_%s_filteredchange' % info),
+           url(r'^(.+)/(.+)/embeddedfilteredchange$', wrap(self.embedded_filtered_change_view), name='%s_%s_embeddedfilteredchange' % info),
+           url(r'^(.+)/filteredadd/$', wrap(self.filtered_add_view), name='%s_%s_filteredadd' % info),
+           url(r'^(.+)/embeddedfilteredadd/$', wrap(self.embedded_filtered_add_view), name='%s_%s_embeddedfilteredadd' % info),
+       ]
+       return my_urls + urls
+
+   def add_extra_context(self, request, extra_context):
+       super(FilteredAdmin, self).add_extra_context(request, extra_context)
+
+       if getattr(request,"hpcService",None) is not None:
+            extra_context["custom_changelist_breadcrumb_url"] = "/admin/hpc/%s/%s/filteredlist/" % (self.model._meta.model_name, str(request.hpcService.id))
+            if getattr(request,"embedded",False):
+                extra_context["custom_add_url"] = "/admin/hpc/%s/%s/embeddedfilteredadd/" % (self.model._meta.model_name, str(request.hpcService.id))
+            else:
+                extra_context["custom_add_url"] = "/admin/hpc/%s/%s/filteredadd/" % (self.model._meta.model_name, str(request.hpcService.id))
+                if len(request.resolver_match.args)>1:
+                    # this is only useful on change views, not changelist views
+                    extra_context["custom_delete_url"] = "/admin/hpc/%s/%s/delete/" % (self.model._meta.model_name, request.resolver_match.args[1])
+
+       extra_context["show_save"] = False
+       extra_context["show_save_and_add_another"] = False
+
+   def changelist_view(self, *args, **kwargs):
+       if "template" in kwargs:
+           _thread_locals.change_list_template = kwargs["template"]
+           del kwargs["template"]
+       else:
+           _thread_locals.change_list_template = "admin/change_list_bc.html"
+       return super(FilteredAdmin, self).changelist_view(*args, **kwargs)
+
+   def filtered_changelist_view(self, request, hpcServiceId, extra_context=None):
+       request.hpcService = HpcService.objects.get(id=hpcServiceId)
+       return self.changelist_view(request, extra_context=extra_context)
+
+   def embedded_filtered_changelist_view(self, request, hpcServiceId, extra_context=None):
+       request.hpcService = HpcService.objects.get(id=hpcServiceId)
+       request.embedded = True
+       return self.changelist_view(request, template="admin/change_list_embedded.html", extra_context=extra_context)
+
+   def change_view(self, *args, **kwargs):
+       if "template" in kwargs:
+           _thread_locals.change_form_template = kwargs["template"]
+           del kwargs["template"]
+       else:
+           _thread_locals.change_form_template = "admin/change_form_bc.html"
+       return super(FilteredAdmin, self).change_view(*args, **kwargs)
+
+   def filtered_change_view(self, request, hpcServiceId, object_id, extra_context=None):
+       request.hpcService = HpcService.objects.get(id=hpcServiceId)
+       return self.change_view(request, object_id, extra_context=extra_context)
+
+   def embedded_filtered_change_view(self, request, hpcServiceId, object_id, extra_context=None):
+       request.hpcService = HpcService.objects.get(id=hpcServiceId)
+       request.embedded = True
+       return self.change_view(request, object_id, template="admin/change_form_embedded.html", extra_context=extra_context)
+
+   def add_view(self, *args, **kwargs):
+       if "template" in kwargs:
+           _thread_locals.change_form_template = kwargs["template"]
+           del kwargs["template"]
+       else:
+           _thread_locals.change_form_template = "admin/change_form_bc.html"
+       return super(FilteredAdmin, self).add_view(*args, **kwargs)
+
+   def filtered_add_view(self, request, hpcServiceId, extra_context=None):
+       request.hpcService = HpcService.objects.get(id=hpcServiceId)
+       return self.add_view(request, extra_context=extra_context)
+
+   def embedded_filtered_add_view(self, request, hpcServiceId, extra_context=None):
+       request.hpcService = HpcService.objects.get(id=hpcServiceId)
+       request.embedded = True
+       return self.add_view(request, template="admin/change_form_embedded.html", extra_context=extra_context)
+
+   def get_queryset(self, request):
+       # request.hpcService will be set in filtered_changelist_view so we can
+       # use it to filter what will be displayed in the list.
+       qs = self.model.objects.all()
+       if (getattr(request,"hpcService",None) is not None) and (hasattr(self.model, "filter_by_hpcService")):
+           qs = self.model.filter_by_hpcService(qs, request.hpcService)
+       return qs
+
+   def get_changelist(self, request, **kwargs):
+       # We implement a custom ChangeList, so the URLs point to the
+       # filtered_change_view rather than the default change_view.
+       return FilteredChangeList
+
+class FilteredInline(XOSTabularInline):
+   def get_change_url(self, id):
+       request = get_request()
+       embedded = getattr(request, "embedded", False)
+       service_id = request.resolver_match.args[0]
+
+       if embedded:
+           reverse_path = "admin:%s_embeddedfilteredchange" % (self.selflink_model._meta.db_table)
+           args = (service_id, id)
+       else:
+           reverse_path = "admin:%s_filteredchange" % (self.selflink_model._meta.db_table)
+           args = (service_id, id)
+
+       try:
+           url = reverse(reverse_path, args=args, current_app=self.selflink_model._meta.app_label)
+       except NoReverseMatch:
+           return None
+
+       return url
+
diff --git a/xos/hpc-onboard.yaml b/xos/hpc-onboard.yaml
new file mode 100644
index 0000000..07a1745
--- /dev/null
+++ b/xos/hpc-onboard.yaml
@@ -0,0 +1,25 @@
+tosca_definitions_version: tosca_simple_yaml_1_0
+
+description: Onboard the CDN service
+
+imports:
+   - custom_types/xos.yaml
+
+topology_template:
+  node_templates:
+    servicecontroller#hpc:
+      type: tosca.nodes.ServiceController
+      properties:
+          base_url: file:///opt/xos_services/hypercache/xos/
+          models: models.py
+          admin: admin.py
+          admin_template: templates/hpcadmin.html  templates/hpctools.html
+          synchronizer: synchronizer/manifest
+          synchronizer_run: hpc-synchronizer.py
+          #tosca_custom_types: exampleservice.yaml
+          #tosca_resource: tosca/resources/vcpeservice.py
+          #rest_service: subdirectory:vsg api/service/vsg/vsgservice.py
+          #rest_tenant: subdirectory:cord api/tenant/cord/vsg.py
+          #private_key: file:///opt/xos/key_import/vsg_rsa
+          #public_key: file:///opt/xos/key_import/vsg_rsa.pub
+
diff --git a/xos/make_synchronizer_manifest.sh b/xos/make_synchronizer_manifest.sh
new file mode 100644
index 0000000..4058982
--- /dev/null
+++ b/xos/make_synchronizer_manifest.sh
@@ -0,0 +1,2 @@
+#! /bin/bash
+find synchronizer -type f | cut -b 14- > synchronizer/manifest 
diff --git a/xos/models.py b/xos/models.py
new file mode 100644
index 0000000..e49bf4e
--- /dev/null
+++ b/xos/models.py
@@ -0,0 +1,229 @@
+from django.db import models
+from core.models import User, Service, SingletonModel, PlCoreBase
+from core.models.plcorebase import StrippedCharField
+import os
+from django.db import models
+from django.forms.models import model_to_dict
+from django.db.models import Q
+
+
+# Create your models here.
+
+class HpcService(Service):
+
+    class Meta:
+        app_label = "hpc"
+        verbose_name = "HPC Service"
+
+    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 instance")
+    watcher_dnsdemux_network = StrippedCharField(max_length=254, null=True, blank=True, help_text="Network for hpc_watcher to contact dnsdemux instance")
+    watcher_dnsredir_network = StrippedCharField(max_length=254, null=True, blank=True, help_text="Network for hpc_watcher to contact dnsredir instance")
+
+    @property
+    def scale(self):
+        hpc_slices = [x for x in self.slices.all() if "hpc" in x.name]
+        if not hpc_slices:
+            return 0
+        return hpc_slices[0].instances.count()
+
+    @scale.setter
+    def scale(self, value):
+        self.set_scale = value
+
+    def save(self, *args, **kwargs):
+        super(HpcService, self).save(*args, **kwargs)
+
+        # scale up/down
+        scale = getattr(self, "set_scale", None)
+        if scale is not None:
+            exclude_slices = [x for x in self.slices.all() if "cmi" in x.name]
+            self.adjust_scale(slice_hint="hpc", scale=scale, exclusive_slices = exclude_slices, max_per_node=1)
+
+class ServiceProvider(PlCoreBase):
+    class Meta:
+        app_label = "hpc"
+
+    hpcService = models.ForeignKey(HpcService)
+    service_provider_id = models.IntegerField(null=True, blank=True)
+    name = models.CharField(max_length=254,help_text="Service Provider Name")
+    description = models.TextField(max_length=254,null=True, blank=True, help_text="Description of Service Provider")
+    enabled = models.BooleanField(default=True)
+
+    def __unicode__(self):  return u'%s' % (self.name)
+
+    @classmethod
+    def filter_by_hpcService(cls, qs, hpcService):
+        # This should be overridden by descendant classes that want to perform
+        # filtering of visible objects by user.
+        return qs.filter(hpcService=hpcService)
+
+class ContentProvider(PlCoreBase):
+    class Meta:
+        app_label = "hpc"
+
+    # legacy vicci content providers already have names.
+    CP_TO_ACCOUNT = {"ON.LAB": "onlabcp",
+                     "Syndicate": "syndicatecp"}
+
+    content_provider_id = models.IntegerField(null=True, blank=True)
+    name = models.CharField(max_length=254)
+    enabled = models.BooleanField(default=True)
+    description = models.TextField(max_length=254,null=True, blank=True,help_text="Description of Content Provider")
+    serviceProvider = models.ForeignKey(ServiceProvider)
+
+    # Note user relationships are directed not requiring a role.
+    users = models.ManyToManyField(User)
+
+    def __unicode__(self):  return u'%s' % (self.name)
+
+    @property
+    def account(self):
+        return self.CP_TO_ACCOUNT.get(self.name, self.name)
+
+    @classmethod
+    def filter_by_hpcService(cls, qs, hpcService):
+        # This should be overridden by descendant classes that want to perform
+        # 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:
+        app_label = "hpc"
+
+    origin_server_id = models.IntegerField(null=True, blank=True)
+    url = models.CharField(max_length=1024)
+    contentProvider = models.ForeignKey(ContentProvider)
+
+    authenticated = models.BooleanField(default=False, help_text="Status for this Site")
+    enabled = models.BooleanField(default=True, help_text="Status for this Site")
+    PROTOCOL_CHOICES = (('http', 'HTTP'),('rtmp', 'RTMP'), ('rtp', 'RTP'),('shout', 'SHOUTcast')) 
+    protocol = models.CharField(default="HTTP", max_length = 12, choices=PROTOCOL_CHOICES)
+    redirects = models.BooleanField(default=True, help_text="Indicates whether Origin Server redirects should be used for this Origin Server")
+    description = models.TextField(null=True, blank=True, max_length=255)
+    
+    def __unicode__(self):  return u'%s' % (self.url)
+
+    @classmethod
+    def filter_by_hpcService(cls, qs, hpcService):
+        # This should be overridden by descendant classes that want to perform
+        # 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"
+
+    cdn_prefix_id = models.IntegerField(null=True, blank=True)
+    prefix = models.CharField(max_length=200, help_text="Registered Prefix for Domain")
+    contentProvider = models.ForeignKey(ContentProvider)
+    description = models.TextField(max_length=254,null=True, blank=True,help_text="Description of Content Provider")
+
+    defaultOriginServer = models.ForeignKey(OriginServer, blank=True, null=True)
+    enabled = models.BooleanField(default=True)
+
+    def __unicode__(self):  return u'%s' % (self.prefix)
+
+    @classmethod
+    def filter_by_hpcService(cls, qs, hpcService):
+        # This should be overridden by descendant classes that want to perform
+        # 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"
+
+    contentProvider = models.ForeignKey(ContentProvider)
+    name = models.CharField(max_length=64, help_text="Name of the Access Map")
+    description = models.TextField(null=True, blank=True,max_length=130)
+    map = models.FileField(upload_to="maps/", help_text="specifies which client requests are allowed")
+
+    def __unicode__(self):  return self.name
+
+class SiteMap(PlCoreBase):
+    class Meta:
+        app_label = "hpc"
+
+    """ can be bound to a ContentProvider, ServiceProvider, or neither """
+    contentProvider = models.ForeignKey(ContentProvider, blank=True, null=True)
+    serviceProvider = models.ForeignKey(ServiceProvider, blank=True, null=True)
+    cdnPrefix = models.ForeignKey(CDNPrefix, blank = True, null=True)
+    hpcService = models.ForeignKey(HpcService, blank = True, null=True)
+    name = models.CharField(max_length=64, help_text="Name of the Site Map")
+    description = models.TextField(null=True, blank=True,max_length=130)
+    map = models.FileField(upload_to="maps/", help_text="specifies how to map requests to hpc instances")
+    map_id = models.IntegerField(null=True, blank=True)
+
+    def __unicode__(self):  return self.name
+
+    def save(self, *args, **kwds):
+        if (self.contentProvider) and (self.serviceProvider or self.cdnPrefix or self.hpcService):
+            raise ValueError("You may only set one of contentProvider, serviceProvider, cdnPrefix, or hpcService")
+        if (self.serviceProvider) and (self.cdnPrefix or self.hpcService):
+            raise ValueError("You may only set one of contentProvider, serviceProvider, cdnPrefix, or hpcService")
+        if (self.cdnPrefix) and (self.hpcService):
+            raise ValueError("You may only set one of contentProvider, serviceProvider, cdnPrefix, or hpcService")
+
+        super(SiteMap, self).save(*args, **kwds)
+
+    @classmethod
+    def filter_by_hpcService(cls, qs, hpcService):
+        # This should be overridden by descendant classes that want to perform
+        # filtering of visible objects by user.
+        return qs.filter(Q(hpcService=hpcService) |
+                                  Q(serviceProvider__hpcService=hpcService) |
+                                  Q(contentProvider__serviceProvider__hpcService=hpcService) |
+                                  Q(cdnPrefix__contentProvider__serviceProvider__hpcService=hpcService))
+
+class HpcHealthCheck(PlCoreBase):
+    class Meta:
+        app_label = "hpc"
+
+    KIND_CHOICES = (('dns', 'DNS'), ('http', 'HTTP'), ('nameserver', 'Name Server'))
+
+    hpcService = models.ForeignKey(HpcService, blank = True, null=True)
+    kind = models.CharField(max_length=30, choices=KIND_CHOICES, default="dns")
+    resource_name = StrippedCharField(max_length=1024, blank=False, null=False)
+    result_contains = StrippedCharField(max_length=1024, blank=True, null=True)
+    result_min_size = models.IntegerField(null=True, blank=True)
+    result_max_size = models.IntegerField(null=True, blank=True)
+
+    def __unicode__(self): return self.resource_name
+
+    @classmethod
+    def filter_by_hpcService(cls, qs, hpcService):
+        # This should be overridden by descendant classes that want to perform
+        # filtering of visible objects by user.
+        return qs.filter(hpcService=hpcService)
+
+
diff --git a/xos/synchronizer/fsck.py b/xos/synchronizer/fsck.py
new file mode 100644
index 0000000..448bfb7
--- /dev/null
+++ b/xos/synchronizer/fsck.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python
+import argparse
+import imp
+import inspect
+import os
+import sys
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "xos.settings")
+sys.path.append("/opt/xos")
+from xos.config import Config, DEFAULT_CONFIG_FN, XOS_DIR
+from xos.logger import Logger, logging
+from synchronizers.base.syncstep import SyncStep
+
+try:
+    from django import setup as django_setup # django 1.7
+except:
+    django_setup = False
+
+logger = Logger(level=logging.INFO)
+
+class XOSConsistencyCheck:
+	def __init__(self):
+                self.sync_steps = []
+		self.load_sync_step_modules()
+
+	def load_sync_step_modules(self, step_dir=None):
+		if step_dir is None:
+			if hasattr(Config(), "observer_steps_dir"):
+				step_dir = Config().observer_steps_dir
+			else:
+				step_dir = XOS_DIR+"/observer/steps"
+
+		for fn in os.listdir(step_dir):
+			pathname = os.path.join(step_dir,fn)
+			if os.path.isfile(pathname) and fn.endswith(".py") and (fn!="__init__.py"):
+				module = imp.load_source(fn[:-3],pathname)
+				for classname in dir(module):
+					c = getattr(module, classname, None)
+
+					# make sure 'c' is a descendent of SyncStep and has a
+					# provides field (this eliminates the abstract base classes
+					# since they don't have a provides)
+
+					if inspect.isclass(c) and issubclass(c, SyncStep) and hasattr(c,"provides") and (c not in self.sync_steps):
+						self.sync_steps.append(c)
+		logger.info('loaded sync steps: %s' % ",".join([x.__name__ for x in self.sync_steps]))
+
+        def run(self):
+            updated = True
+            while updated:
+                updated = False
+
+                for step in self.sync_steps:
+                    if hasattr(step, "consistency_check"):
+                        updated = updated or step(driver=None).consistency_check()
+
+                if updated:
+                    logger.info('re-running consistency checks because something changed')
+
+def main():
+    if not "-C" in sys.argv:
+        print >> sys.stderr, "You probably wanted to use -C " + XOS_DIR + "/hpc_observer/hpc_observer_config"
+
+    # Generate command line parser
+    parser = argparse.ArgumentParser(usage='%(prog)s [options]')
+    # smbaker: util/config.py parses sys.argv[] directly to get config file name; include the option here to avoid
+    #   throwing unrecognized argument exceptions
+    parser.add_argument('-C', '--config', dest='config_file', action='store', default=DEFAULT_CONFIG_FN,
+                        help='Name of config file.')
+    args = parser.parse_args()
+
+    if django_setup: # 1.7
+        django_setup()
+
+    cc = XOSConsistencyCheck()
+    cc.run()
+
+if __name__ == '__main__':
+    main()
+
diff --git a/xos/synchronizer/hpc-synchronizer.py b/xos/synchronizer/hpc-synchronizer.py
new file mode 100755
index 0000000..84bec4f
--- /dev/null
+++ b/xos/synchronizer/hpc-synchronizer.py
@@ -0,0 +1,11 @@
+#!/usr/bin/env python
+
+# This imports and runs ../../xos-observer.py
+
+import importlib
+import os
+import sys
+observer_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),"../../synchronizers/base")
+sys.path.append(observer_path)
+mod = importlib.import_module("xos-synchronizer")
+mod.main()
diff --git a/xos/synchronizer/hpc_synchronizer_config b/xos/synchronizer/hpc_synchronizer_config
new file mode 100644
index 0000000..9d4e70a
--- /dev/null
+++ b/xos/synchronizer/hpc_synchronizer_config
@@ -0,0 +1,36 @@
+
+[plc]
+name=plc
+deployment=VICCI
+
+[db]
+name=xos
+user=postgres
+password=password
+host=localhost
+port=5432
+
+[api]
+host=128.112.171.237
+port=8000
+ssl_key=None
+ssl_cert=None
+ca_ssl_cert=None
+ratelimit_enabled=0
+omf_enabled=0
+mail_support_address=support@localhost
+nova_enabled=True
+
+[observer]
+name=hpc
+dependency_graph=/opt/xos/synchronizers/hpc/model-deps
+steps_dir=/opt/xos/synchronizers/hpc/steps
+deleters_dir=/opt/xos/synchronizers/hpc/deleters
+log_file=console
+#/var/log/hpc.log
+driver=None
+#cmi_hostname=openclouddev0.internet2.edu
+
+[feefie]
+client_id='vicci_dev_central'
+user_id='pl'
diff --git a/xos/synchronizer/hpc_watcher.py b/xos/synchronizer/hpc_watcher.py
new file mode 100644
index 0000000..d2efdcc
--- /dev/null
+++ b/xos/synchronizer/hpc_watcher.py
@@ -0,0 +1,629 @@
+"""
+    hpc_watcher.py
+
+    Daemon to watch the health of HPC and RR instances.
+
+    This deamon uses HpcHealthCheck objects in the Data Model to conduct
+    periodic tests of HPC and RR nodes. Two types of Health Checks are
+    supported:
+
+       kind="dns": checks the request routers to make sure that a DNS
+         name is resolveable and returns the right kind of records.
+
+         resource_name should be set to the domain name to lookup.
+
+         result_contains is option and can be used to hold "A", "CNAME", or
+            a particular address or hostname that should be contained in the
+            query's answer.
+
+       kind="http": checks the hpc nodes to make sure that a URL can be
+         retrieved from the node.
+
+         resource_name should be set to the HostName:Url to fetch. For
+         example, cdn-stream.htm.fiu.edu:/hft2441/intro.mp4
+
+     In addition to the above, HPC heartbeat probes are conducted, similar to
+     the ones that dnsredir conducts.
+
+     The results of health checks are stored in a tag attached to the Instance
+     the healthcheck was conducted against. If all healthchecks of a particular
+     variety were successful for a instance, then "success" will be stored in
+     the tag. Otherwise, the first healthcheck to fail will be stored in the
+     tag.
+
+     Ubuntu prereqs:
+         apt-get install python-pycurl
+         pip install dnslib
+"""
+
+import os
+import socket
+import sys
+sys.path.append("/opt/xos")
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "xos.settings")
+import django
+from django.contrib.contenttypes.models import ContentType
+from core.models import *
+from services.hpc.models import *
+from services.requestrouter.models import *
+django.setup()
+import time
+import pycurl
+import traceback
+import json
+from StringIO import StringIO
+
+from dnslib.dns import DNSRecord,DNSHeader,DNSQuestion,QTYPE
+from dnslib.digparser import DigParser
+
+from threading import Thread, Condition
+
+"""
+from dnslib import *
+q = DNSRecord(q=DNSQuestion("cdn-stream.htm.fiu.edu"))
+a_pkt = q.send("150.135.65.10", tcp=False, timeout=10)
+a = DNSRecord.parse(a_pkt)
+
+from dnslib import *
+q = DNSRecord(q=DNSQuestion("onlab.vicci.org"))
+a_pkt = q.send("150.135.65.10", tcp=False, timeout=10)
+a = DNSRecord.parse(a_pkt)
+"""
+
+class WorkQueue:
+    def __init__(self):
+        self.job_cv = Condition()
+        self.jobs = []
+        self.result_cv = Condition()
+        self.results = []
+        self.outstanding = 0
+
+    def get_job(self):
+        self.job_cv.acquire()
+        while not self.jobs:
+            self.job_cv.wait()
+        result = self.jobs.pop()
+        self.job_cv.release()
+        return result
+
+    def submit_job(self, job):
+        self.job_cv.acquire()
+        self.jobs.append(job)
+        self.job_cv.notify()
+        self.job_cv.release()
+        self.outstanding = self.outstanding + 1
+
+    def get_result(self):
+        self.result_cv.acquire()
+        while not self.results:
+            self.result_cv.wait()
+        result = self.results.pop()
+        self.result_cv.release()
+        self.outstanding = self.outstanding - 1
+        return result
+
+    def submit_result(self, result):
+        self.result_cv.acquire()
+        self.results.append(result)
+        self.result_cv.notify()
+        self.result_cv.release()
+
+class DnsResolver(Thread):
+    def __init__(self, queue):
+        Thread.__init__(self)
+        self.queue = queue
+        self.daemon = True
+        self.start()
+
+    def run(self):
+        while True:
+            job = self.queue.get_job()
+            self.handle_job(job)
+            self.queue.submit_result(job)
+
+    def handle_job(self, job):
+        domain = job["domain"]
+        server = job["server"]
+        port = job["port"]
+        result_contains = job.get("result_contains", None)
+
+        try:
+            q = DNSRecord(q=DNSQuestion(domain)) #, getattr(QTYPE,"A")))
+
+            a_pkt = q.send(server, port, tcp=False, timeout=10)
+            a = DNSRecord.parse(a_pkt)
+
+            found_record = False
+            for record in a.rr:
+                if (not result_contains):
+                    QTYPE_A = getattr(QTYPE,"A")
+                    QTYPE_CNAME = getattr(QTYPE, "CNAME")
+                    if ((record.rtype==QTYPE_A) or (record.qtype==QTYPE_CNAME)):
+                        found_record = True
+                else:
+                    tmp = QTYPE.get(record.rtype) + str(record.rdata)
+                    if (result_contains in tmp):
+                        found_record = True
+
+            if not found_record:
+                if result_contains:
+                    job["status"] =  "%s,No %s records" % (domain, result_contains)
+                else:
+                    job["status"] =  "%s,No A or CNAME records" % domain
+
+                return
+
+        except Exception, e:
+            job["status"] = "%s,Exception: %s" % (domain, str(e))
+            return
+
+        job["status"] = "success"
+
+class HpcHeartbeat(Thread):
+    def __init__(self, queue):
+        Thread.__init__(self)
+        self.queue = queue
+        self.daemon = True
+        self.start()
+
+    def run(self):
+        while True:
+            job = self.queue.get_job()
+            self.handle_job(job)
+            self.queue.submit_result(job)
+
+    def curl_error_message(self, e):
+        if e.args[0] == 6:
+            return "couldn't resolve host"
+        if e.args[0] == 7:
+            return "failed to connect"
+        return "curl error %d" % e.args[0]
+
+    def handle_job(self, job):
+        server = job["server"]
+        port = job["port"]
+
+        try:
+            buffer = StringIO()
+            c = pycurl.Curl()
+
+            c.setopt(c.URL, "http://%s:%s/heartbeat" % (server, port))
+            c.setopt(c.WRITEDATA, buffer)
+            c.setopt(c.HTTPHEADER, ['host: hpc-heartbeat', 'X-heartbeat: 1'])
+            c.setopt(c.TIMEOUT, 10)
+            c.setopt(c.CONNECTTIMEOUT, 10)
+            c.setopt(c.NOSIGNAL, 1)
+
+            try:
+                c.perform()
+                response_code = c.getinfo(c.RESPONSE_CODE)
+            except Exception, e:
+                #traceback.print_exc()
+                job["status"] = self.curl_error_message(e)
+                return
+            finally:
+                c.close()
+
+            if response_code != 200:
+                job["status"] = "error response %d" % response_code
+                return
+
+        except Exception, e:
+            job["status"] = "Exception: %s" % str(e)
+            return
+
+        job["status"] = "success"
+
+class HpcFetchUrl(Thread):
+    def __init__(self, queue):
+        Thread.__init__(self)
+        self.queue = queue
+        self.daemon = True
+        self.start()
+
+    def run(self):
+        while True:
+            job = self.queue.get_job()
+            self.handle_job(job)
+            self.queue.submit_result(job)
+
+    def curl_error_message(self, e):
+        if e.args[0] == 6:
+            return "couldn't resolve host"
+        if e.args[0] == 7:
+            return "failed to connect"
+        return "curl error %d" % e.args[0]
+
+    def handle_job(self, job):
+        server = job["server"]
+        port = job["port"]
+        url = job["url"]
+        domain = job["domain"]
+
+        def progress(download_t, download_d, upload_t, upload_d):
+            # limit download size to a megabyte
+            if (download_d > 1024*1024):
+                return 1
+            else:
+                return 0
+
+        try:
+            buffer = StringIO()
+            c = pycurl.Curl()
+
+            c.setopt(c.URL, "http://%s:%s/%s" % (server, port, url))
+            c.setopt(c.WRITEDATA, buffer)
+            c.setopt(c.HTTPHEADER, ['host: ' + domain])
+            c.setopt(c.TIMEOUT, 10)
+            c.setopt(c.CONNECTTIMEOUT, 10)
+            c.setopt(c.NOSIGNAL, 1)
+            c.setopt(c.NOPROGRESS, 0)
+            c.setopt(c.PROGRESSFUNCTION, progress)
+
+            try:
+                try:
+                    c.perform()
+                except Exception, e:
+                    # prevent callback abort from raising exception
+                    if (e.args[0] != pycurl.E_ABORTED_BY_CALLBACK):
+                        raise
+                response_code = c.getinfo(c.RESPONSE_CODE)
+                bytes_downloaded = int(c.getinfo(c.SIZE_DOWNLOAD))
+                total_time = float(c.getinfo(c.TOTAL_TIME))
+            except Exception, e:
+                #traceback.print_exc()
+                job["status"] = self.curl_error_message(e)
+                return
+            finally:
+                c.close()
+
+            if response_code != 200:
+                job["status"] = "error response %s" %  str(response_code)
+                return
+
+        except Exception, e:
+            #traceback.print_exc()
+            job["status"] = "Exception: %s" % str(e)
+            return
+
+        job["status"] = "success"
+        job["bytes_downloaded"] = bytes_downloaded
+        job["total_time"] = total_time
+
+class WatcherWorker(Thread):
+    def __init__(self, queue):
+        Thread.__init__(self)
+        self.queue = queue
+        self.daemon = True
+        self.start()
+
+    def run(self):
+        while True:
+            job = self.queue.get_job()
+            self.handle_job(job)
+            self.queue.submit_result(job)
+
+    def curl_error_message(self, e):
+        if e.args[0] == 6:
+            return "couldn't resolve host"
+        if e.args[0] == 7:
+            return "failed to connect"
+        return "curl error %d" % e.args[0]
+
+    def handle_job(self, job):
+        server = job["server"]
+        port = job["port"]
+
+        try:
+            buffer = StringIO()
+            c = pycurl.Curl()
+
+            c.setopt(c.URL, "http://%s:%s/" % (server, port))
+            c.setopt(c.WRITEDATA, buffer)
+            c.setopt(c.TIMEOUT, 10)
+            c.setopt(c.CONNECTTIMEOUT, 10)
+            c.setopt(c.NOSIGNAL, 1)
+
+            try:
+                c.perform()
+                response_code = c.getinfo(c.RESPONSE_CODE)
+            except Exception, e:
+                #traceback.print_exc()
+                job["status"] = json.dumps( {"status": self.curl_error_message(e)} )
+                return
+            finally:
+                c.close()
+
+            if response_code != 200:
+                job["status"] = json.dumps( {"status": "error response %d" % response_code} )
+                return
+
+            d = json.loads(buffer.getvalue())
+            d["status"] = "success";
+            job["status"] = json.dumps(d)
+
+        except Exception, e:
+            job["status"] = json.dumps( {"status": "Exception: %s" % str(e)} )
+            return
+
+class BaseWatcher(Thread):
+    def __init__(self):
+        Thread.__init__(self)
+        self.daemon = True
+
+    def get_public_ip(self, service, instance):
+        network_name = None
+        if "hpc" in instance.slice.name:
+            network_name = getattr(service, "watcher_hpc_network", None)
+        elif "demux" in instance.slice.name:
+            network_name = getattr(service, "watcher_dnsdemux_network", None)
+        elif "redir" in instance.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 instance.get_public_ip()
+
+        for ns in instance.ports.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, instance, service, kind, msg, check_error=True):
+        #print instance.node.name, kind, msg
+        if check_error:
+            instance.has_error = (msg!="success")
+
+        instance_type = ContentType.objects.get_for_model(instance)
+
+        t = Tag.objects.filter(service=service, name=kind+".msg", content_type__pk=instance_type.id, object_id=instance.id)
+        if t:
+            t=t[0]
+            if (t.value != msg):
+                t.value = msg
+                t.save()
+        else:
+            Tag(service=service, name=kind+".msg", content_object = instance, value=msg).save()
+
+        t = Tag.objects.filter(service=service, name=kind+".time", content_type__pk=instance_type.id, object_id=instance.id)
+        if t:
+            t=t[0]
+            t.value = str(time.time())
+            t.save()
+        else:
+            Tag(service=service, name=kind+".time", content_object = instance, value=str(time.time())).save()
+
+    def get_service_slices(self, service, kind=None):
+        try:
+            slices = service.slices.all()
+        except:
+            # buggy data model
+            slices = service.service.all()
+
+        if kind:
+            return [x for x in slices if (kind in x.name)]
+        else:
+            return list(slices)
+
+class RRWatcher(BaseWatcher):
+    def __init__(self):
+        BaseWatcher.__init__(self)
+
+        self.resolver_queue = WorkQueue()
+        for i in range(0,10):
+            DnsResolver(queue = self.resolver_queue)
+
+    def check_request_routers(self, service, instances):
+        for instance in instances:
+            instance.has_error = False
+
+            try:
+                ip = self.get_public_ip(service, instance)
+            except Exception, e:
+                self.set_status(instance, service, "watcher.DNS", "exception: %s" % str(e))
+                continue
+            if not ip:
+                try:
+                    ip = socket.gethostbyname(instance.node.name)
+                except:
+                    self.set_status(instance, service, "watcher.DNS", "dns resolution failure")
+                    continue
+
+            if not ip:
+                self.set_status(instance, service, "watcher.DNS", "no IP address")
+                continue
+
+            checks = HpcHealthCheck.objects.filter(kind="dns")
+            if not checks:
+                self.set_status(instance, service, "watcher.DNS", "no DNS HealthCheck tests configured")
+
+            for check in checks:
+                self.resolver_queue.submit_job({"domain": check.resource_name, "server": ip, "port": 53, "instance": instance, "result_contains": check.result_contains})
+
+        while self.resolver_queue.outstanding > 0:
+            result = self.resolver_queue.get_result()
+            instance = result["instance"]
+            if (result["status"]!="success") and (not instance.has_error):
+                self.set_status(instance, service, "watcher.DNS", result["status"])
+
+        for instance in instances:
+            if not instance.has_error:
+                self.set_status(instance, service, "watcher.DNS", "success")
+
+    def run_once(self):
+        for hpcService in HpcService.objects.all():
+            for slice in self.get_service_slices(hpcService, "dnsdemux"):
+                self.check_request_routers(hpcService, slice.instances.all())
+
+        for rrService in RequestRouterService.objects.all():
+            for slice in self.get_service_slices(rrService, "dnsdemux"):
+                self.check_request_routers(rrService, slice.instances.all())
+
+    def run(self):
+        while True:
+            self.run_once()
+            time.sleep(10)
+
+            django.db.reset_queries()
+
+class HpcProber(BaseWatcher):
+    def __init__(self):
+        BaseWatcher.__init__(self)
+
+        self.heartbeat_queue = WorkQueue()
+        for i in range(0, 10):
+            HpcHeartbeat(queue = self.heartbeat_queue)
+
+    def probe_hpc(self, service, instances):
+        for instance in instances:
+            instance.has_error = False
+
+            self.heartbeat_queue.submit_job({"server": instance.node.name, "port": 8009, "instance": instance})
+
+        while self.heartbeat_queue.outstanding > 0:
+            result = self.heartbeat_queue.get_result()
+            instance = result["instance"]
+            if (result["status"]!="success") and (not instance.has_error):
+                self.set_status(instance, service, "watcher.HPC-hb", result["status"])
+
+        for instance in instances:
+            if not instance.has_error:
+                self.set_status(instance, service, "watcher.HPC-hb", "success")
+
+    def run_once(self):
+        for hpcService in HpcService.objects.all():
+            for slice in self.get_service_slices(hpcService, "hpc"):
+                self.probe_hpc(hpcService, slice.instances.all())
+
+    def run(self):
+        while True:
+            self.run_once()
+            time.sleep(10)
+
+            django.db.reset_queries()
+
+class HpcFetcher(BaseWatcher):
+    def __init__(self):
+        BaseWatcher.__init__(self)
+
+        self.fetch_queue = WorkQueue()
+        for i in range(0, 10):
+            HpcFetchUrl(queue = self.fetch_queue)
+
+    def fetch_hpc(self, service, instances):
+        for instance in instances:
+            instance.has_error = False
+            instance.url_status = []
+
+            checks = HpcHealthCheck.objects.filter(kind="http")
+            if not checks:
+                self.set_status(instance, service, "watcher.HPC-fetch", "no HTTP HealthCheck tests configured")
+
+            for check in checks:
+                if (not check.resource_name) or (":" not in check.resource_name):
+                    self.set_status(instance, service, "watcher.HPC-fetch", "malformed resource_name: " + str(check.resource_name))
+                    break
+
+                (domain, url) = check.resource_name.split(":",1)
+
+                self.fetch_queue.submit_job({"server": instance.node.name, "port": 80, "instance": instance, "domain": domain, "url": url})
+
+        while self.fetch_queue.outstanding > 0:
+            result = self.fetch_queue.get_result()
+            instance = result["instance"]
+            if (result["status"] == "success"):
+                instance.url_status.append( (result["domain"] + result["url"], "success", result["bytes_downloaded"], result["total_time"]) )
+            if (result["status"]!="success") and (not instance.has_error):
+                self.set_status(instance, service, "watcher.HPC-fetch", result["status"])
+
+        for instance in instances:
+            self.set_status(instance, service, "watcher.HPC-fetch-urls", json.dumps(instance.url_status), check_error=False)
+            if not instance.has_error:
+                self.set_status(instance, service, "watcher.HPC-fetch", "success")
+
+    def run_once(self):
+        for hpcService in HpcService.objects.all():
+            for slice in self.get_service_slices(hpcService, "hpc"):
+                try:
+                    self.fetch_hpc(hpcService, slice.instances.all())
+                except:
+                    traceback.print_exc()
+
+    def run(self):
+        while True:
+            self.run_once()
+            time.sleep(10)
+
+            django.db.reset_queries()
+
+class WatcherFetcher(BaseWatcher):
+    def __init__(self):
+        BaseWatcher.__init__(self)
+
+        self.fetch_queue = WorkQueue()
+        for i in range(0, 10):
+             WatcherWorker(queue = self.fetch_queue)
+
+    def fetch_watcher(self, service, instances):
+        for instance in instances:
+            try:
+                ip = self.get_public_ip(service, instance)
+            except Exception, e:
+                self.set_status(instance, service, "watcher.watcher", json.dumps({"status": "exception: %s" % str(e)}) )
+                continue
+            if not ip:
+                try:
+                    ip = socket.gethostbyname(instance.node.name)
+                except:
+                    self.set_status(instance, service, "watcher.watcher", json.dumps({"status": "dns resolution failure"}) )
+                    continue
+
+            if not ip:
+                self.set_status(instance, service, "watcher.watcher", json.dumps({"status": "no IP address"}) )
+                continue
+
+            port = 8015
+            if ("redir" in instance.slice.name):
+                port = 8016
+            elif ("demux" in instance.slice.name):
+                port = 8017
+
+            self.fetch_queue.submit_job({"server": ip, "port": port, "instance": instance})
+
+        while self.fetch_queue.outstanding > 0:
+            result = self.fetch_queue.get_result()
+            instance = result["instance"]
+            self.set_status(instance, service, "watcher.watcher", result["status"])
+
+    def run_once(self):
+        for hpcService in HpcService.objects.all():
+            for slice in self.get_service_slices(hpcService):
+                self.fetch_watcher(hpcService, slice.instances.all())
+
+    def run(self):
+        while True:
+            self.run_once()
+            time.sleep(10)
+
+            django.db.reset_queries()
+
+
+if __name__ == "__main__":
+    if "--once" in sys.argv:
+        RRWatcher().run_once()
+        HpcProber().run_once()
+        HpcFetcher().run_once()
+        WatcherFetcher().run_once()
+    else:
+        RRWatcher().start()
+        HpcProber().start()
+        HpcFetcher().start()
+        WatcherFetcher().start()
+
+        print "Running forever..."
+        while True:
+            time.sleep(60)
+
diff --git a/xos/synchronizer/hpclib.py b/xos/synchronizer/hpclib.py
new file mode 100644
index 0000000..bb1c263
--- /dev/null
+++ b/xos/synchronizer/hpclib.py
@@ -0,0 +1,126 @@
+import os
+import base64
+import string
+import sys
+import xmlrpclib
+
+if __name__ == '__main__':
+    sys.path.append("/opt/xos")
+    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "xos.settings")
+
+from xos.config import Config
+from core.models import Service
+from services.hpc.models import HpcService
+from services.requestrouter.models import RequestRouterService
+from xos.logger import Logger, logging
+
+logger = Logger(level=logging.INFO)
+
+class APIHelper:
+    def __init__(self, proxy, auth, method=None):
+        self.proxy = proxy
+        self.auth = auth
+        self.method = method
+
+    def __getattr__(self, name):
+        if name.startswith("_"):
+            return getattr(self, name)
+        else:
+            return APIHelper(self.proxy, self.auth, name)
+
+    def __call__(self, *args):
+        method = getattr(self.proxy, self.method)
+        return method(self.auth, *args)
+
+class CmiClient:
+    def __init__(self, hostname, port=8003, username="apiuser", password="apiuser"):
+        self.connect_api(hostname, port, username, password)
+
+    def connect_api(self, hostname, port=8003, username="apiuser", password="apiuser"):
+        #print "https://%s:%d/COAPI/" % (hostname, port)
+        cob = xmlrpclib.ServerProxy("https://%s:%d/COAPI/" % (hostname, port), allow_none=True)
+        cob_auth = {}
+        cob_auth["Username"] = username
+        cob_auth["AuthString"] = password
+        cob_auth["AuthMethod"] = "password"
+
+        onev = xmlrpclib.ServerProxy("https://%s:%d/ONEV_API/" % (hostname, port), allow_none=True)
+        onev_auth = {}
+        onev_auth["Username"] = username
+        onev_auth["AuthString"] = password
+        onev_auth["AuthMethod"] = "password"
+
+        self.cob = APIHelper(cob, cob_auth)
+        self.onev = APIHelper(onev, onev_auth)
+
+class HpcLibrary:
+    def __init__(self):
+        self._client = None
+
+    def make_account_name(self, x):
+        x=x.lower()
+        y = ""
+        for c in x:
+            if (c in (string.lowercase + string.digits)):
+                y = y + c
+        return y[:20]
+
+    def get_hpc_service(self):
+        hpc_service_name = getattr(Config(), "observer_hpc_service", None)
+        if hpc_service_name:
+            hpc_service = HpcService.objects.filter(name = hpc_service_name)
+        else:
+            hpc_service = HpcService.objects.all()
+
+        if not hpc_service:
+            if hpc_service_name:
+                raise Exception("No HPC Service with name %s" % hpc_service_name)
+            else:
+                raise Exception("No HPC Services")
+        hpc_service = hpc_service[0]
+
+        return hpc_service
+
+    def get_cmi_hostname(self, hpc_service=None):
+        if getattr(Config(),"observer_cmi_hostname",None):
+            return getattr(Config(),"observer_cmi_hostname")
+
+        if (hpc_service is None):
+            hpc_service = self.get_hpc_service()
+
+        if hpc_service.cmi_hostname:
+            return hpc_service.cmi_hostname
+
+        try:
+            slices = hpc_service.slices.all()
+        except:
+            # deal with buggy data model
+            slices = hpc_service.service.all()
+
+        for slice in slices:
+            if slice.name.endswith("cmi"):
+                for instance in slice.instances.all():
+                    if instance.node:
+                         return instance.node.name
+
+        raise Exception("Failed to find a CMI instance")
+
+    @property
+    def client(self):
+        if self._client is None:
+            self._client = CmiClient(self.get_cmi_hostname())
+        return self._client
+
+if __name__ == '__main__':
+    import django
+    django.setup()
+
+    lib = HpcLibrary()
+
+    print "testing API connection to", lib.get_cmi_hostname()
+    lib.client.cob.GetNewObjects()
+    lib.client.onev.ListAll("CDN")
+
+
+
+
diff --git a/xos/synchronizer/manifest b/xos/synchronizer/manifest
new file mode 100644
index 0000000..f0686a7
--- /dev/null
+++ b/xos/synchronizer/manifest
@@ -0,0 +1,17 @@
+manifest
+hpc_watcher.py
+steps/sync_hpcservices.py
+steps/sync_serviceprovider.py
+steps/sync_contentprovider.py
+steps/sync_cdnprefix.py
+steps/sync_originserver.py
+steps/garbage_collector.py
+steps/sync_sitemap.py
+hpc_synchronizer_config
+start.sh
+stop.sh
+hpc-synchronizer.py
+model-deps
+run.sh
+fsck.py
+hpclib.py
diff --git a/xos/synchronizer/model-deps b/xos/synchronizer/model-deps
new file mode 100644
index 0000000..63188f0
--- /dev/null
+++ b/xos/synchronizer/model-deps
@@ -0,0 +1,19 @@
+{
+    "OriginServer": [
+        "ContentProvider"
+    ], 
+    "ContentProvider": [
+        "ServiceProvider"
+    ], 
+    "CDNPrefix": [
+        "ContentProvider"
+    ], 
+    "AccessMap": [
+        "ContentProvider"
+    ], 
+    "SiteMap": [
+        "ContentProvider", 
+        "ServiceProvider", 
+        "CDNPrefix"
+    ]
+}
diff --git a/xos/synchronizer/run.sh b/xos/synchronizer/run.sh
new file mode 100755
index 0000000..9d22047
--- /dev/null
+++ b/xos/synchronizer/run.sh
@@ -0,0 +1,2 @@
+export XOS_DIR=/opt/xos
+python hpc-synchronizer.py  -C $XOS_DIR/synchronizers/hpc/hpc_synchronizer_config
diff --git a/xos/synchronizer/start.sh b/xos/synchronizer/start.sh
new file mode 100755
index 0000000..3153a7d
--- /dev/null
+++ b/xos/synchronizer/start.sh
@@ -0,0 +1,2 @@
+export XOS_DIR=/opt/xos
+nohup python hpc-synchronizer.py  -C $XOS_DIR/synchronizers/hpc/hpc_synchronizer_config > /dev/null 2>&1 &
diff --git a/xos/synchronizer/steps/garbage_collector.py b/xos/synchronizer/steps/garbage_collector.py
new file mode 100644
index 0000000..658f7a1
--- /dev/null
+++ b/xos/synchronizer/steps/garbage_collector.py
@@ -0,0 +1,67 @@
+import os
+import sys
+import base64
+import traceback
+from collections import defaultdict
+from django.db.models import F, Q
+from xos.config import Config
+from xos.logger import Logger, logging
+from synchronizers.base.syncstep import SyncStep
+from services.hpc.models import ServiceProvider, ContentProvider, CDNPrefix, OriginServer
+from core.models import *
+
+# hpclibrary will be in steps/..
+parentdir = os.path.join(os.path.dirname(__file__),"..")
+sys.path.insert(0,parentdir)
+
+from hpclib import HpcLibrary
+
+logger = Logger(level=logging.INFO)
+
+class GarbageCollector(SyncStep, HpcLibrary):
+#    requested_interval = 86400
+    requested_interval = 0
+    provides=[]
+
+    def __init__(self, **args):
+        SyncStep.__init__(self, **args)
+        HpcLibrary.__init__(self)
+
+    def call(self, **args):
+        logger.info("running garbage collector")
+        try:
+            self.gc_originservers()
+            self.gc_cdnprefixes()
+            self.gc_contentproviders()
+            self.gc_serviceproviders()
+        except:
+            traceback.print_exc()
+
+    def gc_onev(self, ps_class, ps_idField, onev_className, onev_idField):
+        # get the CMI's objects
+        onev_objs = self.client.onev.ListAll(onev_className)
+
+        # get the data model's objects,
+        ps_objs = ps_class.objects.filter(enacted__isnull=False)
+        ps_ids = [str(getattr(x,ps_idField,None)) for x in ps_objs]
+
+        # for each onev object, if it's id does not exist in a data model
+        # object, then delete it.
+        for onev_obj in onev_objs:
+            onev_id = onev_obj[onev_idField]
+            if str(onev_id) not in ps_ids:
+                logger.info("garbage collecting %s %s" % (onev_className, str(onev_id)))
+                self.client.onev.Delete(onev_className, onev_id)
+
+    def gc_originservers(self):
+        self.gc_onev(OriginServer, "origin_server_id", "OriginServer", "origin_server_id")
+
+    def gc_cdnprefixes(self):
+        self.gc_onev(CDNPrefix, "cdn_prefix_id", "CDNPrefix", "cdn_prefix_id")
+
+    def gc_contentproviders(self):
+        self.gc_onev(ContentProvider, "content_provider_id", "ContentProvider", "content_provider_id")
+
+    def gc_serviceproviders(self):
+        self.gc_onev(ServiceProvider, "service_provider_id", "ServiceProvider", "service_provider_id")
+
diff --git a/xos/synchronizer/steps/sync_cdnprefix.py b/xos/synchronizer/steps/sync_cdnprefix.py
new file mode 100644
index 0000000..eff3b5d
--- /dev/null
+++ b/xos/synchronizer/steps/sync_cdnprefix.py
@@ -0,0 +1,101 @@
+import os
+import sys
+import base64
+from django.db.models import F, Q
+from xos.config import Config
+from synchronizers.base.syncstep import SyncStep
+from core.models import Service
+from services.hpc.models import ServiceProvider, ContentProvider, CDNPrefix
+from xos.logger import Logger, logging
+
+# hpclibrary will be in steps/..
+parentdir = os.path.join(os.path.dirname(__file__),"..")
+sys.path.insert(0,parentdir)
+
+from hpclib import HpcLibrary
+
+logger = Logger(level=logging.INFO)
+
+class SyncCDNPrefix(SyncStep, HpcLibrary):
+    provides=[CDNPrefix]
+    observes=CDNPrefix
+    requested_interval=0
+
+    def __init__(self, **args):
+        SyncStep.__init__(self, **args)
+        HpcLibrary.__init__(self)
+
+    def filter_hpc_service(self, objs):
+        hpcService = self.get_hpc_service()
+
+        return [x for x in objs if x.contentProvider.serviceProvider.hpcService == hpcService]
+
+    def fetch_pending(self, deleted):
+        #self.consistency_check()
+
+        return self.filter_hpc_service(SyncStep.fetch_pending(self, deleted))
+
+    def consistency_check(self):
+        # set to true if something changed
+        result=False
+
+        # sanity check to make sure our PS objects have CMI objects behind them
+        all_p_ids = [x["cdn_prefix_id"] for x in self.client.onev.ListAll("CDNPrefix")]
+
+        all_p_ids = []
+        all_origins = {}
+        for x in self.client.onev.ListAll("CDNPrefix"):
+            id = x["cdn_prefix_id"]
+            all_p_ids.append(id)
+            all_origins[id] = x.get("default_origin_server", None)
+
+        for p in CDNPrefix.objects.all():
+            if (p.cdn_prefix_id is None):
+                continue
+
+            if (p.cdn_prefix_id not in all_p_ids):
+                logger.info("CDN Prefix %s was not found on CMI" % p.cdn_prefix_id)
+                p.cdn_prefix_id=None
+                p.save()
+                result = True
+
+            if (p.defaultOriginServer!=None) and (all_origins.get(p.cdn_prefix_id,None) != p.defaultOriginServer.url):
+                logger.info("CDN Prefix %s does not have default origin server on CMI" % str(p))
+                p.save() # this will set updated>enacted and force observer to re-sync
+                result = True
+
+        return result
+
+    def sync_record(self, cp):
+        logger.info("sync'ing cdn prefix %s" % str(cp),extra=cp.tologdict())
+
+        if (not cp.contentProvider) or (not cp.contentProvider.content_provider_id):
+            raise Exception("CDN Prefix %s is linked to a contentProvider without an id" % str(cp))
+
+        cpid = cp.contentProvider.content_provider_id
+
+        cp_dict = {"service": "HyperCache", "enabled": cp.enabled, "content_provider_id": cpid, "cdn_prefix": cp.prefix}
+
+        if cp.defaultOriginServer and cp.defaultOriginServer.url:
+            if (not cp.defaultOriginServer.origin_server_id):
+                # It's probably a bad idea to try to set defaultOriginServer before
+                # we've crated defaultOriginServer.
+                raise Exception("cdn prefix %s is waiting for it's default origin server to get an id" % str(cp))
+
+            cp_dict["default_origin_server"] = cp.defaultOriginServer.url
+
+        #print cp_dict
+
+        if not cp.cdn_prefix_id:
+            id = self.client.onev.Create("CDNPrefix", cp_dict)
+            cp.cdn_prefix_id = id
+        else:
+            del cp_dict["content_provider_id"]  # this can't be updated
+            del cp_dict["cdn_prefix"] # this can't be updated either
+            self.client.onev.Update("CDNPrefix", cp.cdn_prefix_id, cp_dict)
+
+        cp.save()
+
+    def delete_record(self, m):
+        if m.cdn_prefix_id is not None:
+            self.client.onev.Delete("CDNPrefix", m.cdn_prefix_id)
diff --git a/xos/synchronizer/steps/sync_contentprovider.py b/xos/synchronizer/steps/sync_contentprovider.py
new file mode 100644
index 0000000..3e30ed3
--- /dev/null
+++ b/xos/synchronizer/steps/sync_contentprovider.py
@@ -0,0 +1,78 @@
+import os
+import sys
+import base64
+from django.db.models import F, Q
+from xos.config import Config
+from synchronizers.base.syncstep import SyncStep
+from core.models import Service
+from services.hpc.models import ServiceProvider, ContentProvider
+from xos.logger import Logger, logging
+
+# hpclibrary will be in steps/..
+parentdir = os.path.join(os.path.dirname(__file__),"..")
+sys.path.insert(0,parentdir)
+
+from hpclib import HpcLibrary
+
+logger = Logger(level=logging.INFO)
+
+class SyncContentProvider(SyncStep, HpcLibrary):
+    provides=[ContentProvider]
+    observes=ContentProvider
+    requested_interval=0
+
+    def __init__(self, **args):
+        SyncStep.__init__(self, **args)
+        HpcLibrary.__init__(self)
+
+    def filter_hpc_service(self, objs):
+        hpcService = self.get_hpc_service()
+
+        return [x for x in objs if x.serviceProvider.hpcService == hpcService]
+
+    def fetch_pending(self, deleted):
+        #self.consistency_check()
+
+        return self.filter_hpc_service(SyncStep.fetch_pending(self, deleted))
+
+    def consistency_check(self):
+        # set to true if something changed
+        result=False
+
+        # sanity check to make sure our PS objects have CMI objects behind them
+        all_cp_ids = [x["content_provider_id"] for x in self.client.onev.ListAll("ContentProvider")]
+        for cp in ContentProvider.objects.all():
+            if (cp.content_provider_id is not None) and (cp.content_provider_id not in all_cp_ids):
+                logger.info("Content provider %s was not found on CMI" % cp.content_provider_id)
+                cp.content_provider_id=None
+                cp.save()
+                result = True
+
+        return result
+
+    def sync_record(self, cp):
+        logger.info("sync'ing content provider %s" % str(cp), extra=cp.tologdict())
+        account_name = self.make_account_name(cp.name)
+
+        if (not cp.serviceProvider) or (not cp.serviceProvider.service_provider_id):
+            raise Exception("ContentProvider %s is linked to a serviceProvider with no id" % str(cp))
+
+        spid = cp.serviceProvider.service_provider_id
+
+        cp_dict = {"account": account_name, "name": cp.name, "enabled": cp.enabled}
+
+        #print cp_dict
+
+        if not cp.content_provider_id:
+            cp_dict["service_provider_id"] = spid
+            id = self.client.onev.Create("ContentProvider", cp_dict)
+            cp.content_provider_id = id
+        else:
+            self.client.onev.Update("ContentProvider", cp.content_provider_id, cp_dict)
+
+        cp.save()
+
+    def delete_record(self, m):
+        if m.content_provider_id is not None:
+            self.client.onev.Delete("ContentProvider", m.content_provider_id)
+
diff --git a/xos/synchronizer/steps/sync_hpcservices.py b/xos/synchronizer/steps/sync_hpcservices.py
new file mode 100644
index 0000000..63bf19b
--- /dev/null
+++ b/xos/synchronizer/steps/sync_hpcservices.py
@@ -0,0 +1,43 @@
+import os
+import sys
+import base64
+from django.db.models import F, Q
+from xos.config import Config
+from synchronizers.base.syncstep import SyncStep
+from core.models import Service
+from services.hpc.models import HpcService
+from services.requestrouter.models import RequestRouterService
+from xos.logger import Logger, logging
+
+# hpclibrary will be in steps/..
+parentdir = os.path.join(os.path.dirname(__file__),"..")
+sys.path.insert(0,parentdir)
+
+from hpclib import HpcLibrary
+
+logger = Logger(level=logging.INFO)
+
+class SyncHpcService(SyncStep, HpcLibrary):
+    provides=[HpcService]
+    observes=HpcService
+    requested_interval=0
+
+    def __init__(self, **args):
+        SyncStep.__init__(self, **args)
+        HpcLibrary.__init__(self)
+
+    def filter_hpc_service(self, objs):
+        hpcService = self.get_hpc_service()
+
+        return [x for x in objs if x == hpcService]
+
+    def fetch_pending(self, deleted):
+        # Looks like deletion is not supported for this object - Sapan
+        if (deleted):
+            return []
+        else:
+            return self.filter_hpc_service(HpcService.objects.filter(Q(enacted__lt=F('updated')) | Q(enacted=None)))
+
+    def sync_record(self, hpc_service):
+        logger.info("sync'ing hpc_service %s" % str(hpc_service),extra=hpc_service.tologdict())
+        hpc_service.save()
diff --git a/xos/synchronizer/steps/sync_originserver.py b/xos/synchronizer/steps/sync_originserver.py
new file mode 100644
index 0000000..bd5b227
--- /dev/null
+++ b/xos/synchronizer/steps/sync_originserver.py
@@ -0,0 +1,92 @@
+import os
+import sys
+import base64
+
+from django.db.models import F, Q
+from xos.config import Config
+from synchronizers.base.syncstep import SyncStep
+from core.models import Service
+from services.hpc.models import ServiceProvider, ContentProvider, CDNPrefix, OriginServer
+from xos.logger import Logger, logging
+
+# hpclibrary will be in steps/..
+parentdir = os.path.join(os.path.dirname(__file__),"..")
+sys.path.insert(0,parentdir)
+
+from hpclib import HpcLibrary
+
+logger = Logger(level=logging.INFO)
+
+class SyncOriginServer(SyncStep, HpcLibrary):
+    provides=[OriginServer]
+    observes=OriginServer
+    requested_interval=0
+
+    def __init__(self, **args):
+        SyncStep.__init__(self, **args)
+        HpcLibrary.__init__(self)
+
+    def filter_hpc_service(self, objs):
+        hpcService = self.get_hpc_service()
+
+        return [x for x in objs if x.contentProvider.serviceProvider.hpcService == hpcService]
+
+    def fetch_pending(self, deleted):
+        #self.consistency_check()
+
+        return self.filter_hpc_service(SyncStep.fetch_pending(self, deleted))
+
+    def consistency_check(self):
+        # set to true if something changed
+        result=False
+
+        # sanity check to make sure our PS objects have CMI objects behind them
+        all_ors_ids = [x["origin_server_id"] for x in self.client.onev.ListAll("OriginServer")]
+        for ors in OriginServer.objects.all():
+            if (ors.origin_server_id is not None) and (ors.origin_server_id not in all_ors_ids):
+                # we have an origin server ID, but it doesn't exist in the CMI
+                # something went wrong
+                # start over
+                logger.info("origin server %s was not found on CMI" % ors.origin_server_id)
+                ors.origin_server_id=None
+                ors.save()
+                result = True
+
+        return result
+
+    def sync_record(self, ors):
+        logger.info("sync'ing origin server %s" % str(ors),extra=ors.tologdict())
+
+        if (not ors.contentProvider) or (not ors.contentProvider.content_provider_id):
+            raise Exception("Origin Server %s is linked to a contentProvider with no id" % str(ors))
+
+        cpid = ors.contentProvider.content_provider_id
+
+        # validation requires URL start with http://
+        url = ors.url
+        if not url.startswith("http://"):
+            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
+
+        if not ors.origin_server_id:
+            id = self.client.onev.Create("OriginServer", ors_dict)
+            ors.origin_server_id = id
+        else:
+            self.client.onev.Update("OriginServer", ors.origin_server_id, ors_dict)
+
+        # ... something breaks (analytics) if the URL starts with http://, so we
+        # change it in cob after we added it via onev.
+        url = url[7:]
+        self.client.cob.UpdateContent(ors.origin_server_id, {"url": url})
+
+        ors.silent = True
+        ors.save()
+
+    def delete_record(self, m):
+        if m.origin_server_id is not None:
+            self.client.onev.Delete("OriginServer", m.origin_server_id)
diff --git a/xos/synchronizer/steps/sync_serviceprovider.py b/xos/synchronizer/steps/sync_serviceprovider.py
new file mode 100644
index 0000000..af6d685
--- /dev/null
+++ b/xos/synchronizer/steps/sync_serviceprovider.py
@@ -0,0 +1,67 @@
+import os
+import sys
+import base64
+from django.db.models import F, Q
+from xos.config import Config
+from synchronizers.base.syncstep import SyncStep
+from core.models import Service
+from services.hpc.models import ServiceProvider
+from xos.logger import Logger, logging
+
+# hpclibrary will be in steps/..
+parentdir = os.path.join(os.path.dirname(__file__),"..")
+sys.path.insert(0,parentdir)
+
+from hpclib import HpcLibrary
+
+logger = Logger(level=logging.INFO)
+
+class SyncServiceProvider(SyncStep, HpcLibrary):
+    provides=[ServiceProvider]
+    observes=ServiceProvider
+    requested_interval=0
+
+    def __init__(self, **args):
+        SyncStep.__init__(self, **args)
+        HpcLibrary.__init__(self)
+
+    def filter_hpc_service(self, objs):
+        hpcService = self.get_hpc_service()
+
+        return [x for x in objs if x.hpcService == hpcService]
+
+    def fetch_pending(self, deleted):
+        #self.consistency_check()
+
+        return self.filter_hpc_service(SyncStep.fetch_pending(self, deleted))
+
+    def consistency_check(self):
+        # set to true if something changed
+        result=False
+
+        # sanity check to make sure our PS objects have CMI objects behind them
+        all_sp_ids = [x["service_provider_id"] for x in self.client.onev.ListAll("ServiceProvider")]
+        for sp in ServiceProvider.objects.all():
+            if (sp.service_provider_id is not None) and (sp.service_provider_id not in all_sp_ids):
+                logger.info("Service provider %s was not found on CMI" % sp.service_provider_id)
+                sp.service_provider_id=None
+                sp.save()
+                result = True
+
+        return result
+
+    def sync_record(self, sp):
+        logger.info("sync'ing service provider %s" % str(sp),extra=sp.tologdict())
+        account_name = self.make_account_name(sp.name)
+        sp_dict = {"account": account_name, "name": sp.name, "enabled": sp.enabled}
+        if not sp.service_provider_id:
+            id = self.client.onev.Create("ServiceProvider", sp_dict)
+            sp.service_provider_id = id
+        else:
+            self.client.onev.Update("ServiceProvider", sp.service_provider_id, sp_dict)
+
+        sp.save()
+
+    def delete_record(self, m):
+        if m.service_provider_id is not None:
+            self.client.onev.Delete("ServiceProvider", m.service_provider_id)
diff --git a/xos/synchronizer/steps/sync_sitemap.py b/xos/synchronizer/steps/sync_sitemap.py
new file mode 100644
index 0000000..a1d177b
--- /dev/null
+++ b/xos/synchronizer/steps/sync_sitemap.py
@@ -0,0 +1,118 @@
+import os
+import sys
+import base64
+from django.db.models import F, Q
+from xos.config import Config
+from synchronizers.base.syncstep import SyncStep
+from core.models import Service
+from services.hpc.models import ServiceProvider, ContentProvider, CDNPrefix, SiteMap
+from xos.logger import Logger, logging
+
+# hpclibrary will be in steps/..
+parentdir = os.path.join(os.path.dirname(__file__),"..")
+sys.path.insert(0,parentdir)
+
+from hpclib import HpcLibrary
+
+logger = Logger(level=logging.INFO)
+
+class SyncSiteMap(SyncStep, HpcLibrary):
+    provides=[SiteMap]
+    observes=SiteMap
+    requested_interval=0
+
+    def __init__(self, **args):
+        SyncStep.__init__(self, **args)
+        HpcLibrary.__init__(self)
+
+    def filter_hpc_service(self, objs):
+        hpcService = self.get_hpc_service()
+
+        filtered_objs = []
+        for x in objs:
+            if ((x.hpcService == hpcService) or
+               ((x.serviceProvider != None) and (x.serviceProvider.hpcService == hpcService)) or
+               ((x.contentProvider != None) and (x.contentProvider.serviceProvider.hpcService == hpcService)) or
+               ((x.cdnPrefix!= None) and (x.cdnPrefix.contentProvider.serviceProvider.hpcService == hpcService))):
+                filtered_objs.append(x)
+
+        return filtered_objs
+
+    def fetch_pending(self, deleted):
+        return self.filter_hpc_service(SyncStep.fetch_pending(self, deleted))
+
+    def consistency_check(self):
+        # set to true if something changed
+        result=False
+
+        # sanity check to make sure our PS objects have CMI objects behind them
+        all_map_ids = [x["map_id"] for x in self.client.onev.ListAll("Map")]
+        for map in SiteMap.objects.all():
+            if (map.map_id is not None) and (map.map_id not in all_map_ids):
+                logger.info("Map %s was not found on CMI" % map.map_id,extra=map.tologdict())
+                map.map_id=None
+                map.save()
+                result = True
+
+        return result
+
+    def update_bind(self, map, map_dict, field_name, to_name, ids):
+        for id in ids:
+            if (not id in map_dict.get(field_name, [])):
+                print "Bind Map", map.map_id, "to", to_name, id
+                self.client.onev.Bind("Map", map.map_id, to_name, id)
+
+        for id in map_dict.get(field_name, []):
+            if (not id in ids):
+                print "Unbind Map", map.map_id, "from", to_name, id
+                self.client.onev.UnBind("map", map.map_id, to_name, id)
+
+    def sync_record(self, map):
+        logger.info("sync'ing SiteMap %s" % str(map),extra=map.tologdict())
+
+        if not map.map:
+            # no contents
+            return
+
+        content = map.map.read()
+
+        map_dict = {"name": map.name, "type": "site", "content": content}
+
+        cdn_prefix_ids=[]
+        service_provider_ids=[]
+        content_provider_ids=[]
+
+        if (map.contentProvider):
+            if not map.contentProvider.content_provider_id:
+                raise Exception("Map %s links to a contentProvider with no id" % map.name)
+            conent_provider_ids = [map.contentProvider.content_provider_id]
+
+        if (map.serviceProvider):
+            if not map.serviceProvider.service_provider_id:
+                raise Exception("Map %s links to a serviceProvider with no id" % map.name)
+            service_provider_ids = [map.serviceProvider.service_provider_id]
+
+        if (map.cdnPrefix):
+            if not map.cdnPrefix.cdn_prefix_id:
+                raise Exception("Map %s links to a cdnPrefix with no id" % map.name)
+            cdn_prefix_ids = [map.cdnPrefix.cdn_prefix_id]
+
+        if not map.map_id:
+            print "Create Map", map_dict
+            id = self.client.onev.Create("Map", map_dict)
+            map.map_id = id
+        else:
+            print "Update Map", map_dict
+            # these things we probably cannot update
+            del map_dict["name"]
+            self.client.onev.Update("Map", map.map_id, map_dict)
+
+        cmi_map_dict = self.client.onev.Read("Map", map.map_id)
+
+        self.update_bind(map, cmi_map_dict, "cdn_prefix_ids", "CDNPrefix", cdn_prefix_ids)
+
+        map.save()
+
+    def delete_record(self, m):
+        if m.map_id is not None:
+            self.client.onev.Delete("Map", m.map_id)
diff --git a/xos/synchronizer/stop.sh b/xos/synchronizer/stop.sh
new file mode 100755
index 0000000..780e25c
--- /dev/null
+++ b/xos/synchronizer/stop.sh
@@ -0,0 +1 @@
+pkill -9 -f hpc-synchronizer.py
diff --git a/xos/synchronizer/supervisor/hpc-observer.conf b/xos/synchronizer/supervisor/hpc-observer.conf
new file mode 100644
index 0000000..f2c79d4
--- /dev/null
+++ b/xos/synchronizer/supervisor/hpc-observer.conf
@@ -0,0 +1,2 @@
+[program:hpc-observer]
+command=python /opt/xos/observers/hpc/hpc-observer.py -C /opt/xos/observers/hpc/hpc_observer_config
diff --git a/xos/synchronizer/supervisor/hpc-watcher.conf b/xos/synchronizer/supervisor/hpc-watcher.conf
new file mode 100644
index 0000000..e0f4eb1
--- /dev/null
+++ b/xos/synchronizer/supervisor/hpc-watcher.conf
@@ -0,0 +1,2 @@
+[program:hpc-watcher]
+command=python /opt/xos/observers/hpc/hpc_watcher.py
diff --git a/xos/templates/hpcadmin.html b/xos/templates/hpcadmin.html
new file mode 100644
index 0000000..5c05020
--- /dev/null
+++ b/xos/templates/hpcadmin.html
@@ -0,0 +1,18 @@
+<div class = "left-nav">
+<ul>
+{% for admin in registered_admins %}
+    <li><a href="{{ admin.url }}">{{ admin.name }}</a></li>
+{% endfor %}
+</ul>
+</div>
+
+<!--
+<ul>
+<li><a href="/admin/hpc/originserver/">Origin Servers</a></li>
+<li><a href="/admin/hpc/contentprovider/">Content Providers</a></li>
+<li><a href="/admin/hpc/serviceprovider/">Server Providers</a></li>
+<li><a href="/admin/hpc/cdnprefix/">CDN Prefixes</a></li>
+<li><a href="/admin/hpc/sitemap/">Site Maps</a></li>
+<li><a href="/admin/hpc/accessmap/">Access Maps</a></li>
+</ul>
+-->
diff --git a/xos/templates/hpctools.html b/xos/templates/hpctools.html
new file mode 100644
index 0000000..28ab5c0
--- /dev/null
+++ b/xos/templates/hpctools.html
@@ -0,0 +1,9 @@
+<div class = "left-nav">
+
+<ul>
+<li><a href="/dashboard/xosHpc">Service Monitor</a></li>
+<li><a href="/dashboard/xosHpcUrls">HPC Node Drilldown</a></li>
+<li><a href="/dashboard/xosHpcNodes">HPC Url Drilldown</a></li>
+</ul>
+
+</div>