blob: 500f0d90ce3d89d05c19808cbbcbea3deb176621 [file] [log] [blame]
Scott Baker3fd18e52018-04-17 09:18:21 -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_service.py
18
19 Synchronize Services. The only type of Service this step knows how to deal with are services that use Kubernetes
20 NodePort to expose ports.
21"""
22
23from synchronizers.new_base.syncstep import SyncStep
24from synchronizers.new_base.modelaccessor import Service
25
26from xosconfig import Config
27from multistructlog import create_logger
28
Scott Baker3fd18e52018-04-17 09:18:21 -070029log = create_logger(Config().get('logging'))
30
31class SyncService(SyncStep):
32
33 """
34 SyncService
35
36 Implements sync step for syncing Services.
37 """
38
39 provides = [Service]
40 observes = Service
41 requested_interval = 0
42
43 def __init__(self, *args, **kwargs):
44 super(SyncService, self).__init__(*args, **kwargs)
Scott Baker13e953c2018-05-17 09:19:15 -070045 self.init_kubernetes_client()
46
47 def init_kubernetes_client(self):
48 from kubernetes.client.rest import ApiException
49 from kubernetes import client as kubernetes_client, config as kubernetes_config
Scott Baker3fd18e52018-04-17 09:18:21 -070050 kubernetes_config.load_incluster_config()
Scott Baker13e953c2018-05-17 09:19:15 -070051 self.kubernetes_client = kubernetes_client
52 self.v1core = kubernetes_client.CoreV1Api()
53 self.ApiException = ApiException
Scott Baker3fd18e52018-04-17 09:18:21 -070054
55 def fetch_pending(self, deletion=False):
56 """ Filter the set of pending objects.
57 As this syncstep can only create Service that exist within Trust Domains, filter out those services that
58 don't have Trust Domains associated with them.
59 """
60 models = super(SyncService, self).fetch_pending(deletion)
61
62 if (not deletion):
63 for model in models[:]:
64 if not self.get_trust_domain(model):
65 log.info("Unable to determine Trust Domain for service %s. Ignoring." % model.name)
66 models.remove(model)
67 elif not model.serviceports.exists():
68 # If there are not ServicePorts, then there's not much for us to do at this time...
69 log.info("Service %s is not interesting. Ignoring." % model.name)
70 models.remove(model)
71
72 return models
73
74 def get_trust_domain(self, o):
75 """ Given a service, determine its Trust Domain.
76
77 The design we've chosen to go with is that a service is pinned to a Trust Domain based on the slices
78 that it contains. It's an error for a service to be directly comprised of slices from multiple
79 trust domains.
80
81 This allows for "logical services", that contain no slices of their own, but are comprised of multiple
82 subservices. For example, EPC.
83 """
84
85 trust_domain = None
86 for slice in o.slices.all():
87 if slice.trust_domain:
88 if (trust_domain is None):
89 trust_domain = slice.trust_domain
90 elif (trust_domain.id != slice.trust_domain.id):
91 # Bail out of we've encountered a situation where a service spans multiple trust domains.
92 log.warning("Service %s is comprised of slices from multiple trust domains." % o.name)
93 return None
94
95 return trust_domain
96
Scott Baker393d0152018-05-21 09:17:49 -070097 def get_service(self, o, trust_domain_name):
Scott Baker3fd18e52018-04-17 09:18:21 -070098 """ Given an XOS Service, read the associated Service from Kubernetes.
99 If no Kubernetes service exists, return None
100 """
101 try:
Scott Baker393d0152018-05-21 09:17:49 -0700102 k8s_service = self.v1core.read_namespaced_service(o.name, trust_domain_name)
Scott Baker13e953c2018-05-17 09:19:15 -0700103 except self.ApiException, e:
Scott Baker3fd18e52018-04-17 09:18:21 -0700104 if e.status == 404:
105 return None
106 raise
107 return k8s_service
108
109 def sync_record(self, o):
110 trust_domain = self.get_trust_domain(o)
Scott Baker393d0152018-05-21 09:17:49 -0700111 k8s_service = self.get_service(o,trust_domain.name)
Scott Baker3fd18e52018-04-17 09:18:21 -0700112
113 if not k8s_service:
Scott Baker13e953c2018-05-17 09:19:15 -0700114 k8s_service = self.kubernetes_client.V1Service()
115 k8s_service.metadata = self.kubernetes_client.V1ObjectMeta(name=o.name)
Scott Baker3fd18e52018-04-17 09:18:21 -0700116
117 ports=[]
118 for service_port in o.serviceports.all():
Scott Baker13e953c2018-05-17 09:19:15 -0700119 port=self.kubernetes_client.V1ServicePort(name = service_port.name,
Scott Baker3fd18e52018-04-17 09:18:21 -0700120 node_port = service_port.external_port,
121 port = service_port.internal_port,
122 target_port = service_port.internal_port,
123 protocol = service_port.protocol)
124 ports.append(port)
125
Scott Baker13e953c2018-05-17 09:19:15 -0700126 k8s_service.spec = self.kubernetes_client.V1ServiceSpec(ports=ports,
Scott Baker3fd18e52018-04-17 09:18:21 -0700127 type="NodePort")
128
Scott Baker13e953c2018-05-17 09:19:15 -0700129 k8s_service = self.v1core.create_namespaced_service(trust_domain.name, k8s_service)
130
131 if (not o.backend_handle):
132 o.backend_handle = k8s_service.metadata.self_link
133 o.save(update_fields=["backend_handle"])
Scott Baker3fd18e52018-04-17 09:18:21 -0700134
135 def delete_record(self, o):
Scott Baker393d0152018-05-21 09:17:49 -0700136 trust_domain_name = None
137 trust_domain = self.get_trust_domain(o)
138 if trust_domain:
139 trust_domain_name = trust_domain.name
140 else:
141 # rely on backend_handle being structured like this one,
142 # /api/v1/namespaces/service1-trust/services/service1
143 if (o.backend_handle):
144 parts = o.backend_handle.split("/")
145 if len(parts)>3:
146 trust_domain_name = parts[-3]
Scott Baker3fd18e52018-04-17 09:18:21 -0700147
Scott Baker393d0152018-05-21 09:17:49 -0700148 if not trust_domain_name:
149 raise Exception("Can't delete service %s because there is no trust domain" % o.name)
150
151 k8s_service = self.get_service(o, trust_domain_name)
152 if not k8s_service:
153 log.info("Kubernetes service does not exist; Nothing to delete.", o=o)
154 return
155 delete_options = self.kubernetes_client.V1DeleteOptions()
156 self.v1core.delete_namespaced_service(o.name, trust_domain_name, delete_options)
157 log.info("Deleted service from kubernetes", handle=o.backend_handle)