blob: 21657b648fa84d3a3941aaba08794788b9f7f2b9 [file] [log] [blame]
Scott Baker82b2b082018-04-16 16:02:14 -07001
2# Copyright 2017-present Open Networking Foundation
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16"""
17 sync_kubernetesserviceinstance.py
18
19 Synchronize KubernetesServiceInstance. See also the related pull_step.
Scott Baker3fd18e52018-04-17 09:18:21 -070020
21 This sync_step is intended to handle the case where callers are creating pods directly, as opposed to using
22 a controller to manage pods for them. It makes some simplifying assumptions, such as each pod has one
23 container and uses one image.
Scott Baker82b2b082018-04-16 16:02:14 -070024"""
25
26from synchronizers.new_base.syncstep import SyncStep
27from synchronizers.new_base.modelaccessor import KubernetesServiceInstance
28
29from xosconfig import Config
30from multistructlog import create_logger
31
Scott Baker82b2b082018-04-16 16:02:14 -070032log = create_logger(Config().get('logging'))
33
34class SyncKubernetesServiceInstance(SyncStep):
35
36 """
37 SyncKubernetesServiceInstance
38
39 Implements sync step for syncing kubernetes service instances.
40 """
41
42 provides = [KubernetesServiceInstance]
43 observes = KubernetesServiceInstance
44 requested_interval = 0
45
46 def __init__(self, *args, **kwargs):
47 super(SyncKubernetesServiceInstance, self).__init__(*args, **kwargs)
Scott Baker13e953c2018-05-17 09:19:15 -070048 self.init_kubernetes_client()
49
50 def init_kubernetes_client(self):
51 from kubernetes.client.rest import ApiException
52 from kubernetes import client as kubernetes_client, config as kubernetes_config
Scott Baker82b2b082018-04-16 16:02:14 -070053 kubernetes_config.load_incluster_config()
Scott Baker13e953c2018-05-17 09:19:15 -070054 self.kubernetes_client = kubernetes_client
55 self.v1core = kubernetes_client.CoreV1Api()
56 self.ApiException = ApiException
Scott Baker82b2b082018-04-16 16:02:14 -070057
Scott Baker3fd18e52018-04-17 09:18:21 -070058 def get_pod(self, o):
59 """ Given a KubernetesServiceInstance, read the pod from Kubernetes.
60 Return None if the pod does not exist.
61 """
62 try:
Scott Baker13e953c2018-05-17 09:19:15 -070063 pod = self.v1core.read_namespaced_pod(o.name, o.slice.trust_domain.name)
64 except self.ApiException, e:
Scott Baker3fd18e52018-04-17 09:18:21 -070065 if e.status == 404:
66 return None
67 raise
68 return pod
Scott Baker82b2b082018-04-16 16:02:14 -070069
Scott Bakerac43a742018-05-07 16:54:03 -070070 def generate_pod_spec(self, o):
Scott Baker13e953c2018-05-17 09:19:15 -070071 pod = self.kubernetes_client.V1Pod()
72 pod.metadata = self.kubernetes_client.V1ObjectMeta(name=o.name)
Scott Bakerac43a742018-05-07 16:54:03 -070073
74 if o.slice.trust_domain:
75 pod.metadata.namespace = o.slice.trust_domain.name
76
77 if o.image.tag:
78 imageName = o.image.name + ":" + o.image.tag
79 else:
80 # TODO(smbaker): Is this case possible?
81 imageName = o.image.name
82
83 volumes = []
84 volume_mounts = []
85
86 # Attach and mount the configmaps
87 for xos_vol in o.kubernetes_config_volume_mounts.all():
Scott Baker13e953c2018-05-17 09:19:15 -070088 k8s_vol = self.kubernetes_client.V1Volume(name=xos_vol.config.name)
89 k8s_vol.config_map = self.kubernetes_client.V1ConfigMapVolumeSource(name=xos_vol.config.name)
Scott Bakerac43a742018-05-07 16:54:03 -070090 volumes.append(k8s_vol)
91
Scott Baker13e953c2018-05-17 09:19:15 -070092 k8s_vol_m = self.kubernetes_client.V1VolumeMount(name=xos_vol.config.name,
Scott Bakerac43a742018-05-07 16:54:03 -070093 mount_path=xos_vol.mount_path,
94 sub_path=xos_vol.sub_path)
95 volume_mounts.append(k8s_vol_m)
96
97 # Attach and mount the secrets
98 for xos_vol in o.kubernetes_secret_volume_mounts.all():
Scott Baker13e953c2018-05-17 09:19:15 -070099 k8s_vol = self.kubernetes_client.V1Volume(name=xos_vol.secret.name)
100 k8s_vol.secret = self.kubernetes_client.V1SecretVolumeSource(secret_name=xos_vol.secret.name)
Scott Bakerac43a742018-05-07 16:54:03 -0700101 volumes.append(k8s_vol)
102
Scott Baker13e953c2018-05-17 09:19:15 -0700103 k8s_vol_m = self.kubernetes_client.V1VolumeMount(name=xos_vol.secret.name,
Scott Bakerac43a742018-05-07 16:54:03 -0700104 mount_path=xos_vol.mount_path,
105 sub_path=xos_vol.sub_path)
106 volume_mounts.append(k8s_vol_m)
107
Scott Baker13e953c2018-05-17 09:19:15 -0700108 container = self.kubernetes_client.V1Container(name=o.name,
Scott Bakerac43a742018-05-07 16:54:03 -0700109 image=imageName,
110 volume_mounts=volume_mounts)
111
Scott Baker13e953c2018-05-17 09:19:15 -0700112 spec = self.kubernetes_client.V1PodSpec(containers=[container], volumes=volumes)
Scott Bakerac43a742018-05-07 16:54:03 -0700113 pod.spec = spec
114
115 if o.slice.principal:
116 pod.spec.service_account = o.slice.principal.name
117
118 return pod
119
Scott Baker3fd18e52018-04-17 09:18:21 -0700120 def sync_record(self, o):
121 if o.xos_managed:
122 if (not o.slice) or (not o.slice.trust_domain):
123 raise Exception("No trust domain for service instance", o=o)
124
125 if (not o.name):
Scott Bakerac43a742018-05-07 16:54:03 -0700126 raise Exception("No name for service instance")
Scott Baker3fd18e52018-04-17 09:18:21 -0700127
128 pod = self.get_pod(o)
129 if not pod:
Scott Bakerac43a742018-05-07 16:54:03 -0700130 pod = self.generate_pod_spec(o)
Scott Baker3fd18e52018-04-17 09:18:21 -0700131
132 log.info("Creating pod", o=o, pod=pod)
133
Scott Baker13e953c2018-05-17 09:19:15 -0700134 pod = self.v1core.create_namespaced_pod(o.slice.trust_domain.name, pod)
Scott Bakerac43a742018-05-07 16:54:03 -0700135 else:
136 log.info("Replacing pod", o=o, pod=pod)
137
138 # TODO: apply changes, perhaps by calling self.generate_pod_spec() and copying in the differences,
139 # to accomodate new volumes that might have been attached, or other changes.
140
141 # If we don't apply any changes to the pod, it's still the case that Kubernetes will pull in new
142 # mounts of existing configmaps during the replace operation, if the configmap contents have changed.
143
Scott Baker13e953c2018-05-17 09:19:15 -0700144 pod = self.v1core.replace_namespaced_pod(o.name, o.slice.trust_domain.name, pod)
Scott Baker3fd18e52018-04-17 09:18:21 -0700145
146 if (not o.backend_handle):
147 o.backend_handle = pod.metadata.self_link
148 o.save(update_fields=["backend_handle"])
Scott Baker82b2b082018-04-16 16:02:14 -0700149
Scott Baker393d0152018-05-21 09:17:49 -0700150 def delete_record(self, o):
151 secret = self.get_pod(o)
152 if not secret:
153 log.info("Kubernetes pod does not exist; Nothing to delete.", o=o)
154 return
155 delete_options = self.kubernetes_client.V1DeleteOptions()
156 self.v1core.delete_namespaced_pod(o.name, o.slice.trust_domain.name, delete_options)
157 log.info("Deleted pod from kubernetes", handle=o.backend_handle)
158
Scott Baker82b2b082018-04-16 16:02:14 -0700159