Initial congressclient
This patch adds client support for congress with keystone and supports the following api commands: $ openstack congress policy list $ openstack congress policy row get $ openstack congress policy rule create $ openstack congress policy rule delete $ openstack congress policy rules list Change-Id: I624b54f6cf2614eaec970c8bdd3766291dcc9489
This commit is contained in:
parent
d44349d9f0
commit
c0612c0c6c
0
congressclient/common/__init__.py
Normal file
0
congressclient/common/__init__.py
Normal file
94
congressclient/common/utils.py
Normal file
94
congressclient/common/utils.py
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
# Copyright 2012-2013 OpenStack, LLC.
|
||||||
|
#
|
||||||
|
# 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 os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from congressclient import exceptions
|
||||||
|
|
||||||
|
|
||||||
|
def env(*vars, **kwargs):
|
||||||
|
"""Search for the first defined of possibly many env vars
|
||||||
|
|
||||||
|
Returns the first environment variable defined in vars, or
|
||||||
|
returns the default defined in kwargs.
|
||||||
|
"""
|
||||||
|
for v in vars:
|
||||||
|
value = os.environ.get(v, None)
|
||||||
|
if value:
|
||||||
|
return value
|
||||||
|
return kwargs.get('default', '')
|
||||||
|
|
||||||
|
|
||||||
|
def get_client_class(api_name, version, version_map):
|
||||||
|
"""Returns the client class for the requested API version
|
||||||
|
|
||||||
|
:param api_name: the name of the API, e.g. 'compute', 'image', etc
|
||||||
|
:param version: the requested API version
|
||||||
|
:param version_map: a dict of client classes keyed by version
|
||||||
|
:rtype: a client class for the requested API version
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
client_path = version_map[str(version)]
|
||||||
|
except (KeyError, ValueError):
|
||||||
|
msg = "Invalid %s client version '%s'. must be one of: %s" % (
|
||||||
|
(api_name, version, ', '.join(version_map.keys())))
|
||||||
|
raise exceptions.UnsupportedVersion(msg)
|
||||||
|
|
||||||
|
return import_class(client_path)
|
||||||
|
|
||||||
|
|
||||||
|
def import_class(import_str):
|
||||||
|
"""Returns a class from a string including module and class
|
||||||
|
|
||||||
|
:param import_str: a string representation of the class name
|
||||||
|
:rtype: the requested class
|
||||||
|
"""
|
||||||
|
mod_str, _sep, class_str = import_str.rpartition('.')
|
||||||
|
__import__(mod_str)
|
||||||
|
return getattr(sys.modules[mod_str], class_str)
|
||||||
|
|
||||||
|
|
||||||
|
def format_list(data):
|
||||||
|
"""Return a formatted strings
|
||||||
|
|
||||||
|
:param data: a list of strings
|
||||||
|
:rtype: a string formatted to a,b,c
|
||||||
|
"""
|
||||||
|
|
||||||
|
return ', '.join(data)
|
||||||
|
|
||||||
|
|
||||||
|
def get_dict_properties(item, fields, mixed_case_fields=[], formatters={}):
|
||||||
|
"""Return a tuple containing the item properties.
|
||||||
|
|
||||||
|
:param item: a single dict resource
|
||||||
|
:param fields: tuple of strings with the desired field names
|
||||||
|
:param mixed_case_fields: tuple of field names to preserve case
|
||||||
|
:param formatters: dictionary mapping field names to callables
|
||||||
|
to format the values
|
||||||
|
"""
|
||||||
|
row = []
|
||||||
|
|
||||||
|
for field in fields:
|
||||||
|
if field in mixed_case_fields:
|
||||||
|
field_name = field.replace(' ', '_')
|
||||||
|
else:
|
||||||
|
field_name = field.lower().replace(' ', '_')
|
||||||
|
data = item[field_name] if field_name in item else ''
|
||||||
|
if field in formatters:
|
||||||
|
row.append(formatters[field](data))
|
||||||
|
else:
|
||||||
|
row.append(data)
|
||||||
|
return tuple(row)
|
16
congressclient/exceptions.py
Normal file
16
congressclient/exceptions.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# Copyright (c) 2012 OpenStack Foundation.
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from congressclient.openstack.common.apiclient.exceptions import * # noqa
|
0
congressclient/osc/__init__.py
Normal file
0
congressclient/osc/__init__.py
Normal file
61
congressclient/osc/osc_plugin.py
Normal file
61
congressclient/osc/osc_plugin.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
# Copyright 2014 VMWare.
|
||||||
|
#
|
||||||
|
# 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 logging
|
||||||
|
|
||||||
|
import keystoneclient
|
||||||
|
|
||||||
|
from congressclient.common import utils
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
DEFAULT_COMPUTE_API_VERSION = '1'
|
||||||
|
API_VERSION_OPTION = 'os_policy_api_version'
|
||||||
|
API_NAME = 'congressclient'
|
||||||
|
API_VERSIONS = {
|
||||||
|
'1': 'congressclient.v1.client.Client',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def make_client(instance):
|
||||||
|
"""Returns a congress service client."""
|
||||||
|
congress_client = utils.get_client_class(
|
||||||
|
API_NAME,
|
||||||
|
instance._api_version[API_NAME],
|
||||||
|
API_VERSIONS)
|
||||||
|
auth = keystoneclient.auth.identity.v2.Password(
|
||||||
|
auth_url=instance._auth_url,
|
||||||
|
username=instance._username,
|
||||||
|
password=instance._password, tenant_name=instance._project_name)
|
||||||
|
session = keystoneclient.session.Session(auth=auth)
|
||||||
|
LOG.debug('instantiating congress client: %s', congress_client)
|
||||||
|
return congress_client(session=session,
|
||||||
|
auth=None,
|
||||||
|
interface='publicURL',
|
||||||
|
service_type='policy',
|
||||||
|
region_name=instance._region_name)
|
||||||
|
|
||||||
|
|
||||||
|
def build_option_parser(parser):
|
||||||
|
"""Hook to add global options."""
|
||||||
|
parser.add_argument(
|
||||||
|
'--os-policy-api-version',
|
||||||
|
metavar='<policy-api-version>',
|
||||||
|
default=utils.env(
|
||||||
|
'OS_POLICY_API_VERSION',
|
||||||
|
default=DEFAULT_COMPUTE_API_VERSION),
|
||||||
|
help='Policy API version, default=' +
|
||||||
|
DEFAULT_COMPUTE_API_VERSION +
|
||||||
|
' (Env: OS_POLICY_API_VERSION)')
|
||||||
|
return parser
|
0
congressclient/osc/v1/__init__.py
Normal file
0
congressclient/osc/v1/__init__.py
Normal file
162
congressclient/osc/v1/policy.py
Normal file
162
congressclient/osc/v1/policy.py
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
# Copyright 2012-2013 OpenStack, LLC.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""Policy action implemenations"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from cliff import command
|
||||||
|
from cliff import lister
|
||||||
|
from cliff import show
|
||||||
|
import six
|
||||||
|
|
||||||
|
from congressclient.common import utils
|
||||||
|
from congressclient.openstack.common import jsonutils
|
||||||
|
|
||||||
|
|
||||||
|
def _format_rule(rule):
|
||||||
|
"""Break up rule string so it fits on screen."""
|
||||||
|
|
||||||
|
rule_split = jsonutils.dumps(rule).split(":-")
|
||||||
|
formatted_string = rule_split[0] + ":-\n"
|
||||||
|
for rule in rule_split[1].split("), "):
|
||||||
|
formatted_string += rule + '\n'
|
||||||
|
return formatted_string
|
||||||
|
|
||||||
|
|
||||||
|
class CreatePolicyRule(show.ShowOne):
|
||||||
|
"""Create a policy rule."""
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__ + '.CreatePolicyRule')
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(CreatePolicyRule, self).get_parser(prog_name)
|
||||||
|
parser.add_argument(
|
||||||
|
'policy_name',
|
||||||
|
metavar="<policy-name>",
|
||||||
|
help="Name or identifier of the policy")
|
||||||
|
parser.add_argument(
|
||||||
|
'rule',
|
||||||
|
metavar="<rule>",
|
||||||
|
help="Policy rule")
|
||||||
|
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
self.log.debug('take_action(%s)' % parsed_args)
|
||||||
|
client = self.app.client_manager.congressclient
|
||||||
|
body = {'rule': parsed_args.rule}
|
||||||
|
data = client.create_policy_rule(parsed_args.policy_name, body)
|
||||||
|
data['rule'] = _format_rule(data['rule'])
|
||||||
|
return zip(*sorted(six.iteritems(data)))
|
||||||
|
|
||||||
|
|
||||||
|
class DeletePolicyRule(command.Command):
|
||||||
|
"""Delete a policy rule."""
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__ + '.DeletePolicyRule')
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(DeletePolicyRule, self).get_parser(prog_name)
|
||||||
|
parser.add_argument(
|
||||||
|
'policy_name',
|
||||||
|
metavar="<policy-name>",
|
||||||
|
help="Name of the policy to delete")
|
||||||
|
parser.add_argument(
|
||||||
|
'rule_id',
|
||||||
|
metavar="<rule-id>",
|
||||||
|
help="ID of the policy to delete")
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
self.log.debug('take_action(%s)' % parsed_args)
|
||||||
|
client = self.app.client_manager.congressclient
|
||||||
|
client.delete_policy_rule(parsed_args.policy_name,
|
||||||
|
parsed_args.rule_id)
|
||||||
|
|
||||||
|
|
||||||
|
class ListPolicyRules(lister.Lister):
|
||||||
|
"""List policy rules."""
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__ + '.ListPolicyRules')
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(ListPolicyRules, self).get_parser(prog_name)
|
||||||
|
parser.add_argument(
|
||||||
|
'policy_name',
|
||||||
|
metavar="<policy-name>",
|
||||||
|
help="Name of the policy")
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
self.log.debug('take_action(%s)' % parsed_args)
|
||||||
|
client = self.app.client_manager.congressclient
|
||||||
|
data = client.list_policy_rules(parsed_args.policy_name)['results']
|
||||||
|
columns = ['id', 'comment', 'rule']
|
||||||
|
formatters = {'PolicyRules': utils.format_list}
|
||||||
|
return (columns,
|
||||||
|
(utils.get_dict_properties(s, columns,
|
||||||
|
formatters=formatters)
|
||||||
|
for s in data))
|
||||||
|
|
||||||
|
|
||||||
|
class ListPolicy(lister.Lister):
|
||||||
|
"""List Policy."""
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__ + '.ListPolicy')
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(ListPolicy, self).get_parser(prog_name)
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
client = self.app.client_manager.congressclient
|
||||||
|
data = client.list_policy()['results']
|
||||||
|
columns = ['id', 'owner_id']
|
||||||
|
formatters = {'Policies': utils.format_list}
|
||||||
|
return (columns,
|
||||||
|
(utils.get_dict_properties(s, columns,
|
||||||
|
formatters=formatters)
|
||||||
|
for s in data))
|
||||||
|
|
||||||
|
|
||||||
|
class GetPolicyRow(lister.Lister):
|
||||||
|
"""Get policy row."""
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__ + '.GetPolicyRow')
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(GetPolicyRow, self).get_parser(prog_name)
|
||||||
|
parser.add_argument(
|
||||||
|
'policy_name',
|
||||||
|
metavar="<policy-name>",
|
||||||
|
help="Name of the policy to show")
|
||||||
|
parser.add_argument(
|
||||||
|
'table',
|
||||||
|
metavar="<table>",
|
||||||
|
help="Table to get the policy row from")
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
self.log.debug('take_action(%s)' % parsed_args)
|
||||||
|
client = self.app.client_manager.congressclient
|
||||||
|
data = client.get_policy_rows(parsed_args.policy_name,
|
||||||
|
parsed_args.table)['results']
|
||||||
|
|
||||||
|
columns = ['data']
|
||||||
|
formatters = {'Policies': utils.format_list}
|
||||||
|
return (columns,
|
||||||
|
(utils.get_dict_properties(s, columns,
|
||||||
|
formatters=formatters)
|
||||||
|
for s in data))
|
@ -1,13 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# 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.
|
|
53
congressclient/tests/common.py
Normal file
53
congressclient/tests/common.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# 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 argparse
|
||||||
|
|
||||||
|
import mock
|
||||||
|
|
||||||
|
from congressclient.tests import utils
|
||||||
|
|
||||||
|
|
||||||
|
class TestCongressBase(utils.TestCommand):
|
||||||
|
def setUp(self):
|
||||||
|
super(TestCongressBase, self).setUp()
|
||||||
|
self.app = mock.Mock(name='app')
|
||||||
|
self.app.client_manager = mock.Mock(name='client_manager')
|
||||||
|
self.namespace = argparse.Namespace()
|
||||||
|
|
||||||
|
given_show_options = [
|
||||||
|
'-f',
|
||||||
|
'shell',
|
||||||
|
'-c',
|
||||||
|
'id',
|
||||||
|
'--prefix',
|
||||||
|
'TST',
|
||||||
|
]
|
||||||
|
then_show_options = [
|
||||||
|
('formatter', 'shell'),
|
||||||
|
('columns', ['id']),
|
||||||
|
('prefix', 'TST'),
|
||||||
|
]
|
||||||
|
given_list_options = [
|
||||||
|
'-f',
|
||||||
|
'csv',
|
||||||
|
'-c',
|
||||||
|
'id',
|
||||||
|
'--quote',
|
||||||
|
'all',
|
||||||
|
]
|
||||||
|
then_list_options = [
|
||||||
|
('formatter', 'csv'),
|
||||||
|
('columns', ['id']),
|
||||||
|
('quote_mode', 'all'),
|
||||||
|
]
|
81
congressclient/tests/fakes.py
Normal file
81
congressclient/tests/fakes.py
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
# Copyright 2013 Nebula Inc.
|
||||||
|
#
|
||||||
|
# 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 sys
|
||||||
|
|
||||||
|
import six
|
||||||
|
|
||||||
|
|
||||||
|
AUTH_TOKEN = "foobar"
|
||||||
|
AUTH_URL = "http://0.0.0.0"
|
||||||
|
|
||||||
|
|
||||||
|
class FakeStdout:
|
||||||
|
def __init__(self):
|
||||||
|
self.content = []
|
||||||
|
|
||||||
|
def write(self, text):
|
||||||
|
self.content.append(text)
|
||||||
|
|
||||||
|
def make_string(self):
|
||||||
|
result = ''
|
||||||
|
for line in self.content:
|
||||||
|
result = result + line
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class FakeApp(object):
|
||||||
|
def __init__(self, _stdout):
|
||||||
|
self.stdout = _stdout
|
||||||
|
self.client_manager = None
|
||||||
|
self.stdin = sys.stdin
|
||||||
|
self.stdout = _stdout or sys.stdout
|
||||||
|
self.stderr = sys.stderr
|
||||||
|
self.restapi = None
|
||||||
|
|
||||||
|
|
||||||
|
class FakeClientManager(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.compute = None
|
||||||
|
self.identity = None
|
||||||
|
self.image = None
|
||||||
|
self.object = None
|
||||||
|
self.volume = None
|
||||||
|
self.network = None
|
||||||
|
self.auth_ref = None
|
||||||
|
|
||||||
|
|
||||||
|
class FakeModule(object):
|
||||||
|
def __init__(self, name, version):
|
||||||
|
self.name = name
|
||||||
|
self.__version__ = version
|
||||||
|
|
||||||
|
|
||||||
|
class FakeResource(object):
|
||||||
|
def __init__(self, manager, info, loaded=False):
|
||||||
|
self.manager = manager
|
||||||
|
self._info = info
|
||||||
|
self._add_details(info)
|
||||||
|
self._loaded = loaded
|
||||||
|
|
||||||
|
def _add_details(self, info):
|
||||||
|
for (k, v) in six.iteritems(info):
|
||||||
|
setattr(self, k, v)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
reprkeys = sorted(k for k in self.__dict__.keys() if k[0] != '_' and
|
||||||
|
k != 'manager')
|
||||||
|
info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys)
|
||||||
|
return "<%s %s>" % (self.__class__.__name__, info)
|
@ -1,28 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
"""
|
|
||||||
test_congressclient
|
|
||||||
----------------------------------
|
|
||||||
|
|
||||||
Tests for `congressclient` module.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from congressclient.tests import base
|
|
||||||
|
|
||||||
|
|
||||||
class TestCongressclient(base.TestCase):
|
|
||||||
|
|
||||||
def test_something(self):
|
|
||||||
pass
|
|
93
congressclient/tests/utils.py
Normal file
93
congressclient/tests/utils.py
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
# Copyright 2012-2013 OpenStack Foundation
|
||||||
|
# Copyright 2013 Nebula Inc.
|
||||||
|
#
|
||||||
|
# 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 os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import fixtures
|
||||||
|
import testtools
|
||||||
|
|
||||||
|
from congressclient.tests import fakes
|
||||||
|
|
||||||
|
|
||||||
|
class TestCase(testtools.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
testtools.TestCase.setUp(self)
|
||||||
|
|
||||||
|
if (os.environ.get("OS_STDOUT_CAPTURE") == "True" or
|
||||||
|
os.environ.get("OS_STDOUT_CAPTURE") == "1"):
|
||||||
|
stdout = self.useFixture(fixtures.StringStream("stdout")).stream
|
||||||
|
self.useFixture(fixtures.MonkeyPatch("sys.stdout", stdout))
|
||||||
|
|
||||||
|
if (os.environ.get("OS_STDERR_CAPTURE") == "True" or
|
||||||
|
os.environ.get("OS_STDERR_CAPTURE") == "1"):
|
||||||
|
stderr = self.useFixture(fixtures.StringStream("stderr")).stream
|
||||||
|
self.useFixture(fixtures.MonkeyPatch("sys.stderr", stderr))
|
||||||
|
|
||||||
|
def assertNotCalled(self, m, msg=None):
|
||||||
|
"""Assert a function was not called."""
|
||||||
|
|
||||||
|
if m.called:
|
||||||
|
if not msg:
|
||||||
|
msg = 'method %s should not have been called' % m
|
||||||
|
self.fail(msg)
|
||||||
|
|
||||||
|
# 2.6 doesn't have the assert dict equals so make sure that it exists
|
||||||
|
if tuple(sys.version_info)[0:2] < (2, 7):
|
||||||
|
|
||||||
|
def assertIsInstance(self, obj, cls, msg=None):
|
||||||
|
"""self.assertTrue(isinstance(obj, cls)), with a nicer message."""
|
||||||
|
|
||||||
|
if not isinstance(obj, cls):
|
||||||
|
standardMsg = '%s is not an instance of %r' % (obj, cls)
|
||||||
|
self.fail(self._formatMessage(msg, standardMsg))
|
||||||
|
|
||||||
|
def assertDictEqual(self, d1, d2, msg=None):
|
||||||
|
# Simple version taken from 2.7
|
||||||
|
self.assertIsInstance(d1, dict,
|
||||||
|
'First argument is not a dictionary')
|
||||||
|
self.assertIsInstance(d2, dict,
|
||||||
|
'Second argument is not a dictionary')
|
||||||
|
if d1 != d2:
|
||||||
|
if msg:
|
||||||
|
self.fail(msg)
|
||||||
|
else:
|
||||||
|
standardMsg = '%r != %r' % (d1, d2)
|
||||||
|
self.fail(standardMsg)
|
||||||
|
|
||||||
|
|
||||||
|
class TestCommand(TestCase):
|
||||||
|
"""Test cliff command classes."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestCommand, self).setUp()
|
||||||
|
# Build up a fake app
|
||||||
|
self.fake_stdout = fakes.FakeStdout()
|
||||||
|
self.app = fakes.FakeApp(self.fake_stdout)
|
||||||
|
self.app.client_manager = fakes.FakeClientManager()
|
||||||
|
|
||||||
|
def check_parser(self, cmd, args, verify_args):
|
||||||
|
cmd_parser = cmd.get_parser('check_parser')
|
||||||
|
try:
|
||||||
|
parsed_args = cmd_parser.parse_args(args)
|
||||||
|
except SystemExit:
|
||||||
|
raise Exception("Argument parse failed")
|
||||||
|
for av in verify_args:
|
||||||
|
attr, value = av
|
||||||
|
if attr:
|
||||||
|
self.assertIn(attr, parsed_args)
|
||||||
|
self.assertEqual(getattr(parsed_args, attr), value)
|
||||||
|
return parsed_args
|
0
congressclient/tests/v1/__init__.py
Normal file
0
congressclient/tests/v1/__init__.py
Normal file
147
congressclient/tests/v1/test_policy.py
Normal file
147
congressclient/tests/v1/test_policy.py
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
# 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 mock
|
||||||
|
|
||||||
|
from congressclient.osc.v1 import policy
|
||||||
|
from congressclient.tests import common
|
||||||
|
|
||||||
|
|
||||||
|
class TestCreatePolicyRule(common.TestCongressBase):
|
||||||
|
|
||||||
|
def test_create_policy_rule(self):
|
||||||
|
policy_name = 'classification'
|
||||||
|
rule = ("port_security_group(port, security_group_name) :-"
|
||||||
|
"neutron:ports(addr_pairs, security_groups, extra_dhcp_opts,"
|
||||||
|
"binding_cap, status, name, admin_state_up, network_id, "
|
||||||
|
"tenant_id, binding_vif, device_owner, mac_address, "
|
||||||
|
"fixed_ips, port, device_id, binding_host_id1), "
|
||||||
|
"neutron:ports.security_groups(security_groups, "
|
||||||
|
"security_group_id), neutron:security_groups(tenant_id2, "
|
||||||
|
"security_group_name, desc2, security_group_id)")
|
||||||
|
|
||||||
|
response = {"comment": "None",
|
||||||
|
"id": "e531f2b3-3d97-42c0-b3b5-b7b6ab532018",
|
||||||
|
"rule": rule}
|
||||||
|
|
||||||
|
arglist = [policy_name, rule]
|
||||||
|
verifylist = [
|
||||||
|
('policy_name', policy_name),
|
||||||
|
('rule', rule),
|
||||||
|
]
|
||||||
|
|
||||||
|
mocker = mock.Mock(return_value=response)
|
||||||
|
self.app.client_manager.congressclient.create_policy_rule = mocker
|
||||||
|
cmd = policy.CreatePolicyRule(self.app, self.namespace)
|
||||||
|
parsed_args = self.check_parser(cmd, arglist, verifylist)
|
||||||
|
result = list(cmd.take_action(parsed_args))
|
||||||
|
filtered = [('comment', 'id', 'rule'),
|
||||||
|
('None', 'e531f2b3-3d97-42c0-b3b5-b7b6ab532018',
|
||||||
|
policy._format_rule(rule))]
|
||||||
|
self.assertEqual(filtered, result)
|
||||||
|
|
||||||
|
|
||||||
|
class TestDeletePolicyRule(common.TestCongressBase):
|
||||||
|
def test_delete_policy_rule(self):
|
||||||
|
policy_name = 'classification'
|
||||||
|
rule_id = 'e531f2b3-3d97-42c0-b3b5-b7b6ab532018'
|
||||||
|
arglist = [
|
||||||
|
policy_name, rule_id
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
('policy_name', policy_name),
|
||||||
|
('rule_id', rule_id)
|
||||||
|
]
|
||||||
|
mocker = mock.Mock(return_value=None)
|
||||||
|
self.app.client_manager.congressclient.delete_policy_rule = mocker
|
||||||
|
cmd = policy.DeletePolicyRule(self.app, self.namespace)
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(cmd, arglist, verifylist)
|
||||||
|
result = cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
mocker.assert_called_with(policy_name, rule_id)
|
||||||
|
self.assertEqual(None, result)
|
||||||
|
|
||||||
|
|
||||||
|
class TestListPolicyRules(common.TestCongressBase):
|
||||||
|
def test_list_policy_rules(self):
|
||||||
|
policy_name = 'classification'
|
||||||
|
rule_id = 'e531f2b3-3d97-42c0-b3b5-b7b6ab532018'
|
||||||
|
arglist = [
|
||||||
|
policy_name
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
('policy_name', policy_name)
|
||||||
|
]
|
||||||
|
response = {
|
||||||
|
"results": [{"comment": "None",
|
||||||
|
"id": rule_id,
|
||||||
|
"rule": "security_group(port, security_group_name)"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
lister = mock.Mock(return_value=response)
|
||||||
|
self.app.client_manager.congressclient.list_policy_rules = lister
|
||||||
|
cmd = policy.ListPolicyRules(self.app, self.namespace)
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(cmd, arglist, verifylist)
|
||||||
|
result = cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
lister.assert_called_with(policy_name)
|
||||||
|
self.assertEqual(['id', 'comment', 'rule'], result[0])
|
||||||
|
|
||||||
|
|
||||||
|
class ListPolicy(common.TestCongressBase):
|
||||||
|
def test_list_policy_rules(self):
|
||||||
|
policy_name = 'classification'
|
||||||
|
arglist = [
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
]
|
||||||
|
response = {
|
||||||
|
"results": [{"id": policy_name,
|
||||||
|
"owner": "system"
|
||||||
|
}]}
|
||||||
|
lister = mock.Mock(return_value=response)
|
||||||
|
self.app.client_manager.congressclient.list_policy = lister
|
||||||
|
cmd = policy.ListPolicy(self.app, self.namespace)
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(cmd, arglist, verifylist)
|
||||||
|
result = cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
lister.assert_called_with()
|
||||||
|
self.assertEqual(['id', 'owner_id'], result[0])
|
||||||
|
|
||||||
|
|
||||||
|
class GetPolicyRow(common.TestCongressBase):
|
||||||
|
|
||||||
|
def test_list_policy_rules(self):
|
||||||
|
policy_name = 'classification'
|
||||||
|
table_name = 'port_security_group'
|
||||||
|
arglist = [
|
||||||
|
policy_name, table_name
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
]
|
||||||
|
response = {"results":
|
||||||
|
[{"data": ["69abc88b-c950-4625-801b-542e84381509",
|
||||||
|
"default"]}]}
|
||||||
|
|
||||||
|
lister = mock.Mock(return_value=response)
|
||||||
|
self.app.client_manager.congressclient.get_policy_rows = lister
|
||||||
|
cmd = policy.GetPolicyRow(self.app, self.namespace)
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(cmd, arglist, verifylist)
|
||||||
|
result = cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
lister.assert_called_with(policy_name, table_name)
|
||||||
|
self.assertEqual(['data'], result[0])
|
0
congressclient/v1/__init__.py
Normal file
0
congressclient/v1/__init__.py
Normal file
52
congressclient/v1/client.py
Normal file
52
congressclient/v1/client.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
# Copyright 2014 VMWare.
|
||||||
|
#
|
||||||
|
# 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 keystoneclient import adapter
|
||||||
|
|
||||||
|
|
||||||
|
class Client(object):
|
||||||
|
policy_rules_path = '/policies/%s/rules'
|
||||||
|
policy_rules_paths = '/policies/%s/rules/%s'
|
||||||
|
policy_rows = '/policies/%s/tables/%s/rows'
|
||||||
|
policy_rules = '/policies/%s/rules'
|
||||||
|
policies = '/policies'
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super(Client, self).__init__()
|
||||||
|
|
||||||
|
kwargs.setdefault('user_agent', 'python-congressclient')
|
||||||
|
self.httpclient = adapter.LegacyJsonAdapter(**kwargs)
|
||||||
|
|
||||||
|
def create_policy_rule(self, policy_name, body=None):
|
||||||
|
resp, body = self.httpclient.post(
|
||||||
|
self.policy_rules_path % policy_name, body=body)
|
||||||
|
return body
|
||||||
|
|
||||||
|
def delete_policy_rule(self, policy_name, rule_id):
|
||||||
|
resp, body = self.httpclient.delete(
|
||||||
|
self.policy_rules_paths % (policy_name, rule_id))
|
||||||
|
return body
|
||||||
|
|
||||||
|
def get_policy_rows(self, policy_name, table):
|
||||||
|
resp, body = self.httpclient.get(self.policy_rows % (policy_name,
|
||||||
|
table))
|
||||||
|
return body
|
||||||
|
|
||||||
|
def list_policy_rules(self, policy_name):
|
||||||
|
resp, body = self.httpclient.get(self.policy_rules % (policy_name))
|
||||||
|
return body
|
||||||
|
|
||||||
|
def list_policy(self):
|
||||||
|
resp, body = self.httpclient.get(self.policies)
|
||||||
|
return body
|
@ -1,2 +1,7 @@
|
|||||||
pbr>=0.6,!=0.7,<1.0
|
pbr>=0.6,!=0.7,<1.0
|
||||||
Babel>=1.3
|
Babel>=1.3
|
||||||
|
cliff>=1.6.0
|
||||||
|
oslo.i18n>=0.3.0
|
||||||
|
python-keystoneclient>=0.10.0
|
||||||
|
requests>=1.2.1
|
||||||
|
six>=1.7.0
|
||||||
|
13
setup.cfg
13
setup.cfg
@ -23,6 +23,17 @@ classifier =
|
|||||||
packages =
|
packages =
|
||||||
congressclient
|
congressclient
|
||||||
|
|
||||||
|
[entry_points]
|
||||||
|
openstack.cli.extension =
|
||||||
|
congressclient = congressclient.osc.osc_plugin
|
||||||
|
|
||||||
|
openstack.congressclient.v1 =
|
||||||
|
congress_policy_rule_create = congressclient.osc.v1.policy:CreatePolicyRule
|
||||||
|
congress_policy_rule_delete = congressclient.osc.v1.policy:DeletePolicyRule
|
||||||
|
congress_policy_rules_list = congressclient.osc.v1.policy:ListPolicyRules
|
||||||
|
congress_policy_list = congressclient.osc.v1.policy:ListPolicy
|
||||||
|
congress_policy_row_get = congressclient.osc.v1.policy:GetPolicyRow
|
||||||
|
|
||||||
[build_sphinx]
|
[build_sphinx]
|
||||||
source-dir = doc/source
|
source-dir = doc/source
|
||||||
build-dir = doc/build
|
build-dir = doc/build
|
||||||
@ -43,4 +54,4 @@ input_file = congressclient/locale/python-congressclient.pot
|
|||||||
[extract_messages]
|
[extract_messages]
|
||||||
keywords = _ gettext ngettext l_ lazy_gettext
|
keywords = _ gettext ngettext l_ lazy_gettext
|
||||||
mapping_file = babel.cfg
|
mapping_file = babel.cfg
|
||||||
output_file = congressclient/locale/python-congressclient.pot
|
output_file = congressclient/locale/python-congressclient.pot
|
||||||
|
8
tenant-list.log
Normal file
8
tenant-list.log
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
+----------------------------------+--------------------+---------+
|
||||||
|
| id | name | enabled |
|
||||||
|
+----------------------------------+--------------------+---------+
|
||||||
|
| 8918ef508b3a48a1ad963cec1d7bec18 | admin | True |
|
||||||
|
| e41ef6f35dab448699a5ca69eff60692 | demo | True |
|
||||||
|
| c01bea38e55d44d3a87bb018b050338c | invisible_to_admin | True |
|
||||||
|
| 3a1208bc143a4ed589288057ea8e0735 | service | True |
|
||||||
|
+----------------------------------+--------------------+---------+
|
@ -1,4 +1,4 @@
|
|||||||
hacking>=0.5.6,<0.8
|
hacking>=0.9.2,<0.10
|
||||||
|
|
||||||
coverage>=3.6
|
coverage>=3.6
|
||||||
discover
|
discover
|
||||||
@ -9,3 +9,5 @@ oslosphinx>=2.2.0.0a2
|
|||||||
testrepository>=0.0.18
|
testrepository>=0.0.18
|
||||||
testscenarios>=0.4
|
testscenarios>=0.4
|
||||||
testtools>=0.9.34
|
testtools>=0.9.34
|
||||||
|
mock>=1.0
|
||||||
|
WebOb>=1.2.3
|
||||||
|
Loading…
Reference in New Issue
Block a user