184 lines
7.2 KiB
Python
184 lines
7.2 KiB
Python
# Copyright (c) 2016 Mirantis, Inc.
|
|
# All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
from kuryr.lib._i18n import _
|
|
from oslo_log import log as logging
|
|
from oslo_serialization import jsonutils
|
|
import six
|
|
|
|
from kuryr_kubernetes import clients
|
|
from kuryr_kubernetes import constants as k_const
|
|
from kuryr_kubernetes.controller.drivers import base as drv_base
|
|
from kuryr_kubernetes import exceptions as k_exc
|
|
from kuryr_kubernetes.handlers import k8s_base
|
|
from kuryr_kubernetes.objects import lbaas as obj_lbaas
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class LBaaSSpecHandler(k8s_base.ResourceEventHandler):
|
|
"""LBaaSSpecHandler handles K8s Service events.
|
|
|
|
LBaaSSpecHandler handles K8s Service events and updates related Endpoints
|
|
with LBaaSServiceSpec when necessary.
|
|
"""
|
|
|
|
OBJECT_KIND = k_const.K8S_OBJ_SERVICE
|
|
|
|
def __init__(self):
|
|
self._drv_project = drv_base.ServiceProjectDriver.get_instance()
|
|
self._drv_subnets = drv_base.ServiceSubnetsDriver.get_instance()
|
|
self._drv_sg = drv_base.ServiceSecurityGroupsDriver.get_instance()
|
|
|
|
def on_present(self, service):
|
|
lbaas_spec = self._get_lbaas_spec(service)
|
|
|
|
if self._has_lbaas_spec_changes(service, lbaas_spec):
|
|
lbaas_spec = self._generate_lbaas_spec(service)
|
|
self._set_lbaas_spec(service, lbaas_spec)
|
|
|
|
def _get_service_ip(self, service):
|
|
spec = service['spec']
|
|
if spec.get('type') == 'ClusterIP':
|
|
return spec.get('clusterIP')
|
|
return None
|
|
|
|
def _get_subnet_id(self, service, project_id, ip):
|
|
subnets_mapping = self._drv_subnets.get_subnets(service, project_id)
|
|
subnet_ids = {
|
|
subnet_id
|
|
for subnet_id, network in six.iteritems(subnets_mapping)
|
|
for subnet in network.subnets.objects
|
|
if ip in subnet.cidr}
|
|
|
|
if len(subnet_ids) != 1:
|
|
raise k_exc.IntegrityError(_(
|
|
"Found %(num)s subnets for service %(link)s IP %(ip)s") % {
|
|
'link': service['metadata']['selfLink'],
|
|
'ip': ip,
|
|
'num': len(subnet_ids)})
|
|
|
|
return subnet_ids.pop()
|
|
|
|
def _generate_lbaas_spec(self, service):
|
|
project_id = self._drv_project.get_project(service)
|
|
ip = self._get_service_ip(service)
|
|
subnet_id = self._get_subnet_id(service, project_id, ip)
|
|
ports = self._generate_lbaas_port_specs(service)
|
|
sg_ids = self._drv_sg.get_security_groups(service, project_id)
|
|
|
|
return obj_lbaas.LBaaSServiceSpec(ip=ip,
|
|
project_id=project_id,
|
|
subnet_id=subnet_id,
|
|
ports=ports,
|
|
security_groups_ids=sg_ids)
|
|
|
|
def _has_lbaas_spec_changes(self, service, lbaas_spec):
|
|
return (self._has_ip_changes(service, lbaas_spec) or
|
|
self._has_port_changes(service, lbaas_spec))
|
|
|
|
def _get_service_ports(self, service):
|
|
return [{'name': port.get('name'),
|
|
'protocol': port.get('protocol', 'TCP'),
|
|
'port': port['port']}
|
|
for port in service['spec']['ports']]
|
|
|
|
def _has_port_changes(self, service, lbaas_spec):
|
|
link = service['metadata']['selfLink']
|
|
|
|
fields = obj_lbaas.LBaaSPortSpec.fields
|
|
svc_port_set = {tuple(port[attr] for attr in fields)
|
|
for port in self._get_service_ports(service)}
|
|
spec_port_set = {tuple(getattr(port, attr) for attr in fields)
|
|
for port in lbaas_spec.ports}
|
|
|
|
if svc_port_set != spec_port_set:
|
|
LOG.debug("LBaaS spec ports %(spec_ports)s != %(svc_ports)s "
|
|
"for %(link)s" % {'spec_ports': spec_port_set,
|
|
'svc_ports': svc_port_set,
|
|
'link': link})
|
|
return svc_port_set != spec_port_set
|
|
|
|
def _has_ip_changes(self, service, lbaas_spec):
|
|
link = service['metadata']['selfLink']
|
|
svc_ip = self._get_service_ip(service)
|
|
|
|
if not lbaas_spec:
|
|
if svc_ip:
|
|
LOG.debug("LBaaS spec is missing for %(link)s"
|
|
% {'link': link})
|
|
return True
|
|
elif str(lbaas_spec.ip) != svc_ip:
|
|
LOG.debug("LBaaS spec IP %(spec_ip)s != %(svc_ip)s for %(link)s"
|
|
% {'spec_ip': lbaas_spec.ip,
|
|
'svc_ip': svc_ip,
|
|
'link': link})
|
|
return True
|
|
|
|
return False
|
|
|
|
def _generate_lbaas_port_specs(self, service):
|
|
return [obj_lbaas.LBaaSPortSpec(**port)
|
|
for port in self._get_service_ports(service)]
|
|
|
|
def _get_endpoints_link(self, service):
|
|
svc_link = service['metadata']['selfLink']
|
|
link_parts = svc_link.split('/')
|
|
|
|
if link_parts[-2] != 'services':
|
|
raise k_exc.IntegrityError(_(
|
|
"Unsupported service link: %(link)s") % {
|
|
'link': svc_link})
|
|
link_parts[-2] = 'endpoints'
|
|
|
|
return "/".join(link_parts)
|
|
|
|
def _set_lbaas_spec(self, service, lbaas_spec):
|
|
# TODO(ivc): extract annotation interactions
|
|
if lbaas_spec is None:
|
|
LOG.debug("Removing LBaaSServiceSpec annotation: %r", lbaas_spec)
|
|
annotation = None
|
|
else:
|
|
lbaas_spec.obj_reset_changes(recursive=True)
|
|
LOG.debug("Setting LBaaSServiceSpec annotation: %r", lbaas_spec)
|
|
annotation = jsonutils.dumps(lbaas_spec.obj_to_primitive(),
|
|
sort_keys=True)
|
|
svc_link = service['metadata']['selfLink']
|
|
ep_link = self._get_endpoints_link(service)
|
|
k8s = clients.get_kubernetes_client()
|
|
|
|
try:
|
|
k8s.annotate(ep_link,
|
|
{k_const.K8S_ANNOTATION_LBAAS_SPEC: annotation})
|
|
except k_exc.K8sClientException:
|
|
# REVISIT(ivc): only raise ResourceNotReady for NotFound
|
|
raise k_exc.ResourceNotReady(ep_link)
|
|
|
|
k8s.annotate(svc_link,
|
|
{k_const.K8S_ANNOTATION_LBAAS_SPEC: annotation},
|
|
resource_version=service['metadata']['resourceVersion'])
|
|
|
|
def _get_lbaas_spec(self, service):
|
|
# TODO(ivc): same as '_set_lbaas_spec'
|
|
try:
|
|
annotations = service['metadata']['annotations']
|
|
annotation = annotations[k_const.K8S_ANNOTATION_LBAAS_SPEC]
|
|
except KeyError:
|
|
return None
|
|
obj_dict = jsonutils.loads(annotation)
|
|
obj = obj_lbaas.LBaaSServiceSpec.obj_from_primitive(obj_dict)
|
|
LOG.debug("Got LBaaSServiceSpec from annotation: %r", obj)
|
|
return obj
|