Add kubernetes pod protectable plugin to Karbor

Because of lots of special requirements(hardware, software, network,
performance, etc) and manual configurations, the environment of
the k8s running on the openstack(devstack) nova instance can not be
deployed on the jenkin job node automatically. So fullstack tests
about pod protection will not be added. I will add guide doc about how
to deploy k8s and protect pod with karbor in future.

Change-Id: Iedf7b15e5a87fcafd3ca61694a49d68f2a5c643a
Implements: blueprint kubernetes-pods-protection-plugin
This commit is contained in:
chenying 2017-08-30 14:59:12 +08:00
parent 4b53170235
commit 45f20e0a39
7 changed files with 216 additions and 7 deletions

View File

@ -48,6 +48,7 @@ RESOURCE_TYPES = (PROJECT_RESOURCE_TYPE,
SHARE_RESOURCE_TYPE,
NETWORK_RESOURCE_TYPE,
DATABASE_RESOURCE_TYPE,
POD_RESOURCE_TYPE
) = ('OS::Keystone::Project',
'OS::Nova::Server',
'OS::Cinder::Volume',
@ -55,6 +56,7 @@ RESOURCE_TYPES = (PROJECT_RESOURCE_TYPE,
'OS::Manila::Share',
'OS::Neutron::Network',
'OS::Trove::Instance',
'OS::Kubernetes::Pod',
)
# plan status
PLAN_STATUS_SUSPENDED = 'suspended'

View File

@ -20,12 +20,13 @@ class ProtectablePlugin(object):
"""
def __init__(self, context=None):
def __init__(self, context=None, conf=None):
super(ProtectablePlugin, self).__init__()
self._context = context
self._conf = conf
def instance(self, context):
return self.__class__(context)
def instance(self, context=None, conf=None):
return self.__class__(context, conf)
@abc.abstractmethod
def get_resource_type(self):

View File

@ -0,0 +1,115 @@
# 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 six
import uuid
from karbor.common import constants
from karbor import exception
from karbor import resource
from karbor.services.protection.client_factory import ClientFactory
from karbor.services.protection import protectable_plugin
from oslo_config import cfg
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
pod_protectable_opts = [
cfg.StrOpt('namespace',
default='default',
help='The namespace name that kubernetes client use.')
]
def register_opts(conf):
conf.register_opts(pod_protectable_opts, group='pod_protectable')
INVALID_POD_STATUS = ['Pending', 'Failed', 'Unknown']
class K8sPodProtectablePlugin(protectable_plugin.ProtectablePlugin):
"""K8s pod protectable plugin"""
_SUPPORT_RESOURCE_TYPE = constants.POD_RESOURCE_TYPE
def __init__(self, context=None, config=None):
super(K8sPodProtectablePlugin, self).__init__(context, config)
self.namespace = None
if self._conf:
register_opts(self._conf)
plugin_cfg = self._conf.pod_protectable
self.namespace = plugin_cfg.namespace
def _client(self, context):
self._client_instance = ClientFactory.create_client(
"k8s", context)
return self._client_instance
def get_resource_type(self):
return self._SUPPORT_RESOURCE_TYPE
def get_parent_resource_types(self):
return (constants.PROJECT_RESOURCE_TYPE)
def list_resources(self, context, parameters=None):
try:
pods = self._client(context).list_namespaced_pod(self.namespace)
except Exception as e:
LOG.exception("List all summary pods from kubernetes failed.")
raise exception.ListProtectableResourceFailed(
type=self._SUPPORT_RESOURCE_TYPE,
reason=six.text_type(e))
else:
return [resource.Resource(
type=self._SUPPORT_RESOURCE_TYPE,
id=uuid.uuid5(uuid.NAMESPACE_OID, "%s:%s" % (
self.namespace, pod.metadata.name)),
name="%s:%s" % (self.namespace, pod.metadata.name),
extra_info={'namespace': self.namespace})
for pod in pods.items
if pod.status.phase not in INVALID_POD_STATUS]
def show_resource(self, context, resource_id, parameters=None):
try:
if not parameters:
raise
name = parameters.get("name", None)
if ":" in name:
pod_namespace, pod_name = name.split(":")
else:
pod_namespace = self.namespace
pod_name = name
pod = self._client(context).read_namespaced_pod(
pod_name, pod_namespace)
except Exception as e:
LOG.exception("Show a summary pod from kubernetes failed.")
raise exception.ProtectableResourceNotFound(
id=resource_id,
type=self._SUPPORT_RESOURCE_TYPE,
reason=six.text_type(e))
else:
if pod.status.phase in INVALID_POD_STATUS:
raise exception.ProtectableResourceInvalidStatus(
id=resource_id, type=self._SUPPORT_RESOURCE_TYPE,
status=pod.status.phase)
return resource.Resource(
type=self._SUPPORT_RESOURCE_TYPE,
id=uuid.uuid5(uuid.NAMESPACE_OID, "%s:%s" % (
self.namespace, pod.metadata.name)),
name="%s:%s" % (pod_namespace, pod.metadata.name),
extra_info={'namespace': pod_namespace})
def get_dependent_resources(self, context, parent_resource):
self.list_resources(context)

View File

@ -16,6 +16,7 @@ from karbor.i18n import _
from karbor.services.protection.graph import build_graph
import six
from oslo_config import cfg
from oslo_log import log as logging
from stevedore import extension
@ -53,11 +54,12 @@ class ProtectableRegistry(object):
def register_plugin(self, plugin):
self._plugin_map[plugin.get_resource_type()] = plugin
def _get_protectable(self, context, resource_type):
def _get_protectable(self, context, resource_type, conf=cfg.CONF):
if resource_type in self._protectable_map:
return self._protectable_map[resource_type]
protectable = self._plugin_map[resource_type].instance(context)
protectable = self._plugin_map[resource_type].instance(
context, conf)
self._protectable_map[resource_type] = protectable
return protectable

View File

@ -0,0 +1,88 @@
# 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 karbor.context import RequestContext
from karbor.resource import Resource
from karbor.services.protection.clients import k8s # noqa
from karbor.services.protection.protectable_plugins.pod \
import K8sPodProtectablePlugin
from kubernetes.client.models.v1_object_meta import V1ObjectMeta
from kubernetes.client.models.v1_pod import V1Pod
from kubernetes.client.models.v1_pod_list import V1PodList
from kubernetes.client.models.v1_pod_status import V1PodStatus
from oslo_config import cfg
from karbor.tests import base
import mock
import uuid
class PodProtectablePluginTest(base.TestCase):
def setUp(self):
super(PodProtectablePluginTest, self).setUp()
self._context = RequestContext(user_id='demo',
project_id='abcd',
auth_token='efgh',
service_catalog=None)
def test_get_resource_type(self):
plugin = K8sPodProtectablePlugin(self._context, cfg.CONF)
self.assertEqual('OS::Kubernetes::Pod', plugin.get_resource_type())
def test_get_parent_resource_types(self):
plugin = K8sPodProtectablePlugin(self._context, cfg.CONF)
self.assertEqual(("OS::Keystone::Project"),
plugin.get_parent_resource_types())
@mock.patch('kubernetes.client.apis.core_v1_api.'
'CoreV1Api.list_namespaced_pod')
def test_list_resources(self, mock_pod_list):
plugin = K8sPodProtectablePlugin(self._context, cfg.CONF)
pod = V1Pod(api_version="v1", kind="Pod",
metadata=V1ObjectMeta(
name="busybox-test",
namespace="default",
uid="dd8236e1-8c6c-11e7-9b7a-fa163e18e097"),
status=V1PodStatus(phase="Running"))
pod_list = V1PodList(items=[pod])
mock_pod_list.return_value = pod_list
self.assertEqual([
Resource('OS::Kubernetes::Pod',
uuid.uuid5(uuid.NAMESPACE_OID, "default:busybox-test"),
'default:busybox-test')],
plugin.list_resources(self._context))
@mock.patch('kubernetes.client.apis.core_v1_api.'
'CoreV1Api.read_namespaced_pod')
def test_show_resource(self, mock_pod_get):
plugin = K8sPodProtectablePlugin(self._context, cfg.CONF)
pod = V1Pod(api_version="v1", kind="Pod",
metadata=V1ObjectMeta(
name="busybox-test",
namespace="default",
uid="dd8236e1-8c6c-11e7-9b7a-fa163e18e097"),
status=V1PodStatus(phase="Running"))
mock_pod_get.return_value = pod
self.assertEqual(Resource(
'OS::Kubernetes::Pod',
uuid.uuid5(uuid.NAMESPACE_OID, "default:busybox-test"),
'default:busybox-test'),
plugin.show_resource(self._context,
uuid.uuid5(uuid.NAMESPACE_OID,
"default:busybox-test"),
{'name': 'default:busybox-test'})
)

View File

@ -20,11 +20,11 @@ _FAKE_TYPE = "Karbor::Test::Fake"
class _FakeProtectablePlugin(ProtectablePlugin):
def __init__(self, cntx):
def __init__(self, cntx, conf=None):
super(_FakeProtectablePlugin, self).__init__(cntx)
self.graph = {}
def instance(self, cntx):
def instance(self, cntx, conf=None):
new = self.__class__(cntx)
new.graph = self.graph
return new

View File

@ -56,6 +56,7 @@ karbor.protectables =
share = karbor.services.protection.protectable_plugins.share:ShareProtectablePlugin
network = karbor.services.protection.protectable_plugins.network:NetworkProtectablePlugin
database = karbor.services.protection.protectable_plugins.database:DatabaseInstanceProtectablePlugin
pod = karbor.services.protection.protectable_plugins.pod:K8sPodProtectablePlugin
karbor.operationengine.engine.timetrigger.time_format =
crontab = karbor.services.operationengine.engine.triggers.timetrigger.timeformats.crontab_time:Crontab
calendar = karbor.services.operationengine.engine.triggers.timetrigger.timeformats.calendar_time:ICal