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:
parent
4b53170235
commit
45f20e0a39
|
@ -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'
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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'})
|
||||
)
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue