Delete VLAN on delete_vserver in Netapp cmode
Currently when DHSS=true with a Netapp cmode backend during vserver creation, lifs and VLANs get created. But on cleanup everything gets removed except the VLANs. This patch removes the vlans at the end of vserver deletion. Netapp prevents deleting VLANs with existing lifs on it. Change-Id: Id0b56bbce8e5e9b8707f22d401d99c7867372a50 Closes-Bug: #1580163
This commit is contained in:
parent
e9776762a4
commit
a1f33c9e8f
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
import copy
|
import copy
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import re
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
@ -522,6 +523,34 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
|||||||
msg_args = {'vlan': vlan, 'port': port, 'err_msg': e.message}
|
msg_args = {'vlan': vlan, 'port': port, 'err_msg': e.message}
|
||||||
raise exception.NetAppException(msg % msg_args)
|
raise exception.NetAppException(msg % msg_args)
|
||||||
|
|
||||||
|
@na_utils.trace
|
||||||
|
def delete_vlan(self, node, port, vlan):
|
||||||
|
try:
|
||||||
|
api_args = {
|
||||||
|
'vlan-info': {
|
||||||
|
'parent-interface': port,
|
||||||
|
'node': node,
|
||||||
|
'vlanid': vlan,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
self.send_request('net-vlan-delete', api_args)
|
||||||
|
except netapp_api.NaApiError as e:
|
||||||
|
p = re.compile('port already has a lif bound.*', re.IGNORECASE)
|
||||||
|
if (e.code == netapp_api.EAPIERROR and re.match(p, e.message)):
|
||||||
|
LOG.debug('VLAN %(vlan)s on port %(port)s node %(node)s '
|
||||||
|
'still used by LIF and cannot be deleted.',
|
||||||
|
{'vlan': vlan, 'port': port, 'node': node})
|
||||||
|
else:
|
||||||
|
msg = _('Failed to delete VLAN %(vlan)s on '
|
||||||
|
'port %(port)s node %(node)s: %(err_msg)s')
|
||||||
|
msg_args = {
|
||||||
|
'vlan': vlan,
|
||||||
|
'port': port,
|
||||||
|
'node': node,
|
||||||
|
'err_msg': e.message
|
||||||
|
}
|
||||||
|
raise exception.NetAppException(msg % msg_args)
|
||||||
|
|
||||||
@na_utils.trace
|
@na_utils.trace
|
||||||
def _ensure_broadcast_domain_for_port(self, node, port,
|
def _ensure_broadcast_domain_for_port(self, node, port,
|
||||||
domain=DEFAULT_BROADCAST_DOMAIN,
|
domain=DEFAULT_BROADCAST_DOMAIN,
|
||||||
|
@ -107,6 +107,11 @@ class NetAppCmodeMultiSVMFileStorageLibrary(
|
|||||||
@na_utils.trace
|
@na_utils.trace
|
||||||
def setup_server(self, network_info, metadata=None):
|
def setup_server(self, network_info, metadata=None):
|
||||||
"""Creates and configures new Vserver."""
|
"""Creates and configures new Vserver."""
|
||||||
|
|
||||||
|
vlan = network_info['segmentation_id']
|
||||||
|
|
||||||
|
@utils.synchronized('netapp-VLAN-%s' % vlan, external=True)
|
||||||
|
def setup_server_with_lock():
|
||||||
LOG.debug('Creating server %s', network_info['server_id'])
|
LOG.debug('Creating server %s', network_info['server_id'])
|
||||||
self._validate_network_type(network_info)
|
self._validate_network_type(network_info)
|
||||||
|
|
||||||
@ -121,6 +126,8 @@ class NetAppCmodeMultiSVMFileStorageLibrary(
|
|||||||
|
|
||||||
return server_details
|
return server_details
|
||||||
|
|
||||||
|
return setup_server_with_lock()
|
||||||
|
|
||||||
@na_utils.trace
|
@na_utils.trace
|
||||||
def _validate_network_type(self, network_info):
|
def _validate_network_type(self, network_info):
|
||||||
"""Raises exception if the segmentation type is incorrect."""
|
"""Raises exception if the segmentation type is incorrect."""
|
||||||
@ -311,6 +318,13 @@ class NetAppCmodeMultiSVMFileStorageLibrary(
|
|||||||
ipspace_name = self._client.get_vserver_ipspace(vserver)
|
ipspace_name = self._client.get_vserver_ipspace(vserver)
|
||||||
|
|
||||||
vserver_client = self._get_api_client(vserver=vserver)
|
vserver_client = self._get_api_client(vserver=vserver)
|
||||||
|
network_interfaces = vserver_client.get_network_interfaces()
|
||||||
|
|
||||||
|
home_port = network_interfaces[0]['home-port']
|
||||||
|
vlan = home_port.split('-')[1]
|
||||||
|
|
||||||
|
@utils.synchronized('netapp-VLAN-%s' % vlan, external=True)
|
||||||
|
def _delete_vserver_with_lock():
|
||||||
self._client.delete_vserver(vserver,
|
self._client.delete_vserver(vserver,
|
||||||
vserver_client,
|
vserver_client,
|
||||||
security_services=security_services)
|
security_services=security_services)
|
||||||
@ -318,3 +332,20 @@ class NetAppCmodeMultiSVMFileStorageLibrary(
|
|||||||
if ipspace_name and not self._client.ipspace_has_data_vservers(
|
if ipspace_name and not self._client.ipspace_has_data_vservers(
|
||||||
ipspace_name):
|
ipspace_name):
|
||||||
self._client.delete_ipspace(ipspace_name)
|
self._client.delete_ipspace(ipspace_name)
|
||||||
|
|
||||||
|
self._delete_vserver_vlan(network_interfaces)
|
||||||
|
|
||||||
|
return _delete_vserver_with_lock()
|
||||||
|
|
||||||
|
@na_utils.trace
|
||||||
|
def _delete_vserver_vlan(self, vserver_network_interfaces):
|
||||||
|
"""Delete Vserver's VLAN configuration from ports"""
|
||||||
|
|
||||||
|
for interface in vserver_network_interfaces:
|
||||||
|
try:
|
||||||
|
home_port = interface['home-port']
|
||||||
|
port, vlan = home_port.split('-')
|
||||||
|
node = interface['home-node']
|
||||||
|
self._client.delete_vlan(node, port, vlan)
|
||||||
|
except exception.NetAppException:
|
||||||
|
LOG.exception(_LE("Deleting Vserver VLAN failed."))
|
||||||
|
@ -82,6 +82,15 @@ SM_SOURCE_VOLUME = 'fake_source_volume'
|
|||||||
SM_DEST_VSERVER = 'fake_destination_vserver'
|
SM_DEST_VSERVER = 'fake_destination_vserver'
|
||||||
SM_DEST_VOLUME = 'fake_destination_volume'
|
SM_DEST_VOLUME = 'fake_destination_volume'
|
||||||
|
|
||||||
|
NETWORK_INTERFACES = [{
|
||||||
|
'interface_name': 'fake_interface',
|
||||||
|
'address': IP_ADDRESS,
|
||||||
|
'vserver': VSERVER_NAME,
|
||||||
|
'netmask': NETMASK,
|
||||||
|
'role': 'data',
|
||||||
|
'home-node': NODE_NAME,
|
||||||
|
'home-port': VLAN_PORT
|
||||||
|
}]
|
||||||
|
|
||||||
IPSPACES = [{
|
IPSPACES = [{
|
||||||
'uuid': 'fake_uuid',
|
'uuid': 'fake_uuid',
|
||||||
|
@ -61,8 +61,9 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
|||||||
self.vserver_client.set_vserver(fake.VSERVER_NAME)
|
self.vserver_client.set_vserver(fake.VSERVER_NAME)
|
||||||
self.vserver_client.connection = mock.MagicMock()
|
self.vserver_client.connection = mock.MagicMock()
|
||||||
|
|
||||||
def _mock_api_error(self, code='fake'):
|
def _mock_api_error(self, code='fake', message='fake'):
|
||||||
return mock.Mock(side_effect=netapp_api.NaApiError(code=code))
|
return mock.Mock(side_effect=netapp_api.NaApiError(code=code,
|
||||||
|
message=message))
|
||||||
|
|
||||||
def test_init_features_ontapi_1_21(self):
|
def test_init_features_ontapi_1_21(self):
|
||||||
|
|
||||||
@ -932,6 +933,53 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
|||||||
fake.PORT,
|
fake.PORT,
|
||||||
fake.VLAN)
|
fake.VLAN)
|
||||||
|
|
||||||
|
def test_delete_vlan(self):
|
||||||
|
|
||||||
|
self.mock_object(self.client, 'send_request')
|
||||||
|
|
||||||
|
vlan_delete_args = {
|
||||||
|
'vlan-info': {
|
||||||
|
'parent-interface': fake.PORT,
|
||||||
|
'node': fake.NODE_NAME,
|
||||||
|
'vlanid': fake.VLAN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.client.delete_vlan(fake.NODE_NAME, fake.PORT, fake.VLAN)
|
||||||
|
|
||||||
|
self.client.send_request.assert_has_calls([
|
||||||
|
mock.call('net-vlan-delete', vlan_delete_args)])
|
||||||
|
|
||||||
|
def test_delete_vlan_still_used(self):
|
||||||
|
|
||||||
|
self.mock_object(self.client,
|
||||||
|
'send_request',
|
||||||
|
self._mock_api_error(code=netapp_api.EAPIERROR,
|
||||||
|
message='Port already has a '
|
||||||
|
'lif bound. '))
|
||||||
|
|
||||||
|
vlan_delete_args = {
|
||||||
|
'vlan-info': {
|
||||||
|
'parent-interface': fake.PORT,
|
||||||
|
'node': fake.NODE_NAME,
|
||||||
|
'vlanid': fake.VLAN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.client.delete_vlan(fake.NODE_NAME, fake.PORT, fake.VLAN)
|
||||||
|
|
||||||
|
self.client.send_request.assert_has_calls([
|
||||||
|
mock.call('net-vlan-delete', vlan_delete_args)])
|
||||||
|
self.assertEqual(1, client_cmode.LOG.debug.call_count)
|
||||||
|
|
||||||
|
def test_delete_vlan_api_error(self):
|
||||||
|
|
||||||
|
self.mock_object(self.client, 'send_request', self._mock_api_error())
|
||||||
|
|
||||||
|
self.assertRaises(exception.NetAppException,
|
||||||
|
self.client.delete_vlan,
|
||||||
|
fake.NODE_NAME,
|
||||||
|
fake.PORT,
|
||||||
|
fake.VLAN)
|
||||||
|
|
||||||
def test_ensure_broadcast_domain_for_port_domain_match(self):
|
def test_ensure_broadcast_domain_for_port_domain_match(self):
|
||||||
|
|
||||||
port_info = {
|
port_info = {
|
||||||
|
@ -28,6 +28,7 @@ from manila.share.drivers.netapp.dataontap.cluster_mode import lib_base
|
|||||||
from manila.share.drivers.netapp.dataontap.cluster_mode import lib_multi_svm
|
from manila.share.drivers.netapp.dataontap.cluster_mode import lib_multi_svm
|
||||||
from manila.share.drivers.netapp import utils as na_utils
|
from manila.share.drivers.netapp import utils as na_utils
|
||||||
from manila import test
|
from manila import test
|
||||||
|
from manila.tests.share.drivers.netapp.dataontap.client import fakes as c_fake
|
||||||
from manila.tests.share.drivers.netapp.dataontap import fakes as fake
|
from manila.tests.share.drivers.netapp.dataontap import fakes as fake
|
||||||
|
|
||||||
|
|
||||||
@ -649,6 +650,11 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
|||||||
self.mock_object(self.library,
|
self.mock_object(self.library,
|
||||||
'_get_api_client',
|
'_get_api_client',
|
||||||
mock.Mock(return_value=vserver_client))
|
mock.Mock(return_value=vserver_client))
|
||||||
|
mock_delete_vserver_vlan = self.mock_object(self.library,
|
||||||
|
'_delete_vserver_vlan')
|
||||||
|
self.mock_object(vserver_client,
|
||||||
|
'get_network_interfaces',
|
||||||
|
mock.Mock(return_value=c_fake.NETWORK_INTERFACES))
|
||||||
security_services = fake.NETWORK_INFO['security_services']
|
security_services = fake.NETWORK_INFO['security_services']
|
||||||
|
|
||||||
self.library._delete_vserver(fake.VSERVER1,
|
self.library._delete_vserver(fake.VSERVER1,
|
||||||
@ -659,6 +665,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
|||||||
self.library._client.delete_vserver.assert_called_once_with(
|
self.library._client.delete_vserver.assert_called_once_with(
|
||||||
fake.VSERVER1, vserver_client, security_services=security_services)
|
fake.VSERVER1, vserver_client, security_services=security_services)
|
||||||
self.assertFalse(self.library._client.delete_ipspace.called)
|
self.assertFalse(self.library._client.delete_ipspace.called)
|
||||||
|
mock_delete_vserver_vlan.assert_called_once_with(
|
||||||
|
c_fake.NETWORK_INTERFACES)
|
||||||
|
|
||||||
def test_delete_vserver_ipspace_has_data_vservers(self):
|
def test_delete_vserver_ipspace_has_data_vservers(self):
|
||||||
|
|
||||||
@ -672,6 +680,11 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
|||||||
self.mock_object(self.library._client,
|
self.mock_object(self.library._client,
|
||||||
'ipspace_has_data_vservers',
|
'ipspace_has_data_vservers',
|
||||||
mock.Mock(return_value=True))
|
mock.Mock(return_value=True))
|
||||||
|
mock_delete_vserver_vlan = self.mock_object(self.library,
|
||||||
|
'_delete_vserver_vlan')
|
||||||
|
self.mock_object(vserver_client,
|
||||||
|
'get_network_interfaces',
|
||||||
|
mock.Mock(return_value=c_fake.NETWORK_INTERFACES))
|
||||||
security_services = fake.NETWORK_INFO['security_services']
|
security_services = fake.NETWORK_INFO['security_services']
|
||||||
|
|
||||||
self.library._delete_vserver(fake.VSERVER1,
|
self.library._delete_vserver(fake.VSERVER1,
|
||||||
@ -682,6 +695,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
|||||||
self.library._client.delete_vserver.assert_called_once_with(
|
self.library._client.delete_vserver.assert_called_once_with(
|
||||||
fake.VSERVER1, vserver_client, security_services=security_services)
|
fake.VSERVER1, vserver_client, security_services=security_services)
|
||||||
self.assertFalse(self.library._client.delete_ipspace.called)
|
self.assertFalse(self.library._client.delete_ipspace.called)
|
||||||
|
mock_delete_vserver_vlan.assert_called_once_with(
|
||||||
|
c_fake.NETWORK_INTERFACES)
|
||||||
|
|
||||||
def test_delete_vserver_with_ipspace(self):
|
def test_delete_vserver_with_ipspace(self):
|
||||||
|
|
||||||
@ -695,6 +710,12 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
|||||||
self.mock_object(self.library._client,
|
self.mock_object(self.library._client,
|
||||||
'ipspace_has_data_vservers',
|
'ipspace_has_data_vservers',
|
||||||
mock.Mock(return_value=False))
|
mock.Mock(return_value=False))
|
||||||
|
mock_delete_vserver_vlan = self.mock_object(self.library,
|
||||||
|
'_delete_vserver_vlan')
|
||||||
|
self.mock_object(vserver_client,
|
||||||
|
'get_network_interfaces',
|
||||||
|
mock.Mock(return_value=c_fake.NETWORK_INTERFACES))
|
||||||
|
|
||||||
security_services = fake.NETWORK_INFO['security_services']
|
security_services = fake.NETWORK_INFO['security_services']
|
||||||
|
|
||||||
self.library._delete_vserver(fake.VSERVER1,
|
self.library._delete_vserver(fake.VSERVER1,
|
||||||
@ -706,3 +727,32 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
|||||||
fake.VSERVER1, vserver_client, security_services=security_services)
|
fake.VSERVER1, vserver_client, security_services=security_services)
|
||||||
self.library._client.delete_ipspace.assert_called_once_with(
|
self.library._client.delete_ipspace.assert_called_once_with(
|
||||||
fake.IPSPACE)
|
fake.IPSPACE)
|
||||||
|
mock_delete_vserver_vlan.assert_called_once_with(
|
||||||
|
c_fake.NETWORK_INTERFACES)
|
||||||
|
|
||||||
|
def test_delete_vserver_vlan(self):
|
||||||
|
|
||||||
|
self.library._delete_vserver_vlan(c_fake.NETWORK_INTERFACES)
|
||||||
|
for interface in c_fake.NETWORK_INTERFACES:
|
||||||
|
home_port = interface['home-port']
|
||||||
|
port, vlan = home_port.split('-')
|
||||||
|
node = interface['home-node']
|
||||||
|
self.library._client.delete_vlan.assert_called_once_with(
|
||||||
|
node, port, vlan)
|
||||||
|
|
||||||
|
def test_delete_vserver_vlan_client_error(self):
|
||||||
|
|
||||||
|
mock_exception_log = self.mock_object(lib_multi_svm.LOG, 'exception')
|
||||||
|
self.mock_object(
|
||||||
|
self.library._client,
|
||||||
|
'delete_vlan',
|
||||||
|
mock.Mock(side_effect=exception.NetAppException("fake error")))
|
||||||
|
|
||||||
|
self.library._delete_vserver_vlan(c_fake.NETWORK_INTERFACES)
|
||||||
|
for interface in c_fake.NETWORK_INTERFACES:
|
||||||
|
home_port = interface['home-port']
|
||||||
|
port, vlan = home_port.split('-')
|
||||||
|
node = interface['home-node']
|
||||||
|
self.library._client.delete_vlan.assert_called_once_with(
|
||||||
|
node, port, vlan)
|
||||||
|
self.assertEqual(1, mock_exception_log.call_count)
|
||||||
|
@ -247,6 +247,7 @@ NETWORK_INFO = {
|
|||||||
'network_allocations': USER_NETWORK_ALLOCATIONS,
|
'network_allocations': USER_NETWORK_ALLOCATIONS,
|
||||||
'admin_network_allocations': ADMIN_NETWORK_ALLOCATIONS,
|
'admin_network_allocations': ADMIN_NETWORK_ALLOCATIONS,
|
||||||
'neutron_subnet_id': '62bf1c2c-18eb-421b-8983-48a6d39aafe0',
|
'neutron_subnet_id': '62bf1c2c-18eb-421b-8983-48a6d39aafe0',
|
||||||
|
'segmentation_id': '1000',
|
||||||
}
|
}
|
||||||
NETWORK_INFO_NETMASK = '255.255.255.0'
|
NETWORK_INFO_NETMASK = '255.255.255.0'
|
||||||
|
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- NetApp cMode driver - configured VLAN will be deleted on Vserver removal
|
Loading…
Reference in New Issue
Block a user