Kuryr-Controller: make handlers pluggable

This patch introduces a new way for configuring which handlers the
Kuryr controller should be using. This will allow people to use
externally provided handlers as long as they are installed as
entrypoints of the right namespace.

Implements: blueprint kuryr-pluggable-handlers

Change-Id: I52ce0ef00771c8587d7f7113cc5eb4839d1309a5
Signed-off-by: Antoni Segura Puimedon <antonisp@celebdor.com>
This commit is contained in:
Antoni Segura Puimedon 2018-03-23 11:46:19 +01:00 committed by Yossi Boaron
parent c8aa90029a
commit d3a6cdcf99
9 changed files with 127 additions and 12 deletions

View File

@ -149,6 +149,10 @@ k8s_opts = [
cfg.IntOpt('watch_retry_timeout',
help=_('Time (in seconds) the watcher retries watching for.'),
default=60),
cfg.ListOpt('enabled_handlers',
help=_("The comma-separated handlers that should be "
"registered for watching in the pipeline."),
default=['vif', 'lb', 'lbaasspec']),
]
neutron_defaults = [

View File

@ -38,6 +38,7 @@ class LBaaSSpecHandler(k8s_base.ResourceEventHandler):
"""
OBJECT_KIND = k_const.K8S_OBJ_SERVICE
OBJECT_WATCH_PATH = "%s/%s" % (k_const.K8S_API_BASE, "services")
def __init__(self):
super(LBaaSSpecHandler, self).__init__()
@ -218,6 +219,7 @@ class LoadBalancerHandler(k8s_base.ResourceEventHandler):
"""
OBJECT_KIND = k_const.K8S_OBJ_ENDPOINTS
OBJECT_WATCH_PATH = "%s/%s" % (k_const.K8S_API_BASE, "endpoints")
def __init__(self):
super(LoadBalancerHandler, self).__init__()

View File

@ -38,6 +38,7 @@ class VIFHandler(k8s_base.ResourceEventHandler):
"""
OBJECT_KIND = constants.K8S_OBJ_POD
OBJECT_WATCH_PATH = "%s/%s" % (constants.K8S_API_BASE, "pods")
def __init__(self):
super(VIFHandler, self).__init__()

View File

@ -16,22 +16,52 @@
import sys
import os_vif
from oslo_config import cfg
from oslo_log import log as logging
from oslo_service import service
from stevedore.named import NamedExtensionManager
from kuryr_kubernetes import clients
from kuryr_kubernetes import config
from kuryr_kubernetes import constants
from kuryr_kubernetes.controller.handlers import lbaas as h_lbaas
from kuryr_kubernetes.controller.handlers import pipeline as h_pipeline
from kuryr_kubernetes.controller.handlers import vif as h_vif
from kuryr_kubernetes.controller.managers import health
from kuryr_kubernetes import objects
from kuryr_kubernetes import watcher
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
def _handler_not_found(names):
LOG.exception('Handlers "%s" were not found.', names)
LOG.critical('Handlers "%s" were not found.', names)
raise SystemExit()
def _handler_not_loaded(manager, entrypoint, exception):
LOG.exception('Exception when loading handlers %s.', entrypoint)
LOG.critical('Handlers entrypoint "%s" failed to load due to %s.',
entrypoint, exception)
raise SystemExit()
def _load_kuryr_ctrlr_handlers():
configured_handlers = CONF.kubernetes.enabled_handlers
LOG.info('Configured handlers: %s', configured_handlers)
handlers = NamedExtensionManager(
'kuryr_kubernetes.controller.handlers',
configured_handlers,
invoke_on_load=True,
on_missing_entrypoints_callback=_handler_not_found,
on_load_failure_callback=_handler_not_loaded)
LOG.info('Loaded handlers: %s', handlers.names())
ctrlr_handlers = []
for handler in handlers.extensions:
ctrlr_handlers.append(handler.obj)
return ctrlr_handlers
class KuryrK8sService(service.Service):
"""Kuryr-Kubernetes controller Service."""
@ -42,12 +72,11 @@ class KuryrK8sService(service.Service):
pipeline = h_pipeline.ControllerPipeline(self.tg)
self.watcher = watcher.Watcher(pipeline, self.tg)
self.health_manager = health.HealthServer()
# TODO(ivc): pluggable resource/handler registration
for resource in ["pods", "services", "endpoints"]:
self.watcher.add("%s/%s" % (constants.K8S_API_BASE, resource))
pipeline.register(h_vif.VIFHandler())
pipeline.register(h_lbaas.LBaaSSpecHandler())
pipeline.register(h_lbaas.LoadBalancerHandler())
handlers = _load_kuryr_ctrlr_handlers()
for handler in handlers:
self.watcher.add(handler.get_watch_path())
pipeline.register(handler)
def start(self):
LOG.info("Service '%s' starting", self.__class__.__name__)

View File

@ -34,9 +34,13 @@ def object_uid(event):
class ResourceEventHandler(dispatch.EventConsumer, health.HealthHandler):
"""Base class for K8s event handlers.
Implementing classes should override the `OBJECT_KIND` attribute with a
valid Kubernetes object type name (e.g. 'Pod' or 'Namespace'; see [1]
for more details).
Implementing classes should override both `OBJECT_KIND` and
'OBJECT_WATCH_PATH' attributes.
The `OBJECT_KIND` should be set to a valid Kubernetes object type
name (e.g. 'Pod' or 'Namespace'; see [1] for more details).
The `OBJECT_WATCH_PATH` should point to object's watched path,
(e.g. for the 'Pod' case the OBJECT_WATCH_PATH should be '/api/v1/pods').
Implementing classes are expected to override any or all of the
`on_added`, `on_present`, `on_modified`, `on_deleted` methods that would
@ -48,10 +52,14 @@ class ResourceEventHandler(dispatch.EventConsumer, health.HealthHandler):
"""
OBJECT_KIND = None
OBJECT_WATCH_PATH = None
def __init__(self):
super(ResourceEventHandler, self).__init__()
def get_watch_path(self):
return self.OBJECT_WATCH_PATH
@property
def consumes(self):
return {object_kind: self.OBJECT_KIND}

View File

@ -0,0 +1,24 @@
# Copyright (c) 2018 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_kubernetes.handlers import k8s_base
class TestHandler(k8s_base.ResourceEventHandler):
OBJECT_KIND = 'DUMMY'
OBJECT_WATCH_PATH = 'DUMMY_PATH'
def __init__(self):
super(TestHandler, self).__init__()

View File

@ -17,6 +17,8 @@ import mock
from kuryr_kubernetes.controller import service
from kuryr_kubernetes.tests import base as test_base
from kuryr_kubernetes.tests.unit.controller.handlers import test_fake_handler
from oslo_config import cfg
class TestControllerService(test_base.TestCase):
@ -39,3 +41,19 @@ class TestControllerService(test_base.TestCase):
m_svc.assert_called()
m_oslo_launch.assert_called()
m_launcher.wait.assert_called()
def test_check_test_handler(self):
cfg.CONF.set_override('enabled_handlers', ['test_handler'],
group='kubernetes')
handlers = service._load_kuryr_ctrlr_handlers()
for handler in handlers:
self.assertEqual(handler.get_watch_path(),
test_fake_handler.TestHandler.OBJECT_WATCH_PATH)
@mock.patch('kuryr_kubernetes.controller.service._handler_not_found')
def test_handler_not_found(self, m_handler_not_found):
cfg.CONF.set_override('enabled_handlers', ['fake_handler'],
group='kubernetes')
service._load_kuryr_ctrlr_handlers()
m_handler_not_found.assert_called()

View File

@ -0,0 +1,23 @@
---
features:
- |
Introduced a pluggable interface for the Kuryr controller handlers.
Each Controller handler associates itself with specific Kubernetes
object kind and is expected to process the events of the watched
Kubernetes API endpoints.
The pluggable handlers framework enable both using externally provided
handlers in Kuryr Controller and controlling which handlers
should be active.
To control which Kuryr Controller handlers should be active, the selected
handlers need to be included at the kuryr.conf at the 'kubernetes'
section.
If not specified, Kuryr Controller will run the default handlers.
For example, to enable only the 'vif' controller handler we should set
the following at kuryr.conf:
.. code-block:: ini
[kubernetes]
enabled_handlers=vif

View File

@ -75,6 +75,12 @@ kuryr_kubernetes.controller.drivers.vif_pool =
nested = kuryr_kubernetes.controller.drivers.vif_pool:NestedVIFPool
multi_pool = kuryr_kubernetes.controller.drivers.vif_pool:MultiVIFPool
kuryr_kubernetes.controller.handlers =
vif = kuryr_kubernetes.controller.handlers.vif:VIFHandler
lbaasspec = kuryr_kubernetes.controller.handlers.lbaas:LBaaSSpecHandler
lb = kuryr_kubernetes.controller.handlers.lbaas:LoadBalancerHandler
test_handler = kuryr_kubernetes.tests.unit.controller.handlers.test_fake_handler:TestHandler
[files]
packages =
kuryr_kubernetes