diff options
-rw-r--r-- | doc/source/cli/osc/v2/network-log.rst | 15 | ||||
-rw-r--r-- | neutronclient/osc/v2/logging/__init__.py | 0 | ||||
-rw-r--r-- | neutronclient/osc/v2/logging/network_log.py | 289 | ||||
-rw-r--r-- | neutronclient/tests/unit/osc/v2/logging/__init__.py | 0 | ||||
-rw-r--r-- | neutronclient/tests/unit/osc/v2/logging/fakes.py | 79 | ||||
-rw-r--r-- | neutronclient/tests/unit/osc/v2/logging/test_network_log.py | 658 | ||||
-rw-r--r-- | neutronclient/v2_0/client.py | 32 | ||||
-rw-r--r-- | releasenotes/notes/support-logging-cli-cd02d3bb03367106.yaml | 5 | ||||
-rw-r--r-- | setup.cfg | 8 |
9 files changed, 1085 insertions, 1 deletions
diff --git a/doc/source/cli/osc/v2/network-log.rst b/doc/source/cli/osc/v2/network-log.rst new file mode 100644 index 0000000..df3a327 --- /dev/null +++ b/doc/source/cli/osc/v2/network-log.rst @@ -0,0 +1,15 @@ +=========== +network log +=========== + +A **network log** is a container to group security groups or ports for logging. +Specified resources can be logged via these event (``ALL``, ``ACCEPT`` or +``DROP``). + +Network v2 + +.. autoprogram-cliff:: openstack.neutronclient.v2 + :command: network loggable resources list + +.. autoprogram-cliff:: openstack.neutronclient.v2 + :command: network log * diff --git a/neutronclient/osc/v2/logging/__init__.py b/neutronclient/osc/v2/logging/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/neutronclient/osc/v2/logging/__init__.py diff --git a/neutronclient/osc/v2/logging/network_log.py b/neutronclient/osc/v2/logging/network_log.py new file mode 100644 index 0000000..becb1cb --- /dev/null +++ b/neutronclient/osc/v2/logging/network_log.py @@ -0,0 +1,289 @@ +# Copyright 2017 FUJTISU LIMITED. +# All Rights Reserved +# +# 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 copy + +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils +from oslo_log import log as logging + +from neutronclient._i18n import _ +from neutronclient.common import utils as nc_utils +from neutronclient.osc import utils as osc_utils + + +LOG = logging.getLogger(__name__) + +_attr_map = ( + ('id', 'ID', osc_utils.LIST_BOTH), + ('description', 'Description', osc_utils.LIST_LONG_ONLY), + ('enabled', 'Enabled', osc_utils.LIST_BOTH), + ('name', 'Name', osc_utils.LIST_BOTH), + ('target_id', 'Target', osc_utils.LIST_LONG_ONLY), + ('project_id', 'Project', osc_utils.LIST_LONG_ONLY), + ('resource_id', 'Resource', osc_utils.LIST_LONG_ONLY), + ('resource_type', 'Type', osc_utils.LIST_BOTH), + ('event', 'Event', osc_utils.LIST_LONG_ONLY), + ('summary', 'Summary', osc_utils.LIST_SHORT_ONLY), +) + +_attr_map_for_loggable = ( + ('type', 'Supported types', osc_utils.LIST_BOTH), +) + +NET_LOG = 'network_log' + + +def _get_common_parser(parser): + parser.add_argument( + '--description', + metavar='<description>', + help=_('Description of the network log')) + enable_group = parser.add_mutually_exclusive_group() + enable_group.add_argument( + '--enable', + action='store_true', + help=_('Enable this log (default is disabled)')) + enable_group.add_argument( + '--disable', + action='store_true', + help=_('Disable this log')) + return parser + + +def _get_common_attrs(client_manager, parsed_args, is_create=True): + attrs = {} + client = client_manager.neutronclient + + if is_create: + if 'project' in parsed_args and parsed_args.project is not None: + attrs['project_id'] = osc_utils.find_project( + client_manager.identity, + parsed_args.project, + parsed_args.project_domain, + ).id + if parsed_args.resource: + attrs['resource_id'] = client.find_resource( + 'security_group', parsed_args.resource)['id'] + if parsed_args.target: + # NOTE(yushiro) Currently, we're supporting only port + attrs['target_id'] = client.find_resource( + 'port', parsed_args.target)['id'] + if parsed_args.event: + attrs['event'] = parsed_args.event + if parsed_args.resource_type: + attrs['resource_type'] = parsed_args.resource_type + if parsed_args.enable: + attrs['enabled'] = True + if parsed_args.disable: + attrs['enabled'] = False + if parsed_args.name: + attrs['name'] = parsed_args.name + if parsed_args.description: + attrs['description'] = parsed_args.description + return attrs + + +class CreateNetworkLog(command.ShowOne): + _description = _("Create a new network log") + + def get_parser(self, prog_name): + parser = super(CreateNetworkLog, self).get_parser(prog_name) + _get_common_parser(parser) + osc_utils.add_project_owner_option_to_parser(parser) + parser.add_argument( + 'name', + metavar='<name>', + help=_('Name for the network log')) + parser.add_argument( + '--event', + metavar='<event>', + choices=['ALL', 'ACCEPT', 'DROP'], + type=nc_utils.convert_to_uppercase, + help=_('An event to store with log')) + # NOTE(yushiro) '--resource-type' is managed by following command: + # "openstack network loggable resource list". Therefore, this option + # shouldn't have "choices" like ['security_group'] + parser.add_argument( + '--resource-type', + metavar='<resource-type>', + required=True, + type=nc_utils.convert_to_lowercase, + help=_('Network log type(s). ' + 'You can see supported type(s) with following command:\n' + '$ openstack network loggable resource list')) + parser.add_argument( + '--resource', + metavar='<resource>', + help=_('Security group (name or ID) for logging. You can control ' + 'for logging target combination with --target option.')) + parser.add_argument( + '--target', + metavar='<target>', + help=_('Port (name or ID) for logging. You can control ' + 'for logging target combination with --resource option.')) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.neutronclient + attrs = _get_common_attrs(self.app.client_manager, parsed_args) + obj = client.create_network_log({'log': attrs})['log'] + columns, display_columns = osc_utils.get_columns(obj, _attr_map) + data = utils.get_dict_properties(obj, columns) + return (display_columns, data) + + +class DeleteNetworkLog(command.Command): + _description = _("Delete network log(s)") + + def get_parser(self, prog_name): + parser = super(DeleteNetworkLog, self).get_parser(prog_name) + parser.add_argument( + 'network_log', + metavar='<network-log>', + nargs='+', + help=_('Network log(s) to delete (name or ID)')) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.neutronclient + result = 0 + for log_res in parsed_args.network_log: + try: + log_id = client.find_resource( + 'log', log_res, cmd_resource=NET_LOG)['id'] + client.delete_network_log(log_id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete network log with " + "name or ID '%(network_log)s': %(e)s"), + {'network_log': log_res, 'e': e}) + + if result > 0: + total = len(parsed_args.network_log) + msg = (_("%(result)s of %(total)s network log(s) " + "failed to delete") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) + + +class ListLoggableResource(command.Lister): + _description = _("List supported loggable resources") + + def get_parser(self, prog_name): + parser = super(ListLoggableResource, self).get_parser(prog_name) + parser.add_argument( + '--long', + action='store_true', + help=_("List additional fields in output") + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.neutronclient + obj = client.list_network_loggable_resources()['loggable_resources'] + headers, columns = osc_utils.get_column_definitions( + _attr_map_for_loggable, long_listing=parsed_args.long) + return (headers, (utils.get_dict_properties(s, columns) for s in obj)) + + +class ListNetworkLog(command.Lister): + _description = _("List network logs") + + def get_parser(self, prog_name): + parser = super(ListNetworkLog, self).get_parser(prog_name) + parser.add_argument( + '--long', + action='store_true', + help=_("List additional fields in output") + ) + # TODO(yushiro): We'll support filtering in the future. + return parser + + def _extend_list(self, data, parsed_args): + ext_data = copy.deepcopy(data) + for d in ext_data: + e_prefix = 'Event: ' + if d['event']: + event = e_prefix + d['event'].upper() + port = '(port) ' + d['target_id'] if d['target_id'] else '' + sg = ('(security_group) ' + d['resource_id'] + if d['resource_id'] else '') + t_prefix = 'Logged: ' + t = sg + ' on ' + port if port and sg else sg + port + target = t_prefix + t if t else t_prefix + '(None specified)' + d['summary'] = ',\n'.join([event, target]) + return ext_data + + def take_action(self, parsed_args): + client = self.app.client_manager.neutronclient + obj = client.list_network_logs()['logs'] + obj_extend = self._extend_list(obj, parsed_args) + headers, columns = osc_utils.get_column_definitions( + _attr_map, long_listing=parsed_args.long) + return (headers, ( + utils.get_dict_properties(s, columns) for s in obj_extend)) + + +class SetNetworkLog(command.Command): + _description = _("Set network log properties") + + def get_parser(self, prog_name): + parser = super(SetNetworkLog, self).get_parser(prog_name) + _get_common_parser(parser) + parser.add_argument( + 'network_log', + metavar='<network-log>', + help=_('Network log to set (name or ID)')) + parser.add_argument( + '--name', + metavar='<name>', + help=_('Name of the network log')) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.neutronclient + log_id = client.find_resource( + 'log', parsed_args.network_log, cmd_resource=NET_LOG)['id'] + attrs = _get_common_attrs(self.app.client_manager, parsed_args, + is_create=False) + try: + client.update_network_log(log_id, {'log': attrs}) + except Exception as e: + msg = (_("Failed to set network log '%(logging)s': %(e)s") + % {'logging': parsed_args.network_log, 'e': e}) + raise exceptions.CommandError(msg) + + +class ShowNetworkLog(command.ShowOne): + _description = _("Display network log details") + + def get_parser(self, prog_name): + parser = super(ShowNetworkLog, self).get_parser(prog_name) + parser.add_argument( + 'network_log', + metavar='<network-log>', + help=_('Network log to show (name or ID)')) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.neutronclient + log_id = client.find_resource( + 'log', parsed_args.network_log, cmd_resource=NET_LOG)['id'] + obj = client.show_network_log(log_id)['log'] + columns, display_columns = osc_utils.get_columns(obj, _attr_map) + data = utils.get_dict_properties(obj, columns) + return (display_columns, data) diff --git a/neutronclient/tests/unit/osc/v2/logging/__init__.py b/neutronclient/tests/unit/osc/v2/logging/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/neutronclient/tests/unit/osc/v2/logging/__init__.py diff --git a/neutronclient/tests/unit/osc/v2/logging/fakes.py b/neutronclient/tests/unit/osc/v2/logging/fakes.py new file mode 100644 index 0000000..856838f --- /dev/null +++ b/neutronclient/tests/unit/osc/v2/logging/fakes.py @@ -0,0 +1,79 @@ +# Copyright 2017 FUJITSU LIMITED +# All Rights Reserved +# +# 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 collections +import copy +import uuid + +import mock + + +class FakeLogging(object): + + def create(self, attrs={}): + """Create a fake network logs + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A OrderedDict faking the network log + """ + self.ordered.update(attrs) + return copy.deepcopy(self.ordered) + + def bulk_create(self, attrs=None, count=2): + """Create multiple fake network logs + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of network logs to fake + :return: + A list of dictionaries faking the network logs + """ + return [self.create(attrs=attrs) for i in range(0, count)] + + def get(self, attrs=None, count=2): + """Create multiple fake network logs + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of network logs to fake + :return: + A list of dictionaries faking the network log + """ + if attrs is None: + self.attrs = self.bulk_create(count=count) + return mock.Mock(side_effect=attrs) + + +class NetworkLog(FakeLogging): + """Fake one or more network log""" + + def __init__(self): + super(NetworkLog, self).__init__() + self.ordered = collections.OrderedDict(( + ('id', 'log-id-' + uuid.uuid4().hex), + ('description', 'my-desc-' + uuid.uuid4().hex), + ('enabled', False), + ('name', 'my-log-' + uuid.uuid4().hex), + ('target_id', None), + ('project_id', 'project-id-' + uuid.uuid4().hex), + ('resource_id', None), + ('resource_type', 'security_group'), + ('event', 'all'), + )) diff --git a/neutronclient/tests/unit/osc/v2/logging/test_network_log.py b/neutronclient/tests/unit/osc/v2/logging/test_network_log.py new file mode 100644 index 0000000..bceaf32 --- /dev/null +++ b/neutronclient/tests/unit/osc/v2/logging/test_network_log.py @@ -0,0 +1,658 @@ +# Copyright 2017 FUJITSU LIMITED +# All Rights Reserved +# +# 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 copy + +import mock +from osc_lib import exceptions +from osc_lib.tests import utils +import testtools + +from neutronclient.osc import utils as osc_utils +from neutronclient.osc.v2.logging import network_log +from neutronclient.tests.unit.osc.v2 import fakes as test_fakes +from neutronclient.tests.unit.osc.v2.logging import fakes + + +_log = fakes.NetworkLog().create() +RES_TYPE_SG = 'security_group' +CONVERT_MAP = { + 'project': 'project_id', + 'enable': 'enabled', + 'disable': 'enabled', + 'target': 'target_id', + 'resource': 'resource_id', + 'event': 'event', +} + + +def _generate_data(ordered_dict=None, data=None): + source = ordered_dict if ordered_dict else _log + if data: + source.update(data) + return tuple(source[key] for key in source) + + +def _generate_req_and_res(verifylist): + request = dict(verifylist) + response = copy.deepcopy(_log) + for key, val in verifylist: + converted = CONVERT_MAP.get(key, key) + del request[key] + if key == 'enable' and val: + new_value = True + elif key == 'disable' and val: + new_value = False + else: + new_value = val + request[converted] = new_value + response[converted] = new_value + return request, response + + +class TestNetworkLog(test_fakes.TestNeutronClientOSCV2): + + def check_results(self, headers, data, exp_req, is_list=False): + if is_list: + req_body = {'logs': [exp_req]} + else: + req_body = {'log': exp_req} + self.mocked.assert_called_once_with(req_body) + self.assertEqual(self.ordered_headers, headers) + self.assertEqual(self.ordered_data, data) + + def setUp(self): + super(TestNetworkLog, self).setUp() + self.neutronclient.find_resource = mock.Mock() + self.neutronclient.find_resource.side_effect = \ + lambda x, y, **k: {'id': y} + osc_utils.find_project = mock.Mock() + osc_utils.find_project.id = _log['project_id'] + self.res = _log + self.headers = ( + 'ID', + 'Description', + 'Enabled', + 'Name', + 'Target', + 'Project', + 'Resource', + 'Type', + 'Event', + ) + self.data = _generate_data() + self.ordered_headers = ( + 'Description', + 'Enabled', + 'Event', + 'ID', + 'Name', + 'Project', + 'Resource', + 'Target', + 'Type', + ) + self.ordered_data = ( + _log['description'], + _log['enabled'], + _log['event'], + _log['id'], + _log['name'], + _log['project_id'], + _log['resource_id'], + _log['target_id'], + _log['resource_type'], + ) + self.ordered_columns = ( + 'description', + 'enabled', + 'event', + 'id', + 'name', + 'project_id', + 'resource_id', + 'target_id', + 'resource_type', + ) + + +class TestCreateNetworkLog(TestNetworkLog): + + def setUp(self): + super(TestCreateNetworkLog, self).setUp() + self.neutronclient.create_network_log = mock.Mock( + return_value={'log': _log}) + self.mocked = self.neutronclient.create_network_log + self.cmd = network_log.CreateNetworkLog(self.app, self.namespace) + + def _update_expect_response(self, request, response): + """Set expected request and response + + :param request + A dictionary of request body(dict of verifylist) + :param response + A OrderedDict of request body + """ + # Update response body + self.neutronclient.create_network_log.return_value = \ + {'log': dict(response)} + osc_utils.find_project.return_value.id = response['project_id'] + # Update response(finally returns 'data') + self.data = _generate_data(ordered_dict=response) + self.ordered_data = tuple( + response[column] for column in self.ordered_columns + ) + + def _set_all_params(self, args={}): + name = args.get('name', 'my-log') + desc = args.get('description', 'my-description-for-log') + event = args.get('event', 'ACCEPT') + resource = args.get('resource', 'id-target-log') + target = args.get('target', 'id-target-log') + resource_type = args.get('resource_type', 'security_group') + project = args.get('project_id', 'id-my-project') + + arglist = [ + name, + '--description', desc, + '--enable', + '--target', target, + '--resource', resource, + '--event', event, + '--project', project, + '--resource-type', resource_type, + ] + verifylist = [ + ('description', desc), + ('enable', True), + ('event', event), + ('name', name), + ('target', target), + ('project', project), + ('resource', target), + ('resource_type', resource_type), + ] + return arglist, verifylist + + def _test_create_with_all_params(self, args={}): + arglist, verifylist = self._set_all_params(args) + request, response = _generate_req_and_res(verifylist) + self._update_expect_response(request, response) + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + headers, data = self.cmd.take_action(parsed_args) + + self.check_results(headers, data, request) + + def test_create_with_no_options_and_raise(self): + arglist = [] + verifylist = [] + + self.assertRaises( + utils.ParserException, + self.check_parser, self.cmd, arglist, verifylist) + + def test_create_with_mandatory_params(self): + name = self.res['name'] + arglist = [ + name, + '--resource-type', RES_TYPE_SG, + ] + verifylist = [ + ('name', name), + ('resource_type', RES_TYPE_SG), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + headers, data = self.cmd.take_action(parsed_args) + expect = { + 'name': self.res['name'], + 'resource_type': self.res['resource_type'], + } + self.mocked.assert_called_once_with({'log': expect}) + self.assertEqual(self.ordered_headers, headers) + self.assertEqual(self.ordered_data, data) + + def test_create_with_disable(self): + name = self.res['name'] + arglist = [ + name, + '--resource-type', RES_TYPE_SG, + '--disable', + ] + verifylist = [ + ('name', name), + ('resource_type', RES_TYPE_SG), + ('disable', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + headers, data = self.cmd.take_action(parsed_args) + expect = { + 'name': self.res['name'], + 'resource_type': self.res['resource_type'], + 'enabled': False, + } + self.mocked.assert_called_once_with({'log': expect}) + self.assertEqual(self.ordered_headers, headers) + self.assertEqual(self.ordered_data, data) + + def test_create_with_all_params(self): + self._test_create_with_all_params() + + def test_create_with_all_params_event_drop(self): + self._test_create_with_all_params({'event': 'DROP'}) + + def test_create_with_all_params_event_all(self): + self._test_create_with_all_params({'event': 'ALL'}) + + def test_create_with_all_params_except_event(self): + arglist, verifylist = self._set_all_params({'event': ''}) + self.assertRaises( + utils.ParserException, + self.check_parser, self.cmd, arglist, verifylist) + + def test_create_with_all_params_event_upper_capitalized(self): + for event in ('all', 'All', 'dROP', 'accePt', 'accept', 'drop'): + arglist, verifylist = self._set_all_params({'event': event}) + self.assertRaises( + testtools.matchers._impl.MismatchError, + self.check_parser, self.cmd, arglist, verifylist) + + def test_create_with_all_params_resource_type_upper_capitalized(self): + for res_type in ('SECURITY_GROUP', 'Security_group', 'security_Group'): + arglist, verifylist = self._set_all_params( + {'resource_type': res_type}) + self.assertRaises( + testtools.matchers._impl.MismatchError, + self.check_parser, self.cmd, arglist, verifylist) + + +class TestListNetworkLog(TestNetworkLog): + + def _setup_summary(self, expect=None): + event = 'Event: ' + self.res['event'].upper() + target = 'Logged: (None specified)' + if expect: + if expect.get('event'): + event = expect['event'] + if expect.get('resource'): + target = expect['resource'] + summary = ',\n'.join([event, target]) + self.short_data = ( + expect['id'] if expect else self.res['id'], + expect['enabled'] if expect else self.res['enabled'], + expect['name'] if expect else self.res['name'], + expect['resource_type'] if expect else self.res['resource_type'], + summary + ) + + def setUp(self): + super(TestListNetworkLog, self).setUp() + self.cmd = network_log.ListNetworkLog(self.app, self.namespace) + + self.short_header = ( + 'ID', + 'Enabled', + 'Name', + 'Type', + 'Summary', + ) + self._setup_summary() + self.neutronclient.list_network_logs = mock.Mock( + return_value={'logs': [self.res]}) + self.mocked = self.neutronclient.list_network_logs + + def test_list_with_long_option(self): + arglist = ['--long'] + verifylist = [('long', True)] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + headers, data = self.cmd.take_action(parsed_args) + + self.mocked.assert_called_once_with() + self.assertEqual(list(self.headers), headers) + self.assertEqual([self.data], list(data)) + + def test_list_with_no_option(self): + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + headers, data = self.cmd.take_action(parsed_args) + + self.mocked.assert_called_once_with() + self.assertEqual(list(self.short_header), headers) + self.assertEqual([self.short_data], list(data)) + + def test_list_with_target_and_resource(self): + arglist = [] + verifylist = [] + target_id = 'aaaaaaaa-aaaa-aaaa-aaaaaaaaaaaaaaaaa' + resource_id = 'bbbbbbbb-bbbb-bbbb-bbbbbbbbbbbbbbbbb' + log = fakes.NetworkLog().create({ + 'target_id': target_id, + 'resource_id': resource_id}) + self.mocked.return_value = {'logs': [log]} + logged = 'Logged: (security_group) %(res_id)s on (port) %(t_id)s' % { + 'res_id': resource_id, 't_id': target_id} + expect_log = copy.deepcopy(log) + expect_log.update({ + 'resource': logged, + 'event': 'Event: ALL'}) + self._setup_summary(expect=expect_log) + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + headers, data = self.cmd.take_action(parsed_args) + + self.mocked.assert_called_once_with() + self.assertEqual(list(self.short_header), headers) + self.assertEqual([self.short_data], list(data)) + + def test_list_with_resource(self): + arglist = [] + verifylist = [] + resource_id = 'bbbbbbbb-bbbb-bbbb-bbbbbbbbbbbbbbbbb' + log = fakes.NetworkLog().create({'resource_id': resource_id}) + self.mocked.return_value = {'logs': [log]} + logged = 'Logged: (security_group) %s' % resource_id + expect_log = copy.deepcopy(log) + expect_log.update({ + 'resource': logged, + 'event': 'Event: ALL'}) + self._setup_summary(expect=expect_log) + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + headers, data = self.cmd.take_action(parsed_args) + + self.mocked.assert_called_once_with() + self.assertEqual(list(self.short_header), headers) + self.assertEqual([self.short_data], list(data)) + + def test_list_with_target(self): + arglist = [] + verifylist = [] + target_id = 'aaaaaaaa-aaaa-aaaa-aaaaaaaaaaaaaaaaa' + log = fakes.NetworkLog().create({'target_id': target_id}) + self.mocked.return_value = {'logs': [log]} + logged = 'Logged: (port) %s' % target_id + expect_log = copy.deepcopy(log) + expect_log.update({ + 'resource': logged, + 'event': 'Event: ALL'}) + self._setup_summary(expect=expect_log) + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + headers, data = self.cmd.take_action(parsed_args) + + self.mocked.assert_called_once_with() + self.assertEqual(list(self.short_header), headers) + self.assertEqual([self.short_data], list(data)) + + +class TestShowNetworkLog(TestNetworkLog): + + def setUp(self): + super(TestShowNetworkLog, self).setUp() + self.neutronclient.show_network_log = mock.Mock( + return_value={'log': self.res}) + self.mocked = self.neutronclient.show_network_log + self.cmd = network_log.ShowNetworkLog(self.app, self.namespace) + + def test_show_filtered_by_id_or_name(self): + target = self.res['id'] + arglist = [target] + verifylist = [('network_log', target)] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + headers, data = self.cmd.take_action(parsed_args) + + self.mocked.assert_called_once_with(target) + self.assertEqual(self.ordered_headers, headers) + self.assertEqual(self.ordered_data, data) + + +class TestSetNetworkLog(TestNetworkLog): + + def setUp(self): + super(TestSetNetworkLog, self).setUp() + self.neutronclient.update_network_log = mock.Mock( + return_value={'log': self.res}) + self.mocked = self.neutronclient.update_network_log + self.cmd = network_log.SetNetworkLog(self.app, self.namespace) + + def test_set_name(self): + target = self.res['id'] + update = 'change' + arglist = [target, '--name', update] + verifylist = [ + ('network_log', target), + ('name', update), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.mocked.assert_called_once_with( + target, {'log': {'name': update}}) + self.assertIsNone(result) + + def test_set_description(self): + target = self.res['id'] + update = 'change-desc' + arglist = [target, '--description', update] + verifylist = [ + ('network_log', target), + ('description', update), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.mocked.assert_called_once_with( + target, {'log': {'description': update}}) + self.assertIsNone(result) + + def test_set_enable(self): + target = self.res['id'] + arglist = [target, '--enable'] + verifylist = [ + ('network_log', target), + ('enable', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.mocked.assert_called_once_with( + target, {'log': {'enabled': True}}) + self.assertIsNone(result) + + def test_set_disable(self): + target = self.res['id'] + arglist = [target, '--disable'] + verifylist = [ + ('network_log', target), + ('disable', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.mocked.assert_called_once_with( + target, {'log': {'enabled': False}}) + self.assertIsNone(result) + + # Illegal tests + def test_illegal_set_resource_type(self): + target = self.res['id'] + resource_type = 'security_group' + arglist = [target, '--resource-type', resource_type] + verifylist = [ + ('network_log', target), + ('resource_type', resource_type), + ] + + self.assertRaises( + utils.ParserException, + self.check_parser, self.cmd, arglist, verifylist) + + def test_illegal_set_event(self): + target = self.res['id'] + for event in ['all', 'accept', 'drop']: + arglist = [target, '--event', event] + verifylist = [ + ('network_log', target), + ('event', event), + ] + self.assertRaises( + utils.ParserException, + self.check_parser, self.cmd, arglist, verifylist) + + def test_illegal_set_resource_id(self): + target = self.res['id'] + resource_id = 'resource-id-for-logged-target' + arglist = [target, '--resource', resource_id] + verifylist = [ + ('network_log', target), + ('resource', resource_id), + ] + + self.assertRaises( + utils.ParserException, + self.check_parser, self.cmd, arglist, verifylist) + + def test_illegal_set_project(self): + target = self.res['id'] + arglist = [ + target, + '--project', + ] + verifylist = [ + ('network_log', target), + ('project', 'other-project'), + ] + self.assertRaises( + utils.ParserException, + self.check_parser, self.cmd, arglist, verifylist) + + def test_illegal_set_project_domain(self): + target = self.res['id'] + arglist = [ + target, + '--project-domain', + ] + verifylist = [ + ('network_log', target), + ('project_domain', 'other-project-domain'), + ] + self.assertRaises( + utils.ParserException, + self.check_parser, self.cmd, arglist, verifylist) + + def test_illegal_set_and_raises(self): + self.neutronclient.update_network_log = mock.Mock( + side_effect=Exception) + target = self.res['id'] + arglist = [target, '--name', 'my-name'] + verifylist = [('network_log', target), ('name', 'my-name')] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, self.cmd.take_action, parsed_args) + + +class TestDeleteNetworkLog(TestNetworkLog): + + def setUp(self): + super(TestDeleteNetworkLog, self).setUp() + self.neutronclient.delete_network_log = mock.Mock( + return_value={'log': self.res}) + self.mocked = self.neutronclient.delete_network_log + self.cmd = network_log.DeleteNetworkLog(self.app, self.namespace) + + def test_delete_with_one_resource(self): + target = self.res['id'] + arglist = [target] + verifylist = [('network_log', [target])] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.mocked.assert_called_once_with(target) + self.assertIsNone(result) + + def test_delete_with_multiple_resources(self): + target1 = 'target1' + target2 = 'target2' + arglist = [target1, target2] + verifylist = [('network_log', [target1, target2])] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.assertIsNone(result) + + self.assertEqual(2, self.mocked.call_count) + for idx, reference in enumerate([target1, target2]): + actual = ''.join(self.mocked.call_args_list[idx][0]) + self.assertEqual(reference, actual) + + def test_delete_with_no_exist_id(self): + self.neutronclient.find_resource.side_effect = Exception + target = 'not_exist' + arglist = [target] + verifylist = [('network_log', [target])] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises( + exceptions.CommandError, self.cmd.take_action, parsed_args) + + +class TestLoggableResource(test_fakes.TestNeutronClientOSCV2): + + def check_results(self, headers, data, exp_req, is_list=False): + if is_list: + req_body = {'logs': [exp_req]} + else: + req_body = {'log': exp_req} + self.mocked.assert_called_once_with(req_body) + self.assertEqual(self.ordered_headers, headers) + self.assertEqual(self.ordered_data, data) + + def setUp(self): + super(TestLoggableResource, self).setUp() + self.headers = ('Supported types',) + self.data = ('security_group', ) + + +class TestListLoggableResource(TestLoggableResource): + + def setUp(self): + super(TestListLoggableResource, self).setUp() + self.cmd = network_log.ListLoggableResource(self.app, self.namespace) + + loggables = { + "loggable_resources": [{"type": "security_group"}] + } + self.neutronclient.list_network_loggable_resources = mock.Mock( + return_value=loggables) + self.mocked = self.neutronclient.list_network_loggable_resources + + def test_list_with_long_option(self): + arglist = ['--long'] + verifylist = [('long', True)] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + headers, data = self.cmd.take_action(parsed_args) + + self.mocked.assert_called_once_with() + self.assertEqual(list(self.headers), headers) + self.assertEqual([self.data], list(data)) + + def test_list_with_no_option(self): + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + headers, data = self.cmd.take_action(parsed_args) + + self.mocked.assert_called_once_with() + self.assertEqual(list(self.headers), headers) + self.assertEqual([self.data], list(data)) diff --git a/neutronclient/v2_0/client.py b/neutronclient/v2_0/client.py index 232320b..dc3a68b 100644 --- a/neutronclient/v2_0/client.py +++ b/neutronclient/v2_0/client.py @@ -1,5 +1,6 @@ # Copyright 2012 OpenStack Foundation. # Copyright 2015 Hewlett-Packard Development Company, L.P. +# Copyright 2017 FUJITSU LIMITED # All Rights Reserved # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -650,6 +651,9 @@ class Client(ClientBase): bgpvpn_router_associations_path = "/bgpvpn/bgpvpns/%s/router_associations" bgpvpn_router_association_path =\ "/bgpvpn/bgpvpns/%s/router_associations/%s" + network_logs_path = "/log/logs" + network_log_path = "/log/logs/%s" + network_loggables_path = "/log/loggable-resources" # API has no way to report plurals, so we have to hard code them EXTED_PLURALS = {'routers': 'router', @@ -708,6 +712,8 @@ class Client(ClientBase): 'port_pair_groups': 'port_pair_group', 'port_chains': 'port_chain', 'service_graphs': 'service_graph', + 'logs': 'log', + 'loggable_resources': 'loggable_resource', } def list_ext(self, collection, path, retrieve_all, **_params): @@ -2287,6 +2293,32 @@ class Client(ClientBase): return self.get(self.sfc_service_graph_path % service_graph, params=_params) + def create_network_log(self, body=None): + """Create a network log.""" + return self.post(self.network_logs_path, body=body) + + def delete_network_log(self, net_log): + """Delete a network log.""" + return self.delete(self.network_log_path % net_log) + + def list_network_logs(self, retrieve_all=True, **_params): + """Fetch a list of all network logs.""" + return self.list( + 'logs', self.network_logs_path, retrieve_all, **_params) + + def show_network_log(self, net_log, **_params): + """Fetch information for a certain network log.""" + return self.get(self.network_log_path % net_log, params=_params) + + def update_network_log(self, net_log, body=None): + """Update a network log.""" + return self.put(self.network_log_path % net_log, body=body) + + def list_network_loggable_resources(self, retrieve_all=True, **_params): + """Fetch a list of supported resource types for network log.""" + return self.list('loggable_resources', self.network_loggables_path, + retrieve_all, **_params) + def __init__(self, **kwargs): """Initialize a new client for the Neutron v2.0 API.""" super(Client, self).__init__(**kwargs) diff --git a/releasenotes/notes/support-logging-cli-cd02d3bb03367106.yaml b/releasenotes/notes/support-logging-cli-cd02d3bb03367106.yaml new file mode 100644 index 0000000..415c30c --- /dev/null +++ b/releasenotes/notes/support-logging-cli-cd02d3bb03367106.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + CLI support for 'Logging' feature, which enable to collect packet logs + for specified resource. Currently, only security-group can be logged. @@ -125,6 +125,13 @@ openstack.neutronclient.v2 = bgpvpn_router_association_list = neutronclient.osc.v2.networking_bgpvpn.router_association:ListBgpvpnRouterAssoc bgpvpn_router_association_show = neutronclient.osc.v2.networking_bgpvpn.router_association:ShowBgpvpnRouterAssoc + network_loggable_resources_list = neutronclient.osc.v2.logging.network_log:ListLoggableResource + network_log_create = neutronclient.osc.v2.logging.network_log:CreateNetworkLog + network_log_delete = neutronclient.osc.v2.logging.network_log:DeleteNetworkLog + network_log_list = neutronclient.osc.v2.logging.network_log:ListNetworkLog + network_log_set = neutronclient.osc.v2.logging.network_log:SetNetworkLog + network_log_show = neutronclient.osc.v2.logging.network_log:ShowNetworkLog + neutron.cli.v2 = bash-completion = neutronclient.shell:BashCompletionCommand @@ -409,7 +416,6 @@ neutron.cli.v2 = vpn-ikepolicy-update = neutronclient.neutron.v2_0.vpn.ikepolicy:UpdateIKEPolicy vpn-ikepolicy-delete = neutronclient.neutron.v2_0.vpn.ikepolicy:DeleteIKEPolicy - [build_sphinx] all_files = 1 build-dir = doc/build |