project cleanup

New implementation of the project cleanup based on the sdk.project_cleanup.
It is implemented as an additional OSC operation and will ideally obsolete the 
`openstack project purge` giving flexibility to extend services support, 
parallelization, filters, etc.

Change-Id: Ie08877f182379f73e5ec5ad4daaf84b3092c829c
This commit is contained in:
Artem Goncharov 2020-06-09 11:35:46 +02:00 committed by Artem Goncharov
parent 01a53fa96f
commit 119d2fae25
6 changed files with 343 additions and 0 deletions

View File

@ -0,0 +1,12 @@
===============
project cleanup
===============
Clean resources associated with a specific project based on OpenStackSDK
implementation
Block Storage v2, v3; Compute v2; Network v2; DNS v2; Orchestrate v1
.. autoprogram-cliff:: openstack.common
:command: project cleanup

View File

@ -270,6 +270,7 @@ Those actions with an opposite action are noted in parens if applicable.
* ``pause`` (``unpause``) - stop one or more servers and leave them in memory
* ``query`` - Query resources by Elasticsearch query string or json format DSL.
* ``purge`` - clean resources associated with a specific project
* ``cleanup`` - flexible clean resources associated with a specific project
* ``reboot`` - forcibly reboot a server
* ``rebuild`` - rebuild a server using (most of) the same arguments as in the original create
* ``remove`` (``add``) - remove an object from a group of objects

View File

@ -0,0 +1,140 @@
# Copyright 2020 OpenStack Foundation
#
# 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 getpass
import logging
import os
import queue
from cliff.formatters import table
from osc_lib.command import command
from openstackclient.i18n import _
from openstackclient.identity import common as identity_common
LOG = logging.getLogger(__name__)
def ask_user_yesno(msg, default=True):
"""Ask user Y/N question
:param str msg: question text
:param bool default: default value
:return bool: User choice
"""
while True:
answer = getpass._raw_input(
'{} [{}]: '.format(msg, 'y/N' if not default else 'Y/n'))
if answer in ('y', 'Y', 'yes'):
return True
elif answer in ('n', 'N', 'no'):
return False
class ProjectCleanup(command.Command):
_description = _("Clean resources associated with a project")
def get_parser(self, prog_name):
parser = super(ProjectCleanup, self).get_parser(prog_name)
parser.add_argument(
'--dry-run',
action='store_true',
help=_("List a project's resources")
)
project_group = parser.add_mutually_exclusive_group(required=True)
project_group.add_argument(
'--auth-project',
action='store_true',
help=_('Delete resources of the project used to authenticate')
)
project_group.add_argument(
'--project',
metavar='<project>',
help=_('Project to clean (name or ID)')
)
parser.add_argument(
'--created-before',
metavar='<YYYY-MM-DDTHH24:MI:SS>',
help=_('Drop resources created before the given time')
)
parser.add_argument(
'--updated-before',
metavar='<YYYY-MM-DDTHH24:MI:SS>',
help=_('Drop resources updated before the given time')
)
identity_common.add_project_domain_option_to_parser(parser)
return parser
def take_action(self, parsed_args):
sdk = self.app.client_manager.sdk_connection
if parsed_args.auth_project:
project_connect = sdk
elif parsed_args.project:
project = sdk.identity.find_project(
name_or_id=parsed_args.project,
ignore_missing=False)
project_connect = sdk.connect_as_project(project)
if project_connect:
status_queue = queue.Queue()
parsed_args.max_width = int(os.environ.get('CLIFF_MAX_TERM_WIDTH',
0))
parsed_args.fit_width = bool(int(os.environ.get('CLIFF_FIT_WIDTH',
0)))
parsed_args.print_empty = False
table_fmt = table.TableFormatter()
self.log.info('Searching resources...')
filters = {}
if parsed_args.created_before:
filters['created_at'] = parsed_args.created_before
if parsed_args.updated_before:
filters['updated_at'] = parsed_args.updated_before
project_connect.project_cleanup(dry_run=True,
status_queue=status_queue,
filters=filters)
data = []
while not status_queue.empty():
resource = status_queue.get_nowait()
data.append(
(type(resource).__name__, resource.id, resource.name))
status_queue.task_done()
status_queue.join()
table_fmt.emit_list(
('Type', 'ID', 'Name'),
data,
self.app.stdout,
parsed_args
)
if parsed_args.dry_run:
return
confirm = ask_user_yesno(
_("These resources will be deleted. Are you sure"),
default=False)
if confirm:
self.log.warning(_('Deleting resources'))
project_connect.project_cleanup(dry_run=False,
status_queue=status_queue,
filters=filters)

View File

@ -0,0 +1,183 @@
# 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 io import StringIO
from unittest import mock
from openstackclient.common import project_cleanup
from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes
from openstackclient.tests.unit import utils as tests_utils
class TestProjectCleanupBase(tests_utils.TestCommand):
def setUp(self):
super(TestProjectCleanupBase, self).setUp()
self.app.client_manager.sdk_connection = mock.Mock()
class TestProjectCleanup(TestProjectCleanupBase):
project = identity_fakes.FakeProject.create_one_project()
def setUp(self):
super(TestProjectCleanup, self).setUp()
self.cmd = project_cleanup.ProjectCleanup(self.app, None)
self.project_cleanup_mock = mock.Mock()
self.sdk_connect_as_project_mock = \
mock.Mock(return_value=self.app.client_manager.sdk_connection)
self.app.client_manager.sdk_connection.project_cleanup = \
self.project_cleanup_mock
self.app.client_manager.sdk_connection.identity.find_project = \
mock.Mock(return_value=self.project)
self.app.client_manager.sdk_connection.connect_as_project = \
self.sdk_connect_as_project_mock
def test_project_no_options(self):
arglist = []
verifylist = []
self.assertRaises(tests_utils.ParserException, self.check_parser,
self.cmd, arglist, verifylist)
def test_project_cleanup_with_filters(self):
arglist = [
'--project', self.project.id,
'--created-before', '2200-01-01',
'--updated-before', '2200-01-02'
]
verifylist = [
('dry_run', False),
('auth_project', False),
('project', self.project.id),
('created_before', '2200-01-01'),
('updated_before', '2200-01-02')
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = None
with mock.patch('sys.stdin', StringIO('y')):
result = self.cmd.take_action(parsed_args)
self.sdk_connect_as_project_mock.assert_called_with(
self.project)
filters = {
'created_at': '2200-01-01',
'updated_at': '2200-01-02'
}
calls = [
mock.call(dry_run=True, status_queue=mock.ANY, filters=filters),
mock.call(dry_run=False, status_queue=mock.ANY, filters=filters)
]
self.project_cleanup_mock.assert_has_calls(calls)
self.assertIsNone(result)
def test_project_cleanup_with_project(self):
arglist = [
'--project', self.project.id,
]
verifylist = [
('dry_run', False),
('auth_project', False),
('project', self.project.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = None
with mock.patch('sys.stdin', StringIO('y')):
result = self.cmd.take_action(parsed_args)
self.sdk_connect_as_project_mock.assert_called_with(
self.project)
calls = [
mock.call(dry_run=True, status_queue=mock.ANY, filters={}),
mock.call(dry_run=False, status_queue=mock.ANY, filters={})
]
self.project_cleanup_mock.assert_has_calls(calls)
self.assertIsNone(result)
def test_project_cleanup_with_project_abort(self):
arglist = [
'--project', self.project.id,
]
verifylist = [
('dry_run', False),
('auth_project', False),
('project', self.project.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = None
with mock.patch('sys.stdin', StringIO('n')):
result = self.cmd.take_action(parsed_args)
self.sdk_connect_as_project_mock.assert_called_with(
self.project)
calls = [
mock.call(dry_run=True, status_queue=mock.ANY, filters={}),
]
self.project_cleanup_mock.assert_has_calls(calls)
self.assertIsNone(result)
def test_project_cleanup_with_dry_run(self):
arglist = [
'--dry-run',
'--project', self.project.id,
]
verifylist = [
('dry_run', True),
('auth_project', False),
('project', self.project.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = None
result = self.cmd.take_action(parsed_args)
self.sdk_connect_as_project_mock.assert_called_with(
self.project)
self.project_cleanup_mock.assert_called_once_with(
dry_run=True, status_queue=mock.ANY, filters={})
self.assertIsNone(result)
def test_project_cleanup_with_auth_project(self):
self.app.client_manager.auth_ref = mock.Mock()
self.app.client_manager.auth_ref.project_id = self.project.id
arglist = [
'--auth-project',
]
verifylist = [
('dry_run', False),
('auth_project', True),
('project', None),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = None
with mock.patch('sys.stdin', StringIO('y')):
result = self.cmd.take_action(parsed_args)
self.sdk_connect_as_project_mock.assert_not_called()
calls = [
mock.call(dry_run=True, status_queue=mock.ANY, filters={}),
mock.call(dry_run=False, status_queue=mock.ANY, filters={})
]
self.project_cleanup_mock.assert_has_calls(calls)
self.assertIsNone(result)

View File

@ -0,0 +1,6 @@
---
features:
- |
Add support for project cleanup based on the OpenStackSDK with
create/update time filters. In the long run this will replace
`openstack project purge` command.

View File

@ -45,6 +45,7 @@ openstack.common =
extension_list = openstackclient.common.extension:ListExtension
extension_show = openstackclient.common.extension:ShowExtension
limits_show = openstackclient.common.limits:ShowLimits
project_cleanup = openstackclient.common.project_cleanup:ProjectCleanup
project_purge = openstackclient.common.project_purge:ProjectPurge
quota_list = openstackclient.common.quota:ListQuota
quota_set = openstackclient.common.quota:SetQuota