GBP Client for GBP resources

Partially-implements: blueprint group-based-policy-abstraction

Change-Id: I6925ab7e3cdbce741f7c3f73c4e810d7ca8b5c7a
This commit is contained in:
Sumit Naiksatam 2014-07-01 15:47:27 -07:00
parent a11f06cd4a
commit e4f3993e2f
22 changed files with 3558 additions and 599 deletions

1
.gitignore vendored
View File

@ -14,6 +14,7 @@ gbp/vcsversion.py
gbpclient/versioninfo
run_tests.err.log
run_tests.log
subunit.log
.autogenerated
.coverage
.testrepository/

26
HACKING.rst Normal file
View File

@ -0,0 +1,26 @@
Neutron Style Commandments
================================
- Step 1: Read the OpenStack Style Commandments
http://docs.openstack.org/developer/hacking/
- Step 2: Read on
Running Tests
-------------
The testing system is based on a combination of tox and testr. The canonical
approach to running tests is to simply run the command `tox`. This will
create virtual environments, populate them with depenedencies and run all of
the tests that OpenStack CI systems run. Behind the scenes, tox is running
`testr run --parallel`, but is set up such that you can supply any additional
testr arguments that are needed to tox. For example, you can run:
`tox -- --analyze-isolation` to cause tox to tell testr to add
--analyze-isolation to its argument list.
It is also possible to run the tests inside of a virtual environment
you have created, or it is possible that you have all of the dependencies
installed locally already. In this case, you can interact with the testr
command directly. Running `testr run` will run the entire test suite. `testr
run --parallel` will run it in parallel (this is the default incantation tox
uses.) More information about testr can be found at:
http://wiki.openstack.org/testr

176
LICENSE Normal file
View File

@ -0,0 +1,176 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.

View File

View File

@ -0,0 +1,3 @@
from neutronclient.neutron import v2_0 as neutronV2_0
_get_resource_plural = neutronV2_0._get_resource_plural

View File

@ -0,0 +1,861 @@
# 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 string
from neutronclient.common import utils
from neutronclient.neutron import v2_0 as neutronV20
from neutronclient.openstack.common.gettextutils import _
from oslo.serialization import jsonutils
def _format_network_service_params(net_svc_policy):
try:
return '\n'.join([jsonutils.dumps(param) for param in
net_svc_policy['network_service_params']])
except (TypeError, KeyError):
return ''
class ListEndpoint(neutronV20.ListCommand):
"""List policy_targets that belong to a given tenant."""
resource = 'endpoint'
log = logging.getLogger(__name__ + '.ListEndpoint')
_formatters = {}
list_columns = ['id', 'name', 'description', 'endpoint_group_id']
pagination_support = True
sorting_support = True
class ShowEndpoint(neutronV20.ShowCommand):
"""Show information of a given policy_target."""
resource = 'endpoint'
log = logging.getLogger(__name__ + '.ShowEndpoint')
class CreateEndpoint(neutronV20.CreateCommand):
"""Create a policy_target for a given tenant."""
resource = 'endpoint'
log = logging.getLogger(__name__ + '.CreateEndpoint')
def add_known_arguments(self, parser):
parser.add_argument(
'--description',
help=_('Description of the policy_target'))
parser.add_argument(
'--endpoint-group', metavar='EPG',
default='',
help=_('group uuid'))
parser.add_argument(
'--port',
default='',
help=_('Neutron Port'))
parser.add_argument(
'name', metavar='NAME',
help=_('Name of policy_target to create'))
def args2body(self, parsed_args):
body = {self.resource: {}, }
neutronV20.update_dict(parsed_args, body[self.resource],
['name', 'tenant_id', 'description'])
if parsed_args.endpoint_group:
body[self.resource]['endpoint_group_id'] = \
neutronV20.find_resourceid_by_name_or_id(
self.get_client(), 'endpoint_group',
parsed_args.endpoint_group)
if parsed_args.port:
body[self.resource]['port_id'] = \
neutronV20.find_resourceid_by_name_or_id(
self.get_client(), 'port',
parsed_args.port)
return body
class DeleteEndpoint(neutronV20.DeleteCommand):
"""Delete a given policy_target."""
resource = 'endpoint'
log = logging.getLogger(__name__ + '.DeleteEndpoint')
class UpdateEndpoint(neutronV20.UpdateCommand):
"""Update policy_target's information."""
resource = 'endpoint'
log = logging.getLogger(__name__ + '.UpdateEndpoint')
class ListEndpointGroup(neutronV20.ListCommand):
"""List groups that belong to a given tenant."""
resource = 'endpoint_group'
log = logging.getLogger(__name__ + '.ListEndpointGroup')
list_columns = ['id', 'name', 'description']
pagination_support = True
sorting_support = True
class ShowEndpointGroup(neutronV20.ShowCommand):
"""Show information of a given group."""
resource = 'endpoint_group'
log = logging.getLogger(__name__ + '.ShowEndpointGroup')
class CreateEndpointGroup(neutronV20.CreateCommand):
"""Create a group for a given tenant."""
resource = 'endpoint_group'
log = logging.getLogger(__name__ + '.CreateEndpointGroup')
def add_known_arguments(self, parser):
parser.add_argument(
'--description',
help=_('Description of the group'))
parser.add_argument(
'name', metavar='NAME',
help=_('Name of group to create'))
parser.add_argument(
'--l2-policy', metavar='L2_POLICY',
default='',
help=_('L2 policy uuid'))
parser.add_argument(
'--provided-contracts', type=utils.str2dict,
default={},
help=_('Dictionary of provided contract uuids'))
parser.add_argument(
'--consumed-contracts', type=utils.str2dict,
default={},
help=_('Dictionary of consumed contract uuids'))
parser.add_argument(
'--network-service-policy', metavar='NETWORK_SERVICE_POLICY',
default='',
help=_('Network service policy uuid'))
parser.add_argument(
'--subnets', type=string.split,
help=_('Subnet to map the group'))
def args2body(self, parsed_args):
body = {self.resource: {}, }
if parsed_args.l2_policy:
body[self.resource]['l2_policy_id'] = \
neutronV20.find_resourceid_by_name_or_id(
self.get_client(), 'l2_policy',
parsed_args.l2_policy)
if parsed_args.network_service_policy:
body[self.resource]['network_service_policy_id'] = \
neutronV20.find_resourceid_by_name_or_id(
self.get_client(), 'network_service_policy',
parsed_args.network_service_policy)
if parsed_args.provided_contracts:
for key in parsed_args.provided_contracts.keys():
id_key = neutronV20.find_resourceid_by_name_or_id(
self.get_client(), 'contract',
key)
parsed_args.provided_contracts[id_key] = \
parsed_args.provided_contracts.pop(key)
if parsed_args.consumed_contracts:
for key in parsed_args.consumed_contracts.keys():
id_key = neutronV20.find_resourceid_by_name_or_id(
self.get_client(), 'contract',
key)
parsed_args.consumed_contracts[id_key] = \
parsed_args.consumed_contracts.pop(key)
if parsed_args.subnets:
for subnet in parsed_args.subnets:
subnet_id = neutronV20.find_resourceid_by_name_or_id(
self.get_client(), 'subnet',
subnet)
parsed_args.subnets.remove(subnet)
parsed_args.subnets.append(subnet_id)
neutronV20.update_dict(parsed_args, body[self.resource],
['name', 'tenant_id', 'description',
'provided_contracts', 'subnets',
'consumed_contracts'])
return body
class DeleteEndpointGroup(neutronV20.DeleteCommand):
"""Delete a given group."""
resource = 'endpoint_group'
log = logging.getLogger(__name__ + '.DeleteEndpointGroup')
class UpdateEndpointGroup(neutronV20.UpdateCommand):
"""Update group's information."""
resource = 'endpoint_group'
log = logging.getLogger(__name__ + '.UpdateEndpointGroup')
def add_known_arguments(self, parser):
parser.add_argument(
'--description',
help=_('Description of the group'))
parser.add_argument(
'--l2-policy', metavar='L2_POLICY',
help=_('L2 policy uuid'))
parser.add_argument(
'--network-service-policy', metavar='NETWORK_SERVICE_POLICY',
help=_('Network Service Policy uuid'))
parser.add_argument(
'--provided-contracts', type=utils.str2dict,
help=_('Dictionary of provided contract uuids'))
parser.add_argument(
'--consumed-contracts', type=utils.str2dict,
help=_('Dictionary of consumed contract uuids'))
parser.add_argument(
'--subnets', type=string.split,
help=_('Subnet to map the group'))
def args2body(self, parsed_args):
body = {self.resource: {}, }
if parsed_args.l2_policy:
body[self.resource]['l2_policy_id'] = \
neutronV20.find_resourceid_by_name_or_id(
self.get_client(), 'l2_policy',
parsed_args.l2_policy)
if parsed_args.network_service_policy:
body[self.resource]['network_service_policy_id'] = \
neutronV20.find_resourceid_by_name_or_id(
self.get_client(), 'network_service_policy',
parsed_args.l2_policy)
if parsed_args.provided_contracts:
for key in parsed_args.provided_contracts.keys():
id_key = neutronV20.find_resourceid_by_name_or_id(
self.get_client(), 'contract',
key)
parsed_args.provided_contracts[id_key] = \
parsed_args.provided_contracts.pop(key)
if parsed_args.consumed_contracts:
for key in parsed_args.consumed_contracts.keys():
id_key = neutronV20.find_resourceid_by_name_or_id(
self.get_client(), 'contract',
key)
parsed_args.consumed_contracts[id_key] = \
parsed_args.consumed_contracts.pop(key)
if parsed_args.subnets:
for subnet in parsed_args.subnets:
subnet_id = neutronV20.find_resourceid_by_name_or_id(
self.get_client(), 'subnet',
subnet)
parsed_args.subnets.remove(subnet)
parsed_args.subnets.append(subnet_id)
neutronV20.update_dict(parsed_args, body[self.resource],
['name', 'tenant_id', 'description',
'provided_contracts', 'subnets',
'consumed_contracts'])
return body
class ListL2Policy(neutronV20.ListCommand):
"""List L2 Policies that belong to a given tenant."""
resource = 'l2_policy'
log = logging.getLogger(__name__ + '.ListL2Policy')
_formatters = {}
list_columns = ['id', 'name', 'description', 'l3_policy_id']
pagination_support = True
sorting_support = True
class ShowL2Policy(neutronV20.ShowCommand):
"""Show information of a given l2_policy."""
resource = 'l2_policy'
log = logging.getLogger(__name__ + '.ShowL2Policy')
class CreateL2Policy(neutronV20.CreateCommand):
"""Create a bridge_domain for a given tenant."""
resource = 'l2_policy'
log = logging.getLogger(__name__ + '.CreateL2Policy')
def add_known_arguments(self, parser):
parser.add_argument(
'--description',
help=_('Description of the l2_policy'))
parser.add_argument(
'--network',
help=_('Network to map the l2_policy'))
parser.add_argument(
'--l3-policy',
default='',
help=_('l3_policy uuid'))
parser.add_argument(
'name', metavar='NAME',
help=_('Name of l2_policy to create'))
def args2body(self, parsed_args):
body = {self.resource: {}, }
neutronV20.update_dict(parsed_args, body[self.resource],
['name', 'tenant_id', 'description'])
if parsed_args.l3_policy:
body[self.resource]['l3_policy_id'] = \
neutronV20.find_resourceid_by_name_or_id(
self.get_client(), 'l3_policy',
parsed_args.l3_policy)
if parsed_args.network:
body[self.resource]['network_id'] = \
neutronV20.find_resourceid_by_name_or_id(
self.get_client(), 'network',
parsed_args.network)
return body
class DeleteL2Policy(neutronV20.DeleteCommand):
"""Delete a given l2_policy."""
resource = 'l2_policy'
log = logging.getLogger(__name__ + '.DeleteL2Policy')
class UpdateL2Policy(neutronV20.UpdateCommand):
"""Update l2_policy's information."""
resource = 'l2_policy'
log = logging.getLogger(__name__ + '.UpdateL2Policy')
class ListL3Policy(neutronV20.ListCommand):
"""List l3_policies that belong to a given tenant."""
resource = 'l3_policy'
log = logging.getLogger(__name__ + '.ListL3Policy')
_formatters = {}
list_columns = ['id', 'name', 'description', 'ip_pool',
'subnet_prefix_length']
pagination_support = True
sorting_support = True
class ShowL3Policy(neutronV20.ShowCommand):
"""Show information of a given l3_policy."""
resource = 'l3_policy'
log = logging.getLogger(__name__ + '.ShowL3Policy')
class CreateL3Policy(neutronV20.CreateCommand):
"""Create a l3_policy for a given tenant."""
resource = 'l3_policy'
log = logging.getLogger(__name__ + '.CreateL3Policy')
def add_known_arguments(self, parser):
parser.add_argument(
'--description',
help=_('Description of the l3_policy'))
parser.add_argument(
'--ip-version',
type=int,
default=4, choices=[4, 6],
help=_('IP version, default is 4'))
parser.add_argument(
'--ip-pool',
help=_('CIDR of IP pool to create, default is 10.0.0.0/8'))
parser.add_argument(
'--subnet-prefix-length',
type=int,
default=24,
help=_('Subnet prefix length, default is 24'))
parser.add_argument(
'name', metavar='NAME',
help=_('Name of l3_policy to create'))
def args2body(self, parsed_args):
body = {self.resource: {}, }
neutronV20.update_dict(parsed_args, body[self.resource],
['name', 'tenant_id', 'description',
'ip_version', 'ip_pool',
'subnet_prefix_length'])
return body
class DeleteL3Policy(neutronV20.DeleteCommand):
"""Delete a given l3_policy."""
resource = 'l3_policy'
log = logging.getLogger(__name__ + '.DeleteL3Policy')
class UpdateL3Policy(neutronV20.UpdateCommand):
"""Update l3_policy's information."""
resource = 'l3_policy'
log = logging.getLogger(__name__ + '.UpdateL3Policy')
class ListNetworkServicePolicy(neutronV20.ListCommand):
"""List Network Service Policies that belong to a given tenant."""
resource = 'network_service_policy'
log = logging.getLogger(__name__ + '.ListNetworkServicePolicy')
_formatters = {'network_servie_params': _format_network_service_params}
list_columns = ['id', 'name', 'description', 'network_service_params']
pagination_support = True
sorting_support = True
class ShowNetworkServicePolicy(neutronV20.ShowCommand):
"""Show information of a given network_service_policy."""
resource = 'network_service_policy'
log = logging.getLogger(__name__ + '.ShowNetworkServicePolicy')
class CreateNetworkServicePolicy(neutronV20.CreateCommand):
"""Create a Network Service Policy for a given tenant."""
resource = 'network_service_policy'
log = logging.getLogger(__name__ + '.CreateNetworkServicePolicy')
def add_known_arguments(self, parser):
parser.add_argument(
'--description',
help=_('Description of the network_service_policy'))
parser.add_argument(
'name',
help=_('Name of network_service_policy to create'))
parser.add_argument(
'--network-service-params',
metavar='type=PARAM_TYPE,name=PARAM_NAME,value=PARAM_VALUE',
action='append', dest='network_service_params',
type=utils.str2dict,
help=_('Network service params for this network service policy'
'(This option can be repeated).'))
def args2body(self, parsed_args):
body = {self.resource: {}, }
"""
if parsed_args.name:
body[self.resource].update({'name': parsed_args.name})
if parsed_args.description:
body[self.resource].update({'description': parsed_args.name})
if parsed_args.network_service_params:
body[self.resource]['network_service_params'] = (
parsed_args.network_sercice_params)
"""
neutronV20.update_dict(parsed_args, body[self.resource],
['name', 'tenant_id', 'description',
'network_service_params'])
return body
class DeleteNetworkServicePolicy(neutronV20.DeleteCommand):
"""Delete a given network_service_policy."""
resource = 'network_service_policy'
log = logging.getLogger(__name__ + '.DeleteNetworkServicePolicy')
class UpdateNetworkServicePolicy(neutronV20.UpdateCommand):
"""Update network_service_policy's information."""
resource = 'network_service_policy'
log = logging.getLogger(__name__ + '.UpdateNetworkServicePolicy')
def add_known_arguments(self, parser):
parser.add_argument(
'--description',
help=_('Description of the network_service_policy'))
parser.add_argument(
'--name',
help=_('Name of network_service_policy to create'))
parser.add_argument(
'--network-service-params',
metavar='type=PARAM_TYPE,name=PARAM_NAME,value=PARAM_VALUE',
action='append', dest='network_service_params',
type=utils.str2dict,
help=_('Network service params for this network service policy'
'(This option can be repeated).'))
def args2body(self, parsed_args):
body = {self.resource: {}, }
neutronV20.update_dict(parsed_args, body[self.resource],
['name', 'tenant_id', 'description',
'network_service_params'])
return body
class ListPolicyClassifier(neutronV20.ListCommand):
"""List classifiers that belong to a given tenant."""
resource = 'policy_classifier'
log = logging.getLogger(__name__ + '.ListPolicyClassifier')
_formatters = {}
list_columns = ['id', 'name', 'protocol', 'port_range', 'direction']
pagination_support = True
sorting_support = True
class ShowPolicyClassifier(neutronV20.ShowCommand):
"""Show information of a given classifier."""
resource = 'policy_classifier'
log = logging.getLogger(__name__ + '.ShowPolicyClassifier')
class CreatePolicyClassifier(neutronV20.CreateCommand):
"""Create a classifier for a given tenant."""
resource = 'policy_classifier'
log = logging.getLogger(__name__ + '.CreatePolicyClassifier')
def add_known_arguments(self, parser):
parser.add_argument(
'--description',
help=_('Description of the policy classifier'))
parser.add_argument(
'--protocol',
choices=['tcp', 'udp', 'icmp'],
help=_('Protocol'))
parser.add_argument(
'--port-range',
help=_('Port range'))
parser.add_argument(
'--direction',
choices=['in', 'out', 'bi', ''],
help=_('Direction'))
parser.add_argument(
'name', metavar='NAME',
help=_('Name of classifier to create'))
def args2body(self, parsed_args):
body = {self.resource: {}, }
neutronV20.update_dict(parsed_args, body[self.resource],
['name', 'tenant_id', 'description',
'protocol', 'port_range', 'direction'])
return body
class DeletePolicyClassifier(neutronV20.DeleteCommand):
"""Delete a given classifier."""
resource = 'policy_classifier'
log = logging.getLogger(__name__ + '.DeletePolicyClassifier')
class UpdatePolicyClassifier(neutronV20.UpdateCommand):
"""Update classifier's information."""
resource = 'policy_classifier'
log = logging.getLogger(__name__ + '.UpdatePolicyClassifier')
class ListPolicyAction(neutronV20.ListCommand):
"""List actions that belong to a given tenant."""
resource = 'policy_action'
log = logging.getLogger(__name__ + '.ListPolicyAction')
_formatters = {}
list_columns = ['id', 'name', 'action_type', 'action_value']
pagination_support = True
sorting_support = True
class ShowPolicyAction(neutronV20.ShowCommand):
"""Show information of a given action."""
resource = 'policy_action'
log = logging.getLogger(__name__ + '.ShowPolicyAction')
class CreatePolicyAction(neutronV20.CreateCommand):
"""Create a action for a given tenant."""
resource = 'policy_action'
log = logging.getLogger(__name__ + '.CreatePolicyAction')
def add_known_arguments(self, parser):
parser.add_argument(
'--description',
help=_('Description of the policy action'))
parser.add_argument(
'--action-type',
help=_('Type of action'))
parser.add_argument(
'--action-value',
help=_('uuid of service for redirect action'))
parser.add_argument(
'name', metavar='NAME',
help=_('Name of action to create'))
def args2body(self, parsed_args):
body = {self.resource: {}, }
neutronV20.update_dict(parsed_args, body[self.resource],
['name', 'tenant_id', 'description',
'action_type', 'action_value'])
return body
class DeletePolicyAction(neutronV20.DeleteCommand):
"""Delete a given action."""
resource = 'policy_action'
log = logging.getLogger(__name__ + '.DeletePolicyAction')
class UpdatePolicyAction(neutronV20.UpdateCommand):
"""Update action's information."""
resource = 'policy_action'
log = logging.getLogger(__name__ + '.UpdatePolicyAction')
class ListPolicyRule(neutronV20.ListCommand):
"""List policy_rules that belong to a given tenant."""
resource = 'policy_rule'
log = logging.getLogger(__name__ + '.ListPolicyRule')
_formatters = {}
list_columns = ['id', 'name', 'enabled', 'classifier_id',
'actions']
pagination_support = True
sorting_support = True
class ShowPolicyRule(neutronV20.ShowCommand):
"""Show information of a given policy_rule."""
resource = 'policy_rule'
log = logging.getLogger(__name__ + '.ShowPolicyRule')
class CreatePolicyRule(neutronV20.CreateCommand):
"""Create a policy_rule for a given tenant."""
resource = 'policy_rule'
log = logging.getLogger(__name__ + '.CreatePolicyRule')
def add_known_arguments(self, parser):
parser.add_argument(
'--description',
help=_('Description of the policy_rule'))
parser.add_argument(
'--enabled', type=bool,
help=_('Enable flag'))
parser.add_argument(
'--classifier',
help=_('uuid of policy classifier'))
parser.add_argument(
'--actions', type=string.split,
help=_('List of policy actions'))
parser.add_argument(
'name', metavar='NAME',
help=_('Name of policy_rule to create'))
def args2body(self, parsed_args):
body = {self.resource: {}, }
if parsed_args.actions:
body[self.resource]['policy_actions'] = [
neutronV20.find_resourceid_by_name_or_id(
self.get_client(),
'policy_action',
elem) for elem in parsed_args.actions]
if parsed_args.classifier:
body[self.resource]['policy_classifier_id'] = \
neutronV20.find_resourceid_by_name_or_id(
self.get_client(),
'policy_classifier',
parsed_args.classifier)
neutronV20.update_dict(parsed_args, body[self.resource],
['name', 'tenant_id', 'description',
'enabled'])
return body
class DeletePolicyRule(neutronV20.DeleteCommand):
"""Delete a given policy_rule."""
resource = 'policy_rule'
log = logging.getLogger(__name__ + '.DeletePolicyRule')
class UpdatePolicyRule(neutronV20.UpdateCommand):
"""Update policy_rule's information."""
resource = 'policy_rule'
log = logging.getLogger(__name__ + '.UpdatePolicyRule')
def add_known_arguments(self, parser):
parser.add_argument(
'--enabled', type=bool,
help=_('Enable flag'))
parser.add_argument(
'--classifier',
help=_('uuid of policy classifier'))
parser.add_argument(
'--actions', type=string.split,
help=_('List of policy actions'))
def args2body(self, parsed_args):
body = {self.resource: {}, }
if parsed_args.actions:
body[self.resource]['policy_actions'] = [
neutronV20.find_resourceid_by_name_or_id(
self.get_client(),
'policy_action',
elem) for elem in parsed_args.actions]
if parsed_args.classifier:
body[self.resource]['policy_classifier_id'] = \
neutronV20.find_resourceid_by_name_or_id(
self.get_client(),
'policy_classifier',
parsed_args.classifier)
neutronV20.update_dict(parsed_args, body[self.resource],
['name', 'description',
'enabled'])
return body
class ListContract(neutronV20.ListCommand):
"""List contracts that belong to a given tenant."""
resource = 'contract'
log = logging.getLogger(__name__ + '.ListContract')
_formatters = {}
list_columns = ['id', 'name', 'ploicy_rules']
pagination_support = True
sorting_support = True
class ShowContract(neutronV20.ShowCommand):
"""Show information of a given contract."""
resource = 'contract'
log = logging.getLogger(__name__ + '.ShowContract')
class CreateContract(neutronV20.CreateCommand):
"""Create a contract for a given tenant."""
resource = 'contract'
log = logging.getLogger(__name__ + '.CreateContract')
def add_known_arguments(self, parser):
parser.add_argument(
'--description',
help=_('Description of the contract'))
parser.add_argument(
'--policy-rules', type=string.split,
help=_('List of policy rules'))
parser.add_argument(
'--child-contracts', type=string.split,
help=_('List of child contracts'))
parser.add_argument(
'name', metavar='NAME',
help=_('Name of contract to create'))
def args2body(self, parsed_args):
body = {self.resource: {}, }
if parsed_args.policy_rules:
body[self.resource]['policy_rules'] = [
neutronV20.find_resourceid_by_name_or_id(
self.get_client(),
'policy_rule',
elem) for elem in parsed_args.policy_rules]
if parsed_args.child_contracts:
body[self.resource]['child_contracts'] = [
neutronV20.find_resourceid_by_name_or_id(
self.get_client(),
'contract',
elem) for elem in parsed_args.child_contracts]
neutronV20.update_dict(parsed_args, body[self.resource],
['name', 'tenant_id', 'description'])
return body
class DeleteContract(neutronV20.DeleteCommand):
"""Delete a given contract."""
resource = 'contract'
log = logging.getLogger(__name__ + '.DeleteContract')
class UpdateContract(neutronV20.UpdateCommand):
"""Update contract's information."""
resource = 'contract'
log = logging.getLogger(__name__ + '.UpdateContract')
def add_known_arguments(self, parser):
parser.add_argument(
'--policy-rules', type=string.split,
help=_('List of policy rules'))
parser.add_argument(
'--child-contracts', type=string.split,
help=_('List of child contracts'))
def args2body(self, parsed_args):
body = {self.resource: {}, }
if parsed_args.policy_rules:
body[self.resource]['policy_rules'] = [
neutronV20.find_resourceid_by_name_or_id(
self.get_client(),
'policy_rule',
elem) for elem in parsed_args.policy_rules]
parsed_args.policy_rules = body[self.resource]['policy_rules']
if parsed_args.child_contracts:
body[self.resource]['child_contracts'] = [
neutronV20.find_resourceid_by_name_or_id(
self.get_client(),
'contract',
elem) for elem in parsed_args.child_contracts]
parsed_args.child_contracts = parsed_args.child_contracts
neutronV20.update_dict(parsed_args, body[self.resource],
['name', 'description', 'policy_rules',
'child_contracts'])
return body

793
gbpclient/gbpshell.py Normal file
View File

@ -0,0 +1,793 @@
# 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.
#
"""
Command-line interface to the GBP APIs
"""
from __future__ import print_function
import argparse
import logging
import os
import sys
from keystoneclient.auth.identity import v2 as v2_auth
from keystoneclient.auth.identity import v3 as v3_auth
from keystoneclient import discover
from keystoneclient.openstack.common.apiclient import exceptions as ks_exc
from keystoneclient import session
import six.moves.urllib.parse as urlparse
from cliff import app
from cliff import commandmanager
from neutronclient.common import clientmanager
from neutronclient.common import exceptions as exc
from neutronclient.common import utils
from neutronclient.openstack.common.gettextutils import _
from neutronclient.openstack.common import strutils
from neutronclient.version import __version__
from gbpclient.gbp.v2_0 import groupbasedpolicy as gbp
VERSION = '2.0'
NEUTRON_API_VERSION = '2.0'
clientmanager.neutron_client.API_VERSIONS = {
'2.0': 'gbpclient.v2_0.client.Client',
}
def run_command(cmd, cmd_parser, sub_argv):
_argv = sub_argv
index = -1
values_specs = []
if '--' in sub_argv:
index = sub_argv.index('--')
_argv = sub_argv[:index]
values_specs = sub_argv[index:]
known_args, _values_specs = cmd_parser.parse_known_args(_argv)
cmd.values_specs = (index == -1 and _values_specs or values_specs)
return cmd.run(known_args)
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 check_non_negative_int(value):
try:
value = int(value)
except ValueError:
raise argparse.ArgumentTypeError(_("invalid int value: %r") % value)
if value < 0:
raise argparse.ArgumentTypeError(_("input value %d is negative") %
value)
return value
COMMAND_V2 = {
'policy-target-create': gbp.CreateEndpoint,
'policy-target-delete': gbp.DeleteEndpoint,
'policy-target-update': gbp.UpdateEndpoint,
'policy-target-list': gbp.ListEndpoint,
'policy-target-show': gbp.ShowEndpoint,
'group-create': gbp.CreateEndpointGroup,
'group-delete': gbp.DeleteEndpointGroup,
'group-update': gbp.UpdateEndpointGroup,
'group-list': gbp.ListEndpointGroup,
'group-show': gbp.ShowEndpointGroup,
'l2policy-create': gbp.CreateL2Policy,
'l2policy-delete': gbp.DeleteL2Policy,
'l2policy-update': gbp.UpdateL2Policy,
'l2policy-list': gbp.ListL2Policy,
'l2policy-show': gbp.ShowL2Policy,
'l3policy-create': gbp.CreateL3Policy,
'l3policy-delete': gbp.DeleteL3Policy,
'l3policy-update': gbp.UpdateL3Policy,
'l3policy-list': gbp.ListL3Policy,
'l3policy-show': gbp.ShowL3Policy,
'network-service-policy-create': gbp.CreateNetworkServicePolicy,
'network-service-policy-delete': gbp.DeleteNetworkServicePolicy,
'network-service-policy-update': gbp.UpdateNetworkServicePolicy,
'network-service-policy-list': gbp.ListNetworkServicePolicy,
'network-service-policy-show': gbp.ShowNetworkServicePolicy,
'policy-classifier-create': gbp.CreatePolicyClassifier,
'policy-classifier-delete': gbp.DeletePolicyClassifier,
'policy-classifier-update': gbp.UpdatePolicyClassifier,
'policy-classifier-list': gbp.ListPolicyClassifier,
'policy-classifier-show': gbp.ShowPolicyClassifier,
'policy-action-create': gbp.CreatePolicyAction,
'policy-action-delete': gbp.DeletePolicyAction,
'policy-action-update': gbp.UpdatePolicyAction,
'policy-action-list': gbp.ListPolicyAction,
'policy-action-show': gbp.ShowPolicyAction,
'policy-rule-create': gbp.CreatePolicyRule,
'policy-rule-delete': gbp.DeletePolicyRule,
'policy-rule-update': gbp.UpdatePolicyRule,
'policy-rule-list': gbp.ListPolicyRule,
'policy-rule-show': gbp.ShowPolicyRule,
'policy-rule-set-create': gbp.CreateContract,
'policy-rule-set-delete': gbp.DeleteContract,
'policy-rule-set-update': gbp.UpdateContract,
'policy-rule-set-list': gbp.ListContract,
'policy-rule-set-show': gbp.ShowContract,
}
COMMANDS = {'2.0': COMMAND_V2}
class HelpAction(argparse.Action):
"""Provide a custom action so the -h and --help options
to the main app will print a list of the commands.
The commands are determined by checking the CommandManager
instance, passed in as the "default" value for the action.
"""
def __call__(self, parser, namespace, values, option_string=None):
outputs = []
max_len = 0
app = self.default
parser.print_help(app.stdout)
app.api_version = '2.0' # Check this
app.stdout.write(_('\nCommands for GBP API v%s:\n') % app.api_version)
command_manager = app.command_manager
for name, ep in sorted(command_manager):
factory = ep.load()
cmd = factory(self, None)
one_liner = cmd.get_description().split('\n')[0]
outputs.append((name, one_liner))
max_len = max(len(name), max_len)
for (name, one_liner) in outputs:
app.stdout.write(' %s %s\n' % (name.ljust(max_len), one_liner))
sys.exit(0)
class GBPShell(app.App):
# verbose logging levels
WARNING_LEVEL = 0
INFO_LEVEL = 1
DEBUG_LEVEL = 2
CONSOLE_MESSAGE_FORMAT = '%(message)s'
DEBUG_MESSAGE_FORMAT = '%(levelname)s: %(name)s %(message)s'
log = logging.getLogger(__name__)
def __init__(self, apiversion):
super(GBPShell, self).__init__(
description=__doc__.strip(),
version=VERSION,
command_manager=commandmanager.CommandManager('gbp.cli'), )
self.commands = COMMANDS
for k, v in self.commands[apiversion].items():
self.command_manager.add_command(k, v)
# This is instantiated in initialize_app() only when using
# password flow auth
self.auth_client = None
self.api_version = apiversion
def build_option_parser(self, description, version):
"""Return an argparse option parser for this application.
Subclasses may override this method to extend
the parser with more global options.
:param description: full description of the application
:paramtype description: str
:param version: version number for the application
:paramtype version: str
"""
parser = argparse.ArgumentParser(
description=description,
add_help=False, )
parser.add_argument(
'--version',
action='version',
version=__version__, )
parser.add_argument(
'-v', '--verbose', '--debug',
action='count',
dest='verbose_level',
default=self.DEFAULT_VERBOSE_LEVEL,
help=_('Increase verbosity of output and show tracebacks on'
' errors. You can repeat this option.'))
parser.add_argument(
'-q', '--quiet',
action='store_const',
dest='verbose_level',
const=0,
help=_('Suppress output except warnings and errors.'))
parser.add_argument(
'-h', '--help',
action=HelpAction,
nargs=0,
default=self, # tricky
help=_("Show this help message and exit."))
parser.add_argument(
'-r', '--retries',
metavar="NUM",
type=check_non_negative_int,
default=0,
help=_("How many times the request to the Neutron server should "
"be retried if it fails."))
# FIXME(bklei): this method should come from python-keystoneclient
self._append_global_identity_args(parser)
return parser
def _append_global_identity_args(self, parser):
# FIXME(bklei): these are global identity (Keystone) arguments which
# should be consistent and shared by all service clients. Therefore,
# they should be provided by python-keystoneclient. We will need to
# refactor this code once this functionality is available in
# python-keystoneclient.
#
# Note: At that time we'll need to decide if we can just abandon
# the deprecated args (--service-type and --endpoint-type).
parser.add_argument(
'--os-service-type', metavar='<os-service-type>',
default=env('OS_NETWORK_SERVICE_TYPE', default='network'),
help=_('Defaults to env[OS_NETWORK_SERVICE_TYPE] or network.'))
parser.add_argument(
'--os-endpoint-type', metavar='<os-endpoint-type>',
default=env('OS_ENDPOINT_TYPE', default='publicURL'),
help=_('Defaults to env[OS_ENDPOINT_TYPE] or publicURL.'))
# FIXME(bklei): --service-type is deprecated but kept in for
# backward compatibility.
parser.add_argument(
'--service-type', metavar='<service-type>',
default=env('OS_NETWORK_SERVICE_TYPE', default='network'),
help=_('DEPRECATED! Use --os-service-type.'))
# FIXME(bklei): --endpoint-type is deprecated but kept in for
# backward compatibility.
parser.add_argument(
'--endpoint-type', metavar='<endpoint-type>',
default=env('OS_ENDPOINT_TYPE', default='publicURL'),
help=_('DEPRECATED! Use --os-endpoint-type.'))
parser.add_argument(
'--os-auth-strategy', metavar='<auth-strategy>',
default=env('OS_AUTH_STRATEGY', default='keystone'),
help=_('DEPRECATED! Only keystone is supported.'))
parser.add_argument(
'--os_auth_strategy',
help=argparse.SUPPRESS)
parser.add_argument(
'--os-auth-url', metavar='<auth-url>',
default=env('OS_AUTH_URL'),
help=_('Authentication URL, defaults to env[OS_AUTH_URL].'))
parser.add_argument(
'--os_auth_url',
help=argparse.SUPPRESS)
project_name_group = parser.add_mutually_exclusive_group()
project_name_group.add_argument(
'--os-tenant-name', metavar='<auth-tenant-name>',
default=env('OS_TENANT_NAME'),
help=_('Authentication tenant name, defaults to '
'env[OS_TENANT_NAME].'))
project_name_group.add_argument(
'--os-project-name',
metavar='<auth-project-name>',
default=utils.env('OS_PROJECT_NAME'),
help='Another way to specify tenant name. '
'This option is mutually exclusive with '
' --os-tenant-name. '
'Defaults to env[OS_PROJECT_NAME].')
parser.add_argument(
'--os_tenant_name',
help=argparse.SUPPRESS)
project_id_group = parser.add_mutually_exclusive_group()
project_id_group.add_argument(
'--os-tenant-id', metavar='<auth-tenant-id>',
default=env('OS_TENANT_ID'),
help=_('Authentication tenant ID, defaults to '
'env[OS_TENANT_ID].'))
project_id_group.add_argument(
'--os-project-id',
metavar='<auth-project-id>',
default=utils.env('OS_PROJECT_ID'),
help='Another way to specify tenant ID. '
'This option is mutually exclusive with '
' --os-tenant-id. '
'Defaults to env[OS_PROJECT_ID].')
parser.add_argument(
'--os-username', metavar='<auth-username>',
default=utils.env('OS_USERNAME'),
help=_('Authentication username, defaults to env[OS_USERNAME].'))
parser.add_argument(
'--os_username',
help=argparse.SUPPRESS)
parser.add_argument(
'--os-user-id', metavar='<auth-user-id>',
default=env('OS_USER_ID'),
help=_('Authentication user ID (Env: OS_USER_ID)'))
parser.add_argument(
'--os_user_id',
help=argparse.SUPPRESS)
parser.add_argument(
'--os-user-domain-id',
metavar='<auth-user-domain-id>',
default=utils.env('OS_USER_DOMAIN_ID'),
help='OpenStack user domain ID. '
'Defaults to env[OS_USER_DOMAIN_ID].')
parser.add_argument(
'--os_user_domain_id',
help=argparse.SUPPRESS)
parser.add_argument(
'--os-user-domain-name',
metavar='<auth-user-domain-name>',
default=utils.env('OS_USER_DOMAIN_NAME'),
help='OpenStack user domain name. '
'Defaults to env[OS_USER_DOMAIN_NAME].')
parser.add_argument(
'--os_user_domain_name',
help=argparse.SUPPRESS)
parser.add_argument(
'--os_project_id',
help=argparse.SUPPRESS)
parser.add_argument(
'--os_project_name',
help=argparse.SUPPRESS)
parser.add_argument(
'--os-project-domain-id',
metavar='<auth-project-domain-id>',
default=utils.env('OS_PROJECT_DOMAIN_ID'),
help='Defaults to env[OS_PROJECT_DOMAIN_ID].')
parser.add_argument(
'--os-project-domain-name',
metavar='<auth-project-domain-name>',
default=utils.env('OS_PROJECT_DOMAIN_NAME'),
help='Defaults to env[OS_PROJECT_DOMAIN_NAME].')
parser.add_argument(
'--os-cert',
metavar='<certificate>',
default=utils.env('OS_CERT'),
help=_("Path of certificate file to use in SSL "
"connection. This file can optionally be "
"prepended with the private key. Defaults "
"to env[OS_CERT]"))
parser.add_argument(
'--os-cacert',
metavar='<ca-certificate>',
default=env('OS_CACERT', default=None),
help=_("Specify a CA bundle file to use in "
"verifying a TLS (https) server certificate. "
"Defaults to env[OS_CACERT]"))
parser.add_argument(
'--os-key',
metavar='<key>',
default=utils.env('OS_KEY'),
help=_("Path of client key to use in SSL "
"connection. This option is not necessary "
"if your key is prepended to your certificate "
"file. Defaults to env[OS_KEY]"))
parser.add_argument(
'--os-password', metavar='<auth-password>',
default=utils.env('OS_PASSWORD'),
help=_('Authentication password, defaults to env[OS_PASSWORD].'))
parser.add_argument(
'--os_password',
help=argparse.SUPPRESS)
parser.add_argument(
'--os-region-name', metavar='<auth-region-name>',
default=env('OS_REGION_NAME'),
help=_('Authentication region name, defaults to '
'env[OS_REGION_NAME].'))
parser.add_argument(
'--os_region_name',
help=argparse.SUPPRESS)
parser.add_argument(
'--os-token', metavar='<token>',
default=env('OS_TOKEN'),
help=_('Authentication token, defaults to env[OS_TOKEN].'))
parser.add_argument(
'--os_token',
help=argparse.SUPPRESS)
parser.add_argument(
'--http-timeout', metavar='<seconds>',
default=env('OS_NETWORK_TIMEOUT', default=None), type=float,
help=_('Timeout in seconds to wait for an HTTP response. Defaults '
'to env[OS_NETWORK_TIMEOUT] or None if not specified.'))
parser.add_argument(
'--os-url', metavar='<url>',
default=env('OS_URL'),
help=_('Defaults to env[OS_URL].'))
parser.add_argument(
'--os_url',
help=argparse.SUPPRESS)
parser.add_argument(
'--insecure',
action='store_true',
default=env('NEUTRONCLIENT_INSECURE', default=False),
help=_("Explicitly allow neutronclient to perform \"insecure\" "
"SSL (https) requests. The server's certificate will "
"not be verified against any certificate authorities. "
"This option should be used with caution."))
def _bash_completion(self):
"""Prints all of the commands and options for bash-completion."""
commands = set()
options = set()
for option, _action in self.parser._option_string_actions.items():
options.add(option)
for command_name, command in self.command_manager:
commands.add(command_name)
cmd_factory = command.load()
cmd = cmd_factory(self, None)
cmd_parser = cmd.get_parser('')
for option, _action in cmd_parser._option_string_actions.items():
options.add(option)
print(' '.join(commands | options))
def run(self, argv):
"""Equivalent to the main program for the application.
:param argv: input arguments and options
:paramtype argv: list of str
"""
try:
index = 0
command_pos = -1
help_pos = -1
help_command_pos = -1
for arg in argv:
if arg == 'bash-completion':
self._bash_completion()
return 0
if arg in self.commands[self.api_version]:
if command_pos == -1:
command_pos = index
elif arg in ('-h', '--help'):
if help_pos == -1:
help_pos = index
elif arg == 'help':
if help_command_pos == -1:
help_command_pos = index
index = index + 1
if command_pos > -1 and help_pos > command_pos:
argv = ['help', argv[command_pos]]
if help_command_pos > -1 and command_pos == -1:
argv[help_command_pos] = '--help'
self.options, remainder = self.parser.parse_known_args(argv)
self.configure_logging()
self.interactive_mode = not remainder
self.initialize_app(remainder)
except Exception as err:
if self.options.verbose_level >= self.DEBUG_LEVEL:
self.log.exception(unicode(err))
raise
else:
self.log.error(unicode(err))
return 1
result = 1
if self.interactive_mode:
_argv = [sys.argv[0]]
sys.argv = _argv
result = self.interact()
else:
result = self.run_subcommand(remainder)
return result
def run_subcommand(self, argv):
subcommand = self.command_manager.find_command(argv)
cmd_factory, cmd_name, sub_argv = subcommand
cmd = cmd_factory(self, self.options)
err = None
result = 1
try:
self.prepare_to_run_command(cmd)
full_name = (cmd_name
if self.interactive_mode
else ' '.join([self.NAME, cmd_name])
)
cmd_parser = cmd.get_parser(full_name)
return run_command(cmd, cmd_parser, sub_argv)
except Exception as err:
if self.options.verbose_level >= self.DEBUG_LEVEL:
self.log.exception(unicode(err))
else:
self.log.error(unicode(err))
try:
self.clean_up(cmd, result, err)
except Exception as err2:
if self.options.verbose_level >= self.DEBUG_LEVEL:
self.log.exception(unicode(err2))
else:
self.log.error(_('Could not clean up: %s'), unicode(err2))
if self.options.verbose_level >= self.DEBUG_LEVEL:
raise
else:
try:
self.clean_up(cmd, result, None)
except Exception as err3:
if self.options.verbose_level >= self.DEBUG_LEVEL:
self.log.exception(unicode(err3))
else:
self.log.error(_('Could not clean up: %s'), unicode(err3))
return result
def authenticate_user(self):
"""Make sure the user has provided all of the authentication
info we need.
"""
if self.options.os_auth_strategy == 'keystone':
if self.options.os_token or self.options.os_url:
# Token flow auth takes priority
if not self.options.os_token:
raise exc.CommandError(
_("You must provide a token via"
" either --os-token or env[OS_TOKEN]"))
if not self.options.os_url:
raise exc.CommandError(
_("You must provide a service URL via"
" either --os-url or env[OS_URL]"))
else:
# Validate password flow auth
project_info = (self.options.os_tenant_name or
self.options.os_tenant_id or
(self.options.os_project_name and
(self.options.project_domain_name or
self.options.project_domain_id)) or
self.options.os_project_id)
if (not self.options.os_username
and not self.options.os_user_id):
raise exc.CommandError(
_("You must provide a username or user ID via"
" --os-username, env[OS_USERNAME] or"
" --os-user_id, env[OS_USER_ID]"))
if not self.options.os_password:
raise exc.CommandError(
_("You must provide a password via"
" either --os-password or env[OS_PASSWORD]"))
if (not project_info):
# tenent is deprecated in Keystone v3. Use the latest
# terminology instead.
raise exc.CommandError(
_("You must provide a project_id or project_name ("
"with project_domain_name or project_domain_id) "
"via "
" --os-project-id (env[OS_PROJECT_ID])"
" --os-project-name (env[OS_PROJECT_NAME]),"
" --os-project-domain-id "
"(env[OS_PROJECT_DOMAIN_ID])"
" --os-project-domain-name "
"(env[OS_PROJECT_DOMAIN_NAME])"))
if not self.options.os_auth_url:
raise exc.CommandError(
_("You must provide an auth url via"
" either --os-auth-url or via env[OS_AUTH_URL]"))
else: # not keystone
if not self.options.os_url:
raise exc.CommandError(
_("You must provide a service URL via"
" either --os-url or env[OS_URL]"))
auth_session = self._get_keystone_session()
self.client_manager = clientmanager.ClientManager(
token=self.options.os_token,
url=self.options.os_url,
auth_url=self.options.os_auth_url,
tenant_name=self.options.os_tenant_name,
tenant_id=self.options.os_tenant_id,
username=self.options.os_username,
user_id=self.options.os_user_id,
password=self.options.os_password,
region_name=self.options.os_region_name,
api_version=self.api_version,
auth_strategy=self.options.os_auth_strategy,
# FIXME (bklei) honor deprecated service_type and
# endpoint type until they are removed
service_type=self.options.os_service_type or
self.options.service_type,
endpoint_type=self.options.os_endpoint_type or self.endpoint_type,
insecure=self.options.insecure,
ca_cert=self.options.os_cacert,
timeout=self.options.http_timeout,
retries=self.options.retries,
raise_errors=False,
session=auth_session,
auth=auth_session.auth,
log_credentials=True)
return
def initialize_app(self, argv):
"""Global app init bits:
* set up API versions
* validate authentication info
"""
super(GBPShell, self).initialize_app(argv)
self.api_version = {'network': self.api_version}
# If the user is not asking for help, make sure they
# have given us auth.
cmd_name = None
if argv:
cmd_info = self.command_manager.find_command(argv)
cmd_factory, cmd_name, sub_argv = cmd_info
if self.interactive_mode or cmd_name != 'help':
self.authenticate_user()
def clean_up(self, cmd, result, err):
self.log.debug('clean_up %s', cmd.__class__.__name__)
if err:
self.log.debug('Got an error: %s', unicode(err))
def configure_logging(self):
"""Create logging handlers for any log output."""
root_logger = logging.getLogger('')
# Set up logging to a file
root_logger.setLevel(logging.DEBUG)
# Send higher-level messages to the console via stderr
console = logging.StreamHandler(self.stderr)
console_level = {self.WARNING_LEVEL: logging.WARNING,
self.INFO_LEVEL: logging.INFO,
self.DEBUG_LEVEL: logging.DEBUG,
}.get(self.options.verbose_level, logging.DEBUG)
console.setLevel(console_level)
if logging.DEBUG == console_level:
formatter = logging.Formatter(self.DEBUG_MESSAGE_FORMAT)
else:
formatter = logging.Formatter(self.CONSOLE_MESSAGE_FORMAT)
logging.getLogger('urllib3.connectionpool').setLevel(logging.WARNING)
console.setFormatter(formatter)
root_logger.addHandler(console)
return
def get_v2_auth(self, v2_auth_url):
return v2_auth.Password(
v2_auth_url,
username=self.options.os_username,
password=self.options.os_password,
tenant_id=self.options.os_tenant_id,
tenant_name=self.options.os_tenant_name)
def get_v3_auth(self, v3_auth_url):
project_id = self.options.os_project_id or self.options.os_tenant_id
project_name = (self.options.os_project_name or
self.options.os_tenant_name)
return v3_auth.Password(
v3_auth_url,
username=self.options.os_username,
password=self.options.os_password,
user_id=self.options.os_user_id,
user_domain_name=self.options.os_user_domain_name,
user_domain_id=self.options.os_user_domain_id,
project_id=project_id,
project_name=project_name,
project_domain_name=self.options.os_project_domain_name,
project_domain_id=self.options.os_project_domain_id
)
def _discover_auth_versions(self, session, auth_url):
# discover the API versions the server is supporting base on the
# given URL
try:
ks_discover = discover.Discover(session=session, auth_url=auth_url)
return (ks_discover.url_for('2.0'), ks_discover.url_for('3.0'))
except ks_exc.ClientException:
# Identity service may not support discover API version.
# Lets try to figure out the API version from the original URL.
url_parts = urlparse.urlparse(auth_url)
(scheme, netloc, path, params, query, fragment) = url_parts
path = path.lower()
if path.startswith('/v3'):
return (None, auth_url)
elif path.startswith('/v2'):
return (auth_url, None)
else:
# not enough information to determine the auth version
msg = _('Unable to determine the Keystone version '
'to authenticate with using the given '
'auth_url. Identity service may not support API '
'version discovery. Please provide a versioned '
'auth_url instead.')
raise exc.CommandError(msg)
def _get_keystone_session(self):
# first create a Keystone session
cacert = self.options.os_cacert or None
cert = self.options.os_cert or None
key = self.options.os_key or None
insecure = self.options.insecure or False
ks_session = session.Session.construct(dict(cacert=cacert,
cert=cert,
key=key,
insecure=insecure))
# discover the supported keystone versions using the given url
(v2_auth_url, v3_auth_url) = self._discover_auth_versions(
session=ks_session,
auth_url=self.options.os_auth_url)
# Determine which authentication plugin to use. First inspect the
# auth_url to see the supported version. If both v3 and v2 are
# supported, then use the highest version if possible.
user_domain_name = self.options.os_user_domain_name or None
user_domain_id = self.options.os_user_domain_id or None
project_domain_name = self.options.os_project_domain_name or None
project_domain_id = self.options.os_project_domain_id or None
domain_info = (user_domain_name or user_domain_id or
project_domain_name or project_domain_id)
if (v2_auth_url and not domain_info) or not v3_auth_url:
ks_session.auth = self.get_v2_auth(v2_auth_url)
else:
ks_session.auth = self.get_v3_auth(v3_auth_url)
return ks_session
def main(argv=sys.argv[1:]):
try:
return GBPShell(NEUTRON_API_VERSION).run(map(strutils.safe_decode,
argv))
except exc.NeutronClientException:
return 1
except Exception as e:
print(unicode(e))
return 1
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))

View File

@ -30,7 +30,6 @@ from keystoneclient import exceptions as ks_exceptions
from keystoneclient.fixture import v2 as ks_v2_fixture
from keystoneclient.fixture import v3 as ks_v3_fixture
from keystoneclient import session
from neutronclient import client
from neutronclient.common import exceptions
from neutronclient.common import utils

View File

@ -1,6 +1,3 @@
# Copyright 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
@ -14,198 +11,56 @@
# under the License.
#
import contextlib
import itertools
import sys
import urllib
import fixtures
from mox3 import mox
from oslotest import base
import requests
import six
import six.moves.urllib.parse as urlparse
from neutronclient.common import constants
from neutronclient.common import exceptions
from neutronclient.neutron import v2_0 as neutronV2_0
from neutronclient import shell
from neutronclient.v2_0 import client
from neutronclient.tests.unit import test_cli20 as neutron_test_cli20
import requests
API_VERSION = "2.0"
FORMAT = 'json'
TOKEN = 'testtoken'
ENDURL = 'localurl'
from gbpclient.gbp import v2_0 as gbpV2_0
from gbpclient import gbpshell
from gbpclient.v2_0 import client as gbpclient
API_VERSION = neutron_test_cli20.API_VERSION
FORMAT = neutron_test_cli20.FORMAT
TOKEN = neutron_test_cli20.TOKEN
ENDURL = neutron_test_cli20.ENDURL
capture_std_streams = neutron_test_cli20.capture_std_streams
end_url = neutron_test_cli20.end_url
@contextlib.contextmanager
def capture_std_streams():
fake_stdout, fake_stderr = six.StringIO(), six.StringIO()
stdout, stderr = sys.stdout, sys.stderr
try:
sys.stdout, sys.stderr = fake_stdout, fake_stderr
yield fake_stdout, fake_stderr
finally:
sys.stdout, sys.stderr = stdout, stderr
class FakeStdout(neutron_test_cli20.FakeStdout):
pass
class FakeStdout:
class MyResp(neutron_test_cli20.MyResp):
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
pass
class MyResp(object):
def __init__(self, status_code, headers=None, reason=None):
self.status_code = status_code
self.headers = headers or {}
self.reason = reason
class MyApp(neutron_test_cli20.MyApp):
pass
class MyApp(object):
def __init__(self, _stdout):
self.stdout = _stdout
class MyUrlComparator(neutron_test_cli20.MyUrlComparator):
pass
def end_url(path, query=None, format=FORMAT):
_url_str = ENDURL + "/v" + API_VERSION + path + "." + format
return query and _url_str + "?" + query or _url_str
class MyComparator(neutron_test_cli20.MyComparator):
pass
class MyUrlComparator(mox.Comparator):
def __init__(self, lhs, client):
self.lhs = lhs
self.client = client
class CLITestV20Base(neutron_test_cli20.CLITestV20Base):
def equals(self, rhs):
lhsp = urlparse.urlparse(self.lhs)
rhsp = urlparse.urlparse(rhs)
return (lhsp.scheme == rhsp.scheme and
lhsp.netloc == rhsp.netloc and
lhsp.path == rhsp.path and
urlparse.parse_qs(lhsp.query) == urlparse.parse_qs(rhsp.query))
def __str__(self):
if self.client and self.client.format != FORMAT:
lhs_parts = self.lhs.split("?", 1)
if len(lhs_parts) == 2:
lhs = ("%s.%s?%s" % (lhs_parts[0][:-4],
self.client.format,
lhs_parts[1]))
else:
lhs = ("%s.%s" % (lhs_parts[0][:-4],
self.client.format))
return lhs
return self.lhs
def __repr__(self):
return str(self)
class MyComparator(mox.Comparator):
def __init__(self, lhs, client):
self.lhs = lhs
self.client = client
def _com_dict(self, lhs, rhs):
if len(lhs) != len(rhs):
return False
for key, value in six.iteritems(lhs):
if key not in rhs:
return False
rhs_value = rhs[key]
if not self._com(value, rhs_value):
return False
return True
def _com_list(self, lhs, rhs):
if len(lhs) != len(rhs):
return False
for lhs_value in lhs:
if lhs_value not in rhs:
return False
return True
def _com(self, lhs, rhs):
if lhs is None:
return rhs is None
if isinstance(lhs, dict):
if not isinstance(rhs, dict):
return False
return self._com_dict(lhs, rhs)
if isinstance(lhs, list):
if not isinstance(rhs, list):
return False
return self._com_list(lhs, rhs)
if isinstance(lhs, tuple):
if not isinstance(rhs, tuple):
return False
return self._com_list(lhs, rhs)
return lhs == rhs
def equals(self, rhs):
if self.client:
rhs = self.client.deserialize(rhs, 200)
return self._com(self.lhs, rhs)
def __repr__(self):
if self.client:
return self.client.serialize(self.lhs)
return str(self.lhs)
class CLITestV20Base(base.BaseTestCase):
format = 'json'
test_id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
id_field = 'id'
def _find_resourceid(self, client, resource, name_or_id,
cmd_resource=None, parent_id=None):
return name_or_id
def _get_attr_metadata(self):
return self.metadata
client.Client.EXTED_PLURALS.update(constants.PLURALS)
client.Client.EXTED_PLURALS.update({'tags': 'tag'})
return {'plurals': client.Client.EXTED_PLURALS,
'xmlns': constants.XML_NS_V20,
constants.EXT_NS: {'prefix': 'http://xxxx.yy.com'}}
shell = gbpshell
client = gbpclient
def setUp(self, plurals=None):
"""Prepare the test environment."""
super(CLITestV20Base, self).setUp()
client.Client.EXTED_PLURALS.update(constants.PLURALS)
if plurals is not None:
client.Client.EXTED_PLURALS.update(plurals)
self.metadata = {'plurals': client.Client.EXTED_PLURALS,
'xmlns': constants.XML_NS_V20,
constants.EXT_NS: {'prefix':
'http://xxxx.yy.com'}}
self.mox = mox.Mox()
self.endurl = ENDURL
self.fake_stdout = FakeStdout()
self.useFixture(fixtures.MonkeyPatch('sys.stdout', self.fake_stdout))
self.useFixture(fixtures.MonkeyPatch(
'neutronclient.neutron.v2_0.find_resourceid_by_name_or_id',
self._find_resourceid))
self.useFixture(fixtures.MonkeyPatch(
'neutronclient.neutron.v2_0.find_resourceid_by_id',
self._find_resourceid))
self.useFixture(fixtures.MonkeyPatch(
'neutronclient.v2_0.client.Client.get_attr_metadata',
self._get_attr_metadata))
self.client = client.Client(token=TOKEN, endpoint_url=self.endurl)
self.client = gbpclient.Client(token=TOKEN, endpoint_url=self.endurl)
def _test_create_resource(self, resource, cmd, name, myid, args,
position_names, position_values,
@ -215,19 +70,9 @@ class CLITestV20Base(base.BaseTestCase):
self.mox.StubOutWithMock(cmd, "get_client")
self.mox.StubOutWithMock(self.client.httpclient, "request")
cmd.get_client().MultipleTimes().AndReturn(self.client)
non_admin_status_resources = ['subnet', 'floatingip', 'security_group',
'security_group_rule', 'qos_queue',
'network_gateway', 'gateway_device',
'credential', 'network_profile',
'policy_profile', 'ikepolicy',
'ipsecpolicy', 'metering_label',
'metering_label_rule', 'net_partition']
if not cmd_resource:
cmd_resource = resource
if (resource in non_admin_status_resources):
body = {resource: {}, }
else:
body = {resource: {'admin_state_up': admin_state_up, }, }
body = {resource: {}, }
if tenant_id:
body[resource].update({'tenant_id': tenant_id})
if tags:
@ -245,8 +90,8 @@ class CLITestV20Base(base.BaseTestCase):
self.client.format = self.format
resstr = self.client.serialize(ress)
# url method body
resource_plural = neutronV2_0._get_resource_plural(cmd_resource,
self.client)
resource_plural = gbpV2_0._get_resource_plural(cmd_resource,
self.client)
path = getattr(self.client, resource_plural + "_path")
if parent_id:
path = path % parent_id
@ -264,7 +109,7 @@ class CLITestV20Base(base.BaseTestCase):
args.extend(['--request-format', self.format])
self.mox.ReplayAll()
cmd_parser = cmd.get_parser('create_' + resource)
shell.run_command(cmd, cmd_parser, args)
gbpshell.run_command(cmd, cmd_parser, args)
self.mox.VerifyAll()
self.mox.UnsetStubs()
_str = self.fake_stdout.make_string()
@ -272,354 +117,10 @@ class CLITestV20Base(base.BaseTestCase):
if name:
self.assertIn(name, _str)
def _test_list_columns(self, cmd, resources,
resources_out, args=('-f', 'json'),
cmd_resources=None, parent_id=None):
self.mox.StubOutWithMock(cmd, "get_client")
self.mox.StubOutWithMock(self.client.httpclient, "request")
cmd.get_client().MultipleTimes().AndReturn(self.client)
self.client.format = self.format
if not cmd_resources:
cmd_resources = resources
resstr = self.client.serialize(resources_out)
path = getattr(self.client, cmd_resources + "_path")
if parent_id:
path = path % parent_id
self.client.httpclient.request(
end_url(path, format=self.format), 'GET',
body=None,
headers=mox.ContainsKeyValue(
'X-Auth-Token', TOKEN)).AndReturn((MyResp(200), resstr))
args = tuple(args) + ('--request-format', self.format)
self.mox.ReplayAll()
cmd_parser = cmd.get_parser("list_" + cmd_resources)
shell.run_command(cmd, cmd_parser, args)
self.mox.VerifyAll()
self.mox.UnsetStubs()
def _test_list_resources(self, resources, cmd, detail=False, tags=(),
fields_1=(), fields_2=(), page_size=None,
sort_key=(), sort_dir=(), response_contents=None,
base_args=None, path=None, cmd_resources=None,
parent_id=None):
self.mox.StubOutWithMock(cmd, "get_client")
self.mox.StubOutWithMock(self.client.httpclient, "request")
cmd.get_client().MultipleTimes().AndReturn(self.client)
if not cmd_resources:
cmd_resources = resources
if response_contents is None:
contents = [{self.id_field: 'myid1', },
{self.id_field: 'myid2', }, ]
else:
contents = response_contents
reses = {resources: contents}
self.client.format = self.format
resstr = self.client.serialize(reses)
# url method body
query = ""
args = base_args if base_args is not None else []
if detail:
args.append('-D')
args.extend(['--request-format', self.format])
if fields_1:
for field in fields_1:
args.append('--fields')
args.append(field)
if tags:
args.append('--')
args.append("--tag")
for tag in tags:
args.append(tag)
if isinstance(tag, unicode):
tag = urllib.quote(tag.encode('utf-8'))
if query:
query += "&tag=" + tag
else:
query = "tag=" + tag
if (not tags) and fields_2:
args.append('--')
if fields_2:
args.append("--fields")
for field in fields_2:
args.append(field)
if detail:
query = query and query + '&verbose=True' or 'verbose=True'
for field in itertools.chain(fields_1, fields_2):
if query:
query += "&fields=" + field
else:
query = "fields=" + field
if page_size:
args.append("--page-size")
args.append(str(page_size))
if query:
query += "&limit=%s" % page_size
else:
query = "limit=%s" % page_size
if sort_key:
for key in sort_key:
args.append('--sort-key')
args.append(key)
if query:
query += '&'
query += 'sort_key=%s' % key
if sort_dir:
len_diff = len(sort_key) - len(sort_dir)
if len_diff > 0:
sort_dir = tuple(sort_dir) + ('asc',) * len_diff
elif len_diff < 0:
sort_dir = sort_dir[:len(sort_key)]
for dir in sort_dir:
args.append('--sort-dir')
args.append(dir)
if query:
query += '&'
query += 'sort_dir=%s' % dir
if path is None:
path = getattr(self.client, cmd_resources + "_path")
if parent_id:
path = path % parent_id
self.client.httpclient.request(
MyUrlComparator(end_url(path, query, format=self.format),
self.client),
'GET',
body=None,
headers=mox.ContainsKeyValue(
'X-Auth-Token', TOKEN)).AndReturn((MyResp(200), resstr))
self.mox.ReplayAll()
cmd_parser = cmd.get_parser("list_" + cmd_resources)
shell.run_command(cmd, cmd_parser, args)
self.mox.VerifyAll()
self.mox.UnsetStubs()
_str = self.fake_stdout.make_string()
if response_contents is None:
self.assertIn('myid1', _str)
return _str
def _test_list_resources_with_pagination(self, resources, cmd,
cmd_resources=None,
parent_id=None):
self.mox.StubOutWithMock(cmd, "get_client")
self.mox.StubOutWithMock(self.client.httpclient, "request")
cmd.get_client().MultipleTimes().AndReturn(self.client)
if not cmd_resources:
cmd_resources = resources
path = getattr(self.client, cmd_resources + "_path")
if parent_id:
path = path % parent_id
fake_query = "marker=myid2&limit=2"
reses1 = {resources: [{'id': 'myid1', },
{'id': 'myid2', }],
'%s_links' % resources: [{'href': end_url(path, fake_query),
'rel': 'next'}]}
reses2 = {resources: [{'id': 'myid3', },
{'id': 'myid4', }]}
self.client.format = self.format
resstr1 = self.client.serialize(reses1)
resstr2 = self.client.serialize(reses2)
self.client.httpclient.request(
end_url(path, "", format=self.format), 'GET',
body=None,
headers=mox.ContainsKeyValue(
'X-Auth-Token', TOKEN)).AndReturn((MyResp(200), resstr1))
self.client.httpclient.request(
MyUrlComparator(end_url(path, fake_query, format=self.format),
self.client), 'GET',
body=None,
headers=mox.ContainsKeyValue(
'X-Auth-Token', TOKEN)).AndReturn((MyResp(200), resstr2))
self.mox.ReplayAll()
cmd_parser = cmd.get_parser("list_" + cmd_resources)
args = ['--request-format', self.format]
shell.run_command(cmd, cmd_parser, args)
self.mox.VerifyAll()
self.mox.UnsetStubs()
def _test_update_resource(self, resource, cmd, myid, args, extrafields,
cmd_resource=None, parent_id=None):
self.mox.StubOutWithMock(cmd, "get_client")
self.mox.StubOutWithMock(self.client.httpclient, "request")
cmd.get_client().MultipleTimes().AndReturn(self.client)
if not cmd_resource:
cmd_resource = resource
body = {resource: extrafields}
path = getattr(self.client, cmd_resource + "_path")
if parent_id:
path = path % (parent_id, myid)
else:
path = path % myid
self.client.format = self.format
# Work around for LP #1217791. XML deserializer called from
# MyComparator does not decodes XML string correctly.
if self.format == 'json':
mox_body = MyComparator(body, self.client)
else:
mox_body = self.client.serialize(body)
self.client.httpclient.request(
MyUrlComparator(end_url(path, format=self.format),
self.client),
'PUT',
body=mox_body,
headers=mox.ContainsKeyValue(
'X-Auth-Token', TOKEN)).AndReturn((MyResp(204), None))
args.extend(['--request-format', self.format])
self.mox.ReplayAll()
cmd_parser = cmd.get_parser("update_" + cmd_resource)
shell.run_command(cmd, cmd_parser, args)
self.mox.VerifyAll()
self.mox.UnsetStubs()
_str = self.fake_stdout.make_string()
self.assertIn(myid, _str)
def _test_show_resource(self, resource, cmd, myid, args, fields=(),
cmd_resource=None, parent_id=None):
self.mox.StubOutWithMock(cmd, "get_client")
self.mox.StubOutWithMock(self.client.httpclient, "request")
cmd.get_client().MultipleTimes().AndReturn(self.client)
if not cmd_resource:
cmd_resource = resource
query = "&".join(["fields=%s" % field for field in fields])
expected_res = {resource:
{self.id_field: myid,
'name': 'myname', }, }
self.client.format = self.format
resstr = self.client.serialize(expected_res)
path = getattr(self.client, cmd_resource + "_path")
if parent_id:
path = path % (parent_id, myid)
else:
path = path % myid
self.client.httpclient.request(
end_url(path, query, format=self.format), 'GET',
body=None,
headers=mox.ContainsKeyValue(
'X-Auth-Token', TOKEN)).AndReturn((MyResp(200), resstr))
args.extend(['--request-format', self.format])
self.mox.ReplayAll()
cmd_parser = cmd.get_parser("show_" + cmd_resource)
shell.run_command(cmd, cmd_parser, args)
self.mox.VerifyAll()
self.mox.UnsetStubs()
_str = self.fake_stdout.make_string()
self.assertIn(myid, _str)
self.assertIn('myname', _str)
def _test_delete_resource(self, resource, cmd, myid, args,
cmd_resource=None, parent_id=None):
self.mox.StubOutWithMock(cmd, "get_client")
self.mox.StubOutWithMock(self.client.httpclient, "request")
cmd.get_client().MultipleTimes().AndReturn(self.client)
if not cmd_resource:
cmd_resource = resource
path = getattr(self.client, cmd_resource + "_path")
if parent_id:
path = path % (parent_id, myid)
else:
path = path % (myid)
self.client.httpclient.request(
end_url(path, format=self.format), 'DELETE',
body=None,
headers=mox.ContainsKeyValue(
'X-Auth-Token', TOKEN)).AndReturn((MyResp(204), None))
args.extend(['--request-format', self.format])
self.mox.ReplayAll()
cmd_parser = cmd.get_parser("delete_" + cmd_resource)
shell.run_command(cmd, cmd_parser, args)
self.mox.VerifyAll()
self.mox.UnsetStubs()
_str = self.fake_stdout.make_string()
self.assertIn(myid, _str)
def _test_update_resource_action(self, resource, cmd, myid, action, args,
body, retval=None, cmd_resource=None):
self.mox.StubOutWithMock(cmd, "get_client")
self.mox.StubOutWithMock(self.client.httpclient, "request")
cmd.get_client().MultipleTimes().AndReturn(self.client)
if not cmd_resource:
cmd_resource = resource
path = getattr(self.client, cmd_resource + "_path")
path_action = '%s/%s' % (myid, action)
self.client.httpclient.request(
end_url(path % path_action, format=self.format), 'PUT',
body=MyComparator(body, self.client),
headers=mox.ContainsKeyValue(
'X-Auth-Token', TOKEN)).AndReturn((MyResp(204), retval))
args.extend(['--request-format', self.format])
self.mox.ReplayAll()
cmd_parser = cmd.get_parser("delete_" + cmd_resource)
shell.run_command(cmd, cmd_parser, args)
self.mox.VerifyAll()
self.mox.UnsetStubs()
_str = self.fake_stdout.make_string()
self.assertIn(myid, _str)
class ClientV2TestJson(CLITestV20Base):
def test_do_request_unicode(self):
self.client.format = self.format
self.mox.StubOutWithMock(self.client.httpclient, "request")
unicode_text = u'\u7f51\u7edc'
# url with unicode
action = u'/test'
expected_action = action.encode('utf-8')
# query string with unicode
params = {'test': unicode_text}
expect_query = urllib.urlencode({'test':
unicode_text.encode('utf-8')})
# request body with unicode
body = params
expect_body = self.client.serialize(body)
# headers with unicode
self.client.httpclient.auth_token = unicode_text
expected_auth_token = unicode_text.encode('utf-8')
self.client.httpclient.request(
end_url(expected_action, query=expect_query, format=self.format),
'PUT', body=expect_body,
headers=mox.ContainsKeyValue(
'X-Auth-Token',
expected_auth_token)).AndReturn((MyResp(200), expect_body))
self.mox.ReplayAll()
res_body = self.client.do_request('PUT', action, body=body,
params=params)
self.mox.VerifyAll()
self.mox.UnsetStubs()
# test response with unicode
self.assertEqual(res_body, body)
def test_do_request_error_without_response_body(self):
self.client.format = self.format
self.mox.StubOutWithMock(self.client.httpclient, "request")
params = {'test': 'value'}
expect_query = six.moves.urllib.parse.urlencode(params)
self.client.httpclient.auth_token = 'token'
self.client.httpclient.request(
MyUrlComparator(end_url(
'/test', query=expect_query, format=self.format), self.client),
'PUT', body='',
headers=mox.ContainsKeyValue('X-Auth-Token', 'token')
).AndReturn((MyResp(400, reason='An error'), ''))
self.mox.ReplayAll()
error = self.assertRaises(exceptions.NeutronClientException,
self.client.do_request, 'PUT', '/test',
body='', params=params)
self.assertEqual("An error", str(error))
self.mox.VerifyAll()
self.mox.UnsetStubs()
class ClientV2UnicodeTestXML(ClientV2TestJson):
format = 'xml'
pass
class CLITestV20ExceptionHandler(CLITestV20Base):
@ -634,7 +135,7 @@ class CLITestV20ExceptionHandler(CLITestV20Base):
'detail': error_detail}}
e = self.assertRaises(expected_exception,
client.exception_handler_v20,
gbpclient.exception_handler_v20,
status_code, error_content)
self.assertEqual(status_code, e.status_code)
@ -645,44 +146,13 @@ class CLITestV20ExceptionHandler(CLITestV20Base):
expected_msg = error_msg
self.assertEqual(expected_msg, e.message)
def test_exception_handler_v20_ip_address_in_use(self):
err_msg = ('Unable to complete operation for network '
'fake-network-uuid. The IP address fake-ip is in use.')
self._test_exception_handler_v20(
exceptions.IpAddressInUseClient, 409, err_msg,
'IpAddressInUse', err_msg, '')
def test_exception_handler_v20_neutron_known_error(self):
known_error_map = [
('NetworkNotFound', exceptions.NetworkNotFoundClient, 404),
('PortNotFound', exceptions.PortNotFoundClient, 404),
('NetworkInUse', exceptions.NetworkInUseClient, 409),
('PortInUse', exceptions.PortInUseClient, 409),
('StateInvalid', exceptions.StateInvalidClient, 400),
('IpAddressInUse', exceptions.IpAddressInUseClient, 409),
('IpAddressGenerationFailure',
exceptions.IpAddressGenerationFailureClient, 409),
('MacAddressInUse', exceptions.MacAddressInUseClient, 409),
('ExternalIpAddressExhausted',
exceptions.ExternalIpAddressExhaustedClient, 400),
('OverQuota', exceptions.OverQuotaClient, 409),
]
error_msg = 'dummy exception message'
error_detail = 'sample detail'
for server_exc, client_exc, status_code in known_error_map:
self._test_exception_handler_v20(
client_exc, status_code,
error_msg + '\n' + error_detail,
server_exc, error_msg, error_detail)
# TODO(Sumit): This needs to be adapted for GBP
pass
def test_exception_handler_v20_neutron_known_error_without_detail(self):
error_msg = 'Network not found'
error_detail = ''
self._test_exception_handler_v20(
exceptions.NetworkNotFoundClient, 404,
error_msg,
'NetworkNotFound', error_msg, error_detail)
# TODO(Sumit): This needs to be adapted for GBP
pass
def test_exception_handler_v20_unknown_error_to_per_code_exception(self):
for status_code, client_exc in exceptions.HTTP_EXCEPTION_MAP.items():

View File

@ -0,0 +1,137 @@
# 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
from gbpclient.gbp.v2_0 import groupbasedpolicy as gbp
from gbpclient.tests.unit import test_cli20
class CLITestV20NetworkServicePolicyJSON(test_cli20.CLITestV20Base):
def setUp(self):
super(CLITestV20NetworkServicePolicyJSON, self).setUp()
def test_create_nsp_with_mandatory_params(self):
"""network-service-policy-create with mandatory params."""
resource = 'network_service_policy'
cmd = gbp.CreateNetworkServicePolicy(test_cli20.MyApp(sys.stdout),
None)
name = 'my-name'
tenant_id = 'my-tenant'
my_id = 'my-id'
args = ['--tenant-id', tenant_id,
name]
position_names = ['name', ]
position_values = [name, ]
self._test_create_resource(resource, cmd, name, my_id, args,
position_names, position_values,
tenant_id=tenant_id)
def test_create_network_service_policy_with_all_params(self):
"""network-service-policy-create with all params."""
resource = 'network_service_policy'
cmd = gbp.CreateNetworkServicePolicy(test_cli20.MyApp(sys.stdout),
None)
name = 'myname'
tenant_id = 'mytenant'
description = 'Mynsp'
my_id = 'someid'
network_svc_params = "type=ip_single,name=vip,value=self_subnet"
args = ['--tenant_id', tenant_id,
'--description', description,
'--network-service-params', network_svc_params,
name]
position_names = ['name', 'description', 'network_service_params']
net_params = [{"type": "ip_single", "name": "vip",
"value": "self_subnet"}]
position_values = [name, description, net_params]
self._test_create_resource(resource, cmd, name, my_id, args,
position_names, position_values,
tenant_id=tenant_id)
def test_list_network_service_policies(self):
"""network-sercvice-policy-list."""
resources = 'network_service_policies'
cmd = gbp.ListNetworkServicePolicy(test_cli20.MyApp(sys.stdout),
None)
self._test_list_resources(resources, cmd, True)
def test_list_network_service_policies_with_pagination(self):
"""network-sercvice-policy-list."""
resources = 'network_service_policies'
cmd = gbp.ListNetworkServicePolicy(test_cli20.MyApp(sys.stdout),
None)
self._test_list_resources_with_pagination(resources, cmd)
def test_list_network_sercice_policies_sort(self):
"""network-service-policy-list --sort-key name --sort-key id
--sort-key asc --sort-key desc
"""
resources = 'network_service_policies'
cmd = gbp.ListNetworkServicePolicy(test_cli20.MyApp(sys.stdout),
None)
self._test_list_resources(resources, cmd,
sort_key=["name", "id"],
sort_dir=["asc", "desc"])
def test_list_network_service_polices_limit(self):
"""network-service-policy-list -P."""
resources = 'network_service_policies'
cmd = gbp.ListNetworkServicePolicy(test_cli20.MyApp(sys.stdout),
None)
self._test_list_resources(resources, cmd, page_size=1000)
def test_show_network_service_policy_id(self):
"""network-service-policy-show test_id."""
resource = 'network_service_policy'
cmd = gbp.ShowNetworkServicePolicy(test_cli20.MyApp(sys.stdout), None)
args = ['--fields', 'id', self.test_id]
self._test_show_resource(resource, cmd, self.test_id, args, ['id'])
def test_show_network_service_policy_id_name(self):
"""network-service-policy-show."""
resource = 'network_service_policy'
cmd = gbp.ShowNetworkServicePolicy(test_cli20.MyApp(sys.stdout), None)
args = ['--fields', 'id', '--fields', 'name', self.test_id]
self._test_show_resource(resource, cmd, self.test_id,
args, ['id', 'name'])
def test_update_network_service_policy(self):
"""network-service-policy-update myid --name myname --tags a b."""
resource = 'network_service_policy'
cmd = gbp.UpdateNetworkServicePolicy(test_cli20.MyApp(sys.stdout),
None)
self._test_update_resource(resource, cmd, 'myid',
['myid', '--name', 'myname',
'--tags', 'a', 'b'],
{'name': 'myname', 'tags': ['a', 'b'], })
def test_update_network_service_policy_with_allparams(self):
resource = 'network_service_policy'
new_name = "new_name"
cmd = gbp.UpdateNetworkServicePolicy(test_cli20.MyApp(sys.stdout),
None)
body = {
'name': new_name
}
args = ['myid', '--name', new_name, '--request-format', 'json']
self._test_update_resource(resource, cmd, 'myid', args, body)
def test_delete_network_service_policy(self):
"""network-service-policy-delete my-id."""
resource = 'network_service_policy'
cmd = gbp.DeleteNetworkServicePolicy(test_cli20.MyApp(sys.stdout),
None)
my_id = 'my-id'
args = [my_id]
self._test_delete_resource(resource, cmd, my_id, args)

View File

@ -0,0 +1,135 @@
# 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
from gbpclient.gbp.v2_0 import groupbasedpolicy as gbp
from gbpclient.tests.unit import test_cli20
class CLITestV20PolicyActionJSON(test_cli20.CLITestV20Base):
def setUp(self):
super(CLITestV20PolicyActionJSON, self).setUp()
def test_create_policy_action_with_mandatory_params(self):
"""grouppolicy-policy-action-create with all mandatory params."""
resource = 'policy_action'
cmd = gbp.CreatePolicyAction(test_cli20.MyApp(sys.stdout), None)
name = 'my-name'
tenant_id = 'my-tenant'
my_id = 'my-id'
args = ['--tenant-id', tenant_id,
name]
position_names = ['name', ]
position_values = [name, ]
self._test_create_resource(resource, cmd, name, my_id, args,
position_names, position_values,
tenant_id=tenant_id)
def test_create_policy_action_with_all_params(self):
"""grouppolicy-policy-action-create with all params."""
resource = 'policy_action'
cmd = gbp.CreatePolicyAction(test_cli20.MyApp(sys.stdout), None)
name = 'my-name'
tenant_id = 'my-tenant'
description = 'My PolicyAction'
my_id = 'my-id'
action_type = "allow"
action_value = "1234"
args = ['--tenant-id', tenant_id,
'--description', description,
'--action-type', action_type,
'--action-value', action_value,
name]
position_names = ['name', ]
position_values = [name, ]
self._test_create_resource(resource, cmd, name, my_id, args,
position_names, position_values,
tenant_id=tenant_id,
description=description,
action_type=action_type,
action_value=action_value)
def test_list_policy_actions(self):
"""grouppolicy-policy-action-list."""
resources = 'policy_actions'
cmd = gbp.ListPolicyAction(test_cli20.MyApp(sys.stdout), None)
self._test_list_resources(resources, cmd, True)
def test_list_policy_actions_pagination(self):
"""grouppolicy-policy-action-list."""
resources = 'policy_actions'
cmd = gbp.ListPolicyAction(test_cli20.MyApp(sys.stdout), None)
self._test_list_resources_with_pagination(resources, cmd)
def test_list_policy_actions_sort(self):
"""grouppolicy-policy-action-list --sort-key name --sort-key id
--sort-key asc --sort-key desc
"""
resources = 'policy_actions'
cmd = gbp.ListPolicyAction(test_cli20.MyApp(sys.stdout), None)
self._test_list_resources(resources, cmd,
sort_key=["name", "id"],
sort_dir=["asc", "desc"])
def test_list_policy_actions_limit(self):
"""grouppolicy-policy-action-list -P."""
resources = 'policy_actions'
cmd = gbp.ListPolicyAction(test_cli20.MyApp(sys.stdout), None)
self._test_list_resources(resources, cmd, page_size=1000)
def test_show_policy_action_id(self):
"""grouppolicy-policy-action-show test_id."""
resource = 'policy_action'
cmd = gbp.ShowPolicyAction(test_cli20.MyApp(sys.stdout), None)
args = ['--fields', 'id', self.test_id]
self._test_show_resource(resource, cmd, self.test_id, args, ['id'])
def test_show_policy_action_id_name(self):
"""grouppolicy-policy-action-show."""
resource = 'policy_action'
cmd = gbp.ShowPolicyAction(test_cli20.MyApp(sys.stdout), None)
args = ['--fields', 'id', '--fields', 'name', self.test_id]
self._test_show_resource(resource, cmd, self.test_id,
args, ['id', 'name'])
def test_update_policy_action(self):
"""grouppolicy-policy-action-update myid --name myname --tags a b."""
resource = 'policy_action'
cmd = gbp.UpdatePolicyAction(test_cli20.MyApp(sys.stdout), None)
self._test_update_resource(resource, cmd, 'myid',
['myid', '--name', 'myname',
'--tags', 'a', 'b'],
{'name': 'myname', 'tags': ['a', 'b'], })
def test_update_policy_action_with_allparams(self):
resource = 'policy_action'
action_type = "allow"
action_value = "1234"
cmd = gbp.UpdatePolicyAction(test_cli20.MyApp(sys.stdout), None)
body = {
'action_type': action_type,
'action_value': action_value
}
args = ['myid',
'--action-type', action_type,
'--action-value', action_value, ]
self._test_update_resource(resource, cmd, 'myid', args, body)
def test_delete_policy_action(self):
"""grouppolicy-policy-action-delete my-id."""
resource = 'policy_action'
cmd = gbp.DeletePolicyAction(test_cli20.MyApp(sys.stdout), None)
my_id = 'my-id'
args = [my_id]
self._test_delete_resource(resource, cmd, my_id, args)

View File

@ -0,0 +1,141 @@
# 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
from gbpclient.gbp.v2_0 import groupbasedpolicy as gbp
from gbpclient.tests.unit import test_cli20
class CLITestV20PolicyClassifierJSON(test_cli20.CLITestV20Base):
def setUp(self):
super(CLITestV20PolicyClassifierJSON, self).setUp()
def test_create_policy_classifier_with_mandatory_params(self):
"""grouppolicy-policy-classifier-create with all mandatory params."""
resource = 'policy_classifier'
cmd = gbp.CreatePolicyClassifier(test_cli20.MyApp(sys.stdout), None)
name = 'my-name'
tenant_id = 'my-tenant'
my_id = 'my-id'
args = ['--tenant-id', tenant_id,
name]
position_names = ['name', ]
position_values = [name, ]
self._test_create_resource(resource, cmd, name, my_id, args,
position_names, position_values,
tenant_id=tenant_id)
def test_create_policy_classifier_with_all_params(self):
"""grouppolicy-policy-classifier-create with all params."""
resource = 'policy_classifier'
cmd = gbp.CreatePolicyClassifier(test_cli20.MyApp(sys.stdout), None)
name = 'my-name'
tenant_id = 'my-tenant'
description = 'My PolicyClassifier'
my_id = 'my-id'
protocol = 'tcp'
port_range = '10-80'
direction = 'in'
args = ['--tenant-id', tenant_id,
'--description', description,
'--protocol', protocol,
'--port-range', port_range,
'--direction', direction,
name]
position_names = ['name', ]
position_values = [name, ]
self._test_create_resource(resource, cmd, name, my_id, args,
position_names, position_values,
tenant_id=tenant_id,
description=description,
protocol=protocol,
port_range=port_range,
direction=direction)
def test_list_policy_classifiers(self):
"""grouppolicy-policy-classifier-list."""
resources = 'policy_classifiers'
cmd = gbp.ListPolicyClassifier(test_cli20.MyApp(sys.stdout), None)
self._test_list_resources(resources, cmd, True)
def test_list_policy_classifiers_pagination(self):
"""grouppolicy-policy-classifier-list."""
resources = 'policy_classifiers'
cmd = gbp.ListPolicyClassifier(test_cli20.MyApp(sys.stdout), None)
self._test_list_resources_with_pagination(resources, cmd)
def test_list_policy_classifiers_sort(self):
"""grouppolicy-policy-classifier-list --sort-key name --sort-key id
--sort-key asc --sort-key desc
"""
resources = 'policy_classifiers'
cmd = gbp.ListPolicyClassifier(test_cli20.MyApp(sys.stdout), None)
self._test_list_resources(resources, cmd,
sort_key=["name", "id"],
sort_dir=["asc", "desc"])
def test_list_policy_classifiers_limit(self):
"""grouppolicy-policy-classifier-list -P."""
resources = 'policy_classifiers'
cmd = gbp.ListPolicyClassifier(test_cli20.MyApp(sys.stdout), None)
self._test_list_resources(resources, cmd, page_size=1000)
def test_show_policy_classifier_id(self):
"""grouppolicy-policy-classifier-show test_id."""
resource = 'policy_classifier'
cmd = gbp.ShowPolicyClassifier(test_cli20.MyApp(sys.stdout), None)
args = ['--fields', 'id', self.test_id]
self._test_show_resource(resource, cmd, self.test_id, args, ['id'])
def test_show_policy_classifier_id_name(self):
"""grouppolicy-policy-classifier-show."""
resource = 'policy_classifier'
cmd = gbp.ShowPolicyClassifier(test_cli20.MyApp(sys.stdout), None)
args = ['--fields', 'id', '--fields', 'name', self.test_id]
self._test_show_resource(resource, cmd, self.test_id,
args, ['id', 'name'])
def test_update_policy_classifier(self):
"""grouppolicy-policy-classifier-update myid --name myname --tags a b.
"""
resource = 'policy_classifier'
cmd = gbp.UpdatePolicyClassifier(test_cli20.MyApp(sys.stdout), None)
self._test_update_resource(resource, cmd, 'myid',
['myid', '--name', 'myname',
'--tags', 'a', 'b'],
{'name': 'myname', 'tags': ['a', 'b'], })
def test_update_policy_classifier_with_allparams(self):
resource = 'policy_classifier'
protocol = 'tcp'
port_range = '10-80'
direction = 'in'
cmd = gbp.UpdatePolicyClassifier(test_cli20.MyApp(sys.stdout), None)
body = {
'protocol': protocol,
'port_range': port_range,
'direction': direction
}
args = ['myid', '--protocol', protocol,
'--port-range', port_range,
'--direction', direction, ]
self._test_update_resource(resource, cmd, 'myid', args, body)
def test_delete_policy_classifier(self):
"""grouppolicy-policy-classifier-delete my-id."""
resource = 'policy_classifier'
cmd = gbp.DeletePolicyClassifier(test_cli20.MyApp(sys.stdout), None)
my_id = 'my-id'
args = [my_id]
self._test_delete_resource(resource, cmd, my_id, args)

View File

@ -0,0 +1,142 @@
# 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
from gbpclient.gbp.v2_0 import groupbasedpolicy as gbp
from gbpclient.tests.unit import test_cli20
class CLITestV20PolicyRuleJSON(test_cli20.CLITestV20Base):
def setUp(self):
super(CLITestV20PolicyRuleJSON, self).setUp()
def test_create_policy_rule_with_mandatory_params(self):
"""grouppolicy-policy-rule-create with all mandatory params."""
resource = 'policy_rule'
cmd = gbp.CreatePolicyRule(test_cli20.MyApp(sys.stdout), None)
name = 'my-name'
tenant_id = 'my-tenant'
my_id = 'my-id'
args = ['--tenant-id', tenant_id,
name]
position_names = ['name', ]
position_values = [name, ]
self._test_create_resource(resource, cmd, name, my_id, args,
position_names, position_values,
tenant_id=tenant_id)
def test_create_policy_rule_with_all_params(self):
"""grouppolicy-policy-rule-create with all params."""
resource = 'policy_rule'
cmd = gbp.CreatePolicyRule(test_cli20.MyApp(sys.stdout), None)
name = 'my-name'
tenant_id = 'my-tenant'
description = 'My PolicyRule'
my_id = 'my-id'
enabled = True
policy_classifier_id = 'pc-id'
policy_actions_res = ["pa1", "pa2"]
policy_actions_arg = "pa1 pa2"
args = ['--tenant-id', tenant_id,
'--description', description,
'--enabled', "True",
'--classifier', policy_classifier_id,
'--actions', policy_actions_arg,
name]
position_names = ['name', ]
position_values = [name, ]
self._test_create_resource(resource, cmd, name, my_id, args,
position_names, position_values,
tenant_id=tenant_id,
description=description,
enabled=enabled,
policy_classifier_id=policy_classifier_id,
policy_actions=policy_actions_res)
def test_list_policy_rules(self):
"""grouppolicy-policy-rule-list."""
resources = 'policy_rules'
cmd = gbp.ListPolicyRule(test_cli20.MyApp(sys.stdout), None)
self._test_list_resources(resources, cmd, True)
def test_list_policy_rules_pagination(self):
"""grouppolicy-policy-rule-list."""
resources = 'policy_rules'
cmd = gbp.ListPolicyRule(test_cli20.MyApp(sys.stdout), None)
self._test_list_resources_with_pagination(resources, cmd)
def test_list_policy_rules_sort(self):
"""grouppolicy-policy-rule-list --sort-key name --sort-key id
--sort-key asc --sort-key desc
"""
resources = 'policy_rules'
cmd = gbp.ListPolicyRule(test_cli20.MyApp(sys.stdout), None)
self._test_list_resources(resources, cmd,
sort_key=["name", "id"],
sort_dir=["asc", "desc"])
def test_list_policy_rules_limit(self):
"""grouppolicy-policy-rule-list -P."""
resources = 'policy_rules'
cmd = gbp.ListPolicyRule(test_cli20.MyApp(sys.stdout), None)
self._test_list_resources(resources, cmd, page_size=1000)
def test_show_policy_classifier_id(self):
"""grouppolicy-policy-rule-show test_id."""
resource = 'policy_rule'
cmd = gbp.ShowPolicyRule(test_cli20.MyApp(sys.stdout), None)
args = ['--fields', 'id', self.test_id]
self._test_show_resource(resource, cmd, self.test_id, args, ['id'])
def test_show_policy_classifier_id_name(self):
"""grouppolicy-policy-rule-show."""
resource = 'policy_rule'
cmd = gbp.ShowPolicyRule(test_cli20.MyApp(sys.stdout), None)
args = ['--fields', 'id', '--fields', 'name', self.test_id]
self._test_show_resource(resource, cmd, self.test_id,
args, ['id', 'name'])
def test_update_policy_rule(self):
"""grouppolicy-policy-rule-update myid --name myname --tags a b."""
resource = 'policy_rule'
cmd = gbp.UpdatePolicyRule(test_cli20.MyApp(sys.stdout), None)
self._test_update_resource(resource, cmd, 'myid',
['myid', '--name', 'myname',
'--tags', 'a', 'b'],
{'name': 'myname', 'tags': ['a', 'b'], })
def test_update_policy_rule_with_allparams(self):
resource = 'policy_rule'
enabled = True
policy_classifier_id = 'pc-id'
policy_actions_res = ["pa1", "pa2"]
policy_actions_arg = "pa1 pa2"
cmd = gbp.UpdatePolicyRule(test_cli20.MyApp(sys.stdout), None)
body = {
'policy_classifier_id': policy_classifier_id,
'enabled': enabled,
'policy_actions': policy_actions_res
}
args = ['myid', '--enabled', "True",
'--classifier', policy_classifier_id,
'--actions', policy_actions_arg, ]
self._test_update_resource(resource, cmd, 'myid', args, body)
def test_delete_policy_classifier(self):
"""grouppolicy-policy-rule-delete my-id."""
resource = 'policy_rule'
cmd = gbp.DeletePolicyRule(test_cli20.MyApp(sys.stdout), None)
my_id = 'my-id'
args = [my_id]
self._test_delete_resource(resource, cmd, my_id, args)

View File

624
gbpclient/v2_0/client.py Normal file
View File

@ -0,0 +1,624 @@
# 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 time
import urllib
from neutronclient import client
from neutronclient.common import _
from neutronclient.common import constants
from neutronclient.common import exceptions
from neutronclient.common import serializer
from neutronclient.common import utils
import requests
import six.moves.urllib.parse as urlparse
_logger = logging.getLogger(__name__)
def exception_handler_v20(status_code, error_content):
"""Exception handler for API v2.0 client
This routine generates the appropriate
Neutron exception according to the contents of the
response body
:param status_code: HTTP error status code
:param error_content: deserialized body of error response
"""
error_dict = None
if isinstance(error_content, dict):
error_dict = error_content.get('NeutronError')
# Find real error type
bad_neutron_error_flag = False
if error_dict:
# If Neutron key is found, it will definitely contain
# a 'message' and 'type' keys?
try:
error_type = error_dict['type']
error_message = error_dict['message']
if error_dict['detail']:
error_message += "\n" + error_dict['detail']
except Exception:
bad_neutron_error_flag = True
if not bad_neutron_error_flag:
# If corresponding exception is defined, use it.
client_exc = getattr(exceptions, '%sClient' % error_type, None)
# Otherwise look up per status-code client exception
if not client_exc:
client_exc = exceptions.HTTP_EXCEPTION_MAP.get(status_code)
if client_exc:
raise client_exc(message=error_message,
status_code=status_code)
else:
raise exceptions.NeutronClientException(
status_code=status_code, message=error_message)
else:
raise exceptions.NeutronClientException(status_code=status_code,
message=error_dict)
else:
message = None
if isinstance(error_content, dict):
message = error_content.get('message')
if message:
raise exceptions.NeutronClientException(status_code=status_code,
message=message)
# If we end up here the exception was not a neutron error
msg = "%s-%s" % (status_code, error_content)
raise exceptions.NeutronClientException(status_code=status_code,
message=msg)
class APIParamsCall(object):
"""A Decorator to add support for format and tenant overriding
and filters
"""
def __init__(self, function):
self.function = function
def __get__(self, instance, owner):
def with_params(*args, **kwargs):
_format = instance.format
if 'format' in kwargs:
instance.format = kwargs['format']
ret = self.function(instance, *args, **kwargs)
instance.format = _format
return ret
return with_params
class Client(object):
"""Client for the GBP API.
:param string username: Username for authentication. (optional)
:param string user_id: User ID for authentication. (optional)
:param string password: Password for authentication. (optional)
:param string token: Token for authentication. (optional)
:param string tenant_name: Tenant name. (optional)
:param string tenant_id: Tenant id. (optional)
:param string auth_url: Keystone service endpoint for authorization.
:param string service_type: Network service type to pull from the
keystone catalog (e.g. 'network') (optional)
:param string endpoint_type: Network service endpoint type to pull from the
keystone catalog (e.g. 'publicURL',
'internalURL', or 'adminURL') (optional)
:param string region_name: Name of a region to select when choosing an
endpoint from the service catalog.
:param string endpoint_url: A user-supplied endpoint URL for the neutron
service. Lazy-authentication is possible for API
service calls if endpoint is set at
instantiation.(optional)
:param integer timeout: Allows customization of the timeout for client
http requests. (optional)
:param bool insecure: SSL certificate validation. (optional)
:param string ca_cert: SSL CA bundle file to use. (optional)
:param integer retries: How many times idempotent (GET, PUT, DELETE)
requests to Neutron server should be retried if
they fail (default: 0).
:param bool raise_errors: If True then exceptions caused by connection
failure are propagated to the caller.
(default: True)
:param session: Keystone client auth session to use. (optional)
:param auth: Keystone auth plugin to use. (optional)
Example::
from gbpclient.v2_0 import client
gbp = client.Client(username=USER,
password=PASS,
tenant_name=TENANT_NAME,
auth_url=KEYSTONE_URL)
ptgs = gbp.list_policy_target_groups()
...
"""
endpoints_path = "/grouppolicy/endpoints"
endpoint_path = "/grouppolicy/endpoints/%s"
endpoint_groups_path = "/grouppolicy/endpoint_groups"
endpoint_group_path = "/grouppolicy/endpoint_groups/%s"
l2_policies_path = "/grouppolicy/l2_policies"
l2_policy_path = "/grouppolicy/l2_policies/%s"
l3_policies_path = "/grouppolicy/l3_policies"
l3_policy_path = "/grouppolicy/l3_policies/%s"
network_service_policies_path = "/grouppolicy/network_service_policies"
network_service_policy_path = "/grouppolicy/network_service_policies/%s"
policy_classifiers_path = "/grouppolicy/policy_classifiers"
policy_classifier_path = "/grouppolicy/policy_classifiers/%s"
policy_actions_path = "/grouppolicy/policy_actions"
policy_action_path = "/grouppolicy/policy_actions/%s"
policy_rules_path = "/grouppolicy/policy_rules"
policy_rule_path = "/grouppolicy/policy_rules/%s"
contracts_path = "/grouppolicy/contracts"
contract_path = "/grouppolicy/contracts/%s"
# API has no way to report plurals, so we have to hard code them
EXTED_PLURALS = {'endpoints': 'endpoint',
'endpoint_groups': 'endpoint_group',
'l2_policies': 'l2_policy',
'l3_policies': 'l3_policy',
'network_service_policies': 'network_service_policy',
'policy_classifiers': 'policy_classifier',
'policy_actions': 'policy_action',
'policy_rules': 'policy_rule',
'contracts': 'contract',
}
# 8192 Is the default max URI len for eventlet.wsgi.server
MAX_URI_LEN = 8192
def get_attr_metadata(self):
if self.format == 'json':
return {}
old_request_format = self.format
self.format = 'json'
exts = self.list_extensions()['extensions']
self.format = old_request_format
ns = dict([(ext['alias'], ext['namespace']) for ext in exts])
self.EXTED_PLURALS.update(constants.PLURALS)
return {'plurals': self.EXTED_PLURALS,
'xmlns': constants.XML_NS_V20,
constants.EXT_NS: ns}
@APIParamsCall
def list_extensions(self, **_params):
"""Fetch a list of all exts on server side."""
return self.get(self.extensions_path, params=_params)
@APIParamsCall
def show_extension(self, ext_alias, **_params):
"""Fetch a list of all exts on server side."""
return self.get(self.extension_path % ext_alias, params=_params)
@APIParamsCall
def list_endpoints(self, retrieve_all=True, **_params):
"""Fetches a list of all endpoints for a tenant."""
# Pass filters in "params" argument to do_request
return self.list('endpoints', self.endpoints_path, retrieve_all,
**_params)
@APIParamsCall
def show_endpoint(self, endpoint, **_params):
"""Fetches information of a certain endpoint."""
return self.get(self.endpoint_path % (endpoint), params=_params)
@APIParamsCall
def create_endpoint(self, body=None):
"""Creates a new endpoint."""
return self.post(self.endpoints_path, body=body)
@APIParamsCall
def update_endpoint(self, endpoint, body=None):
"""Updates a endpoint."""
return self.put(self.endpoint_path % (endpoint), body=body)
@APIParamsCall
def delete_endpoint(self, endpoint):
"""Deletes the specified endpoint."""
return self.delete(self.endpoint_path % (endpoint))
@APIParamsCall
def list_endpoint_groups(self, retrieve_all=True, **_params):
"""Fetches a list of all endpoint_groups for a tenant."""
# Pass filters in "params" argument to do_request
return self.list('endpoint_groups', self.endpoint_groups_path,
retrieve_all, **_params)
@APIParamsCall
def show_endpoint_group(self, endpoint_group, **_params):
"""Fetches information of a certain endpoint_group."""
return self.get(self.endpoint_group_path % (endpoint_group),
params=_params)
@APIParamsCall
def create_endpoint_group(self, body=None):
"""Creates a new endpoint_group."""
return self.post(self.endpoint_groups_path, body=body)
@APIParamsCall
def update_endpoint_group(self, endpoint_group, body=None):
"""Updates a endpoint_group."""
return self.put(self.endpoint_group_path % (endpoint_group),
body=body)
@APIParamsCall
def delete_endpoint_group(self, endpoint_group):
"""Deletes the specified endpoint_group."""
return self.delete(self.endpoint_group_path % (endpoint_group))
@APIParamsCall
def list_l2_policies(self, retrieve_all=True, **_params):
"""Fetches a list of all l2_policies for a tenant."""
# Pass filters in "params" argument to do_request
return self.list('l2_policies', self.l2_policies_path,
retrieve_all, **_params)
@APIParamsCall
def show_l2_policy(self, l2_policy, **_params):
"""Fetches information of a certain l2_policy."""
return self.get(self.l2_policy_path % (l2_policy),
params=_params)
@APIParamsCall
def create_l2_policy(self, body=None):
"""Creates a new l2_policy."""
return self.post(self.l2_policies_path, body=body)
@APIParamsCall
def update_l2_policy(self, l2_policy, body=None):
"""Updates a l2_policy."""
return self.put(self.l2_policy_path % (l2_policy), body=body)
@APIParamsCall
def delete_l2_policy(self, l2_policy):
"""Deletes the specified l2_policy."""
return self.delete(self.l2_policy_path % (l2_policy))
@APIParamsCall
def list_network_service_policies(self, retrieve_all=True, **_params):
"""Fetches a list of all network_service_policies for a tenant."""
# Pass filters in "params" argument to do_request
return self.list('network_service_policies',
self.network_service_policies_path,
retrieve_all, **_params)
@APIParamsCall
def show_network_service_policy(self, network_service_policy, **_params):
"""Fetches information of a certain network_service_policy."""
return self.get(
self.network_service_policy_path % (network_service_policy),
params=_params)
@APIParamsCall
def create_network_service_policy(self, body=None):
"""Creates a new network_service_policy."""
return self.post(self.network_service_policies_path, body=body)
@APIParamsCall
def update_network_service_policy(self, network_service_policy, body=None):
"""Updates a network_service_policy."""
return self.put(
self.network_service_policy_path % (network_service_policy),
body=body)
@APIParamsCall
def delete_network_service_policy(self, network_service_policy):
"""Deletes the specified network_service_policy."""
return self.delete(
self.network_service_policy_path % (network_service_policy))
@APIParamsCall
def list_l3_policies(self, retrieve_all=True, **_params):
"""Fetches a list of all l3_policies for a tenant."""
# Pass filters in "params" argument to do_request
return self.list('l3_policies', self.l3_policies_path,
retrieve_all, **_params)
@APIParamsCall
def show_l3_policy(self, l3_policy, **_params):
"""Fetches information of a certain l3_policy."""
return self.get(self.l3_policy_path % (l3_policy),
params=_params)
@APIParamsCall
def create_l3_policy(self, body=None):
"""Creates a new l3_policy."""
return self.post(self.l3_policies_path, body=body)
@APIParamsCall
def update_l3_policy(self, l3_policy, body=None):
"""Updates a l3_policy."""
return self.put(self.l3_policy_path % (l3_policy),
body=body)
@APIParamsCall
def delete_l3_policy(self, l3_policy):
"""Deletes the specified l3_policy."""
return self.delete(self.l3_policy_path % (l3_policy))
@APIParamsCall
def list_policy_classifiers(self, retrieve_all=True, **_params):
"""Fetches a list of all policy_classifiers for a tenant."""
# Pass filters in "params" argument to do_request
return self.list('policy_classifiers', self.policy_classifiers_path,
retrieve_all, **_params)
@APIParamsCall
def show_policy_classifier(self, policy_classifier, **_params):
"""Fetches information of a certain policy_classifier."""
return self.get(self.policy_classifier_path % (policy_classifier),
params=_params)
@APIParamsCall
def create_policy_classifier(self, body=None):
"""Creates a new policy_classifier."""
return self.post(self.policy_classifiers_path, body=body)
@APIParamsCall
def update_policy_classifier(self, policy_classifier, body=None):
"""Updates a policy_classifier."""
return self.put(self.policy_classifier_path % (policy_classifier),
body=body)
@APIParamsCall
def delete_policy_classifier(self, policy_classifier):
"""Deletes the specified policy_classifier."""
return self.delete(self.policy_classifier_path % (policy_classifier))
@APIParamsCall
def list_policy_actions(self, retrieve_all=True, **_params):
"""Fetches a list of all policy_actions for a tenant."""
# Pass filters in "params" argument to do_request
return self.list('policy_actions', self.policy_actions_path,
retrieve_all, **_params)
@APIParamsCall
def show_policy_action(self, policy_action, **_params):
"""Fetches information of a certain policy_action."""
return self.get(self.policy_action_path % (policy_action),
params=_params)
@APIParamsCall
def create_policy_action(self, body=None):
"""Creates a new policy_action."""
return self.post(self.policy_actions_path, body=body)
@APIParamsCall
def update_policy_action(self, policy_action, body=None):
"""Updates a policy_action."""
return self.put(self.policy_action_path % (policy_action), body=body)
@APIParamsCall
def delete_policy_action(self, policy_action):
"""Deletes the specified policy_action."""
return self.delete(self.policy_action_path % (policy_action))
@APIParamsCall
def list_policy_rules(self, retrieve_all=True, **_params):
"""Fetches a list of all policy_rules for a tenant."""
# Pass filters in "params" argument to do_request
return self.list('policy_rules', self.policy_rules_path, retrieve_all,
**_params)
@APIParamsCall
def show_policy_rule(self, policy_rule, **_params):
"""Fetches information of a certain policy_rule."""
return self.get(self.policy_rule_path % (policy_rule), params=_params)
@APIParamsCall
def create_policy_rule(self, body=None):
"""Creates a new policy_rule."""
return self.post(self.policy_rules_path, body=body)
@APIParamsCall
def update_policy_rule(self, policy_rule, body=None):
"""Updates a policy_rule."""
return self.put(self.policy_rule_path % (policy_rule), body=body)
@APIParamsCall
def delete_policy_rule(self, policy_rule):
"""Deletes the specified policy_rule."""
return self.delete(self.policy_rule_path % (policy_rule))
@APIParamsCall
def list_contracts(self, retrieve_all=True, **_params):
"""Fetches a list of all contracts for a tenant."""
# Pass filters in "params" argument to do_request
return self.list('contracts', self.contracts_path, retrieve_all,
**_params)
@APIParamsCall
def show_contract(self, contract, **_params):
"""Fetches information of a certain contract."""
return self.get(self.contract_path % (contract), params=_params)
@APIParamsCall
def create_contract(self, body=None):
"""Creates a new contract."""
return self.post(self.contracts_path, body=body)
@APIParamsCall
def update_contract(self, contract, body=None):
"""Updates a contract."""
return self.put(self.contract_path % (contract), body=body)
@APIParamsCall
def delete_contract(self, contract):
"""Deletes the specified contract."""
return self.delete(self.contract_path % (contract))
def __init__(self, **kwargs):
"""Initialize a new client for the GBP v2.0 API."""
super(Client, self).__init__()
self.retries = kwargs.pop('retries', 0)
self.raise_errors = kwargs.pop('raise_errors', True)
self.httpclient = client.construct_http_client(**kwargs)
self.version = '2.0'
self.format = 'json'
self.action_prefix = "/v%s" % (self.version)
self.retry_interval = 1
def _handle_fault_response(self, status_code, response_body):
# Create exception with HTTP status code and message
_logger.debug("Error message: %s", response_body)
# Add deserialized error message to exception arguments
try:
des_error_body = self.deserialize(response_body, status_code)
except Exception:
# If unable to deserialized body it is probably not a
# Neutron error
des_error_body = {'message': response_body}
# Raise the appropriate exception
exception_handler_v20(status_code, des_error_body)
def _check_uri_length(self, action):
uri_len = len(self.httpclient.endpoint_url) + len(action)
if uri_len > self.MAX_URI_LEN:
raise exceptions.RequestURITooLong(
excess=uri_len - self.MAX_URI_LEN)
def do_request(self, method, action, body=None, headers=None, params=None):
# Add format and tenant_id
action += ".%s" % self.format
action = self.action_prefix + action
if type(params) is dict and params:
params = utils.safe_encode_dict(params)
action += '?' + urllib.urlencode(params, doseq=1)
# Ensure client always has correct uri - do not guesstimate anything
self.httpclient.authenticate_and_fetch_endpoint_url()
self._check_uri_length(action)
if body:
body = self.serialize(body)
self.httpclient.content_type = self.content_type()
resp, replybody = self.httpclient.do_request(action, method, body=body)
status_code = resp.status_code
if status_code in (requests.codes.ok,
requests.codes.created,
requests.codes.accepted,
requests.codes.no_content):
return self.deserialize(replybody, status_code)
else:
if not replybody:
replybody = resp.reason
self._handle_fault_response(status_code, replybody)
def get_auth_info(self):
return self.httpclient.get_auth_info()
def serialize(self, data):
"""Serializes a dictionary into either XML or JSON.
A dictionary with a single key can be passed and
it can contain any structure.
"""
if data is None:
return None
elif type(data) is dict:
return serializer.Serializer(
self.get_attr_metadata()).serialize(data, self.content_type())
else:
raise Exception(_("Unable to serialize object of type = '%s'") %
type(data))
def deserialize(self, data, status_code):
"""Deserializes an XML or JSON string into a dictionary."""
if status_code == 204:
return data
return serializer.Serializer(self.get_attr_metadata()).deserialize(
data, self.content_type())['body']
def content_type(self, _format=None):
"""Returns the mime-type for either 'xml' or 'json'.
Defaults to the currently set format.
"""
_format = _format or self.format
return "application/%s" % (_format)
def retry_request(self, method, action, body=None,
headers=None, params=None):
"""Call do_request with the default retry configuration.
Only idempotent requests should retry failed connection attempts.
:raises: ConnectionFailed if the maximum # of retries is exceeded
"""
max_attempts = self.retries + 1
for i in range(max_attempts):
try:
return self.do_request(method, action, body=body,
headers=headers, params=params)
except exceptions.ConnectionFailed:
# Exception has already been logged by do_request()
if i < self.retries:
_logger.debug('Retrying connection to Neutron service')
time.sleep(self.retry_interval)
elif self.raise_errors:
raise
if self.retries:
msg = (_("Failed to connect to Neutron server after %d attempts")
% max_attempts)
else:
msg = _("Failed to connect Neutron server")
raise exceptions.ConnectionFailed(reason=msg)
def delete(self, action, body=None, headers=None, params=None):
return self.retry_request("DELETE", action, body=body,
headers=headers, params=params)
def get(self, action, body=None, headers=None, params=None):
return self.retry_request("GET", action, body=body,
headers=headers, params=params)
def post(self, action, body=None, headers=None, params=None):
# Do not retry POST requests to avoid the orphan objects problem.
return self.do_request("POST", action, body=body,
headers=headers, params=params)
def put(self, action, body=None, headers=None, params=None):
return self.retry_request("PUT", action, body=body,
headers=headers, params=params)
def list(self, collection, path, retrieve_all=True, **params):
if retrieve_all:
res = []
for r in self._pagination(collection, path, **params):
res.extend(r[collection])
return {collection: res}
else:
return self._pagination(collection, path, **params)
def _pagination(self, collection, path, **params):
if params.get('page_reverse', False):
linkrel = 'previous'
else:
linkrel = 'next'
next = True
while next:
res = self.get(path, params=params)
yield res
next = False
try:
for link in res['%s_links' % collection]:
if link['rel'] == linkrel:
query_str = urlparse.urlparse(link['href']).query
params = urlparse.parse_qs(query_str)
next = True
break
except KeyError:
break

227
run_tests.sh Executable file
View File

@ -0,0 +1,227 @@
#!/bin/bash
set -eu
function usage {
echo "Usage: $0 [OPTION]..."
echo "Run Group Based Policy's test suite(s)"
echo ""
echo " -V, --virtual-env Always use virtualenv. Install automatically if not present"
echo " -N, --no-virtual-env Don't use virtualenv. Run tests in local environment"
echo " -s, --no-site-packages Isolate the virtualenv from the global Python environment"
echo " -r, --recreate-db Recreate the test database (deprecated, as this is now the default)."
echo " -n, --no-recreate-db Don't recreate the test database."
echo " -f, --force Force a clean re-build of the virtual environment. Useful when dependencies have been added."
echo " -u, --update Update the virtual environment with any newer package versions"
echo " -p, --pep8 Just run PEP8 and HACKING compliance check"
echo " -P, --no-pep8 Don't run static code checks"
echo " -c, --coverage Generate coverage report"
echo " -d, --debug Run tests with testtools instead of testr. This allows you to use the debugger."
echo " -h, --help Print this usage message"
echo " --virtual-env-path <path> Location of the virtualenv directory"
echo " Default: \$(pwd)"
echo " --virtual-env-name <name> Name of the virtualenv directory"
echo " Default: .venv"
echo " --tools-path <dir> Location of the tools directory"
echo " Default: \$(pwd)"
echo ""
echo "Note: with no options specified, the script will try to run the tests in a virtual environment,"
echo " If no virtualenv is found, the script will ask if you would like to create one. If you "
echo " prefer to run tests NOT in a virtual environment, simply pass the -N option."
exit
}
function process_options {
i=1
while [ $i -le $# ]; do
case "${!i}" in
-h|--help) usage;;
-V|--virtual-env) always_venv=1; never_venv=0;;
-N|--no-virtual-env) always_venv=0; never_venv=1;;
-s|--no-site-packages) no_site_packages=1;;
-r|--recreate-db) recreate_db=1;;
-n|--no-recreate-db) recreate_db=0;;
-f|--force) force=1;;
-u|--update) update=1;;
-p|--pep8) just_pep8=1;;
-P|--no-pep8) no_pep8=1;;
-c|--coverage) coverage=1;;
-d|--debug) debug=1;;
--virtual-env-path)
(( i++ ))
venv_path=${!i}
;;
--virtual-env-name)
(( i++ ))
venv_dir=${!i}
;;
--tools-path)
(( i++ ))
tools_path=${!i}
;;
-*) testropts="$testropts ${!i}";;
*) testrargs="$testrargs ${!i}"
esac
(( i++ ))
done
}
tool_path=${tools_path:-$(pwd)}
venv_path=${venv_path:-$(pwd)}
venv_dir=${venv_name:-.venv}
with_venv=tools/with_venv.sh
always_venv=0
never_venv=0
force=0
no_site_packages=0
installvenvopts=
testrargs=
testropts=
wrapper=""
just_pep8=0
no_pep8=0
coverage=0
debug=0
recreate_db=1
update=0
LANG=en_US.UTF-8
LANGUAGE=en_US:en
LC_ALL=C
process_options $@
# Make our paths available to other scripts we call
export venv_path
export venv_dir
export venv_name
export tools_dir
export venv=${venv_path}/${venv_dir}
if [ $no_site_packages -eq 1 ]; then
installvenvopts="--no-site-packages"
fi
function run_tests {
# Cleanup *pyc
${wrapper} find . -type f -name "*.pyc" -delete
if [ $debug -eq 1 ]; then
if [ "$testropts" = "" ] && [ "$testrargs" = "" ]; then
# Default to running all tests if specific test is not
# provided.
testrargs="discover ./gbp/"
fi
${wrapper} python -m testtools.run $testropts $testrargs
# Short circuit because all of the testr and coverage stuff
# below does not make sense when running testtools.run for
# debugging purposes.
return $?
fi
if [ $coverage -eq 1 ]; then
TESTRTESTS="$TESTRTESTS --coverage"
else
TESTRTESTS="$TESTRTESTS --slowest"
fi
# Just run the test suites in current environment
set +e
testrargs=`echo "$testrargs" | sed -e's/^\s*\(.*\)\s*$/\1/'`
TESTRTESTS="$TESTRTESTS --testr-args='--subunit $testropts $testrargs'"
OS_TEST_PATH=`echo $testrargs|grep -o 'gbp\.tests[^[:space:]:]*\+'|tr . /`
if [ -d "$OS_TEST_PATH" ]; then
wrapper="OS_TEST_PATH=$OS_TEST_PATH $wrapper"
elif [ -d "$(dirname $OS_TEST_PATH)" ]; then
wrapper="OS_TEST_PATH=$(dirname $OS_TEST_PATH) $wrapper"
fi
echo "Running \`${wrapper} $TESTRTESTS\`"
bash -c "${wrapper} $TESTRTESTS | ${wrapper} subunit2pyunit"
RESULT=$?
set -e
copy_subunit_log
if [ $coverage -eq 1 ]; then
echo "Generating coverage report in covhtml/"
# Don't compute coverage for common code, which is tested elsewhere
${wrapper} coverage combine
${wrapper} coverage html --include='gbp/*' --omit='gbp/openstack/common/*' -d covhtml -i
fi
return $RESULT
}
function copy_subunit_log {
LOGNAME=`cat .testrepository/next-stream`
LOGNAME=$(($LOGNAME - 1))
LOGNAME=".testrepository/${LOGNAME}"
cp $LOGNAME subunit.log
}
function run_pep8 {
echo "Running flake8 ..."
${wrapper} flake8
}
TESTRTESTS="python setup.py testr"
if [ $never_venv -eq 0 ]
then
# Remove the virtual environment if --force used
if [ $force -eq 1 ]; then
echo "Cleaning virtualenv..."
rm -rf ${venv}
fi
if [ $update -eq 1 ]; then
echo "Updating virtualenv..."
python tools/install_venv.py $installvenvopts
fi
if [ -e ${venv} ]; then
wrapper="${with_venv}"
else
if [ $always_venv -eq 1 ]; then
# Automatically install the virtualenv
python tools/install_venv.py $installvenvopts
wrapper="${with_venv}"
else
echo -e "No virtual environment found...create one? (Y/n) \c"
read use_ve
if [ "x$use_ve" = "xY" -o "x$use_ve" = "x" -o "x$use_ve" = "xy" ]; then
# Install the virtualenv and run the test suite in it
python tools/install_venv.py $installvenvopts
wrapper=${with_venv}
fi
fi
fi
fi
# Delete old coverage data from previous runs
if [ $coverage -eq 1 ]; then
${wrapper} coverage erase
fi
if [ $just_pep8 -eq 1 ]; then
run_pep8
exit
fi
if [ $recreate_db -eq 1 ]; then
rm -f tests.sqlite
fi
run_tests
# NOTE(sirp): we only want to run pep8 when we're running the full-test suite,
# not when we're running tests individually. To handle this, we need to
# distinguish between options (testropts), which begin with a '-', and
# arguments (testrargs).
if [ -z "$testrargs" ]; then
if [ $no_pep8 -eq 0 ]; then
run_pep8
fi
fi

View File

@ -28,7 +28,7 @@ setup-hooks =
[entry_points]
console_scripts =
gbp = gbpclient.shell:main
gbp = gbpclient.gbpshell:main
[build_sphinx]
all_files = 1

View File

@ -15,4 +15,5 @@ oslotest>=1.1.0.0a2
python-subunit>=0.0.18
sphinx>=1.1.2,!=1.2.0,<1.3
testrepository>=0.0.18
testscenarios>=0.4
testtools>=0.9.34

74
tools/install_venv.py Normal file
View File

@ -0,0 +1,74 @@
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Copyright 2010 OpenStack Foundation
# Copyright 2013 IBM Corp.
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
#
# 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 ConfigParser
import os
import sys
import install_venv_common as install_venv # flake8: noqa
def print_help(project, venv, root):
help = """
%(project)s development environment setup is complete.
%(project)s development uses virtualenv to track and manage Python
dependencies while in development and testing.
To activate the %(project)s virtualenv for the extent of your current
shell session you can run:
$ source %(venv)s/bin/activate
Or, if you prefer, you can run commands in the virtualenv on a case by
case basis by running:
$ %(root)s/tools/with_venv.sh <your command>
"""
print help % dict(project=project, venv=venv, root=root)
def main(argv):
root = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
if os.environ.get('tools_path'):
root = os.environ['tools_path']
venv = os.path.join(root, '.venv')
if os.environ.get('venv'):
venv = os.environ['venv']
pip_requires = os.path.join(root, 'requirements.txt')
test_requires = os.path.join(root, 'test-requirements.txt')
py_version = "python%s.%s" % (sys.version_info[0], sys.version_info[1])
setup_cfg = ConfigParser.ConfigParser()
setup_cfg.read('setup.cfg')
project = setup_cfg.get('metadata', 'name')
install = install_venv.InstallVenv(
root, venv, pip_requires, test_requires, py_version, project)
options = install.parse_args(argv)
install.check_python_version()
install.check_dependencies()
install.create_virtualenv(no_site_packages=options.no_site_packages)
install.install_dependencies()
print_help(project, venv, root)
if __name__ == '__main__':
main(sys.argv)

View File

@ -0,0 +1,172 @@
# Copyright 2013 OpenStack Foundation
# Copyright 2013 IBM Corp.
#
# 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.
"""Provides methods needed by installation script for OpenStack development
virtual environments.
Since this script is used to bootstrap a virtualenv from the system's Python
environment, it should be kept strictly compatible with Python 2.6.
Synced in from openstack-common
"""
from __future__ import print_function
import optparse
import os
import subprocess
import sys
class InstallVenv(object):
def __init__(self, root, venv, requirements,
test_requirements, py_version,
project):
self.root = root
self.venv = venv
self.requirements = requirements
self.test_requirements = test_requirements
self.py_version = py_version
self.project = project
def die(self, message, *args):
print(message % args, file=sys.stderr)
sys.exit(1)
def check_python_version(self):
if sys.version_info < (2, 6):
self.die("Need Python Version >= 2.6")
def run_command_with_code(self, cmd, redirect_output=True,
check_exit_code=True):
"""Runs a command in an out-of-process shell.
Returns the output of that command. Working directory is self.root.
"""
if redirect_output:
stdout = subprocess.PIPE
else:
stdout = None
proc = subprocess.Popen(cmd, cwd=self.root, stdout=stdout)
output = proc.communicate()[0]
if check_exit_code and proc.returncode != 0:
self.die('Command "%s" failed.\n%s', ' '.join(cmd), output)
return (output, proc.returncode)
def run_command(self, cmd, redirect_output=True, check_exit_code=True):
return self.run_command_with_code(cmd, redirect_output,
check_exit_code)[0]
def get_distro(self):
if (os.path.exists('/etc/fedora-release') or
os.path.exists('/etc/redhat-release')):
return Fedora(
self.root, self.venv, self.requirements,
self.test_requirements, self.py_version, self.project)
else:
return Distro(
self.root, self.venv, self.requirements,
self.test_requirements, self.py_version, self.project)
def check_dependencies(self):
self.get_distro().install_virtualenv()
def create_virtualenv(self, no_site_packages=True):
"""Creates the virtual environment and installs PIP.
Creates the virtual environment and installs PIP only into the
virtual environment.
"""
if not os.path.isdir(self.venv):
print('Creating venv...', end=' ')
if no_site_packages:
self.run_command(['virtualenv', '-q', '--no-site-packages',
self.venv])
else:
self.run_command(['virtualenv', '-q', self.venv])
print('done.')
else:
print("venv already exists...")
pass
def pip_install(self, *args):
self.run_command(['tools/with_venv.sh',
'pip', 'install', '--upgrade'] + list(args),
redirect_output=False)
def install_dependencies(self):
print('Installing dependencies with pip (this can take a while)...')
# First things first, make sure our venv has the latest pip and
# setuptools and pbr
self.pip_install('pip>=1.4')
self.pip_install('setuptools')
self.pip_install('pbr')
self.pip_install('-r', self.requirements, '-r', self.test_requirements)
def parse_args(self, argv):
"""Parses command-line arguments."""
parser = optparse.OptionParser()
parser.add_option('-n', '--no-site-packages',
action='store_true',
help="Do not inherit packages from global Python "
"install.")
return parser.parse_args(argv[1:])[0]
class Distro(InstallVenv):
def check_cmd(self, cmd):
return bool(self.run_command(['which', cmd],
check_exit_code=False).strip())
def install_virtualenv(self):
if self.check_cmd('virtualenv'):
return
if self.check_cmd('easy_install'):
print('Installing virtualenv via easy_install...', end=' ')
if self.run_command(['easy_install', 'virtualenv']):
print('Succeeded')
return
else:
print('Failed')
self.die('ERROR: virtualenv not found.\n\n%s development'
' requires virtualenv, please install it using your'
' favorite package management tool' % self.project)
class Fedora(Distro):
"""This covers all Fedora-based distributions.
Includes: Fedora, RHEL, CentOS, Scientific Linux
"""
def check_pkg(self, pkg):
return self.run_command_with_code(['rpm', '-q', pkg],
check_exit_code=False)[1] == 0
def install_virtualenv(self):
if self.check_cmd('virtualenv'):
return
if not self.check_pkg('python-virtualenv'):
self.die("Please install 'python-virtualenv'.")
super(Fedora, self).install_virtualenv()

View File

@ -1,27 +0,0 @@
_policy_opts="" # lazy init
_policy_flags="" # lazy init
_policy_opts_exp="" # lazy init
_policy()
{
local cur prev nbc cflags
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
if [ "x$_policy_opts" == "x" ] ; then
nbc="`policy bash-completion`"
_policy_opts="`echo "$nbc" | sed -e "s/--[a-z0-9_-]*//g" -e "s/\s\s*/ /g"`"
_policy_flags="`echo " $nbc" | sed -e "s/ [^-][^-][a-z0-9_-]*//g" -e "s/\s\s*/ /g"`"
_policy_opts_exp="`echo "$_policy_opts" | sed -e "s/\s/|/g"`"
fi
if [[ " ${COMP_WORDS[@]} " =~ " "($_policy_opts_exp)" " && "$prev" != "help" ]] ; then
COMPLETION_CACHE=~/.policyclient/*/*-cache
cflags="$_policy_flags "$(cat $COMPLETION_CACHE 2> /dev/null | tr '\n' ' ')
COMPREPLY=($(compgen -W "${cflags}" -- ${cur}))
else
COMPREPLY=($(compgen -W "${_policy_opts}" -- ${cur}))
fi
return 0
}
complete -F _policy policy

4
tools/with_venv.sh Executable file
View File

@ -0,0 +1,4 @@
#!/bin/bash
TOOLS=`dirname $0`
VENV=$TOOLS/../.venv
source $VENV/bin/activate && $@