CORD-2965 Kubernetes Synchronizer

Change-Id: Ie5c02b9ad1c65af686598bab0b36350ac1caef64
diff --git a/xos/synchronizer/steps/sync_kubernetesserviceinstance.py b/xos/synchronizer/steps/sync_kubernetesserviceinstance.py
index 400396d..27badfa 100644
--- a/xos/synchronizer/steps/sync_kubernetesserviceinstance.py
+++ b/xos/synchronizer/steps/sync_kubernetesserviceinstance.py
@@ -17,6 +17,10 @@
     sync_kubernetesserviceinstance.py
 
     Synchronize KubernetesServiceInstance. See also the related pull_step.
+
+    This sync_step is intended to handle the case where callers are creating pods directly, as opposed to using
+    a controller to manage pods for them. It makes some simplifying assumptions, such as each pod has one
+    container and uses one image.
 """
 
 from synchronizers.new_base.syncstep import SyncStep
@@ -25,6 +29,7 @@
 from xosconfig import Config
 from multistructlog import create_logger
 
+from kubernetes.client.rest import ApiException
 from kubernetes import client as kubernetes_client, config as kubernetes_config
 
 log = create_logger(Config().get('logging'))
@@ -43,16 +48,62 @@
 
     def __init__(self, *args, **kwargs):
         super(SyncKubernetesServiceInstance, self).__init__(*args, **kwargs)
-
         kubernetes_config.load_incluster_config()
         self.v1 = kubernetes_client.CoreV1Api()
 
-    def sync_record(self, o):
-        # TODO(smbaker): implement sync step here
-        pass
+    def get_pod(self, o):
+        """ Given a KubernetesServiceInstance, read the pod from Kubernetes.
+            Return None if the pod does not exist.
+        """
+        try:
+            pod = self.v1.read_namespaced_pod(o.name, o.slice.trust_domain.name)
+        except ApiException, e:
+            if e.status == 404:
+                return None
+            raise
+        return pod
 
+    def sync_record(self, o):
+        if o.xos_managed:
+            if (not o.slice) or (not o.slice.trust_domain):
+                raise Exception("No trust domain for service instance", o=o)
+
+            if (not o.name):
+                raise Exception("No name for service instance", o=o)
+
+            pod = self.get_pod(o)
+            if not pod:
+                # make a pod!
+                pod = kubernetes_client.V1Pod()
+                pod.metadata = kubernetes_client.V1ObjectMeta(name=o.name)
+
+                if o.slice.trust_domain:
+                    pod.metadata.namespace = o.slice.trust_domain.name
+
+                if o.image.tag:
+                    imageName = o.image.name + ":" + o.image.tag
+                else:
+                    # TODO(smbaker): Is this case possible?
+                    imageName = o.image.name
+
+                container=kubernetes_client.V1Container(name=o.name,
+                                                        image=imageName)
+
+                spec = kubernetes_client.V1PodSpec(containers=[container])
+                pod.spec = spec
+
+                if o.slice.principal:
+                    pod.spec.service_account = o.slice.principal.name
+
+                log.info("Creating pod", o=o, pod=pod)
+
+                pod = self.v1.create_namespaced_pod(o.slice.trust_domain.name, pod)
+
+            if (not o.backend_handle):
+                o.backend_handle = pod.metadata.self_link
+                o.save(update_fields=["backend_handle"])
 
     def delete_record(self, port):
-        # TODO(smbaker): implement delete sync step here
+        # TODO(smbaker): Implement delete step
         pass