blob: 333f6756135d4c0e45da8ce167b5f549edd518c4 [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
29from kubernetes.client.rest import ApiException
30from kubernetes import client as kubernetes_client, config as kubernetes_config
31
32log = create_logger(Config().get('logging'))
33
34class SyncService(SyncStep):
35
36 """
37 SyncService
38
39 Implements sync step for syncing Services.
40 """
41
42 provides = [Service]
43 observes = Service
44 requested_interval = 0
45
46 def __init__(self, *args, **kwargs):
47 super(SyncService, self).__init__(*args, **kwargs)
48 kubernetes_config.load_incluster_config()
49 self.v1 = kubernetes_client.CoreV1Api()
50
51 def fetch_pending(self, deletion=False):
52 """ Filter the set of pending objects.
53 As this syncstep can only create Service that exist within Trust Domains, filter out those services that
54 don't have Trust Domains associated with them.
55 """
56 models = super(SyncService, self).fetch_pending(deletion)
57
58 if (not deletion):
59 for model in models[:]:
60 if not self.get_trust_domain(model):
61 log.info("Unable to determine Trust Domain for service %s. Ignoring." % model.name)
62 models.remove(model)
63 elif not model.serviceports.exists():
64 # If there are not ServicePorts, then there's not much for us to do at this time...
65 log.info("Service %s is not interesting. Ignoring." % model.name)
66 models.remove(model)
67
68 return models
69
70 def get_trust_domain(self, o):
71 """ Given a service, determine its Trust Domain.
72
73 The design we've chosen to go with is that a service is pinned to a Trust Domain based on the slices
74 that it contains. It's an error for a service to be directly comprised of slices from multiple
75 trust domains.
76
77 This allows for "logical services", that contain no slices of their own, but are comprised of multiple
78 subservices. For example, EPC.
79 """
80
81 trust_domain = None
82 for slice in o.slices.all():
83 if slice.trust_domain:
84 if (trust_domain is None):
85 trust_domain = slice.trust_domain
86 elif (trust_domain.id != slice.trust_domain.id):
87 # Bail out of we've encountered a situation where a service spans multiple trust domains.
88 log.warning("Service %s is comprised of slices from multiple trust domains." % o.name)
89 return None
90
91 return trust_domain
92
93 def get_service(self, o, trust_domain):
94 """ Given an XOS Service, read the associated Service from Kubernetes.
95 If no Kubernetes service exists, return None
96 """
97 try:
98 k8s_service = self.v1.read_namespaced_service(o.name, trust_domain.name)
99 except ApiException, e:
100 if e.status == 404:
101 return None
102 raise
103 return k8s_service
104
105 def sync_record(self, o):
106 trust_domain = self.get_trust_domain(o)
107 k8s_service = self.get_service(o,trust_domain)
108
109 if not k8s_service:
110 k8s_service = kubernetes_client.V1Service()
111 k8s_service.metadata = kubernetes_client.V1ObjectMeta(name=o.name)
112
113 ports=[]
114 for service_port in o.serviceports.all():
115 port=kubernetes_client.V1ServicePort(name = service_port.name,
116 node_port = service_port.external_port,
117 port = service_port.internal_port,
118 target_port = service_port.internal_port,
119 protocol = service_port.protocol)
120 ports.append(port)
121
122 k8s_service.spec = kubernetes_client.V1ServiceSpec(ports=ports,
123 type="NodePort")
124
125 self.v1.create_namespaced_service(trust_domain.name, k8s_service)
126
127 def delete_record(self, o):
128 # TODO(smbaker): Implement delete step
129 pass
130