Merge "Adding the disable feature"

This commit is contained in:
Zuul 2019-10-15 16:20:27 +00:00 committed by Gerrit Code Review
commit 0f517cf361
24 changed files with 332 additions and 11 deletions

View File

@ -72,6 +72,11 @@ def create_argument_parser():
"--os-identity-api-version", default=3,
help="Identity API version, default=3"
)
parser.add_argument(
"--disable-only", action="store_true",
help="Disable resources of the project rather than purging them. "
"Useful if you want to keep the resources but disable them."
)
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument(
@ -164,7 +169,9 @@ class CredentialsManager(object):
def runner(resource_mngr, options, exit):
try:
if not (options.dry_run or options.resource):
if not (options.dry_run or
options.resource or
options.disable_only):
resource_mngr.wait_for_check_prerequisite(exit)
for resource in resource_mngr.list():
@ -172,18 +179,27 @@ def runner(resource_mngr, options, exit):
if exit.is_set():
return
if resource_mngr.should_delete(resource):
resource_func_to_call = None
if options.disable_only:
resource_func_to_call = resource_mngr.disable
logging.info("Going to disable resource %s",
resource_mngr.to_str(resource))
elif resource_mngr.should_delete(resource):
resource_func_to_call = resource_mngr.delete
logging.info("Going to delete %s",
resource_mngr.to_str(resource))
# If we are in dry run mode, don't actually delete the resource
if resource_func_to_call:
# If we are in dry run mode, don't actually delete/disable
# the resource
if options.dry_run:
continue
# If we want to delete only specific resources, many things
# can go wrong, so we basically ignore all exceptions.
# If we want to delete/disable only specific resources, many
# things can go wrong, so we basically ignore all exceptions.
exc = os_exceptions.OpenStackCloudException
utils.call_and_ignore_exc(exc, resource_mngr.delete, resource)
utils.call_and_ignore_exc(exc, resource_func_to_call, resource)
except Exception as exc:
log = logging.error

View File

@ -136,6 +136,14 @@ class ServiceResource(six.with_metaclass(CodingStyleMixin,
def delete(self, resource):
raise NotImplementedError
def disable(self, resource):
msg = "The disable feature is not supported for %s, No action will" \
"be taken against the resource(id=%s, name=%s)."
logging.warning(
msg, self.__class__.__name__,
resource.get('id'), resource.get('name')
)
@staticmethod
@abc.abstractmethod
def to_str(resource):

View File

@ -59,6 +59,14 @@ class Volumes(base.ServiceResource):
def delete(self, resource):
self.cloud.delete_volume(resource['id'])
def disable(self, resource):
# Since there is no disable for volume setting it to readonly
# is good enough so no update on it
self.cloud.update_volume(
resource['id'],
metadata={'readonly': 'true'}
)
@staticmethod
def to_str(resource):
return "Volume (id='{}', name='{}')".format(

View File

@ -53,6 +53,9 @@ class Volumes(base.ServiceResource):
def delete(self, resource: Dict[str, Any]) -> None:
...
def disable(self, resource: Dict[str, Any]) -> None:
...
@staticmethod
def to_str(resource: Dict[str, Any]) -> str:
...

View File

@ -43,6 +43,9 @@ class Images(base.ServiceResource, ListImagesMixin):
def delete(self, resource):
self.cloud.delete_image(resource['id'])
def disable(self, resource):
self.cloud.image.deactivate_image(resource['id'])
@staticmethod
def to_str(resource):
return "Image (id='{}', name='{}')".format(

View File

@ -32,6 +32,9 @@ class Images(base.ServiceResource, ListImagesMixin):
def delete(self, resource: Dict[str, Any]) -> None:
...
def disable(self, resource: Dict[str, Any]) -> None:
...
@staticmethod
def to_str(resource: Dict[str, Any]) -> str:
...

View File

@ -27,6 +27,12 @@ class FloatingIPs(base.ServiceResource):
def delete(self, resource):
self.cloud.delete_floating_ip(resource['id'])
def disable(self, resource):
self.cloud.network.update_ip(
resource['id'],
port_id=None,
)
@staticmethod
def to_str(resource):
return "Floating IP (id='{}')".format(resource['id'])
@ -80,6 +86,12 @@ class Routers(base.ServiceResource):
def delete(self, resource):
self.cloud.delete_router(resource['id'])
def disable(self, resource):
self.cloud.update_router(
resource['id'],
admin_state_up=False
)
@staticmethod
def to_str(resource):
return "Router (id='{}', name='{}')".format(
@ -102,6 +114,9 @@ class Ports(base.ServiceResource):
def delete(self, resource):
self.cloud.delete_port(resource['id'])
def disable(self, resource):
self.cloud.update_port(resource['id'], admin_state_up=False)
@staticmethod
def to_str(resource):
return "Port (id='{}', network_id='{}, device_owner='{}')'".format(
@ -133,6 +148,9 @@ class Networks(base.ServiceResource):
def delete(self, resource):
self.cloud.delete_network(resource['id'])
def disable(self, resource):
self.cloud.update_network(resource['id'], admin_state_up=False)
@staticmethod
def to_str(resource):
return "Network (id='{}', name='{}')".format(

View File

@ -26,6 +26,9 @@ class FloatingIPs(base.ServiceResource):
def delete(self, resource: Dict[str, Any]) -> None:
...
def disable(self, resource: Dict[str, Any]) -> None:
...
@staticmethod
def to_str(resource: Dict[str, Any]) -> str:
...
@ -56,6 +59,9 @@ class Routers(base.ServiceResource):
def delete(self, resource: Dict[str, Any]) -> None:
...
def disable(self, resource: Dict[str, Any]) -> None:
...
@staticmethod
def to_str(resource: Dict[str, Any]) -> str:
...
@ -68,6 +74,9 @@ class Ports(base.ServiceResource):
def delete(self, resource: Dict[str, Any]) -> None:
...
def disable(self, resource: Dict[str, Any]) -> None:
...
@staticmethod
def to_str(resource: Dict[str, Any]) -> str:
...
@ -83,6 +92,9 @@ class Networks(base.ServiceResource):
def delete(self, resource: Dict[str, Any]) -> None:
...
def disable(self, resource: Dict[str, Any]) -> None:
...
@staticmethod
def to_str(resource: Dict[str, Any]) -> str:
...

View File

@ -21,6 +21,9 @@ class Servers(base.ServiceResource):
def delete(self, resource):
self.cloud.delete_server(resource['id'])
def disable(self, resource):
self.cloud.compute.stop_server(resource['id'])
@staticmethod
def to_str(resource):
return "VM (id='{}', name='{}')".format(

View File

@ -23,6 +23,9 @@ class Servers(base.ServiceResource):
def delete(self, resource: Dict[str, Any]) -> None:
...
def disable(self, resource: Dict[str, Any]) -> None:
...
@staticmethod
def to_str(resource: Dict[str, Any]) -> str:
...

View File

@ -25,6 +25,12 @@ class LoadBalancers(base.ServiceResource):
self.cloud.load_balancer.delete_load_balancer(
resource['id'], cascade=True)
def disable(self, resource):
self.cloud.load_balancer.update_load_balancer(
resource['id'],
admin_state_up=False
)
@staticmethod
def to_str(resource):
return "Octavia LoadBalancer (id='{}', name='{}')".format(

View File

@ -23,6 +23,9 @@ class LoadBalancers(base.ServiceResource):
def delete(self, resource: Dict[str, Any]) -> None:
...
def disable(self, resource: Dict[str, Any]) -> None:
...
@staticmethod
def to_str(resource: Dict[str, Any]) -> str:
...

View File

@ -62,6 +62,14 @@ class Containers(base.ServiceResource, ListObjectsMixin):
def delete(self, resource):
self.cloud.delete_container(urllib_parse.quote(resource['name']))
def disable(self, resource):
# There is no disable for the swift container just removing the
# write/read access to the conatianer, so only admin can access
self.cloud.object_store.set_container_metadata(
urllib_parse.quote(resource['name']),
write_acl=None, read_acl=None
)
@staticmethod
def to_str(resource):
return "Container (name='{}')".format(resource['name'])

View File

@ -49,6 +49,9 @@ class Containers(base.ServiceResource, ListObjectsMixin):
def delete(self, resource: Dict[str, Any]) -> None:
...
def disable(self, resource: Dict[str, Any]) -> None:
...
@staticmethod
def to_str(resource: Dict[str, Any]) -> str:
...

View File

@ -32,6 +32,11 @@ class TestBackups(unittest.TestCase):
self.assertIsNone(cinder.Backups(self.creds_manager).delete(backup))
self.cloud.delete_volume_backup.assert_called_once_with(backup['id'])
def test_disable(self):
backup = mock.MagicMock()
with self.assertLogs(level='WARNING'):
cinder.Backups(self.creds_manager).disable(backup)
def test_to_string(self):
backup = mock.MagicMock()
self.assertIn("Volume Backup",
@ -55,6 +60,11 @@ class TestSnapshots(unittest.TestCase):
self.cloud.delete_volume_snapshot.assert_called_once_with(
snapshot['id'])
def test_disable(self):
snapshot = mock.MagicMock()
with self.assertLogs(level='WARNING'):
cinder.Snapshots(self.creds_manager).disable(snapshot)
def test_to_string(self):
snapshot = mock.MagicMock()
self.assertIn("Volume Snapshot ",
@ -97,6 +107,14 @@ class TestVolumes(unittest.TestCase):
self.assertIsNone(cinder.Volumes(self.creds_manager).delete(volume))
self.cloud.delete_volume.assert_called_once_with(volume['id'])
def test_disable(self):
volume = mock.MagicMock()
cinder.Volumes(self.creds_manager).disable(volume)
self.cloud.update_volume.assert_called_once_with(
volume['id'],
metadata={'readonly': 'true'}
)
def test_to_string(self):
volume = mock.MagicMock()
self.assertIn("Volume ",

View File

@ -38,6 +38,11 @@ class TestZones(unittest.TestCase):
self.assertIsNone(designate.Zones(self.creds_manager).delete(zone))
self.cloud.delete_zone.assert_called_once_with(zone['id'])
def test_disable(self):
zone = mock.MagicMock()
with self.assertLogs(level='WARNING'):
designate.Zones(self.creds_manager).disable(zone)
def test_to_string(self):
stack = mock.MagicMock()
self.assertIn("Designate Zone",

View File

@ -79,6 +79,13 @@ class TestImages(unittest.TestCase):
self.assertIsNone(glance.Images(self.creds_manager).delete(image))
self.cloud.delete_image.assert_called_once_with(image['id'])
def test_disable(self):
image = mock.MagicMock()
self.assertIsNone(glance.Images(self.creds_manager).disable(image))
self.cloud.image.deactivate_image.assert_called_once_with(
image['id']
)
def test_to_string(self):
image = mock.MagicMock()
self.assertIn("Image (",

View File

@ -38,6 +38,11 @@ class TestStacks(unittest.TestCase):
self.assertIsNone(heat.Stacks(self.creds_manager).delete(stack))
self.cloud.delete_stack.assert_called_once_with(stack['id'], wait=True)
def test_disable(self):
stack = mock.MagicMock()
with self.assertLogs(level='WARNING'):
heat.Stacks(self.creds_manager).disable(stack)
def test_to_string(self):
stack = mock.MagicMock()
self.assertIn("Heat Stack",

View File

@ -47,6 +47,14 @@ class TestFloatingIPs(unittest.TestCase):
self.cloud.delete_floating_ip.assert_called_once_with(
fip['id'])
def test_disable(self):
fip = mock.MagicMock()
self.assertIsNone(neutron.FloatingIPs(self.creds_manager).disable(
fip
))
self.cloud.network.update_ip.assert_called_once_with(
fip['id'], port_id=None)
def test_to_string(self):
fip = mock.MagicMock()
self.assertIn("Floating IP ",
@ -95,6 +103,13 @@ class TestRouterInterfaces(unittest.TestCase):
port_id=iface['id']
)
def test_disable(self):
iface = mock.MagicMock()
with self.assertLogs(level='WARNING'):
neutron.RouterInterfaces(self.creds_manager).disable(
iface
)
def test_to_string(self):
iface = mock.MagicMock()
self.assertIn(
@ -135,6 +150,14 @@ class TestRouters(unittest.TestCase):
self.assertIsNone(neutron.Routers(self.creds_manager).delete(router))
self.cloud.delete_router.assert_called_once_with(router['id'])
def test_disable(self):
router = mock.MagicMock()
self.assertIsNone(neutron.Routers(self.creds_manager).disable(router))
self.cloud.update_router.assert_called_once_with(
router['id'],
admin_state_up=False
)
def test_to_string(self):
router = mock.MagicMock()
self.assertIn("Router (",
@ -163,6 +186,14 @@ class TestPorts(unittest.TestCase):
self.assertIsNone(neutron.Ports(self.creds_manager).delete(port))
self.cloud.delete_port.assert_called_once_with(port['id'])
def test_disable(self):
port = mock.MagicMock()
self.assertIsNone(neutron.Ports(self.creds_manager).disable(port))
self.cloud.update_port.assert_called_once_with(
port['id'],
admin_state_up=False
)
def test_to_string(self):
port = mock.MagicMock()
self.assertIn("Port (",
@ -207,6 +238,14 @@ class TestNetworks(unittest.TestCase):
self.assertIsNone(neutron.Networks(self.creds_manager).delete(nw))
self.cloud.delete_network.assert_called_once_with(nw['id'])
def test_disable(self):
nw = mock.MagicMock()
self.assertIsNone(neutron.Networks(self.creds_manager).disable(nw))
self.cloud.update_network.assert_called_once_with(
nw['id'],
admin_state_up=False
)
def test_to_string(self):
nw = mock.MagicMock()
self.assertIn("Network (",
@ -234,6 +273,11 @@ class TestSecurityGroups(unittest.TestCase):
neutron.SecurityGroups(self.creds_manager).delete(sg))
self.cloud.delete_security_group.assert_called_once_with(sg['id'])
def test_disable(self):
sg = mock.MagicMock()
with self.assertLogs(level='WARNING'):
neutron.SecurityGroups(self.creds_manager).disable(sg)
def test_to_string(self):
sg = mock.MagicMock()
self.assertIn("Security Group (",

View File

@ -32,6 +32,11 @@ class TestServers(unittest.TestCase):
self.assertIsNone(nova.Servers(self.creds_manager).delete(server))
self.cloud.delete_server.assert_called_once_with(server['id'])
def test_disable(self):
server = mock.MagicMock()
self.assertIsNone(nova.Servers(self.creds_manager).disable(server))
self.cloud.compute.stop_server.assert_called_once_with(server['id'])
def test_to_string(self):
server = mock.MagicMock()
self.assertIn("VM (",

View File

@ -44,6 +44,13 @@ class TestLoadBalancers(unittest.TestCase):
(self.cloud.load_balancer.delete_load_balancer
.assert_called_once_with(lb['id'], cascade=True))
def test_disable(self):
lb = mock.MagicMock()
self.assertIsNone(octavia.LoadBalancers(self.creds_manager).disable(
lb))
(self.cloud.load_balancer.update_load_balancer
.assert_called_once_with(lb['id'], admin_state_up=False))
def test_to_string(self):
stack = mock.MagicMock()
self.assertIn("Octavia LoadBalancer",

View File

@ -89,6 +89,11 @@ class TestObjects(unittest.TestCase):
urllib_parse.quote(obj['name'])
)
def test_disable(self):
obj = {'name': 'toto', 'container_name': 'foo'}
with self.assertLogs(level='WARNING'):
swift.Objects(self.creds_manager).disable(obj)
def test_to_string(self):
obj = mock.MagicMock()
self.assertIn("Object '",
@ -128,6 +133,17 @@ class TestContainers(unittest.TestCase):
urllib_parse.quote(cont['name'])
)
def test_disable(self):
cont = {'bytes': 8,
'count': 2,
'last_modified': '2019-06-05T15:20:59.450120',
'name': 'Pouet éêù #'}
self.assertIsNone(swift.Containers(self.creds_manager).disable(cont))
self.cloud.object_store.set_container_metadata.assert_called_once_with(
urllib_parse.quote(cont['name']),
read_acl=None, write_acl=None
)
def test_to_string(self):
container = mock.MagicMock()
self.assertIn("Container (",

View File

@ -74,10 +74,10 @@ class TestFunctions(unittest.TestCase):
self.assertEqual('foo', options.purge_project)
self.assertEqual(['Networks', 'Volumes'], options.resource)
def test_runner(self):
def test_runner_delete(self):
resources = [mock.Mock(), mock.Mock(), mock.Mock()]
resource_manager = mock.Mock(list=mock.Mock(return_value=resources))
options = mock.Mock(dry_run=False, resource=False)
options = mock.Mock(dry_run=False, resource=False, disable_only=False)
exit = mock.Mock(is_set=mock.Mock(side_effect=[False, False, True]))
main.runner(resource_manager, options, exit)
@ -95,10 +95,43 @@ class TestFunctions(unittest.TestCase):
resource_manager.delete.call_args_list
)
def test_runner_dry_run(self):
def test_runner_disable(self):
resources = [mock.Mock(), mock.Mock(), mock.Mock()]
resource_manager = mock.Mock(list=mock.Mock(return_value=resources))
options = mock.Mock(dry_run=False, resource=False, disable_only=True)
exit = mock.Mock(is_set=mock.Mock(side_effect=[False, False, True]))
main.runner(resource_manager, options, exit)
resource_manager.list.assert_called_once_with()
resource_manager.wait_for_check_prerequisite.assert_not_called()
resource_manager.should_delete.assert_not_called()
resource_manager.delete.assert_not_called()
self.assertEqual(2, resource_manager.disable.call_count)
self.assertEqual(
[mock.call(resources[0]), mock.call(resources[1])],
resource_manager.disable.call_args_list
)
def test_runner_disable_dry_run(self):
resources = [mock.Mock(), mock.Mock(), mock.Mock()]
resource_manager = mock.Mock(list=mock.Mock(return_value=resources))
options = mock.Mock(dry_run=True, resource=False, disable_only=True)
exit = mock.Mock(is_set=mock.Mock(side_effect=[False, False, True]))
main.runner(resource_manager, options, exit)
resource_manager.list.assert_called_once_with()
resource_manager.wait_for_check_prerequisite.assert_not_called()
resource_manager.should_delete.assert_not_called()
resource_manager.delete.assert_not_called()
resource_manager.disable.assert_not_called()
resource_manager.assert_not_called()
def test_runner_delete_dry_run(self):
resources = [mock.Mock(), mock.Mock()]
resource_manager = mock.Mock(list=mock.Mock(return_value=resources))
options = mock.Mock(dry_run=True)
options = mock.Mock(dry_run=True, disable_only=False)
exit = mock.Mock(is_set=mock.Mock(return_value=False))
main.runner(resource_manager, options, exit)
@ -109,7 +142,7 @@ class TestFunctions(unittest.TestCase):
def test_runner_resource(self):
resources = [mock.Mock()]
resource_manager = mock.Mock(list=mock.Mock(return_value=resources))
options = mock.Mock(dry_run=False, resource=True)
options = mock.Mock(dry_run=False, resource=True, disable_only=False)
exit = mock.Mock(is_set=mock.Mock(return_value=False))
main.runner(resource_manager, options, exit)
resource_manager.wait_for_check_prerequisite.assert_not_called()

View File

@ -72,6 +72,77 @@ function assert_volume {
fi
}
########################
# Disable asserts
########################
function test_neutron_disable {
if [[ $(openstack port list -c Status -f value --device-owner compute:nova --project $demo_project_id | grep -ic 'active' ) -gt 0 ]]; then
echo "Some of the ports is not disabled yet :)"
exit 1
fi
if [[ $(openstack port list -c Status -f value --device-owner '' --project $demo_project_id | grep -ic 'active' ) -gt 0 ]]; then
echo "Some of the ports is not disabled yet :)"
exit 1
fi
if [[ $(openstack network list --no-share --long -c State -f value --project $demo_project_id | grep -ic 'UP' ) -gt 0 ]]; then
echo "Some of the networks is not disabled yet :)"
exit 1
fi
for router in $(openstack router list -c ID -f value --project $demo_project_id); do
if [[ $(openstack router show $router -c admin_state_up -f value | grep -ic 'true' ) -gt 0 ]]; then
echo "Some of the routers is not disabled yet :)"
exit 1
fi
done
}
function test_cinder_disable {
if [[ $(openstack volume list --long -c Properties -f value | grep -qvi 'readonly') ]]; then
echo "Cinder volume is not disabled :)"
exit 1
fi
}
function test_glance_disable {
if [[ $(openstack image list --long -c Project -c Status -f value| grep $demo_project_id | grep -ic 'active' ) -gt 0 ]]; then
echo "Some of the images is not disabled yet :)"
exit 1
fi
}
function test_nova_disable {
if [[ $(openstack server list -c Status -f value | grep -ic 'active' ) -gt 0 ]]; then
echo "Some of the servers is not disabled yet :)"
exit 1
fi
}
function test_loadbalancer_disable {
for loadbalancer in $(openstack loadbalancer list -c id -f value --project $demo_project_id); do
if [[ $(openstack loadbalancer show $loadbalancer -c admin_state_up -f value | grep -ic 'true' ) -gt 0 ]]; then
echo "Some of the loadbalancers is not disabled yet :)"
exit 1
fi
done
}
function test_swift_disable {
for container in $(openstack container list -c Name -f value); do
if [[ $(openstack container show $container -f json | grep -iq 'read[-_]acl:.*' ) ]]; then
echo "Some of the containers is not disabled yet :)"
exit 1
fi
if [[ $(openstack container show $container -f json | grep -iq 'write[-_]acl:.*' ) ]]; then
echo "Some of the containers is not disabled yet :)"
exit 1
fi
done
}
########################
@ -113,9 +184,22 @@ done
echo "Done populating. Moving on to cleanup."
########################
# Disable
########################
source $DEVSTACK_DIR/openrc admin admin
demo_project_id=$(openstack project show demo -c id -f value | awk '{print $1}')
source $DEVSTACK_DIR/openrc demo demo
assert_compute && assert_network && assert_volume
tox -e run -- --os-cloud devstack --purge-own-project --verbose --disable-only # disable demo/demo
test_neutron_disable && test_cinder_disable && test_glance_disable \
&& test_nova_disable && test_loadbalancer_disable && test_swift_disable
########################
### Cleanup
########################
source $DEVSTACK_DIR/openrc admin admin
tox -e run -- --os-cloud devstack-admin --purge-own-project --verbose # purges admin/admin
source $DEVSTACK_DIR/openrc demo demo