import python-neutronclient of 1bce6e437e2dd1fa5de6fc2ccdd0ee8ac3f44d18

Imports python-neutornclient whose changeset is as follows

> commit 1bce6e437e2dd1fa5de6fc2ccdd0ee8ac3f44d18
> Merge: 9c77675 cbdd56d
> Author: Jenkins <jenkins@review.openstack.org>
> Date:   Sat Jun 14 04:46:39 2014 +0000
>
>     Merge "Add OverQuotaClient as exception to neutronclient"

Change-Id: I596915c60d349eb5afbc3de245f580f2dd37e270
changes/52/104852/1
Isaku Yamahata 2014-07-04 18:56:07 +09:00
parent 1639b8be19
commit 872ab5e336
124 changed files with 18303 additions and 1 deletions

7
.coveragerc Normal file
View File

@ -0,0 +1,7 @@
[run]
branch = True
source = neutronclient
omit = neutronclient/openstack/*,neutronclient/tests/*
[report]
ignore-errors = True

21
.gitignore vendored Normal file
View File

@ -0,0 +1,21 @@
*.pyc
*.DS_Store
*.egg
*.sw?
AUTHORS
ChangeLog
build/*
build-stamp
cover/*
doc/build/
doc/source/api/
python_neutronclient.egg-info/*
neutron/vcsversion.py
neutronclient/versioninfo
run_tests.err.log
run_tests.log
.autogenerated
.coverage
.testrepository/
.tox/
.venv/

View File

@ -1,4 +1,4 @@
[gerrit]
host=review.openstack.org
port=29418
project=stackforge/python-tackerclient.git
project=openstack/python-neutronclient.git

42
.pylintrc Normal file
View File

@ -0,0 +1,42 @@
# The format of this file isn't really documented; just use --generate-rcfile
[MASTER]
# Add <file or directory> to the black list. It should be a base name, not a
# path. You may set this option multiple times.
ignore=test
[Messages Control]
# NOTE(justinsb): We might want to have a 2nd strict pylintrc in future
# C0111: Don't require docstrings on every method
# W0511: TODOs in code comments are fine.
# W0142: *args and **kwargs are fine.
# W0622: Redefining id is fine.
disable=C0111,W0511,W0142,W0622
[Basic]
# Variable names can be 1 to 31 characters long, with lowercase and underscores
variable-rgx=[a-z_][a-z0-9_]{0,30}$
# Argument names can be 2 to 31 characters long, with lowercase and underscores
argument-rgx=[a-z_][a-z0-9_]{1,30}$
# Method names should be at least 3 characters long
# and be lowecased with underscores
method-rgx=([a-z_][a-z0-9_]{2,50}|setUp|tearDown)$
# Module names matching quantum-* are ok (files in bin/)
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+)|(quantum-[a-z0-9_-]+))$
# Don't require docstrings on tests.
no-docstring-rgx=((__.*__)|([tT]est.*)|setUp|tearDown)$
[Design]
max-public-methods=100
min-public-methods=0
max-args=6
[Variables]
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid to define new builtins when possible.
# _ is used by our localization
additional-builtins=_

4
.testr.conf Normal file
View File

@ -0,0 +1,4 @@
[DEFAULT]
test_command=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 ${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION
test_id_option=--load-list $IDFILE
test_list_option=--list

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.

6
MANIFEST.in Normal file
View File

@ -0,0 +1,6 @@
include tox.ini
include LICENSE README.rst HACKING.rst
include AUTHORS
include ChangeLog
include tools/*
recursive-include tests *

1
README.rst Normal file
View File

@ -0,0 +1 @@
This is the client API library for Neutron.

55
doc/source/conf.py Normal file
View File

@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
#
project = 'python-neutronclient'
# -- General configuration ---------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The master toctree document.
master_doc = 'index'
# General information about the project.
copyright = u'OpenStack Foundation'
# If true, '()' will be appended to :func: etc. cross-reference text.
add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
add_module_names = True
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# -- Options for HTML output ---------------------------------------------
# The theme to use for HTML and HTML Help pages. Major themes that come with
# Sphinx are currently 'default' and 'sphinxdoc'.
html_theme = 'nature'
# Output file base name for HTML help builder.
htmlhelp_basename = '%sdoc' % project
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author,
# documentclass [howto/manual]).
latex_documents = [
('index',
'%s.tex' % project,
u'%s Documentation' % project,
u'OpenStack Foundation', 'manual'),
]
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {'http://docs.python.org/': None}

63
doc/source/index.rst Normal file
View File

@ -0,0 +1,63 @@
Python bindings to the OpenStack Network API
============================================
In order to use the python neutron client directly, you must first obtain an auth token and identify which endpoint you wish to speak to. Once you have done so, you can use the API like so::
>>> import logging
>>> from neutronclient.neutron import client
>>> logging.basicConfig(level=logging.DEBUG)
>>> neutron = client.Client('2.0', endpoint_url=OS_URL, token=OS_TOKEN)
>>> neutron.format = 'json'
>>> network = {'name': 'mynetwork', 'admin_state_up': True}
>>> neutron.create_network({'network':network})
>>> networks = neutron.list_networks(name='mynetwork')
>>> print networks
>>> network_id = networks['networks'][0]['id']
>>> neutron.delete_network(network_id)
Command-line Tool
=================
In order to use the CLI, you must provide your OpenStack username, password, tenant, and auth endpoint. Use the corresponding configuration options (``--os-username``, ``--os-password``, ``--os-tenant-name``, and ``--os-auth-url``) or set them in environment variables::
export OS_USERNAME=user
export OS_PASSWORD=pass
export OS_TENANT_NAME=tenant
export OS_AUTH_URL=http://auth.example.com:5000/v2.0
The command line tool will attempt to reauthenticate using your provided credentials for every request. You can override this behavior by manually supplying an auth token using ``--os-url`` and ``--os-auth-token``. You can alternatively set these environment variables::
export OS_URL=http://neutron.example.org:9696/
export OS_TOKEN=3bcc3d3a03f44e3d8377f9247b0ad155
If neutron server does not require authentication, besides these two arguments or environment variables (We can use any value as token.), we need manually supply ``--os-auth-strategy`` or set the environment variable::
export OS_AUTH_STRATEGY=noauth
Once you've configured your authentication parameters, you can run ``neutron -h`` to see a complete listing of available commands.
Release Notes
=============
2.0
-----
* support Neutron API 2.0
2.2.0
-----
* add security group commands
* add Lbaas commands
* allow options put after positional arguments
* add NVP queue and net gateway commands
* add commands for agent management extensions
* add commands for DHCP and L3 agents scheduling
* support XML request format
* support pagination options
2.2.2
-----
* improved support for listing a large number of filtered subnets
* add --endpoint-type and OS_ENDPOINT_TYPE to shell client
* made the publicURL the default endpoint instead of adminURL
* add ability to update security group name (requires 2013.2-Havana or later)
* add flake8 and pbr support for testing and building

128
neutron_test.sh Executable file
View File

@ -0,0 +1,128 @@
#!/bin/bash
set -x
function die() {
local exitcode=$?
set +o xtrace
echo $@
exit $exitcode
}
noauth_tenant_id=me
if [ $1 == 'noauth' ]; then
NOAUTH="--tenant_id $noauth_tenant_id"
else
NOAUTH=
fi
FORMAT=" --request-format xml"
# test the CRUD of network
network=mynet1
neutron net-create $FORMAT $NOAUTH $network || die "fail to create network $network"
temp=`neutron net-list $FORMAT -- --name $network --fields id | wc -l`
echo $temp
if [ $temp -ne 5 ]; then
die "networks with name $network is not unique or found"
fi
network_id=`neutron net-list -- --name $network --fields id | tail -n 2 | head -n 1 | cut -d' ' -f 2`
echo "ID of network with name $network is $network_id"
neutron net-show $FORMAT $network || die "fail to show network $network"
neutron net-show $FORMAT $network_id || die "fail to show network $network_id"
neutron net-update $FORMAT $network --admin_state_up False || die "fail to update network $network"
neutron net-update $FORMAT $network_id --admin_state_up True || die "fail to update network $network_id"
neutron net-list $FORMAT -c id -- --id fakeid || die "fail to list networks with column selection on empty list"
# test the CRUD of subnet
subnet=mysubnet1
cidr=10.0.1.3/24
neutron subnet-create $FORMAT $NOAUTH $network $cidr --name $subnet || die "fail to create subnet $subnet"
tempsubnet=`neutron subnet-list $FORMAT -- --name $subnet --fields id | wc -l`
echo $tempsubnet
if [ $tempsubnet -ne 5 ]; then
die "subnets with name $subnet is not unique or found"
fi
subnet_id=`neutron subnet-list $FORMAT -- --name $subnet --fields id | tail -n 2 | head -n 1 | cut -d' ' -f 2`
echo "ID of subnet with name $subnet is $subnet_id"
neutron subnet-show $FORMAT $subnet || die "fail to show subnet $subnet"
neutron subnet-show $FORMAT $subnet_id || die "fail to show subnet $subnet_id"
neutron subnet-update $FORMAT $subnet --dns_namesevers host1 || die "fail to update subnet $subnet"
neutron subnet-update $FORMAT $subnet_id --dns_namesevers host2 || die "fail to update subnet $subnet_id"
# test the crud of ports
port=myport1
neutron port-create $FORMAT $NOAUTH $network --name $port || die "fail to create port $port"
tempport=`neutron port-list $FORMAT -- --name $port --fields id | wc -l`
echo $tempport
if [ $tempport -ne 5 ]; then
die "ports with name $port is not unique or found"
fi
port_id=`neutron port-list $FORMAT -- --name $port --fields id | tail -n 2 | head -n 1 | cut -d' ' -f 2`
echo "ID of port with name $port is $port_id"
neutron port-show $FORMAT $port || die "fail to show port $port"
neutron port-show $FORMAT $port_id || die "fail to show port $port_id"
neutron port-update $FORMAT $port --device_id deviceid1 || die "fail to update port $port"
neutron port-update $FORMAT $port_id --device_id deviceid2 || die "fail to update port $port_id"
# test quota commands RUD
DEFAULT_NETWORKS=10
DEFAULT_PORTS=50
tenant_id=tenant_a
tenant_id_b=tenant_b
neutron quota-update $FORMAT --tenant_id $tenant_id --network 30 || die "fail to update quota for tenant $tenant_id"
neutron quota-update $FORMAT --tenant_id $tenant_id_b --network 20 || die "fail to update quota for tenant $tenant_id"
networks=`neutron quota-list $FORMAT -c network -c tenant_id | grep $tenant_id | awk '{print $2}'`
if [ $networks -ne 30 ]; then
die "networks quota should be 30"
fi
networks=`neutron quota-list $FORMAT -c network -c tenant_id | grep $tenant_id_b | awk '{print $2}'`
if [ $networks -ne 20 ]; then
die "networks quota should be 20"
fi
networks=`neutron quota-show $FORMAT --tenant_id $tenant_id | grep network | awk -F'|' '{print $3}'`
if [ $networks -ne 30 ]; then
die "networks quota should be 30"
fi
neutron quota-delete $FORMAT --tenant_id $tenant_id || die "fail to delete quota for tenant $tenant_id"
networks=`neutron quota-show $FORMAT --tenant_id $tenant_id | grep network | awk -F'|' '{print $3}'`
if [ $networks -ne $DEFAULT_NETWORKS ]; then
die "networks quota should be $DEFAULT_NETWORKS"
fi
# update self
if [ "t$NOAUTH" = "t" ]; then
# with auth
neutron quota-update $FORMAT --port 99 || die "fail to update quota for self"
ports=`neutron quota-show $FORMAT | grep port | awk -F'|' '{print $3}'`
if [ $ports -ne 99 ]; then
die "ports quota should be 99"
fi
ports=`neutron quota-list $FORMAT -c port | grep 99 | awk '{print $2}'`
if [ $ports -ne 99 ]; then
die "ports quota should be 99"
fi
neutron quota-delete $FORMAT || die "fail to delete quota for tenant self"
ports=`neutron quota-show $FORMAT | grep port | awk -F'|' '{print $3}'`
if [ $ports -ne $DEFAULT_PORTS ]; then
die "ports quota should be $DEFAULT_PORTS"
fi
else
# without auth
neutron quota-update $FORMAT --port 100
if [ $? -eq 0 ]; then
die "without valid context on server, quota update command should fail."
fi
neutron quota-show $FORMAT
if [ $? -eq 0 ]; then
die "without valid context on server, quota show command should fail."
fi
neutron quota-delete $FORMAT
if [ $? -eq 0 ]; then
die "without valid context on server, quota delete command should fail."
fi
neutron quota-list $FORMAT || die "fail to update quota for self"
fi

View File

276
neutronclient/client.py Normal file
View File

@ -0,0 +1,276 @@
# 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
#
# 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.
#
try:
import json
except ImportError:
import simplejson as json
import logging
import os
from keystoneclient import access
import requests
from neutronclient.common import exceptions
from neutronclient.common import utils
from neutronclient.openstack.common.gettextutils import _
_logger = logging.getLogger(__name__)
if os.environ.get('NEUTRONCLIENT_DEBUG'):
ch = logging.StreamHandler()
_logger.setLevel(logging.DEBUG)
_logger.addHandler(ch)
_requests_log_level = logging.DEBUG
else:
_requests_log_level = logging.WARNING
logging.getLogger("requests").setLevel(_requests_log_level)
class HTTPClient(object):
"""Handles the REST calls and responses, include authn."""
USER_AGENT = 'python-neutronclient'
def __init__(self, username=None, user_id=None,
tenant_name=None, tenant_id=None,
password=None, auth_url=None,
token=None, region_name=None, timeout=None,
endpoint_url=None, insecure=False,
endpoint_type='publicURL',
auth_strategy='keystone', ca_cert=None, log_credentials=False,
service_type='network',
**kwargs):
self.username = username
self.user_id = user_id
self.tenant_name = tenant_name
self.tenant_id = tenant_id
self.password = password
self.auth_url = auth_url.rstrip('/') if auth_url else None
self.service_type = service_type
self.endpoint_type = endpoint_type
self.region_name = region_name
self.timeout = timeout
self.auth_token = token
self.auth_tenant_id = None
self.auth_user_id = None
self.content_type = 'application/json'
self.endpoint_url = endpoint_url
self.auth_strategy = auth_strategy
self.log_credentials = log_credentials
if insecure:
self.verify_cert = False
else:
self.verify_cert = ca_cert if ca_cert else True
def _cs_request(self, *args, **kwargs):
kargs = {}
kargs.setdefault('headers', kwargs.get('headers', {}))
kargs['headers']['User-Agent'] = self.USER_AGENT
if 'content_type' in kwargs:
kargs['headers']['Content-Type'] = kwargs['content_type']
kargs['headers']['Accept'] = kwargs['content_type']
else:
kargs['headers']['Content-Type'] = self.content_type
kargs['headers']['Accept'] = self.content_type
if 'body' in kwargs:
kargs['body'] = kwargs['body']
args = utils.safe_encode_list(args)
kargs = utils.safe_encode_dict(kargs)
if self.log_credentials:
log_kargs = kargs
else:
log_kargs = self._strip_credentials(kargs)
utils.http_log_req(_logger, args, log_kargs)
try:
resp, body = self.request(*args, **kargs)
except requests.exceptions.SSLError as e:
raise exceptions.SslCertificateValidationError(reason=e)
except Exception as e:
# Wrap the low-level connection error (socket timeout, redirect
# limit, decompression error, etc) into our custom high-level
# connection exception (it is excepted in the upper layers of code)
_logger.debug("throwing ConnectionFailed : %s", e)
raise exceptions.ConnectionFailed(reason=e)
utils.http_log_resp(_logger, resp, body)
status_code = self.get_status_code(resp)
if status_code == 401:
raise exceptions.Unauthorized(message=body)
return resp, body
def _strip_credentials(self, kwargs):
if kwargs.get('body') and self.password:
log_kwargs = kwargs.copy()
log_kwargs['body'] = kwargs['body'].replace(self.password,
'REDACTED')
return log_kwargs
else:
return kwargs
def authenticate_and_fetch_endpoint_url(self):
if not self.auth_token:
self.authenticate()
elif not self.endpoint_url:
self.endpoint_url = self._get_endpoint_url()
def request(self, url, method, **kwargs):
kwargs.setdefault('headers', kwargs.get('headers', {}))
kwargs['headers']['User-Agent'] = self.USER_AGENT
kwargs['headers']['Accept'] = 'application/json'
if 'body' in kwargs:
kwargs['headers']['Content-Type'] = 'application/json'
kwargs['data'] = kwargs['body']
del kwargs['body']
resp = requests.request(
method,
url,
verify=self.verify_cert,
**kwargs)
return resp, resp.text
def do_request(self, url, method, **kwargs):
self.authenticate_and_fetch_endpoint_url()
# Perform the request once. If we get a 401 back then it
# might be because the auth token expired, so try to
# re-authenticate and try again. If it still fails, bail.
try:
kwargs.setdefault('headers', {})
if self.auth_token is None:
self.auth_token = ""
kwargs['headers']['X-Auth-Token'] = self.auth_token
resp, body = self._cs_request(self.endpoint_url + url, method,
**kwargs)
return resp, body
except exceptions.Unauthorized:
self.authenticate()
kwargs.setdefault('headers', {})
kwargs['headers']['X-Auth-Token'] = self.auth_token
resp, body = self._cs_request(
self.endpoint_url + url, method, **kwargs)
return resp, body
def _extract_service_catalog(self, body):
"""Set the client's service catalog from the response data."""
self.auth_ref = access.AccessInfo.factory(body=body)
self.service_catalog = self.auth_ref.service_catalog
self.auth_token = self.auth_ref.auth_token
self.auth_tenant_id = self.auth_ref.tenant_id
self.auth_user_id = self.auth_ref.user_id
if not self.endpoint_url:
self.endpoint_url = self.service_catalog.url_for(
attr='region', filter_value=self.region_name,
service_type=self.service_type,
endpoint_type=self.endpoint_type)
def _authenticate_keystone(self):
if self.user_id:
creds = {'userId': self.user_id,
'password': self.password}
else:
creds = {'username': self.username,
'password': self.password}
if self.tenant_id:
body = {'auth': {'passwordCredentials': creds,
'tenantId': self.tenant_id, }, }
else:
body = {'auth': {'passwordCredentials': creds,
'tenantName': self.tenant_name, }, }
if self.auth_url is None:
raise exceptions.NoAuthURLProvided()
token_url = self.auth_url + "/tokens"
resp, resp_body = self._cs_request(token_url, "POST",
body=json.dumps(body),
content_type="application/json",
allow_redirects=True)
status_code = self.get_status_code(resp)
if status_code != 200:
raise exceptions.Unauthorized(message=resp_body)
if resp_body:
try:
resp_body = json.loads(resp_body)
except ValueError:
pass
else:
resp_body = None
self._extract_service_catalog(resp_body)
def _authenticate_noauth(self):
if not self.endpoint_url:
message = _('For "noauth" authentication strategy, the endpoint '
'must be specified either in the constructor or '
'using --os-url')
raise exceptions.Unauthorized(message=message)
def authenticate(self):
if self.auth_strategy == 'keystone':
self._authenticate_keystone()
elif self.auth_strategy == 'noauth':
self._authenticate_noauth()
else:
err_msg = _('Unknown auth strategy: %s') % self.auth_strategy
raise exceptions.Unauthorized(message=err_msg)
def _get_endpoint_url(self):
if self.auth_url is None:
raise exceptions.NoAuthURLProvided()
url = self.auth_url + '/tokens/%s/endpoints' % self.auth_token
try:
resp, body = self._cs_request(url, "GET")
except exceptions.Unauthorized:
# rollback to authenticate() to handle case when neutron client
# is initialized just before the token is expired
self.authenticate()
return self.endpoint_url
body = json.loads(body)
for endpoint in body.get('endpoints', []):
if (endpoint['type'] == 'network' and
endpoint.get('region') == self.region_name):
if self.endpoint_type not in endpoint:
raise exceptions.EndpointTypeNotFound(
type_=self.endpoint_type)
return endpoint[self.endpoint_type]
raise exceptions.EndpointNotFound()
def get_auth_info(self):
return {'auth_token': self.auth_token,
'auth_tenant_id': self.auth_tenant_id,
'auth_user_id': self.auth_user_id,
'endpoint_url': self.endpoint_url}
def get_status_code(self, response):
"""Returns the integer status code from the response.
Either a Webob.Response (used in testing) or requests.Response
is returned.
"""
if hasattr(response, 'status_int'):
return response.status_int
else:
return response.status_code

View File

@ -0,0 +1,26 @@
# Copyright 2011 VMware, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import gettext
t = gettext.translation('neutronclient', fallback=True)
try:
ugettext = t.ugettext # Python 2
except AttributeError:
ugettext = t.gettext # Python 3
def _(msg):
return ugettext(msg)

View File

@ -0,0 +1,105 @@
# 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
#
# 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.
#
"""Manage access to the clients, including authenticating when needed.
"""
import logging
from neutronclient import client
from neutronclient.neutron import client as neutron_client
LOG = logging.getLogger(__name__)
class ClientCache(object):
"""Descriptor class for caching created client handles.
"""
def __init__(self, factory):
self.factory = factory
self._handle = None
def __get__(self, instance, owner):
# Tell the ClientManager to login to keystone
if self._handle is None:
self._handle = self.factory(instance)
return self._handle
class ClientManager(object):
"""Manages access to API clients, including authentication.
"""
neutron = ClientCache(neutron_client.make_client)
# Provide support for old quantum commands (for example
# in stable versions)
quantum = neutron
def __init__(self, token=None, url=None,
auth_url=None,
endpoint_type=None,
tenant_name=None,
tenant_id=None,
username=None,
user_id=None,
password=None,
region_name=None,
api_version=None,
auth_strategy=None,
insecure=False,
ca_cert=None,
log_credentials=False,
service_type=None,
):
self._token = token
self._url = url
self._auth_url = auth_url
self._service_type = service_type
self._endpoint_type = endpoint_type
self._tenant_name = tenant_name
self._tenant_id = tenant_id
self._username = username
self._user_id = user_id
self._password = password
self._region_name = region_name
self._api_version = api_version
self._service_catalog = None
self._auth_strategy = auth_strategy
self._insecure = insecure
self._ca_cert = ca_cert
self._log_credentials = log_credentials
return
def initialize(self):
if not self._url:
httpclient = client.HTTPClient(
username=self._username,
user_id=self._user_id,
tenant_name=self._tenant_name,
tenant_id=self._tenant_id,
password=self._password,
region_name=self._region_name,
auth_url=self._auth_url,
service_type=self._service_type,
endpoint_type=self._endpoint_type,
insecure=self._insecure,
ca_cert=self._ca_cert,
log_credentials=self._log_credentials)
httpclient.authenticate()
# Populate other password flow attributes
self._token = httpclient.auth_token
self._url = httpclient.endpoint_url

View File

@ -0,0 +1,40 @@
# 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
#
# 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.
#
"""
OpenStack base command
"""
from cliff import command
class OpenStackCommand(command.Command):
"""Base class for OpenStack commands
"""
api = None
def run(self, parsed_args):
if not self.api:
return
else:
return super(OpenStackCommand, self).run(parsed_args)
def get_data(self, parsed_args):
pass
def take_action(self, parsed_args):
return self.get_data(parsed_args)

View File

@ -0,0 +1,43 @@
# Copyright (c) 2012 OpenStack Foundation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
EXT_NS = '_extension_ns'
XML_NS_V20 = 'http://openstack.org/quantum/api/v2.0'
XSI_NAMESPACE = "http://www.w3.org/2001/XMLSchema-instance"
XSI_ATTR = "xsi:nil"
XSI_NIL_ATTR = "xmlns:xsi"
TYPE_XMLNS = "xmlns:quantum"
TYPE_ATTR = "quantum:type"
VIRTUAL_ROOT_KEY = "_v_root"
ATOM_NAMESPACE = "http://www.w3.org/2005/Atom"
ATOM_XMLNS = "xmlns:atom"
ATOM_LINK_NOTATION = "{%s}link" % ATOM_NAMESPACE
TYPE_BOOL = "bool"
TYPE_INT = "int"
TYPE_LONG = "long"
TYPE_FLOAT = "float"
TYPE_LIST = "list"
TYPE_DICT = "dict"
PLURALS = {'networks': 'network',
'ports': 'port',
'subnets': 'subnet',
'dns_nameservers': 'dns_nameserver',
'host_routes': 'host_route',
'allocation_pools': 'allocation_pool',
'fixed_ips': 'fixed_ip',
'extensions': 'extension'}

View File

@ -0,0 +1,223 @@
# Copyright 2011 VMware, Inc
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from neutronclient.common import _
"""
Neutron base exception handling.
Exceptions are classified into three categories:
* Exceptions corresponding to exceptions from neutron server:
This type of exceptions should inherit one of exceptions
in HTTP_EXCEPTION_MAP.
* Exceptions from client library:
This type of exceptions should inherit NeutronClientException.
* Exceptions from CLI code:
This type of exceptions should inherit NeutronCLIError.
"""
class NeutronException(Exception):
"""Base Neutron Exception
To correctly use this class, inherit from it and define
a 'message' property. That message will get printf'd
with the keyword arguments provided to the constructor.
"""
message = _("An unknown exception occurred.")
def __init__(self, message=None, **kwargs):
if message:
self.message = message
try:
self._error_string = self.message % kwargs
except Exception:
# at least get the core message out if something happened
self._error_string = self.message
def __str__(self):
return self._error_string
class NeutronClientException(NeutronException):
"""Base exception which exceptions from Neutron are mapped into.
NOTE: on the client side, we use different exception types in order
to allow client library users to handle server exceptions in try...except
blocks. The actual error message is the one generated on the server side.
"""
def __init__(self, message=None, **kwargs):
if 'status_code' in kwargs:
self.status_code = kwargs['status_code']
super(NeutronClientException, self).__init__(message, **kwargs)
# Base exceptions from Neutron
class BadRequest(NeutronClientException):
status_code = 400
class Unauthorized(NeutronClientException):
status_code = 401
message = _("Unauthorized: bad credentials.")
class Forbidden(NeutronClientException):
status_code = 403
message = _("Forbidden: your credentials don't give you access to this "
"resource.")
class NotFound(NeutronClientException):
status_code = 404
class Conflict(NeutronClientException):
status_code = 409
class InternalServerError(NeutronClientException):
status_code = 500
class ServiceUnavailable(NeutronClientException):
status_code = 503
HTTP_EXCEPTION_MAP = {
400: BadRequest,
401: Unauthorized,
403: Forbidden,
404: NotFound,
409: Conflict,
500: InternalServerError,
503: ServiceUnavailable,
}
# Exceptions mapped to Neutron server exceptions
# These are defined if a user of client library needs specific exception.
# Exception name should be <Neutron Exception Name> + 'Client'
# e.g., NetworkNotFound -> NetworkNotFoundClient
class NetworkNotFoundClient(NotFound):
pass
class PortNotFoundClient(NotFound):
pass
class StateInvalidClient(BadRequest):
pass
class NetworkInUseClient(Conflict):
pass
class PortInUseClient(Conflict):
pass
class IpAddressInUseClient(Conflict):
pass
class OverQuotaClient(Conflict):
pass
# TODO(amotoki): It is unused in Neutron, but it is referred to
# in Horizon code. After Horizon code is updated, remove it.
class AlreadyAttachedClient(Conflict):
pass
class IpAddressGenerationFailureClient(Conflict):
pass
class ExternalIpAddressExhaustedClient(BadRequest):
pass
# Exceptions from client library
class NoAuthURLProvided(Unauthorized):
message = _("auth_url was not provided to the Neutron client")
class EndpointNotFound(NeutronClientException):
message = _("Could not find Service or Region in Service Catalog.")
class EndpointTypeNotFound(NeutronClientException):
message = _("Could not find endpoint type %(type_)s in Service Catalog.")
class AmbiguousEndpoints(NeutronClientException):
message = _("Found more than one matching endpoint in Service Catalog: "
"%(matching_endpoints)")
class RequestURITooLong(NeutronClientException):
"""Raised when a request fails with HTTP error 414."""
def __init__(self, **kwargs):
self.excess = kwargs.get('excess', 0)
super(RequestURITooLong, self).__init__(**kwargs)
class ConnectionFailed(NeutronClientException):
message = _("Connection to neutron failed: %(reason)s")
class SslCertificateValidationError(NeutronClientException):
message = _("SSL certificate validation has failed: %(reason)s")
class MalformedResponseBody(NeutronClientException):
message = _("Malformed response body: %(reason)s")
class InvalidContentType(NeutronClientException):
message = _("Invalid content type %(content_type)s.")
# Command line exceptions
class NeutronCLIError(NeutronException):
"""Exception raised when command line parsing fails."""
pass
class CommandError(NeutronCLIError):
pass
class UnsupportedVersion(NeutronCLIError):
"""Indicates that the user is trying to use an unsupported
version of the API
"""
pass
class NeutronClientNoUniqueMatch(NeutronCLIError):
message = _("Multiple %(resource)s matches found for name '%(name)s',"
" use an ID to be more specific.")

View File

@ -0,0 +1,409 @@
# Copyright 2013 OpenStack Foundation.
# All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
###
### Codes from neutron wsgi
###
import logging
from xml.etree import ElementTree as etree
from xml.parsers import expat
from neutronclient.common import constants
from neutronclient.common import exceptions as exception
from neutronclient.openstack.common.gettextutils import _
from neutronclient.openstack.common import jsonutils
LOG = logging.getLogger(__name__)
class ActionDispatcher(object):
"""Maps method name to local methods through action name."""
def dispatch(self, *args, **kwargs):
"""Find and call local method."""
action = kwargs.pop('action', 'default')
action_method = getattr(self, str(action), self.default)
return action_method(*args, **kwargs)
def default(self, data):
raise NotImplementedError()
class DictSerializer(ActionDispatcher):
"""Default request body serialization."""
def serialize(self, data, action='default'):
return self.dispatch(data, action=action)
def default(self, data):
return ""
class JSONDictSerializer(DictSerializer):
"""Default JSON request body serialization."""
def default(self, data):
def sanitizer(obj):
return unicode(obj)
return jsonutils.dumps(data, default=sanitizer)
class XMLDictSerializer(DictSerializer):
def __init__(self, metadata=None, xmlns=None):
"""XMLDictSerializer constructor.
:param metadata: information needed to deserialize xml into
a dictionary.
:param xmlns: XML namespace to include with serialized xml
"""
super(XMLDictSerializer, self).__init__()
self.metadata = metadata or {}
if not xmlns:
xmlns = self.metadata.get('xmlns')
if not xmlns:
xmlns = constants.XML_NS_V20
self.xmlns = xmlns
def default(self, data):
"""Default serializer of XMLDictSerializer.
:param data: expect data to contain a single key as XML root, or
contain another '*_links' key as atom links. Other
case will use 'VIRTUAL_ROOT_KEY' as XML root.
"""
try:
links = None
has_atom = False
if data is None:
root_key = constants.VIRTUAL_ROOT_KEY
root_value = None
else:
link_keys = [k for k in data.iterkeys() or []
if k.endswith('_links')]
if link_keys:
links = data.pop(link_keys[0], None)
has_atom = True
root_key = (len(data) == 1 and
data.keys()[0] or constants.VIRTUAL_ROOT_KEY)
root_value = data.get(root_key, data)
doc = etree.Element("_temp_root")
used_prefixes = []
self._to_xml_node(doc, self.metadata, root_key,
root_value, used_prefixes)
if links:
self._create_link_nodes(list(doc)[0], links)
return self.to_xml_string(list(doc)[0], used_prefixes, has_atom)
except AttributeError as e:
LOG.exception(str(e))
return ''
def __call__(self, data):
# Provides a migration path to a cleaner WSGI layer, this
# "default" stuff and extreme extensibility isn't being used
# like originally intended
return self.default(data)
def to_xml_string(self, node, used_prefixes, has_atom=False):
self._add_xmlns(node, used_prefixes, has_atom)
return etree.tostring(node, encoding='UTF-8')
#NOTE (ameade): the has_atom should be removed after all of the
# xml serializers and view builders have been updated to the current
# spec that required all responses include the xmlns:atom, the has_atom
# flag is to prevent current tests from breaking
def _add_xmlns(self, node, used_prefixes, has_atom=False):
node.set('xmlns', self.xmlns)
node.set(constants.TYPE_XMLNS, self.xmlns)
if has_atom:
node.set(constants.ATOM_XMLNS, constants.ATOM_NAMESPACE)
node.set(constants.XSI_NIL_ATTR, constants.XSI_NAMESPACE)
ext_ns = self.metadata.get(constants.EXT_NS, {})
for prefix in used_prefixes:
if prefix in ext_ns:
node.set('xmlns:' + prefix, ext_ns[prefix])
def _to_xml_node(self, parent, metadata, nodename, data, used_prefixes):
"""Recursive method to convert data members to XML nodes."""
result = etree.SubElement(parent, nodename)
if ":" in nodename:
used_prefixes.append(nodename.split(":", 1)[0])
#TODO(bcwaldon): accomplish this without a type-check
if isinstance(data, list):
if not data:
result.set(
constants.TYPE_ATTR,
constants.TYPE_LIST)
return result
singular = metadata.get('plurals', {}).get(nodename, None)
if singular is None:
if nodename.endswith('s'):
singular = nodename[:-1]
else:
singular = 'item'
for item in data:
self._to_xml_node(result, metadata, singular, item,
used_prefixes)
#TODO(bcwaldon): accomplish this without a type-check
elif isinstance(data, dict):
if not data:
result.set(
constants.TYPE_ATTR,
constants.TYPE_DICT)
return result
attrs = metadata.get('attributes', {}).get(nodename, {})
for k, v in sorted(data.items()):
if k in attrs:
result.set(k, str(v))
else:
self._to_xml_node(result, metadata, k, v,
used_prefixes)
elif data is None:
result.set(constants.XSI_ATTR, 'true')
else:
if isinstance(data, bool):
result.set(
constants.TYPE_ATTR,
constants.TYPE_BOOL)
elif isinstance(data, int):
result.set(
constants.TYPE_ATTR,
constants.TYPE_INT)
elif isinstance(data, long):
result.set(
constants.TYPE_ATTR,
constants.TYPE_LONG)
elif isinstance(data, float):
result.set(
constants.TYPE_ATTR,
constants.TYPE_FLOAT)
LOG.debug(_("Data %(data)s type is %(type)s"),
{'data': data,
'type': type(data)})
if isinstance(data, str):
result.text = unicode(data, 'utf-8')
else:
result.text = unicode(data)
return result
def _create_link_nodes(self, xml_doc, links):
for link in links:
link_node = etree.SubElement(xml_doc, 'atom:link')
link_node.set('rel', link['rel'])
link_node.set('href', link['href'])
class TextDeserializer(ActionDispatcher):
"""Default request body deserialization."""
def deserialize(self, datastring, action='default'):
return self.dispatch(datastring, action=action)
def default(self, datastring):
return {}
class JSONDeserializer(TextDeserializer):
def _from_json(self, datastring):
try:
return jsonutils.loads(datastring)
except ValueError:
msg = _("Cannot understand JSON")
raise exception.MalformedResponseBody(reason=msg)
def default(self, datastring):
return {'body': self._from_json(datastring)}
class XMLDeserializer(TextDeserializer):
def __init__(self, metadata=None):
"""XMLDeserializer constructor.
:param metadata: information needed to deserialize xml into
a dictionary.
"""
super(XMLDeserializer, self).__init__()
self.metadata = metadata or {}
xmlns = self.metadata.get('xmlns')
if not xmlns:
xmlns = constants.XML_NS_V20
self.xmlns = xmlns
def _get_key(self, tag):
tags = tag.split("}", 1)
if len(tags) == 2:
ns = tags[0][1:]
bare_tag = tags[1]
ext_ns = self.metadata.get(constants.EXT_NS, {})
if ns == self.xmlns:
return bare_tag
for prefix, _ns in ext_ns.items():
if ns == _ns:
return prefix + ":" + bare_tag
else:
return tag
def