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:
parent
45f20e0a39
commit
892f96c9a6
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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 []
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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")))
|
||||
|
|
|
@ -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',
|
||||
|
|
Loading…
Reference in New Issue