Add pod to the parent resource types of volume protectable plugins

Change-Id: I3e3a76450ba91e9b04400841b13a23efcbd5cca2
Implements: blueprint kubernetes-pods-protection-plugin
This commit is contained in:
chenying 2017-09-05 15:49:50 +08:00
parent 45f20e0a39
commit 892f96c9a6
7 changed files with 187 additions and 14 deletions

View File

@ -206,10 +206,11 @@ class ProtectablesController(wsgi.Controller):
for instance in instances:
protectable_id = instance.get("id")
instance["type"] = protectable_type
protectable_name = instance.get("name", None)
if protectable_id is None:
raise exception.InvalidProtectableInstance()
dependents = self.protection_api.list_protectable_dependents(
context, protectable_id, protectable_type)
context, protectable_id, protectable_type, protectable_name)
instance["dependent_resources"] = dependents
retval_instances = self._view_builder.detail_list(req, instances)
@ -289,7 +290,8 @@ class ProtectablesController(wsgi.Controller):
raise exc.HTTPInternalServerError(explanation=msg)
dependents = self.protection_api.list_protectable_dependents(
context, protectable_id, protectable_type)
context, protectable_id, protectable_type,
instance.get("name", None))
instance["dependent_resources"] = dependents
retval_instance = self._view_builder.detail(req, instance)

View File

@ -82,11 +82,13 @@ class API(base.Base):
def list_protectable_dependents(self, context,
protectable_id,
protectable_type):
protectable_type,
protectable_name):
return self.protection_rpcapi.list_protectable_dependents(
context,
protectable_id,
protectable_type
protectable_type,
protectable_name
)
def show_protectable_instance(self, context,

View File

@ -359,14 +359,16 @@ class ProtectionManager(manager.Manager):
@messaging.expected_exceptions(exception.ListProtectableResourceFailed)
def list_protectable_dependents(self, context,
protectable_id,
protectable_type):
protectable_type,
protectable_name):
LOG.info("Start to list dependents of resource (type:%(type)s, "
"id:%(id)s)",
"id:%(id)s, name:%(name)s)",
{'type': protectable_type,
'id': protectable_id})
'id': protectable_id,
'name': protectable_name})
parent_resource = Resource(type=protectable_type, id=protectable_id,
name="")
name=protectable_name)
registry = self.protectable_registry
try:

View File

@ -36,11 +36,18 @@ class VolumeProtectablePlugin(protectable_plugin.ProtectablePlugin):
return self._client_instance
def _k8s_client(self, context):
self._k8s_client_instance = ClientFactory.create_client(
"k8s", context)
return self._k8s_client_instance
def get_resource_type(self):
return self._SUPPORT_RESOURCE_TYPE
def get_parent_resource_types(self):
return (constants.SERVER_RESOURCE_TYPE,
constants.POD_RESOURCE_TYPE,
constants.PROJECT_RESOURCE_TYPE)
def list_resources(self, context, parameters=None):
@ -78,7 +85,55 @@ class VolumeProtectablePlugin(protectable_plugin.ProtectablePlugin):
id=volume.id, name=volume.name,
extra_info={'availability_zone': volume.availability_zone})
def get_dependent_resources(self, context, parent_resource):
def _get_dependent_resources_by_pod(self, context, parent_resource):
try:
name = parent_resource.name
pod_namespace, pod_name = name.split(":")
pod = self._k8s_client(context).read_namespaced_pod(
pod_name, pod_namespace)
if not pod.spec.volumes:
return []
mounted_vol_list = []
for volume in pod.spec.volumes:
volume_pvc = volume.persistent_volume_claim
volume_cinder = volume.cinder
if volume_pvc:
pvc_name = volume_pvc.claim_name
pvc = self._k8s_client(
context).read_namespaced_persistent_volume_claim(
pvc_name, pod_namespace)
pv_name = pvc.spec.volume_name
if pv_name:
pv = self._k8s_client(
context).read_persistent_volume(pv_name)
if pv.spec.cinder:
mounted_vol_list.append(
pv.spec.cinder.volume_id)
elif volume_cinder:
mounted_vol_list.append(
volume_cinder.volume_id)
except Exception as e:
LOG.exception("Get mounted volumes from kubernetes "
"pod failed.")
raise exception.ProtectableResourceNotFound(
id=parent_resource.id,
type=parent_resource.type,
reason=six.text_type(e))
try:
volumes = self._client(context).volumes.list(detailed=True)
except Exception as e:
LOG.exception("List all detailed volumes from cinder failed.")
raise exception.ListProtectableResourceFailed(
type=self._SUPPORT_RESOURCE_TYPE,
reason=six.text_type(e))
else:
return [resource.Resource(
type=self._SUPPORT_RESOURCE_TYPE, id=vol.id, name=vol.name,
extra_info={'availability_zone': vol.availability_zone})
for vol in volumes if (vol.id in mounted_vol_list)]
def _get_dependent_resources_by_server(self, context, parent_resource):
def _is_attached_to(vol):
if parent_resource.type == constants.SERVER_RESOURCE_TYPE:
return any([s.get('server_id') == parent_resource.id
@ -88,7 +143,6 @@ class VolumeProtectablePlugin(protectable_plugin.ProtectablePlugin):
vol,
'os-vol-tenant-attr:tenant_id'
) == parent_resource.id
try:
volumes = self._client(context).volumes.list(detailed=True)
except Exception as e:
@ -101,3 +155,15 @@ class VolumeProtectablePlugin(protectable_plugin.ProtectablePlugin):
type=self._SUPPORT_RESOURCE_TYPE, id=vol.id, name=vol.name,
extra_info={'availability_zone': vol.availability_zone})
for vol in volumes if _is_attached_to(vol)]
def get_dependent_resources(self, context, parent_resource):
if parent_resource.type in (constants.SERVER_RESOURCE_TYPE,
constants.PROJECT_RESOURCE_TYPE):
return self._get_dependent_resources_by_server(context,
parent_resource)
if parent_resource.type == constants.POD_RESOURCE_TYPE:
return self._get_dependent_resources_by_pod(context,
parent_resource)
return []

View File

@ -121,13 +121,15 @@ class ProtectionAPI(object):
def list_protectable_dependents(self,
ctxt, protectable_id=None,
protectable_type=None):
protectable_type=None,
protectable_name=None):
cctxt = self.client.prepare(version='1.0')
return cctxt.call(
ctxt,
'list_protectable_dependents',
protectable_id=protectable_id,
protectable_type=protectable_type)
protectable_type=protectable_type,
protectable_name=protectable_name)
def show_protectable_instance(self,
ctxt, protectable_type=None,

View File

@ -20,6 +20,26 @@ from karbor.resource import Resource
from karbor.services.protection.protectable_plugins.volume \
import VolumeProtectablePlugin
from kubernetes.client.models.v1_cinder_volume_source \
import V1CinderVolumeSource
from kubernetes.client.models.v1_object_meta import V1ObjectMeta
from kubernetes.client.models.v1_persistent_volume import V1PersistentVolume
from kubernetes.client.models.v1_persistent_volume_claim \
import V1PersistentVolumeClaim
from kubernetes.client.models.v1_persistent_volume_claim_spec \
import V1PersistentVolumeClaimSpec
from kubernetes.client.models.v1_persistent_volume_claim_status \
import V1PersistentVolumeClaimStatus
from kubernetes.client.models.v1_persistent_volume_claim_volume_source \
import V1PersistentVolumeClaimVolumeSource
from kubernetes.client.models.v1_persistent_volume_spec \
import V1PersistentVolumeSpec
from kubernetes.client.models.v1_pod import V1Pod
from kubernetes.client.models.v1_pod_spec import V1PodSpec
from kubernetes.client.models.v1_pod_status import V1PodStatus
from kubernetes.client.models.v1_volume import V1Volume
from karbor.tests import base
from oslo_config import cfg
@ -64,7 +84,8 @@ class VolumeProtectablePluginTest(base.TestCase):
def test_get_parent_resource_types(self):
plugin = VolumeProtectablePlugin(self._context)
self.assertItemsEqual(("OS::Nova::Server", "OS::Keystone::Project"),
self.assertItemsEqual(("OS::Nova::Server", "OS::Kubernetes::Pod",
"OS::Keystone::Project"),
plugin.get_parent_resource_types())
@mock.patch.object(volumes.VolumeManager, 'list')
@ -129,3 +150,81 @@ class VolumeProtectablePluginTest(base.TestCase):
[Resource('OS::Cinder::Volume', '123', 'name123',
{'availability_zone': 'az1'})],
plugin.get_dependent_resources(self._context, project))
@mock.patch.object(volumes.VolumeManager, 'list')
@mock.patch('kubernetes.client.apis.core_v1_api.'
'CoreV1Api.read_persistent_volume')
@mock.patch('kubernetes.client.apis.core_v1_api.'
'CoreV1Api.read_namespaced_persistent_volume_claim')
@mock.patch('kubernetes.client.apis.core_v1_api.'
'CoreV1Api.read_namespaced_pod')
def test_get_pod_dependent_resources(self, mock_pod_read,
mock_pvc_read,
mock_pv_read,
mock_volume_list):
plugin = VolumeProtectablePlugin(self._context)
pod = V1Pod(api_version="v1", kind="Pod",
metadata=V1ObjectMeta(
name="busybox-test",
namespace="default",
uid="dd8236e1-8c6c-11e7-9b7a-fa163e18e097"),
spec=V1PodSpec(
volumes=[V1Volume(
persistent_volume_claim=(
V1PersistentVolumeClaimVolumeSource(
claim_name="cinder-claim1'")))]),
status=V1PodStatus(phase="Running"))
pvc = V1PersistentVolumeClaim(
api_version="v1",
kind="PersistentVolumeClaim",
metadata=V1ObjectMeta(
name="cinder-claim1",
namespace="default",
uid="fec036b7-9123-11e7-a930-fa163e18e097"),
spec=V1PersistentVolumeClaimSpec(
access_modes=["ReadWriteOnce"],
volume_name="pvc-fec036b7-9123-11e7-a930-fa163e18e097"),
status=V1PersistentVolumeClaimStatus(phase="Bound"))
pv = V1PersistentVolume(
api_version="v1",
kind="PersistentVolume",
metadata=V1ObjectMeta(
name="pvc-fec036b7-9123-11e7-a930-fa163e18e097",
namespace="None",
uid="ff43c217-9123-11e7-a930-fa163e18e097"),
spec=V1PersistentVolumeSpec(
cinder=V1CinderVolumeSource(
fs_type=None,
read_only=None,
volume_id="7daedb1d-fc99-4a35-ab1b-b64971271d17"
)),
status=V1PersistentVolumeClaimStatus(phase="Bound"))
volumes = [
mock.Mock(name='Volume',
id='7daedb1d-fc99-4a35-ab1b-b64971271d17',
availability_zone='az1'),
mock.Mock(name='Volume',
id='7daedb1d-fc99-4a35-ab1b-b64922441d17',
availability_zone='az1'),
]
setattr(volumes[0], 'name', 'name123')
setattr(volumes[1], 'name', 'name456')
mock_pod_read.return_value = pod
mock_pvc_read.return_value = pvc
mock_pv_read.return_value = pv
mock_volume_list.return_value = volumes
self.assertEqual(
[Resource('OS::Cinder::Volume',
'7daedb1d-fc99-4a35-ab1b-b64971271d17',
'name123',
{'availability_zone': 'az1'})],
plugin.get_dependent_resources(
self._context,
Resource(id="c88b92a8-e8b4-504c-bad4-343d92061871",
name="default:busybox-test",
type="OS::Kubernetes::Pod")))

View File

@ -113,7 +113,7 @@ class ProtectionServiceTest(base.TestCase):
fake_cntx = mock.MagicMock()
result = self.pro_manager.list_protectable_dependents(
fake_cntx, 'fake_id', 'OS::Nova::Server')
fake_cntx, 'fake_id', 'OS::Nova::Server', "")
self.assertEqual([{'type': 'OS::Cinder::Volume', 'id': '123456',
'name': 'name123', 'extra_info': None},
{'type': 'OS::Cinder::Volume', 'id': '654321',