resolve merge conflicts
diff --git a/xos/openstack_observer/steps/sync_ports.py b/xos/openstack_observer/steps/sync_ports.py
new file mode 100644
index 0000000..da08e36
--- /dev/null
+++ b/xos/openstack_observer/steps/sync_ports.py
@@ -0,0 +1,227 @@
+import os
+import base64
+from django.db.models import F, Q
+from xos.config import Config
+from observer.openstacksyncstep import OpenStackSyncStep
+from core.models import Controller
+from core.models.network import *
+from util.logger import observer_logger as logger
+
+class SyncPorts(OpenStackSyncStep):
+    requested_interval = 0 # 3600
+    provides=[Port]
+    observes=Port
+
+    #     The way it works is to enumerate the all of the ports that quantum
+    #     has, and then work backward from each port's network-id to determine
+    #     which Network is associated from the port.
+
+    def call(self, **args):
+        logger.info("sync'ing network instances")
+
+        ports = Port.objects.all()
+        ports_by_id = {}
+        ports_by_neutron_port = {}
+        for port in ports:
+            ports_by_id[port.id] = port
+            ports_by_neutron_port[port.port_id] = port
+
+        networks = Network.objects.all()
+        networks_by_id = {}
+        for network in networks:
+            for nd in network.controllernetworks.all():
+                networks_by_id[nd.net_id] = network
+
+        #logger.info("networks_by_id = ")
+        #for (network_id, network) in networks_by_id.items():
+        #    logger.info("   %s: %s" % (network_id, network.name))
+
+        instances = Instance.objects.all()
+        instances_by_instance_uuid = {}
+        for instance in instances:
+            instances_by_instance_uuid[instance.instance_uuid] = instance
+
+        # Get all ports in all controllers
+
+        ports_by_id = {}
+        templates_by_id = {}
+        for controller in Controller.objects.all():
+            if not controller.admin_tenant:
+                logger.info("controller %s has no admin_tenant" % controller)
+                continue
+            try:
+                driver = self.driver.admin_driver(controller = controller,tenant='admin')
+                ports = driver.shell.quantum.list_ports()["ports"]
+            except:
+                logger.log_exc("failed to get ports from controller %s" % controller)
+                continue
+
+            for port in ports:
+                ports_by_id[port["id"]] = port
+
+            # public-nat and public-dedicated networks don't have a net-id anywhere
+            # in the data model, so build up a list of which ids map to which network
+            # templates.
+            try:
+                neutron_networks = driver.shell.quantum.list_networks()["networks"]
+            except:
+                print "failed to get networks from controller %s" % controller
+                continue
+            for network in neutron_networks:
+                for template in NetworkTemplate.objects.all():
+                    if template.shared_network_name == network["name"]:
+                        templates_by_id[network["id"]] = template
+
+        for port in ports_by_id.values():
+            #logger.info("port %s" % str(port))
+            if port["id"] in ports_by_neutron_port:
+                # we already have it
+                #logger.info("already accounted for port %s" % port["id"])
+                continue
+
+            if port["device_owner"] != "compute:nova":
+                # we only want the ports that connect to instances
+                #logger.info("port %s is not a compute port, it is a %s" % (port["id"], port["device_owner"]))
+                continue
+
+            instance = instances_by_instance_uuid.get(port['device_id'], None)
+            if not instance:
+                logger.info("no instance for port %s device_id %s" % (port["id"], port['device_id']))
+                continue
+
+            network = networks_by_id.get(port['network_id'], None)
+            if not network:
+                # maybe it's public-nat or public-dedicated. Search the templates for
+                # the id, then see if the instance's slice has some network that uses
+                # that template
+                template = templates_by_id.get(port['network_id'], None)
+                if template and instance.slice:
+                    for candidate_network in instance.slice.networks.all():
+                         if candidate_network.template == template:
+                             network=candidate_network
+            if not network:
+                logger.info("no network for port %s network %s" % (port["id"], port["network_id"]))
+
+                # we know it's associated with a instance, but we don't know
+                # which network it is part of.
+
+                continue
+
+            if network.template.shared_network_name:
+                # If it's a shared network template, then more than one network
+                # object maps to the quantum network. We have to do a whole bunch
+                # of extra work to find the right one.
+                networks = network.template.network_set.all()
+                network = None
+                for candidate_network in networks:
+                    if (candidate_network.owner == instance.slice):
+                        print "found network", candidate_network
+                        network = candidate_network
+
+                if not network:
+                    logger.info("failed to find the correct network for a shared template for port %s network %s" % (port["id"], port["network_id"]))
+                    continue
+
+            if not port["fixed_ips"]:
+                logger.info("port %s has no fixed_ips" % port["id"])
+                continue
+
+            ip=port["fixed_ips"][0]["ip_address"]
+            logger.info("creating Port (%s, %s, %s, %s)" % (str(network), str(instance), ip, str(port["id"])))
+
+            ns = Port(network=network,
+                               instance=instance,
+                               ip=ip,
+                               port_id=port["id"])
+
+            try:
+                ns.save()
+            except:
+                logger.log_exc("failed to save port %s" % str(ns))
+                continue
+
+        # For ports that were created by the user, find that ones
+        # that don't have neutron ports, and create them.
+        for port in Port.objects.filter(port_id__isnull=True, instance__isnull=False):
+            #logger.info("XXX working on port %s" % port)
+            controller = port.instance.node.site_deployment.controller
+            if controller:
+                cn=port.network.controllernetworks.filter(controller=controller)
+                if not cn:
+                    logger.log_exc("no controllernetwork for %s" % port)
+                    continue
+                cn=cn[0]
+                if cn.lazy_blocked:
+                    cn.lazy_blocked=False
+                    cn.save()
+                    logger.info("deferring port %s because controllerNetwork was lazy-blocked" % port)
+                    continue
+                if not cn.net_id:
+                    logger.info("deferring port %s because controllerNetwork does not have a port-id yet" % port)
+                    continue
+                try:
+                    # We need to use a client driver that specifies the tenant
+                    # of the destination instance. Nova-compute will not connect
+                    # ports to instances if the port's tenant does not match
+                    # the instance's tenant.
+
+                    # A bunch of stuff to compensate for OpenStackDriver.client_driveR()
+                    # not being in working condition.
+                    from openstack.client import OpenStackClient
+                    from openstack.driver import OpenStackDriver
+                    caller = port.network.owner.creator
+                    auth = {'username': caller.email,
+                            'password': caller.remote_password,
+                            'tenant': port.instance.slice.name} # port.network.owner.name}
+                    client = OpenStackClient(controller=controller, **auth) # cacert=self.config.nova_ca_ssl_cert,
+                    driver = OpenStackDriver(client=client)
+
+                    port = driver.shell.quantum.create_port({"port": {"network_id": cn.net_id}})["port"]
+                    port.port_id = port["id"]
+                    if port["fixed_ips"]:
+                        port.ip = port["fixed_ips"][0]["ip_address"]
+                except:
+                    logger.log_exc("failed to create neutron port for %s" % port)
+                    continue
+                port.save()
+
+        # Now, handle port forwarding
+        # We get the list of Ports again, since we might have just
+        # added a few. Then, for each one of them we find it's quantum port and
+        # make sure quantum's nat:forward_ports argument is the same.
+
+        for port in Port.objects.all():
+            try:
+                nat_list = port.network.nat_list
+            except (TypeError, ValueError), e:
+                logger.info("Failed to decode nat_list: %s" % str(e))
+                continue
+
+            if not port.port_id:
+                continue
+
+            neutron_port = ports_by_id.get(port.port_id, None)
+            if not neutron_port:
+                continue
+
+            neutron_nat_list = neutron_port.get("nat:forward_ports", None)
+            if not neutron_nat_list:
+                # make sure that None and the empty set are treated identically
+                neutron_nat_list = []
+
+            if (neutron_nat_list != nat_list):
+                logger.info("Setting nat:forward_ports for port %s network %s instance %s to %s" % (str(port.port_id), str(port.network.id), str(port.instance), str(nat_list)))
+                try:
+                    driver = self.driver.admin_driver(controller=port.instance.node.site_deployment.controller,tenant='admin')
+                    driver.shell.quantum.update_port(port.port_id, {"port": {"nat:forward_ports": nat_list}})
+                except:
+                    logger.log_exc("failed to update port with nat_list %s" % str(nat_list))
+                    continue
+            else:
+                #logger.info("port %s network %s instance %s nat %s is already set" % (str(port.port_id), str(port.network.id), str(port.instance), str(nat_list)))
+                pass
+
+    def delete_record(self, network_instance):
+        # Nothing to do, this is an OpenCloud object
+        pass
+