Add support for service type=LoadBalancer
Service loadbalancerIP could be one of the following : 1. loadbalancerIP allocated from pre-defined pool k8s service.spec.type = 'LoadBalancer' 2. loadbalancerIP specified by user k8s service.spec.type = 'LoadBalancer' and service.spec.loadBalancerIP='x.y.z.t' This commit extend service capability to support '1' and '2' Implements: blueprint k8s-service-type-loadbalancer Change-Id: I98f56692e143aa7ab14dd9920139819c7026acce
This commit is contained in:
parent
f8a2021f5d
commit
5b3b02bb0b
|
@ -222,6 +222,9 @@ function configure_neutron_defaults {
|
|||
sg_ids=$(echo $(neutron security-group-list \
|
||||
--project-id "$project_id" -c id -f value) | tr ' ' ',')
|
||||
|
||||
ext_svc_subnet_id="$(neutron subnet-show -c id -f value \
|
||||
"${KURYR_NEUTRON_DEFAULT_EXT_SVC_SUBNET}")"
|
||||
|
||||
local use_octavia
|
||||
use_octavia=$(trueorfalse True KURYR_K8S_LBAAS_USE_OCTAVIA)
|
||||
if [[ "$use_octavia" == "True" && \
|
||||
|
@ -275,6 +278,7 @@ function configure_neutron_defaults {
|
|||
if [ -n "$OVS_BRIDGE" ]; then
|
||||
iniset "$KURYR_CONFIG" neutron_defaults ovs_bridge "$OVS_BRIDGE"
|
||||
fi
|
||||
iniset "$KURYR_CONFIG" neutron_defaults external_svc_subnet "$ext_svc_subnet_id"
|
||||
iniset "$KURYR_CONFIG" octavia_defaults member_mode "$KURYR_K8S_OCTAVIA_MEMBER_MODE"
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ KURYR_NEUTRON_DEFAULT_POD_SUBNET=${KURYR_NEUTRON_DEFAULT_POD_SUBNET:-k8s-pod-sub
|
|||
KURYR_NEUTRON_DEFAULT_SERVICE_SUBNET=${KURYR_NEUTRON_DEFAULT_SERVICE_SUBNET:-k8s-service-subnet}
|
||||
KURYR_NEUTRON_DEFAULT_SUBNETPOOL_ID=${KURYR_NEUTRON_DEFAULT_SUBNETPOOL_ID:-}
|
||||
KURYR_NEUTRON_DEFAULT_ROUTER=${KURYR_NEUTRON_DEFAULT_ROUTER:-}
|
||||
KURYR_NEUTRON_DEFAULT_EXT_SVC_SUBNET=${KURYR_NEUTRON_DEFAULT_EXT_SVC_SUBNET:-public-subnet}
|
||||
|
||||
# Etcd
|
||||
ETCD_PORT=${ETCD_PORT:-2379}
|
||||
|
|
|
@ -8,8 +8,10 @@ be implemented in the following way:
|
|||
* **Service**: It is translated to a single **LoadBalancer** and as many
|
||||
**Listeners** and **Pools** as ports the Kubernetes Service spec defines.
|
||||
* **ClusterIP**: It is translated to a LoadBalancer's VIP.
|
||||
* **loadBalancerIP**: Translated to public IP associated with the LoadBalancer's VIP.
|
||||
* **Endpoints**: The Endpoint object is translated to a LoadBalancer's VIP.
|
||||
|
||||
|
||||
.. _services: https://kubernetes.io/docs/concepts/services-networking/service/
|
||||
.. _LBaaS API: https://wiki.openstack.org/wiki/Neutron/LBaaS/API_2.0
|
||||
|
||||
|
@ -396,6 +398,53 @@ The services and pods subnets should be created.
|
|||
the pod subnet, follow the `Making the Pods be able to reach the Kubernetes
|
||||
API`_ section
|
||||
|
||||
#. For the external services (type=LoadBalancer) case,
|
||||
two methods are supported:
|
||||
|
||||
* Pool - external IPs are allocated from pre-defined pool
|
||||
* User - user specify the external IP address
|
||||
|
||||
In case 'Pool' method should be supported, execute the next steps
|
||||
|
||||
A. Create an external/provider network
|
||||
B. Create subnet/pool range of external CIDR
|
||||
C. Connect external subnet to kuryr-kubernetes router
|
||||
D. Configure Kuryr.conf public ip subnet to point to the external subnet::
|
||||
|
||||
[neutron_defaults]
|
||||
external_svc_subnet= external_subnet_id
|
||||
|
||||
From this point for each K8s service of type=LoadBalancer and in which
|
||||
'load-balancer-ip' is not specified, an external IP from
|
||||
'external_svc_subnet' will be allocated.
|
||||
|
||||
For the 'User' case, user should first create an external/floating IP::
|
||||
|
||||
$#openstack floating ip create --subnet <ext-subnet-id> <ext-netowrk-id>
|
||||
$openstack floating ip create --subnet 48ddcfec-1b29-411b-be92-8329cc09fc12 3b4eb25e-e103-491f-a640-a6246d588561
|
||||
+---------------------------+--------------------------------------+
|
||||
| Field | Value |
|
||||
+---------------------+--------------------------------------+
|
||||
| created_at | 2017-10-02T09:22:37Z |
|
||||
| description | |
|
||||
| fixed_ip_address | None |
|
||||
| floating_ip_address | 172.24.4.13 |
|
||||
| floating_network_id | 3b4eb25e-e103-491f-a640-a6246d588561 |
|
||||
| id | 1157e2fd-de64-492d-b955-88ea203b4c37 |
|
||||
| name | 172.24.4.13 |
|
||||
| port_id | None |
|
||||
| project_id | 6556471f4f7b40e2bde1fc6e4aba0eef |
|
||||
| revision_number | 0 |
|
||||
| router_id | None |
|
||||
| status | DOWN |
|
||||
| updated_at | 2017-10-02T09:22:37Z |
|
||||
+---------------------+--------------------------------------+
|
||||
|
||||
and then create k8s service with type=LoadBalancer and load-balancer-ip=<floating_ip> (e.g: 172.24.4.13)
|
||||
|
||||
In both 'User' and 'Pool' methods, the extrenal IP address could be find
|
||||
in k8s service status information (under loadbalancer/ingress/ip)
|
||||
|
||||
Alternative configuration
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
It is actually possible to avoid this routing by performing a deployment change
|
||||
|
|
|
@ -88,6 +88,10 @@ k8s_opts = [
|
|||
help=_('Enable port debug to force kuryr port names to be '
|
||||
'set to their corresponding pod names.'),
|
||||
default=False),
|
||||
cfg.StrOpt('service_public_ip_driver',
|
||||
help=_("The driver that provides external IP for LB at "
|
||||
"Kubernetes"),
|
||||
default='neutron_floating_ip'),
|
||||
]
|
||||
|
||||
neutron_defaults = [
|
||||
|
@ -104,6 +108,8 @@ neutron_defaults = [
|
|||
sample_default="br-int"),
|
||||
cfg.StrOpt('service_subnet',
|
||||
help=_("Default Neutron subnet ID for Kubernetes services")),
|
||||
cfg.StrOpt('external_svc_subnet',
|
||||
help=_("Default external subnet for Kubernetes services")),
|
||||
]
|
||||
|
||||
octavia_defaults = [
|
||||
|
|
|
@ -420,3 +420,48 @@ class VIFPoolDriver(PodVIFDriver):
|
|||
vif resources.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class ServicePubIpDriver(DriverBase):
|
||||
"""Manages loadbalancerIP/public ip for neutron lbaas."""
|
||||
|
||||
ALIAS = 'service_public_ip'
|
||||
|
||||
@abc.abstractmethod
|
||||
def acquire_service_pub_ip_info(self, spec_type, spec_lb_ip, project_id):
|
||||
"""Get k8s service loadbalancer IP info based on service spec
|
||||
|
||||
:param spec_type: service.spec.type field
|
||||
:param spec_lb_ip: service spec LoadBlaceIP field
|
||||
:param project_id: openstack project id
|
||||
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractmethod
|
||||
def release_pub_ip(self, service_pub_ip_info):
|
||||
"""Release (if needed) based on service_pub_ip_info content
|
||||
|
||||
:param service_pub_ip_info: service loadbalancer IP info
|
||||
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractmethod
|
||||
def associate_pub_ip(self, service_pub_ip_info, vip_port_id):
|
||||
"""Associate loadbalancer IP to lbaas VIP port ID
|
||||
|
||||
:param service_pub_ip_info: service loadbalancer IP info
|
||||
:param vip_port_id: Lbaas VIP port id
|
||||
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractmethod
|
||||
def disassociate_pub_ip(self, service_pub_ip_info):
|
||||
"""Disassociate loadbalancer IP and lbaas VIP port ID
|
||||
|
||||
:param service_pub_ip_info: service loadbalancer IP info
|
||||
|
||||
"""
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
# Copyright (c) 2017 RedHat, 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 import exceptions as kl_exc
|
||||
from kuryr_kubernetes import clients
|
||||
from kuryr_kubernetes import config
|
||||
from kuryr_kubernetes.controller.drivers import base
|
||||
from kuryr_kubernetes.controller.drivers import public_ip
|
||||
from kuryr_kubernetes.objects import lbaas as obj_lbaas
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FloatingIpServicePubIPDriver(base.ServicePubIpDriver):
|
||||
"""Manages floating ip for neutron lbaas.
|
||||
|
||||
Service loadbalancerIP support the following :
|
||||
1. No loadbalancer IP - k8s service.spec.type != 'LoadBalancer'
|
||||
2. Floating IP allocated from pool -
|
||||
k8s service.spec.type = 'LoadBalancer' and
|
||||
service.spec.loadBalancerIP NOT defined
|
||||
3. Floating IP specified by the user -
|
||||
k8s service.spec.type = 'LoadBalancer' and
|
||||
service.spec.loadBalancerIP is defined.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(FloatingIpServicePubIPDriver, self).__init__()
|
||||
self._drv_pub_ip = public_ip.FipPubIpDriver()
|
||||
|
||||
def acquire_service_pub_ip_info(self, spec_type, spec_lb_ip, project_id):
|
||||
|
||||
if spec_type != 'LoadBalancer':
|
||||
return None
|
||||
|
||||
if spec_lb_ip:
|
||||
user_specified_ip = spec_lb_ip.format()
|
||||
res_id = self._drv_pub_ip.is_ip_available(user_specified_ip)
|
||||
if res_id:
|
||||
service_pub_ip_info = (obj_lbaas.LBaaSPubIp(
|
||||
ip_id=res_id,
|
||||
ip_addr=str(user_specified_ip),
|
||||
alloc_method='user'))
|
||||
|
||||
return service_pub_ip_info
|
||||
else:
|
||||
# user specified IP is not valid
|
||||
LOG.error("IP=%s is not available", user_specified_ip)
|
||||
return None
|
||||
else:
|
||||
LOG.debug("Trying to allocate public ip from pool")
|
||||
|
||||
# get public subnet id from kuryr.conf
|
||||
external_svc_subnet = config.CONF.neutron_defaults.external_svc_subnet
|
||||
if not external_svc_subnet:
|
||||
raise cfg.RequiredOptError('external_svc_subnet',
|
||||
cfg.OptGroup('neutron_defaults'))
|
||||
|
||||
neutron = clients.get_neutron_client()
|
||||
n_subnet = neutron.show_subnet(external_svc_subnet).get('subnet')
|
||||
if not n_subnet:
|
||||
LOG.error(
|
||||
"No subnet found for external_svc_subnet=%s",
|
||||
external_svc_subnet)
|
||||
raise kl_exc.NoResourceException
|
||||
|
||||
public_network_id = n_subnet['network_id']
|
||||
|
||||
res_id, alloc_ip_addr = (self._drv_pub_ip.allocate_ip
|
||||
(public_network_id,
|
||||
external_svc_subnet,
|
||||
project_id,
|
||||
'kuryr_lb'))
|
||||
service_pub_ip_info = obj_lbaas.LBaaSPubIp(ip_id=res_id,
|
||||
ip_addr=alloc_ip_addr,
|
||||
alloc_method='pool')
|
||||
|
||||
return service_pub_ip_info
|
||||
|
||||
def release_pub_ip(self, service_pub_ip_info):
|
||||
if not service_pub_ip_info:
|
||||
return
|
||||
if service_pub_ip_info.alloc_method == 'pool':
|
||||
retcode = self._drv_pub_ip.free_ip(service_pub_ip_info.ip_id)
|
||||
if not retcode:
|
||||
LOG.error("Failed to delete public_ip_id =%s !",
|
||||
service_pub_ip_info.ip_id)
|
||||
|
||||
def associate_pub_ip(self, service_pub_ip_info, vip_port_id):
|
||||
if (not service_pub_ip_info or
|
||||
not vip_port_id or
|
||||
not service_pub_ip_info.ip_id):
|
||||
return
|
||||
self._drv_pub_ip.associate(
|
||||
service_pub_ip_info.ip_id, vip_port_id)
|
||||
|
||||
def disassociate_pub_ip(self, service_pub_ip_info):
|
||||
if not service_pub_ip_info or not service_pub_ip_info.ip_id:
|
||||
return
|
||||
self._drv_pub_ip.disassociate(service_pub_ip_info.ip_id)
|
|
@ -111,6 +111,21 @@ class LBaaSv2Driver(base.LBaaSDriver):
|
|||
neutron.delete_lbaas_member,
|
||||
member.id, member.pool_id)
|
||||
|
||||
def _get_vip_port_id(self, loadbalancer):
|
||||
neutron = clients.get_neutron_client()
|
||||
try:
|
||||
fixed_ips = ['subnet_id=%s' % str(loadbalancer.subnet_id),
|
||||
'ip_address=%s' % str(loadbalancer.ip)]
|
||||
ports = neutron.list_ports(fixed_ips=fixed_ips)
|
||||
except n_exc.NeutronClientException as ex:
|
||||
LOG.error("Port with fixed ips %s not found!", fixed_ips)
|
||||
raise ex
|
||||
|
||||
if ports['ports']:
|
||||
return ports['ports'][0].get("id")
|
||||
|
||||
return None
|
||||
|
||||
def _create_loadbalancer(self, loadbalancer):
|
||||
neutron = clients.get_neutron_client()
|
||||
response = neutron.create_loadbalancer({'loadbalancer': {
|
||||
|
@ -120,6 +135,7 @@ class LBaaSv2Driver(base.LBaaSDriver):
|
|||
'vip_address': str(loadbalancer.ip),
|
||||
'vip_subnet_id': loadbalancer.subnet_id}})
|
||||
loadbalancer.id = response['loadbalancer']['id']
|
||||
loadbalancer.port_id = self._get_vip_port_id(loadbalancer)
|
||||
return loadbalancer
|
||||
|
||||
def _find_loadbalancer(self, loadbalancer):
|
||||
|
@ -133,6 +149,7 @@ class LBaaSv2Driver(base.LBaaSDriver):
|
|||
|
||||
try:
|
||||
loadbalancer.id = response['loadbalancers'][0]['id']
|
||||
loadbalancer.port_id = self._get_vip_port_id(loadbalancer)
|
||||
except (KeyError, IndexError):
|
||||
return None
|
||||
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
# Copyright (c) 2017 RedHat, 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.
|
||||
import abc
|
||||
from kuryr_kubernetes import clients
|
||||
from neutronclient.common import exceptions as n_exc
|
||||
from oslo_log import log as logging
|
||||
import six
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class BasePubIpDriver(object):
|
||||
"""Base class for public IP functionality."""
|
||||
|
||||
@abc.abstractmethod
|
||||
def is_ip_available(self, ip_addr):
|
||||
"""check availability of ip address
|
||||
|
||||
:param ip_address:
|
||||
:returns res_id in case ip is available returns resources id else None
|
||||
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractmethod
|
||||
def allocate_ip(self, pub_net_id, pub_subnet_id, project_id, description):
|
||||
"""allocate ip address from public network id
|
||||
|
||||
:param pub_net_id: public network id
|
||||
:param pub_subnet_id: public subnet id
|
||||
:param project_id:
|
||||
:param description: string describing request
|
||||
:returns res_id , ip_addr
|
||||
:res_id - resource id
|
||||
:ip_addr - ip aaddress
|
||||
|
||||
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractmethod
|
||||
def free_ip(self, res_id):
|
||||
"""free ip by resource ID
|
||||
|
||||
:param res_id: resource_id
|
||||
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractmethod
|
||||
def associate(self, res_id, vip_port_id):
|
||||
"""Associate VIP port id with resource_id
|
||||
|
||||
:param res_id: id represents pub ip resource
|
||||
:param vip_port_id: VIP port id
|
||||
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractmethod
|
||||
def disassociate(self, res_id):
|
||||
"""Clear association between res_id to any vip port
|
||||
|
||||
:param res_id: id represents pub ip resource
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class FipPubIpDriver(BasePubIpDriver):
|
||||
"""Floating IP implementation for public IP capability ."""
|
||||
|
||||
def is_ip_available(self, ip_addr):
|
||||
if ip_addr:
|
||||
neutron = clients.get_neutron_client()
|
||||
floating_ips_list = neutron.list_floatingips(
|
||||
floating_ip_address=ip_addr)
|
||||
for entry in floating_ips_list['floatingips']:
|
||||
if not entry:
|
||||
continue
|
||||
if (entry['floating_ip_address'] == ip_addr and
|
||||
not entry['port_id']):
|
||||
return entry['id']
|
||||
# floating IP not available
|
||||
LOG.error("Floating IP=%s not available", ip_addr)
|
||||
else:
|
||||
LOG.error("Invalid parameter ip_addr=%s", ip_addr)
|
||||
return None
|
||||
|
||||
def allocate_ip(self, pub_net_id, pub_subnet_id, project_id, description):
|
||||
|
||||
neutron = clients.get_neutron_client()
|
||||
try:
|
||||
response = neutron.create_floatingip({'floatingip': {
|
||||
'tenant_id': project_id,
|
||||
'project_id': project_id,
|
||||
'floating_network_id': pub_net_id,
|
||||
'subnet_id': pub_subnet_id,
|
||||
'description': description}})
|
||||
except n_exc.NeutronClientException as ex:
|
||||
LOG.error("Failed to create floating IP - subnetid=%s ",
|
||||
pub_subnet_id)
|
||||
raise ex
|
||||
return response['floatingip']['id'], response[
|
||||
'floatingip']['floating_ip_address']
|
||||
|
||||
def free_ip(self, res_id):
|
||||
neutron = clients.get_neutron_client()
|
||||
try:
|
||||
neutron.delete_floatingip(res_id)
|
||||
except n_exc.NeutronClientException as ex:
|
||||
LOG.error("Failed to delete floating_ip_id =%s !",
|
||||
res_id)
|
||||
raise ex
|
||||
|
||||
def _update(self, res_id, vip_port_id):
|
||||
response = None
|
||||
neutron = clients.get_neutron_client()
|
||||
try:
|
||||
response = neutron.update_floatingip(
|
||||
res_id, {'floatingip': {'port_id': vip_port_id, }})
|
||||
except n_exc.NeutronClientException as ex:
|
||||
LOG.error("Failed to update_floatingip ,floating_ip_id=%s,"
|
||||
"response=%s!", res_id, response)
|
||||
raise ex
|
||||
|
||||
def associate(self, res_id, vip_port_id):
|
||||
self._update(res_id, vip_port_id)
|
||||
|
||||
def disassociate(self, res_id):
|
||||
self._update(res_id, None)
|
|
@ -27,6 +27,8 @@ from kuryr_kubernetes.objects import lbaas as obj_lbaas
|
|||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
SUPPORTED_SERVICE_TYPES = ('ClusterIP', 'LoadBalancer')
|
||||
|
||||
|
||||
class LBaaSSpecHandler(k8s_base.ResourceEventHandler):
|
||||
"""LBaaSSpecHandler handles K8s Service events.
|
||||
|
@ -54,10 +56,14 @@ class LBaaSSpecHandler(k8s_base.ResourceEventHandler):
|
|||
lbaas_spec = self._generate_lbaas_spec(service)
|
||||
self._set_lbaas_spec(service, lbaas_spec)
|
||||
|
||||
def _get_service_ip(self, service):
|
||||
def _is_supported_type(self, service):
|
||||
spec = service['spec']
|
||||
if spec.get('type') == 'ClusterIP':
|
||||
return spec.get('clusterIP')
|
||||
return spec.get('type') in SUPPORTED_SERVICE_TYPES
|
||||
|
||||
def _get_service_ip(self, service):
|
||||
if self._is_supported_type(service):
|
||||
return service['spec'].get('clusterIP')
|
||||
return None
|
||||
|
||||
def _should_ignore(self, service):
|
||||
return not(self._has_selector(service))
|
||||
|
@ -88,12 +94,16 @@ class LBaaSSpecHandler(k8s_base.ResourceEventHandler):
|
|||
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)
|
||||
spec_type = service['spec'].get('type')
|
||||
spec_lb_ip = service['spec'].get('loadBalancerIP')
|
||||
|
||||
return obj_lbaas.LBaaSServiceSpec(ip=ip,
|
||||
project_id=project_id,
|
||||
subnet_id=subnet_id,
|
||||
ports=ports,
|
||||
security_groups_ids=sg_ids)
|
||||
security_groups_ids=sg_ids,
|
||||
type=spec_type,
|
||||
lb_ip=spec_lb_ip)
|
||||
|
||||
def _has_lbaas_spec_changes(self, service, lbaas_spec):
|
||||
return (self._has_ip_changes(service, lbaas_spec) or
|
||||
|
@ -207,6 +217,7 @@ class LoadBalancerHandler(k8s_base.ResourceEventHandler):
|
|||
self._drv_lbaas = drv_base.LBaaSDriver.get_instance()
|
||||
self._drv_pod_project = drv_base.PodProjectDriver.get_instance()
|
||||
self._drv_pod_subnets = drv_base.PodSubnetsDriver.get_instance()
|
||||
self._drv_service_pub_ip = drv_base.ServicePubIpDriver.get_instance()
|
||||
|
||||
def on_present(self, endpoints):
|
||||
lbaas_spec = self._get_lbaas_spec(endpoints)
|
||||
|
@ -452,25 +463,74 @@ class LoadBalancerHandler(k8s_base.ResourceEventHandler):
|
|||
if l.id not in removed_ids]
|
||||
return bool(removed_ids)
|
||||
|
||||
def _update_lb_status(self, endpoints, lb_ip_address):
|
||||
status_data = {"loadBalancer": {
|
||||
"ingress": [{"ip": lb_ip_address.format()}]}}
|
||||
k8s = clients.get_kubernetes_client()
|
||||
svc_link = self._get_service_link(endpoints)
|
||||
try:
|
||||
k8s.patch_status(svc_link, status_data)
|
||||
except k_exc.K8sClientException:
|
||||
# REVISIT(ivc): only raise ResourceNotReady for NotFound
|
||||
raise k_exc.ResourceNotReady(svc_link)
|
||||
|
||||
def _get_service_link(self, endpoints):
|
||||
ep_link = endpoints['metadata']['selfLink']
|
||||
link_parts = ep_link.split('/')
|
||||
|
||||
if link_parts[-2] != 'endpoints':
|
||||
raise k_exc.IntegrityError(_(
|
||||
"Unsupported endpoints link: %(link)s") % {
|
||||
'link': ep_link})
|
||||
link_parts[-2] = 'services'
|
||||
return "/".join(link_parts)
|
||||
|
||||
def _sync_lbaas_loadbalancer(self, endpoints, lbaas_state, lbaas_spec):
|
||||
changed = False
|
||||
lb = lbaas_state.loadbalancer
|
||||
|
||||
if lb and lb.ip != lbaas_spec.ip:
|
||||
# if loadbalancerIP was associated to lbaas VIP, disassociate it.
|
||||
if lbaas_state.service_pub_ip_info:
|
||||
self._drv_service_pub_ip.disassociate_pub_ip(
|
||||
lbaas_state.service_pub_ip_info)
|
||||
|
||||
self._drv_lbaas.release_loadbalancer(
|
||||
endpoints=endpoints,
|
||||
loadbalancer=lb)
|
||||
lb = None
|
||||
changed = True
|
||||
|
||||
if not lb and lbaas_spec.ip:
|
||||
lb = self._drv_lbaas.ensure_loadbalancer(
|
||||
endpoints=endpoints,
|
||||
project_id=lbaas_spec.project_id,
|
||||
subnet_id=lbaas_spec.subnet_id,
|
||||
ip=lbaas_spec.ip,
|
||||
security_groups_ids=lbaas_spec.security_groups_ids)
|
||||
changed = True
|
||||
if not lb:
|
||||
if lbaas_spec.ip:
|
||||
lb = self._drv_lbaas.ensure_loadbalancer(
|
||||
endpoints=endpoints,
|
||||
project_id=lbaas_spec.project_id,
|
||||
subnet_id=lbaas_spec.subnet_id,
|
||||
ip=lbaas_spec.ip,
|
||||
security_groups_ids=lbaas_spec.security_groups_ids)
|
||||
if lbaas_state.service_pub_ip_info is None:
|
||||
service_pub_ip_info = (
|
||||
self._drv_service_pub_ip.acquire_service_pub_ip_info(
|
||||
lbaas_spec.type,
|
||||
lbaas_spec.lb_ip,
|
||||
lbaas_spec.project_id))
|
||||
if service_pub_ip_info:
|
||||
# if loadbalancerIP should be defined for lbaas,
|
||||
# associate it to lbaas VIP
|
||||
# and update k8s-service status with
|
||||
# loadbalancerIP address
|
||||
self._drv_service_pub_ip.associate_pub_ip(
|
||||
service_pub_ip_info, lb.port_id)
|
||||
self._update_lb_status(
|
||||
endpoints, service_pub_ip_info.ip_addr)
|
||||
lbaas_state.service_pub_ip_info = service_pub_ip_info
|
||||
changed = True
|
||||
elif lbaas_state.service_pub_ip_info:
|
||||
self._drv_service_pub_ip.release_pub_ip(
|
||||
lbaas_state.service_pub_ip_info)
|
||||
lbaas_state.service_pub_ip_info = None
|
||||
changed = True
|
||||
|
||||
lbaas_state.loadbalancer = lb
|
||||
return changed
|
||||
|
|
|
@ -78,6 +78,29 @@ class K8sClient(object):
|
|||
raise exc.K8sClientException(response.text)
|
||||
return response.json()
|
||||
|
||||
def _get_url_and_header(self, path):
|
||||
url = self._base_url + path
|
||||
header = {'Content-Type': 'application/merge-patch+json',
|
||||
'Accept': 'application/json'}
|
||||
if self.token:
|
||||
header.update({'Authorization': 'Bearer %s' % self.token})
|
||||
|
||||
return url, header
|
||||
|
||||
def patch_status(self, path, data):
|
||||
|
||||
LOG.debug("Patch_status %(path)s: %(data)s", {
|
||||
'path': path, 'data': data})
|
||||
path = path + '/status'
|
||||
url, header = self._get_url_and_header(path)
|
||||
|
||||
response = requests.patch(url, json={"status": data},
|
||||
headers=header, cert=self.cert,
|
||||
verify=self.verify_server)
|
||||
if response.ok:
|
||||
return response.json().get('status')
|
||||
raise exc.K8sClientException(response.text)
|
||||
|
||||
def annotate(self, path, annotations, resource_version=None):
|
||||
"""Pushes a resource annotation to the K8s API resource
|
||||
|
||||
|
@ -88,11 +111,9 @@ class K8sClient(object):
|
|||
"""
|
||||
LOG.debug("Annotate %(path)s: %(names)s", {
|
||||
'path': path, 'names': list(annotations)})
|
||||
url = self._base_url + path
|
||||
header = {'Content-Type': 'application/merge-patch+json',
|
||||
'Accept': 'application/json'}
|
||||
if self.token:
|
||||
header.update({'Authorization': 'Bearer %s' % self.token})
|
||||
|
||||
url, header = self._get_url_and_header(path)
|
||||
|
||||
while itertools.count(1):
|
||||
data = jsonutils.dumps({
|
||||
"metadata": {
|
||||
|
|
|
@ -30,6 +30,7 @@ class LBaaSLoadBalancer(k_obj.KuryrK8sObjectBase):
|
|||
'name': obj_fields.StringField(),
|
||||
'ip': obj_fields.IPAddressField(),
|
||||
'subnet_id': obj_fields.UUIDField(),
|
||||
'port_id': obj_fields.UUIDField(),
|
||||
}
|
||||
|
||||
|
||||
|
@ -76,6 +77,17 @@ class LBaaSMember(k_obj.KuryrK8sObjectBase):
|
|||
}
|
||||
|
||||
|
||||
@obj_base.VersionedObjectRegistry.register
|
||||
class LBaaSPubIp(k_obj.KuryrK8sObjectBase):
|
||||
VERSION = '1.0'
|
||||
|
||||
fields = {
|
||||
'ip_id': obj_fields.UUIDField(),
|
||||
'ip_addr': obj_fields.IPAddressField(),
|
||||
'alloc_method': obj_fields.StringField(),
|
||||
}
|
||||
|
||||
|
||||
@obj_base.VersionedObjectRegistry.register
|
||||
class LBaaSState(k_obj.KuryrK8sObjectBase):
|
||||
VERSION = '1.0'
|
||||
|
@ -90,6 +102,9 @@ class LBaaSState(k_obj.KuryrK8sObjectBase):
|
|||
default=[]),
|
||||
'members': obj_fields.ListOfObjectsField(LBaaSMember.__name__,
|
||||
default=[]),
|
||||
'service_pub_ip_info': obj_fields.ObjectField(LBaaSPubIp.__name__,
|
||||
nullable=True,
|
||||
default=None),
|
||||
}
|
||||
|
||||
|
||||
|
@ -115,4 +130,6 @@ class LBaaSServiceSpec(k_obj.KuryrK8sObjectBase):
|
|||
'project_id': obj_fields.StringField(nullable=True, default=None),
|
||||
'subnet_id': obj_fields.UUIDField(nullable=True, default=None),
|
||||
'security_groups_ids': k_fields.ListOfUUIDField(default=[]),
|
||||
'type': obj_fields.StringField(nullable=True, default=None),
|
||||
'lb_ip': obj_fields.IPAddressField(nullable=True, default=None),
|
||||
}
|
||||
|
|
|
@ -0,0 +1,327 @@
|
|||
# Copyright (c) 2017 RedHat, 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 import exceptions as kl_exc
|
||||
import mock
|
||||
from neutronclient.common import exceptions as n_exc
|
||||
|
||||
from kuryr_kubernetes.controller.drivers import lb_public_ip\
|
||||
as d_lb_public_ip
|
||||
from kuryr_kubernetes.controller.drivers import public_ip
|
||||
from kuryr_kubernetes.objects import lbaas as obj_lbaas
|
||||
from kuryr_kubernetes.tests import base as test_base
|
||||
from kuryr_kubernetes.tests.unit import kuryr_fixtures as k_fix
|
||||
from oslo_config import cfg
|
||||
|
||||
|
||||
class TestFloatingIpServicePubIPDriverDriver(test_base.TestCase):
|
||||
def test_acquire_service_pub_ip_info_clusterip(self):
|
||||
cls = d_lb_public_ip.FloatingIpServicePubIPDriver
|
||||
m_driver = mock.Mock(spec=cls)
|
||||
m_driver._drv_pub_ip = public_ip.FipPubIpDriver()
|
||||
project_id = mock.sentinel.project_id
|
||||
cur_service_pub_ip_info = None
|
||||
service = {'spec': {'type': 'ClusterIP'}}
|
||||
|
||||
self.assertIsNone(cls.acquire_service_pub_ip_info
|
||||
(m_driver, service, project_id,
|
||||
cur_service_pub_ip_info))
|
||||
|
||||
def test_acquire_service_pub_ip_info_usr_specified_ip(self):
|
||||
cls = d_lb_public_ip.FloatingIpServicePubIPDriver
|
||||
m_driver = mock.Mock(spec=cls)
|
||||
m_driver._drv_pub_ip = public_ip.FipPubIpDriver()
|
||||
neutron = self.useFixture(k_fix.MockNeutronClient()).client
|
||||
|
||||
floating_ip = {'floating_ip_address': '1.2.3.4', 'port_id': None,
|
||||
'id': 'a2a62ea7-e3bf-40df-8c09-aa0c29876a6b'}
|
||||
neutron.list_floatingips.return_value = {'floatingips': [floating_ip]}
|
||||
project_id = mock.sentinel.project_id
|
||||
spec_type = 'LoadBalancer'
|
||||
spec_lb_ip = '1.2.3.4'
|
||||
|
||||
expected_resp = \
|
||||
obj_lbaas.LBaaSPubIp(ip_id=floating_ip['id'],
|
||||
ip_addr=floating_ip['floating_ip_address'],
|
||||
alloc_method='user')
|
||||
|
||||
self.assertEqual(expected_resp, cls.acquire_service_pub_ip_info
|
||||
(m_driver, spec_type, spec_lb_ip, project_id))
|
||||
|
||||
def test_acquire_service_pub_ip_info_user_specified_non_exist_fip(self):
|
||||
cls = d_lb_public_ip.FloatingIpServicePubIPDriver
|
||||
m_driver = mock.Mock(spec=cls)
|
||||
m_driver._drv_pub_ip = public_ip.FipPubIpDriver()
|
||||
neutron = self.useFixture(k_fix.MockNeutronClient()).client
|
||||
|
||||
floating_ip = {'floating_ip_address': '1.2.3.5',
|
||||
'port_id': None}
|
||||
neutron.list_floatingips.return_value = {'floatingips': [floating_ip]}
|
||||
|
||||
project_id = mock.sentinel.project_id
|
||||
|
||||
spec_type = 'LoadBalancer'
|
||||
spec_lb_ip = '1.2.3.4'
|
||||
|
||||
self.assertIsNone(cls.acquire_service_pub_ip_info
|
||||
(m_driver, spec_type,
|
||||
spec_lb_ip, project_id))
|
||||
|
||||
def test_acquire_service_pub_ip_info_user_specified_occupied_fip(self):
|
||||
cls = d_lb_public_ip.FloatingIpServicePubIPDriver
|
||||
m_driver = mock.Mock(spec=cls)
|
||||
m_driver._drv_pub_ip = public_ip.FipPubIpDriver()
|
||||
neutron = self.useFixture(k_fix.MockNeutronClient()).client
|
||||
|
||||
floating_ip = {'floating_ip_address': '1.2.3.4',
|
||||
'port_id': 'ec29d641-fec4-4f67-928a-124a76b3a8e6'}
|
||||
neutron.list_floatingips.return_value = {'floatingips': [floating_ip]}
|
||||
|
||||
project_id = mock.sentinel.project_id
|
||||
spec_type = 'LoadBalancer'
|
||||
spec_lb_ip = '1.2.3.4'
|
||||
|
||||
self.assertIsNone(cls.acquire_service_pub_ip_info
|
||||
(m_driver, spec_type,
|
||||
spec_lb_ip, project_id))
|
||||
|
||||
@mock.patch('kuryr_kubernetes.config.CONF')
|
||||
def test_acquire_service_pub_ip_info_pool_subnet_not_defined(self, m_cfg):
|
||||
driver = d_lb_public_ip.FloatingIpServicePubIPDriver()
|
||||
public_subnet = ''
|
||||
m_cfg.neutron_defaults.external_svc_subnet = public_subnet
|
||||
neutron = self.useFixture(k_fix.MockNeutronClient()).client
|
||||
neutron.list_floatingips.return_value = {}
|
||||
project_id = mock.sentinel.project_id
|
||||
spec_type = 'LoadBalancer'
|
||||
spec_lb_ip = None
|
||||
|
||||
self.assertRaises(
|
||||
cfg.RequiredOptError,
|
||||
driver.acquire_service_pub_ip_info,
|
||||
spec_type, spec_lb_ip, project_id)
|
||||
|
||||
@mock.patch('kuryr_kubernetes.config.CONF')
|
||||
def test_acquire_service_pub_ip_info_pool_subnet_not_exist(self, m_cfg):
|
||||
driver = d_lb_public_ip.FloatingIpServicePubIPDriver()
|
||||
neutron = self.useFixture(k_fix.MockNeutronClient()).client
|
||||
public_subnet = mock.sentinel.public_subnet
|
||||
m_cfg.neutron_defaults.external_svc_subnet = public_subnet
|
||||
|
||||
neutron.show_subnet.return_value = {}
|
||||
|
||||
project_id = mock.sentinel.project_id
|
||||
spec_type = 'LoadBalancer'
|
||||
spec_lb_ip = None
|
||||
|
||||
self.assertRaises(
|
||||
kl_exc.NoResourceException,
|
||||
driver.acquire_service_pub_ip_info,
|
||||
spec_type, spec_lb_ip, project_id)
|
||||
|
||||
@mock.patch('kuryr_kubernetes.config.CONF')
|
||||
def test_acquire_service_pub_ip_info_alloc_from_pool(self, m_cfg):
|
||||
cls = d_lb_public_ip.FloatingIpServicePubIPDriver
|
||||
m_driver = mock.Mock(spec=cls)
|
||||
m_driver._drv_pub_ip = public_ip.FipPubIpDriver()
|
||||
neutron = self.useFixture(k_fix.MockNeutronClient()).client
|
||||
m_cfg.neutron_defaults.external_svc_subnet =\
|
||||
mock.sentinel.external_svc_subnet
|
||||
|
||||
neutron.show_subnet.return_value =\
|
||||
{'subnet': {'network_id': 'ec29d641-fec4-4f67-928a-124a76b3a8e6'}}
|
||||
floating_ip = {'floating_ip_address': '1.2.3.5',
|
||||
'id': 'ec29d641-fec4-4f67-928a-124a76b3a888'}
|
||||
neutron.create_floatingip.return_value = {'floatingip': floating_ip}
|
||||
|
||||
project_id = mock.sentinel.project_id
|
||||
spec_type = 'LoadBalancer'
|
||||
spec_lb_ip = None
|
||||
|
||||
expected_resp = \
|
||||
obj_lbaas.LBaaSPubIp(ip_id=floating_ip['id'],
|
||||
ip_addr=floating_ip['floating_ip_address'],
|
||||
alloc_method='pool')
|
||||
|
||||
self.assertEqual(expected_resp, cls.acquire_service_pub_ip_info
|
||||
(m_driver, spec_type, spec_lb_ip, project_id))
|
||||
|
||||
def test_release_pub_ip_empty_lb_ip_info(self):
|
||||
cls = d_lb_public_ip.FloatingIpServicePubIPDriver
|
||||
m_driver = mock.Mock(spec=cls)
|
||||
service_pub_ip_info = None
|
||||
|
||||
self.assertIsNone(cls.release_pub_ip
|
||||
(m_driver, service_pub_ip_info))
|
||||
|
||||
def test_release_pub_ip_alloc_method_non_pool(self):
|
||||
cls = d_lb_public_ip.FloatingIpServicePubIPDriver
|
||||
m_driver = mock.Mock(spec=cls)
|
||||
|
||||
floating_ip = {'floating_ip_address': '1.2.3.5',
|
||||
'id': 'ec29d641-fec4-4f67-928a-124a76b3a888'}
|
||||
|
||||
service_pub_ip_info = \
|
||||
obj_lbaas.LBaaSPubIp(ip_id=floating_ip['id'],
|
||||
ip_addr=floating_ip['floating_ip_address'],
|
||||
alloc_method='kk')
|
||||
|
||||
self.assertIsNone(
|
||||
cls.release_pub_ip(m_driver, service_pub_ip_info))
|
||||
|
||||
def test_release_pub_ip_alloc_method_user(self):
|
||||
cls = d_lb_public_ip.FloatingIpServicePubIPDriver
|
||||
m_driver = mock.Mock(spec=cls)
|
||||
|
||||
floating_ip = {'floating_ip_address': '1.2.3.5',
|
||||
'id': 'ec29d641-fec4-4f67-928a-124a76b3a888'}
|
||||
|
||||
service_pub_ip_info = \
|
||||
obj_lbaas.LBaaSPubIp(ip_id=floating_ip['id'],
|
||||
ip_addr=floating_ip['floating_ip_address'],
|
||||
alloc_method='user')
|
||||
|
||||
self.assertIsNone(cls.release_pub_ip
|
||||
(m_driver, service_pub_ip_info))
|
||||
|
||||
def test_release_pub_ip_alloc_method_pool_neutron_exception(self):
|
||||
cls = d_lb_public_ip.FloatingIpServicePubIPDriver
|
||||
m_driver = mock.Mock(spec=cls)
|
||||
m_driver._drv_pub_ip = public_ip.FipPubIpDriver()
|
||||
neutron = self.useFixture(k_fix.MockNeutronClient()).client
|
||||
neutron.delete_floatingip.side_effect = n_exc.NeutronClientException
|
||||
|
||||
floating_ip = {'floating_ip_address': '1.2.3.5',
|
||||
'id': 'ec29d641-fec4-4f67-928a-124a76b3a888'}
|
||||
|
||||
service_pub_ip_info = \
|
||||
obj_lbaas.LBaaSPubIp(ip_id=floating_ip['id'],
|
||||
ip_addr=floating_ip['floating_ip_address'],
|
||||
alloc_method='pool')
|
||||
|
||||
self.assertRaises(
|
||||
n_exc.NeutronClientException, cls.release_pub_ip,
|
||||
m_driver, service_pub_ip_info)
|
||||
|
||||
def test_release_pub_ip_alloc_method_pool_neutron_succeeded(self):
|
||||
cls = d_lb_public_ip.FloatingIpServicePubIPDriver
|
||||
m_driver = mock.Mock(spec=cls)
|
||||
m_driver._drv_pub_ip = public_ip.FipPubIpDriver()
|
||||
neutron = self.useFixture(k_fix.MockNeutronClient()).client
|
||||
neutron.delete_floatingip.return_value = None
|
||||
|
||||
floating_ip = {'floating_ip_address': '1.2.3.5',
|
||||
'id': 'ec29d641-fec4-4f67-928a-124a76b3a888'}
|
||||
|
||||
service_pub_ip_info = \
|
||||
obj_lbaas.LBaaSPubIp(ip_id=floating_ip['id'],
|
||||
ip_addr=floating_ip['floating_ip_address'],
|
||||
alloc_method='pool')
|
||||
|
||||
self.assertIsNone(cls.release_pub_ip
|
||||
(m_driver, service_pub_ip_info))
|
||||
|
||||
def test_associate_pub_ip_empty_params(self):
|
||||
cls = d_lb_public_ip.FloatingIpServicePubIPDriver
|
||||
m_driver = mock.Mock(spec=cls)
|
||||
neutron = self.useFixture(k_fix.MockNeutronClient()).client
|
||||
neutron.update_floatingip.return_value = None
|
||||
|
||||
service_pub_ip_info = None
|
||||
vip_port_id = None
|
||||
|
||||
self.assertIsNone(cls.associate_pub_ip
|
||||
(m_driver, service_pub_ip_info, vip_port_id))
|
||||
|
||||
def test_associate_lb_fip_id_not_exist(self):
|
||||
cls = d_lb_public_ip.FloatingIpServicePubIPDriver
|
||||
m_driver = mock.Mock(spec=cls)
|
||||
neutron = self.useFixture(k_fix.MockNeutronClient()).client
|
||||
neutron.update_floatingip.return_value = None
|
||||
m_driver._drv_pub_ip = public_ip.FipPubIpDriver()
|
||||
|
||||
floating_ip = {'floating_ip_address': '1.2.3.5',
|
||||
'id': 'ec29d641-fec4-4f67-928a-124a76b3a888'}
|
||||
service_pub_ip_info = \
|
||||
obj_lbaas.LBaaSPubIp(ip_id=0,
|
||||
ip_addr=floating_ip['floating_ip_address'],
|
||||
alloc_method='pool')
|
||||
|
||||
vip_port_id = 'ec29d641-fec4-4f67-928a-124a76b3a777'
|
||||
|
||||
self.assertIsNone(cls.associate_pub_ip
|
||||
(m_driver, service_pub_ip_info, vip_port_id))
|
||||
|
||||
def test_associate_lb_fip_id_not_exist_neutron_exception(self):
|
||||
cls = d_lb_public_ip.FloatingIpServicePubIPDriver
|
||||
m_driver = mock.Mock(spec=cls)
|
||||
m_driver._drv_pub_ip = public_ip.FipPubIpDriver()
|
||||
neutron = self.useFixture(k_fix.MockNeutronClient()).client
|
||||
neutron.update_floatingip.side_effect = n_exc.NeutronClientException
|
||||
|
||||
floating_ip = {'floating_ip_address': '1.2.3.5',
|
||||
'id': 'ec29d641-fec4-4f67-928a-124a76b3a888'}
|
||||
service_pub_ip_info = \
|
||||
obj_lbaas.LBaaSPubIp(ip_id=floating_ip['id'],
|
||||
ip_addr=floating_ip['floating_ip_address'],
|
||||
alloc_method='pool')
|
||||
vip_port_id = 'ec29d641-fec4-4f67-928a-124a76b3a777'
|
||||
|
||||
self.assertRaises(
|
||||
n_exc.NeutronClientException, cls.associate_pub_ip,
|
||||
m_driver, service_pub_ip_info, vip_port_id)
|
||||
|
||||
def test_disassociate_pub_ip_empty_param(self):
|
||||
cls = d_lb_public_ip.FloatingIpServicePubIPDriver
|
||||
m_driver = mock.Mock(spec=cls)
|
||||
neutron = self.useFixture(k_fix.MockNeutronClient()).client
|
||||
neutron.update_floatingip.return_value = None
|
||||
service_pub_ip_info = None
|
||||
|
||||
self.assertIsNone(cls.disassociate_pub_ip
|
||||
(m_driver, service_pub_ip_info))
|
||||
|
||||
def test_disassociate_pub_ip_fip_id_not_exist(self):
|
||||
cls = d_lb_public_ip.FloatingIpServicePubIPDriver
|
||||
m_driver = mock.Mock(spec=cls)
|
||||
m_driver._drv_pub_ip = public_ip.FipPubIpDriver()
|
||||
neutron = self.useFixture(k_fix.MockNeutronClient()).client
|
||||
neutron.update_floatingip.return_value = None
|
||||
floating_ip = {'floating_ip_address': '1.2.3.5',
|
||||
'id': 'ec29d641-fec4-4f67-928a-124a76b3a888'}
|
||||
|
||||
service_pub_ip_info = \
|
||||
obj_lbaas.LBaaSPubIp(ip_id=0,
|
||||
ip_addr=floating_ip['floating_ip_address'],
|
||||
alloc_method='pool')
|
||||
|
||||
self.assertIsNone(cls.disassociate_pub_ip
|
||||
(m_driver, service_pub_ip_info))
|
||||
|
||||
def test_disassociate_pub_ip_neutron_exception(self):
|
||||
cls = d_lb_public_ip.FloatingIpServicePubIPDriver
|
||||
m_driver = mock.Mock(spec=cls)
|
||||
m_driver._drv_pub_ip = public_ip.FipPubIpDriver()
|
||||
neutron = self.useFixture(k_fix.MockNeutronClient()).client
|
||||
neutron.update_floatingip.side_effect = n_exc.NeutronClientException
|
||||
floating_ip = {'floating_ip_address': '1.2.3.5',
|
||||
'id': 'ec29d641-fec4-4f67-928a-124a76b3a888'}
|
||||
service_pub_ip_info = \
|
||||
obj_lbaas.LBaaSPubIp(ip_id=floating_ip['id'],
|
||||
ip_addr=floating_ip['floating_ip_address'],
|
||||
alloc_method='pool')
|
||||
|
||||
self.assertRaises(
|
||||
n_exc.NeutronClientException, cls.disassociate_pub_ip,
|
||||
m_driver, service_pub_ip_info)
|
|
@ -0,0 +1,188 @@
|
|||
# Copyright (c) 2017 RedHat, 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.
|
||||
import mock
|
||||
from neutronclient.common import exceptions as n_exc
|
||||
|
||||
from kuryr_kubernetes.controller.drivers import public_ip\
|
||||
as d_public_ip
|
||||
from kuryr_kubernetes.tests import base as test_base
|
||||
from kuryr_kubernetes.tests.unit import kuryr_fixtures as k_fix
|
||||
|
||||
|
||||
class TestFipPubIpDriver(test_base.TestCase):
|
||||
def test_is_ip_available_none_param(self):
|
||||
cls = d_public_ip.FipPubIpDriver
|
||||
m_driver = mock.Mock(spec=cls)
|
||||
fip_ip_addr = None
|
||||
fip_id = cls.is_ip_available(m_driver, fip_ip_addr)
|
||||
|
||||
self.assertIsNone(fip_id)
|
||||
|
||||
def test_is_ip_available_empty_param(self):
|
||||
cls = d_public_ip.FipPubIpDriver
|
||||
m_driver = mock.Mock(spec=cls)
|
||||
fip_ip_addr = None
|
||||
fip_id = cls.is_ip_available(m_driver, fip_ip_addr)
|
||||
|
||||
self.assertIsNone(fip_id)
|
||||
|
||||
def test_is_ip_available_ip_not_exist(self):
|
||||
cls = d_public_ip.FipPubIpDriver
|
||||
m_driver = mock.Mock(spec=cls)
|
||||
neutron = self.useFixture(k_fix.MockNeutronClient()).client
|
||||
|
||||
floating_ip = {'floating_ip_address': '1.2.3.4', 'port_id': None,
|
||||
'id': 'a2a62ea7-e3bf-40df-8c09-aa0c29876a6b'}
|
||||
neutron.list_floatingips.return_value = {'floatingips': [floating_ip]}
|
||||
|
||||
fip_ip_addr = '1.1.1.1'
|
||||
fip_id = cls.is_ip_available(m_driver, fip_ip_addr)
|
||||
self.assertIsNone(fip_id)
|
||||
|
||||
def test_is_ip_available_empty_fip_list(self):
|
||||
cls = d_public_ip.FipPubIpDriver
|
||||
m_driver = mock.Mock(spec=cls)
|
||||
neutron = self.useFixture(k_fix.MockNeutronClient()).client
|
||||
|
||||
floating_ip = None
|
||||
neutron.list_floatingips.return_value = {'floatingips': [floating_ip]}
|
||||
|
||||
fip_ip_addr = '1.1.1.1'
|
||||
fip_id = cls.is_ip_available(m_driver, fip_ip_addr)
|
||||
self.assertIsNone(fip_id)
|
||||
|
||||
def test_is_ip_available_occupied_fip(self):
|
||||
cls = d_public_ip.FipPubIpDriver
|
||||
m_driver = mock.Mock(spec=cls)
|
||||
neutron = self.useFixture(k_fix.MockNeutronClient()).client
|
||||
floating_ip = {'floating_ip_address': '1.2.3.4',
|
||||
'port_id': 'ec29d641-fec4-4f67-928a-124a76b3a8e6'}
|
||||
neutron.list_floatingips.return_value = {'floatingips': [floating_ip]}
|
||||
fip_ip_addr = '1.2.3.4'
|
||||
fip_id = cls.is_ip_available(m_driver, fip_ip_addr)
|
||||
self.assertIsNone(fip_id)
|
||||
|
||||
def test_is_ip_available_ip_exist_and_available(self):
|
||||
cls = d_public_ip.FipPubIpDriver
|
||||
m_driver = mock.Mock(spec=cls)
|
||||
neutron = self.useFixture(k_fix.MockNeutronClient()).client
|
||||
|
||||
floating_ip = {'floating_ip_address': '1.2.3.4', 'port_id': None,
|
||||
'id': 'a2a62ea7-e3bf-40df-8c09-aa0c29876a6b'}
|
||||
neutron.list_floatingips.return_value = {'floatingips': [floating_ip]}
|
||||
|
||||
fip_ip_addr = '1.2.3.4'
|
||||
fip_id = cls.is_ip_available(m_driver, fip_ip_addr)
|
||||
self.assertEqual(fip_id, 'a2a62ea7-e3bf-40df-8c09-aa0c29876a6b')
|
||||
|
||||
def test_allocate_ip_all_green(self):
|
||||
cls = d_public_ip.FipPubIpDriver
|
||||
m_driver = mock.Mock(spec=cls)
|
||||
pub_net_id = mock.sentinel.pub_net_id
|
||||
pub_subnet_id = mock.sentinel.pub_subnet_id
|
||||
project_id = mock.sentinel.project_id
|
||||
description = mock.sentinel.description
|
||||
|
||||
neutron = self.useFixture(k_fix.MockNeutronClient()).client
|
||||
floating_ip = {'floating_ip_address': '1.2.3.5',
|
||||
'id': 'ec29d641-fec4-4f67-928a-124a76b3a888'}
|
||||
neutron.create_floatingip.return_value = {'floatingip': floating_ip}
|
||||
|
||||
fip_id, fip_addr = cls.allocate_ip(
|
||||
m_driver, pub_net_id, pub_subnet_id, project_id, description)
|
||||
self.assertEqual(fip_id, floating_ip['id'])
|
||||
self.assertEqual(fip_addr, floating_ip['floating_ip_address'])
|
||||
|
||||
def test_allocate_ip_neutron_exception(self):
|
||||
cls = d_public_ip.FipPubIpDriver
|
||||
m_driver = mock.Mock(spec=cls)
|
||||
pub_net_id = mock.sentinel.pub_net_id
|
||||
pub_subnet_id = mock.sentinel.pub_subnet_id
|
||||
project_id = mock.sentinel.project_id
|
||||
description = mock.sentinel.description
|
||||
|
||||
neutron = self.useFixture(k_fix.MockNeutronClient()).client
|
||||
neutron.create_floatingip.side_effect = n_exc.NeutronClientException
|
||||
|
||||
self.assertRaises(
|
||||
n_exc.NeutronClientException, cls.allocate_ip,
|
||||
m_driver, pub_net_id, pub_subnet_id, project_id, description)
|
||||
|
||||
def test_free_ip_neutron_exception(self):
|
||||
cls = d_public_ip.FipPubIpDriver
|
||||
m_driver = mock.Mock(spec=cls)
|
||||
res_id = mock.sentinel.res_id
|
||||
|
||||
neutron = self.useFixture(k_fix.MockNeutronClient()).client
|
||||
neutron.delete_floatingip.side_effect = n_exc.NeutronClientException
|
||||
|
||||
self.assertRaises(
|
||||
n_exc.NeutronClientException, cls.free_ip, m_driver, res_id)
|
||||
|
||||
def test_free_ip_succeeded(self):
|
||||
cls = d_public_ip.FipPubIpDriver
|
||||
m_driver = mock.Mock(spec=cls)
|
||||
res_id = mock.sentinel.res_id
|
||||
|
||||
neutron = self.useFixture(k_fix.MockNeutronClient()).client
|
||||
neutron.delete_floatingip.return_value = None
|
||||
try:
|
||||
cls.free_ip(m_driver, res_id)
|
||||
except Exception:
|
||||
self.fail("Encountered an unexpected exception.")
|
||||
|
||||
def test_associate_neutron_exception(self):
|
||||
cls = d_public_ip.FipPubIpDriver
|
||||
m_driver = mock.Mock(spec=cls)
|
||||
res_id = mock.sentinel.res_id
|
||||
vip_port_id = mock.sentinel.vip_port_id
|
||||
|
||||
neutron = self.useFixture(k_fix.MockNeutronClient()).client
|
||||
neutron.update_floatingip.side_effect = n_exc.NeutronClientException
|
||||
retcode = cls.associate(m_driver, res_id, vip_port_id)
|
||||
self.assertIsNone(retcode)
|
||||
|
||||
def test_associate_succeeded(self):
|
||||
cls = d_public_ip.FipPubIpDriver
|
||||
m_driver = mock.Mock(spec=cls)
|
||||
res_id = mock.sentinel.res_id
|
||||
vip_port_id = mock.sentinel.vip_port_id
|
||||
|
||||
neutron = self.useFixture(k_fix.MockNeutronClient()).client
|
||||
neutron.update_floatingip.return_value = None
|
||||
|
||||
retcode = cls.associate(m_driver, res_id, vip_port_id)
|
||||
self.assertIsNone(retcode)
|
||||
|
||||
def test_disassociate_neutron_exception(self):
|
||||
cls = d_public_ip.FipPubIpDriver
|
||||
m_driver = mock.Mock(spec=cls)
|
||||
res_id = mock.sentinel.res_id
|
||||
|
||||
neutron = self.useFixture(k_fix.MockNeutronClient()).client
|
||||
neutron.update_floatingip.side_effect = n_exc.NeutronClientException
|
||||
self.assertIsNone(cls.disassociate
|
||||
(m_driver, res_id))
|
||||
|
||||
def test_disassociate_succeeded(self):
|
||||
cls = d_public_ip.FipPubIpDriver
|
||||
m_driver = mock.Mock(spec=cls)
|
||||
res_id = mock.sentinel.res_id
|
||||
|
||||
neutron = self.useFixture(k_fix.MockNeutronClient()).client
|
||||
neutron.update_floatingip.return_value = None
|
||||
|
||||
self.assertIsNone(cls.disassociate
|
||||
(m_driver, res_id))
|
|
@ -39,7 +39,6 @@ class TestLBaaSSpecHandler(test_base.TestCase):
|
|||
m_get_drv_project.return_value = mock.sentinel.drv_project
|
||||
m_get_drv_subnets.return_value = mock.sentinel.drv_subnets
|
||||
m_get_drv_sg.return_value = mock.sentinel.drv_sg
|
||||
|
||||
handler = h_lbaas.LBaaSSpecHandler()
|
||||
|
||||
self.assertEqual(mock.sentinel.drv_project, handler._drv_project)
|
||||
|
@ -51,11 +50,16 @@ class TestLBaaSSpecHandler(test_base.TestCase):
|
|||
old_spec = mock.sentinel.old_spec
|
||||
new_spec = mock.sentinel.new_spec
|
||||
|
||||
project_id = mock.sentinel.project_id
|
||||
m_drv_project = mock.Mock()
|
||||
m_drv_project.get_project.return_value = project_id
|
||||
|
||||
m_handler = mock.Mock(spec=h_lbaas.LBaaSSpecHandler)
|
||||
m_handler._get_lbaas_spec.return_value = old_spec
|
||||
m_handler._has_lbaas_spec_changes.return_value = True
|
||||
m_handler._generate_lbaas_spec.return_value = new_spec
|
||||
m_handler._should_ignore.return_value = False
|
||||
m_handler._drv_project = m_drv_project
|
||||
|
||||
h_lbaas.LBaaSSpecHandler.on_present(m_handler, svc_event)
|
||||
|
||||
|
@ -105,13 +109,28 @@ class TestLBaaSSpecHandler(test_base.TestCase):
|
|||
ret = h_lbaas.LBaaSSpecHandler._get_service_ip(m_handler, svc_body)
|
||||
self.assertEqual(mock.sentinel.cluster_ip, ret)
|
||||
|
||||
def test_get_service_ip_not_cluster_ip(self):
|
||||
svc_body = {'spec': {'type': 'notClusterIP',
|
||||
svc_body = {'spec': {'type': 'LoadBalancer',
|
||||
'clusterIP': mock.sentinel.cluster_ip}}
|
||||
m_handler = mock.Mock(spec=h_lbaas.LBaaSSpecHandler)
|
||||
|
||||
ret = h_lbaas.LBaaSSpecHandler._get_service_ip(m_handler, svc_body)
|
||||
self.assertIsNone(ret)
|
||||
self.assertEqual(mock.sentinel.cluster_ip, ret)
|
||||
|
||||
def test_is_supported_type_clusterip(self):
|
||||
m_handler = mock.Mock(spec=h_lbaas.LBaaSSpecHandler)
|
||||
svc_body = {'spec': {'type': 'ClusterIP',
|
||||
'clusterIP': mock.sentinel.cluster_ip}}
|
||||
|
||||
ret = h_lbaas.LBaaSSpecHandler._is_supported_type(m_handler, svc_body)
|
||||
self.assertEqual(ret, True)
|
||||
|
||||
def test_is_supported_type_loadbalancer(self):
|
||||
m_handler = mock.Mock(spec=h_lbaas.LBaaSSpecHandler)
|
||||
svc_body = {'spec': {'type': 'LoadBalancer',
|
||||
'clusterIP': mock.sentinel.cluster_ip}}
|
||||
|
||||
ret = h_lbaas.LBaaSSpecHandler._is_supported_type(m_handler, svc_body)
|
||||
self.assertEqual(ret, True)
|
||||
|
||||
def _make_test_net_obj(self, cidr_list):
|
||||
subnets = [osv_subnet.Subnet(cidr=cidr) for cidr in cidr_list]
|
||||
|
@ -141,6 +160,8 @@ class TestLBaaSSpecHandler(test_base.TestCase):
|
|||
spec_ctor_path = 'kuryr_kubernetes.objects.lbaas.LBaaSServiceSpec'
|
||||
with mock.patch(spec_ctor_path) as m_spec_ctor:
|
||||
m_spec_ctor.return_value = mock.sentinel.ret_obj
|
||||
service = {'spec': {'type': 'ClusterIP'}}
|
||||
|
||||
ret_obj = h_lbaas.LBaaSSpecHandler._generate_lbaas_spec(
|
||||
m_handler, service)
|
||||
self.assertEqual(mock.sentinel.ret_obj, ret_obj)
|
||||
|
@ -149,7 +170,9 @@ class TestLBaaSSpecHandler(test_base.TestCase):
|
|||
project_id=project_id,
|
||||
subnet_id=subnet_id,
|
||||
ports=ports,
|
||||
security_groups_ids=sg_ids)
|
||||
security_groups_ids=sg_ids,
|
||||
type='ClusterIP',
|
||||
lb_ip=None)
|
||||
|
||||
m_drv_project.get_project.assert_called_once_with(service)
|
||||
m_handler._get_service_ip.assert_called_once_with(service)
|
||||
|
@ -358,22 +381,26 @@ class FakeLBaaSDriver(drv_base.LBaaSDriver):
|
|||
|
||||
|
||||
class TestLoadBalancerHandler(test_base.TestCase):
|
||||
@mock.patch('kuryr_kubernetes.controller.drivers.base'
|
||||
'.ServicePubIpDriver.get_instance')
|
||||
@mock.patch('kuryr_kubernetes.controller.drivers.base'
|
||||
'.PodSubnetsDriver.get_instance')
|
||||
@mock.patch('kuryr_kubernetes.controller.drivers.base'
|
||||
'.PodProjectDriver.get_instance')
|
||||
@mock.patch('kuryr_kubernetes.controller.drivers.base'
|
||||
'.LBaaSDriver.get_instance')
|
||||
def test_init(self, m_get_drv_lbaas, m_get_drv_project, m_get_drv_subnets):
|
||||
def test_init(self, m_get_drv_lbaas, m_get_drv_project,
|
||||
m_get_drv_subnets, m_get_drv_service_pub_ip):
|
||||
m_get_drv_lbaas.return_value = mock.sentinel.drv_lbaas
|
||||
m_get_drv_project.return_value = mock.sentinel.drv_project
|
||||
m_get_drv_subnets.return_value = mock.sentinel.drv_subnets
|
||||
|
||||
m_get_drv_service_pub_ip.return_value = mock.sentinel.drv_lb_ip
|
||||
handler = h_lbaas.LoadBalancerHandler()
|
||||
|
||||
self.assertEqual(mock.sentinel.drv_lbaas, handler._drv_lbaas)
|
||||
self.assertEqual(mock.sentinel.drv_project, handler._drv_pod_project)
|
||||
self.assertEqual(mock.sentinel.drv_subnets, handler._drv_pod_subnets)
|
||||
self.assertEqual(mock.sentinel.drv_lb_ip, handler._drv_service_pub_ip)
|
||||
|
||||
def test_on_present(self):
|
||||
lbaas_spec = mock.sentinel.lbaas_spec
|
||||
|
|
|
@ -65,6 +65,9 @@ kuryr_kubernetes.controller.drivers.pod_vif =
|
|||
kuryr_kubernetes.controller.drivers.endpoints_lbaas =
|
||||
lbaasv2 = kuryr_kubernetes.controller.drivers.lbaasv2:LBaaSv2Driver
|
||||
|
||||
kuryr_kubernetes.controller.drivers.service_public_ip =
|
||||
neutron_floating_ip = kuryr_kubernetes.controller.drivers.lb_public_ip:FloatingIpServicePubIPDriver
|
||||
|
||||
kuryr_kubernetes.controller.drivers.vif_pool =
|
||||
noop = kuryr_kubernetes.controller.drivers.vif_pool:NoopVIFPool
|
||||
neutron = kuryr_kubernetes.controller.drivers.vif_pool:NeutronVIFPool
|
||||
|
|
Loading…
Reference in New Issue