merge trunk
This commit is contained in:
176
LICENSE
Normal file
176
LICENSE
Normal 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.
|
||||
|
||||
31
README
31
README
@@ -105,4 +105,35 @@ There are a few requirements to writing your own plugin:
|
||||
4) Launch the Quantum Service, and your plug-in is configured and ready to
|
||||
manage a Cloud Networking Fabric.
|
||||
|
||||
# -- Extensions
|
||||
|
||||
1) Creating Extensions:
|
||||
a) Extension files should be placed under ./extensions folder.
|
||||
b) The extension file should have a class with the same name as the filename.
|
||||
This class should implement the contract required by the extension framework.
|
||||
See ExtensionDescriptor class in ./quantum/common/extensions.py for details
|
||||
c) To stop a file in ./extensions folder from being loaded as an extension,
|
||||
the filename should start with an "_"
|
||||
For an example of an extension file look at Foxinsocks class in
|
||||
./tests/unit/extensions/foxinsocks.py
|
||||
The unit tests in ./tests/unit/test_extensions.py document all the ways in
|
||||
which you can use extensions
|
||||
|
||||
2) Associating plugins with extensions:
|
||||
a) A Plugin can advertize all the extensions it supports through the
|
||||
'supported_extension_aliases' attribute. Eg:
|
||||
|
||||
class SomePlugin:
|
||||
...
|
||||
supported_extension_aliases = ['extension1_alias',
|
||||
'extension2_alias',
|
||||
'extension3_alias']
|
||||
Any extension not in this list will not be loaded for the plugin
|
||||
|
||||
b) Extension Interfaces for plugins (optional)
|
||||
The extension can mandate an interface that plugins have to support with the
|
||||
'get_plugin_interface' method in the extension.
|
||||
For an example see the FoxInSocksPluginInterface in foxinsocks.py.
|
||||
|
||||
The QuantumEchoPlugin lists foxinsox in its supported_extension_aliases
|
||||
and implements the method from FoxInSocksPluginInterface.
|
||||
|
||||
@@ -11,15 +11,22 @@ bind_host = 0.0.0.0
|
||||
# Port the bind the API server to
|
||||
bind_port = 9696
|
||||
|
||||
# Path to the extensions
|
||||
api_extensions_path = extensions
|
||||
|
||||
[composite:quantum]
|
||||
use = egg:Paste#urlmap
|
||||
/: quantumversions
|
||||
/v0.1: quantumapi
|
||||
|
||||
[pipeline:quantumapi]
|
||||
pipeline = extensions quantumapiapp
|
||||
|
||||
[filter:extensions]
|
||||
paste.filter_factory = quantum.common.extensions:plugin_aware_extension_middleware_factory
|
||||
|
||||
[app:quantumversions]
|
||||
paste.app_factory = quantum.api.versions:Versions.factory
|
||||
|
||||
[app:quantumapi]
|
||||
[app:quantumapiapp]
|
||||
paste.app_factory = quantum.api:APIRouterV01.factory
|
||||
|
||||
|
||||
|
||||
@@ -5,11 +5,28 @@ verbose = True
|
||||
# Show debugging output in logs (sets DEBUG log level output)
|
||||
debug = False
|
||||
|
||||
[app:quantum]
|
||||
paste.app_factory = quantum.service:app_factory
|
||||
|
||||
# Address to bind the API server
|
||||
bind_host = 0.0.0.0
|
||||
|
||||
# Port the bind the API server to
|
||||
bind_port = 9696
|
||||
bind_port = 9696
|
||||
|
||||
# Path to the extensions
|
||||
api_extensions_path = extensions
|
||||
|
||||
[composite:quantum]
|
||||
use = egg:Paste#urlmap
|
||||
/: quantumversions
|
||||
/v0.1: quantumapi
|
||||
|
||||
[pipeline:quantumapi]
|
||||
pipeline = extensions quantumapiapp
|
||||
|
||||
[filter:extensions]
|
||||
paste.filter_factory = quantum.common.extensions:plugin_aware_extension_middleware_factory
|
||||
|
||||
[app:quantumversions]
|
||||
paste.app_factory = quantum.api.versions:Versions.factory
|
||||
|
||||
[app:quantumapiapp]
|
||||
paste.app_factory = quantum.api:APIRouterV01.factory
|
||||
|
||||
@@ -5,11 +5,20 @@ verbose = True
|
||||
# Show debugging output in logs (sets DEBUG log level output)
|
||||
debug = False
|
||||
|
||||
[app:quantum]
|
||||
paste.app_factory = quantum.l2Network.service:app_factory
|
||||
|
||||
# Address to bind the API server
|
||||
bind_host = 0.0.0.0
|
||||
|
||||
# Port the bind the API server to
|
||||
bind_port = 9696
|
||||
|
||||
# Path to the extensions
|
||||
api_extensions_path = unit/extensions
|
||||
|
||||
[pipeline:extensions_app_with_filter]
|
||||
pipeline = extensions extensions_test_app
|
||||
|
||||
[filter:extensions]
|
||||
paste.filter_factory = quantum.common.extensions:plugin_aware_extension_middleware_factory
|
||||
|
||||
[app:extensions_test_app]
|
||||
paste.app_factory = tests.unit.test_extensions:app_factory
|
||||
|
||||
15
extensions/__init__.py
Normal file
15
extensions/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2011 OpenStack LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
@@ -48,7 +48,8 @@ class APIRouterV01(wsgi.Router):
|
||||
|
||||
def _setup_routes(self, mapper, options):
|
||||
# Loads the quantum plugin
|
||||
plugin = manager.QuantumManager(options).get_plugin()
|
||||
plugin = manager.QuantumManager.get_plugin(options)
|
||||
|
||||
uri_prefix = '/tenants/{tenant_id}/'
|
||||
mapper.resource('network', 'networks',
|
||||
controller=networks.Controller(plugin),
|
||||
|
||||
@@ -31,6 +31,7 @@ class Fault(webob.exc.HTTPException):
|
||||
401: "unauthorized",
|
||||
420: "networkNotFound",
|
||||
421: "networkInUse",
|
||||
422: "networkNameExists",
|
||||
430: "portNotFound",
|
||||
431: "requestedStateInvalid",
|
||||
432: "portInUse",
|
||||
@@ -91,6 +92,22 @@ class NetworkInUse(webob.exc.HTTPClientError):
|
||||
explanation = ('Unable to remove the network: attachments still plugged.')
|
||||
|
||||
|
||||
class NetworkNameExists(webob.exc.HTTPClientError):
|
||||
"""
|
||||
subclass of :class:`~HTTPClientError`
|
||||
|
||||
This indicates that the server could not set the network name to the
|
||||
specified value because another network for the same tenant already has
|
||||
that name.
|
||||
|
||||
code: 422, title: Network Name Exists
|
||||
"""
|
||||
code = 422
|
||||
title = 'Network Name Exists'
|
||||
explanation = ('Unable to set network name: tenant already has network' \
|
||||
' with same name.')
|
||||
|
||||
|
||||
class PortNotFound(webob.exc.HTTPClientError):
|
||||
"""
|
||||
subclass of :class:`~HTTPClientError`
|
||||
|
||||
@@ -107,12 +107,15 @@ class Controller(common.QuantumController):
|
||||
self._network_ops_param_list)
|
||||
except exc.HTTPError as e:
|
||||
return faults.Fault(e)
|
||||
network = self._plugin.\
|
||||
try:
|
||||
network = self._plugin.\
|
||||
create_network(tenant_id,
|
||||
request_params['net-name'])
|
||||
builder = networks_view.get_view_builder(request)
|
||||
result = builder.build(network)
|
||||
return dict(networks=result)
|
||||
builder = networks_view.get_view_builder(request)
|
||||
result = builder.build(network)
|
||||
return dict(networks=result)
|
||||
except exception.NetworkNameExists as e:
|
||||
return faults.Fault(faults.NetworkNameExists(e))
|
||||
|
||||
def update(self, request, tenant_id, id):
|
||||
""" Updates the name for the network with the given id """
|
||||
@@ -128,6 +131,8 @@ class Controller(common.QuantumController):
|
||||
return exc.HTTPAccepted()
|
||||
except exception.NetworkNotFound as e:
|
||||
return faults.Fault(faults.NetworkNotFound(e))
|
||||
except exception.NetworkNameExists as e:
|
||||
return faults.Fault(faults.NetworkNameExists(e))
|
||||
|
||||
def delete(self, request, tenant_id, id):
|
||||
""" Destroys the network with the given id """
|
||||
|
||||
@@ -104,7 +104,7 @@ def detail_net(manager, *args):
|
||||
def api_detail_net(client, *args):
|
||||
tid, nid = args
|
||||
try:
|
||||
res = client.list_network_details(nid)["networks"]["network"]
|
||||
res = client.show_network_details(nid)["networks"]["network"]
|
||||
except Exception, e:
|
||||
LOG.error("Failed to get network details: %s" % e)
|
||||
return
|
||||
@@ -119,7 +119,7 @@ def api_detail_net(client, *args):
|
||||
print "Remote Interfaces on Virtual Network:%s\n" % nid
|
||||
for port in ports["ports"]:
|
||||
pid = port["id"]
|
||||
res = client.list_port_attachments(nid, pid)
|
||||
res = client.show_port_attachment(nid, pid)
|
||||
LOG.debug(res)
|
||||
remote_iface = res["attachment"]
|
||||
print "\tRemote interface:%s" % remote_iface
|
||||
@@ -214,7 +214,7 @@ def detail_port(manager, *args):
|
||||
def api_detail_port(client, *args):
|
||||
tid, nid, pid = args
|
||||
try:
|
||||
port = client.list_port_details(nid, pid)["ports"]["port"]
|
||||
port = client.show_port_details(nid, pid)["ports"]["port"]
|
||||
except Exception, e:
|
||||
LOG.error("Failed to get port details: %s" % e)
|
||||
return
|
||||
|
||||
@@ -57,7 +57,8 @@ class Client(object):
|
||||
attachment_path = "/networks/%s/ports/%s/attachment"
|
||||
|
||||
def __init__(self, host="127.0.0.1", port=9696, use_ssl=False, tenant=None,
|
||||
format="xml", testingStub=None, key_file=None, cert_file=None):
|
||||
format="xml", testingStub=None, key_file=None, cert_file=None,
|
||||
logger=None):
|
||||
"""
|
||||
Creates a new client to some service.
|
||||
|
||||
@@ -79,6 +80,7 @@ class Client(object):
|
||||
self.testingStub = testingStub
|
||||
self.key_file = key_file
|
||||
self.cert_file = cert_file
|
||||
self.logger = logger
|
||||
|
||||
def get_connection_type(self):
|
||||
"""
|
||||
@@ -118,6 +120,9 @@ class Client(object):
|
||||
if type(params) is dict:
|
||||
action += '?' + urllib.urlencode(params)
|
||||
|
||||
if body:
|
||||
body = self.serialize(body)
|
||||
|
||||
try:
|
||||
connection_type = self.get_connection_type()
|
||||
headers = headers or {"Content-Type":
|
||||
@@ -132,14 +137,26 @@ class Client(object):
|
||||
else:
|
||||
c = connection_type(self.host, self.port)
|
||||
|
||||
if self.logger:
|
||||
self.logger.debug("Quantum Client Request:\n" \
|
||||
+ method + " " + action + "\n")
|
||||
if body:
|
||||
self.logger.debug(body)
|
||||
|
||||
c.request(method, action, body, headers)
|
||||
res = c.getresponse()
|
||||
status_code = self.get_status_code(res)
|
||||
data = res.read()
|
||||
|
||||
if self.logger:
|
||||
self.logger.debug("Quantum Client Reply (code = %s) :\n %s" \
|
||||
% (str(status_code), data))
|
||||
|
||||
if status_code in (httplib.OK,
|
||||
httplib.CREATED,
|
||||
httplib.ACCEPTED,
|
||||
httplib.NO_CONTENT):
|
||||
return self.deserialize(res)
|
||||
return self.deserialize(data, status_code)
|
||||
else:
|
||||
raise Exception("Server returned error: %s" % res.read())
|
||||
|
||||
@@ -158,13 +175,18 @@ class Client(object):
|
||||
return response.status
|
||||
|
||||
def serialize(self, data):
|
||||
if type(data) is dict:
|
||||
if data is None:
|
||||
return None
|
||||
elif type(data) is dict:
|
||||
return Serializer().serialize(data, self.content_type())
|
||||
else:
|
||||
raise Exception("unable to deserialize object of type = '%s'" \
|
||||
% type(data))
|
||||
|
||||
def deserialize(self, data):
|
||||
if self.get_status_code(data) == 202:
|
||||
return data.read()
|
||||
return Serializer().deserialize(data.read(), self.content_type())
|
||||
def deserialize(self, data, status_code):
|
||||
if status_code == 202:
|
||||
return data
|
||||
return Serializer().deserialize(data, self.content_type())
|
||||
|
||||
def content_type(self, format=None):
|
||||
if not format:
|
||||
@@ -174,97 +196,94 @@ class Client(object):
|
||||
@api_call
|
||||
def list_networks(self):
|
||||
"""
|
||||
Queries the server for a list of networks
|
||||
Fetches a list of all networks for a tenant
|
||||
"""
|
||||
return self.do_request("GET", self.networks_path)
|
||||
|
||||
@api_call
|
||||
def list_network_details(self, network):
|
||||
def show_network_details(self, network):
|
||||
"""
|
||||
Queries the server for the details of a certain network
|
||||
Fetches the details of a certain network
|
||||
"""
|
||||
return self.do_request("GET", self.network_path % (network))
|
||||
|
||||
@api_call
|
||||
def create_network(self, body=None):
|
||||
"""
|
||||
Creates a new network on the server
|
||||
Creates a new network
|
||||
"""
|
||||
body = self.serialize(body)
|
||||
return self.do_request("POST", self.networks_path, body=body)
|
||||
|
||||
@api_call
|
||||
def update_network(self, network, body=None):
|
||||
"""
|
||||
Updates a network on the server
|
||||
Updates a network
|
||||
"""
|
||||
body = self.serialize(body)
|
||||
return self.do_request("PUT", self.network_path % (network), body=body)
|
||||
|
||||
@api_call
|
||||
def delete_network(self, network):
|
||||
"""
|
||||
Deletes a network on the server
|
||||
Deletes the specified network
|
||||
"""
|
||||
return self.do_request("DELETE", self.network_path % (network))
|
||||
|
||||
@api_call
|
||||
def list_ports(self, network):
|
||||
"""
|
||||
Queries the server for a list of ports on a given network
|
||||
Fetches a list of ports on a given network
|
||||
"""
|
||||
return self.do_request("GET", self.ports_path % (network))
|
||||
|
||||
@api_call
|
||||
def list_port_details(self, network, port):
|
||||
def show_port_details(self, network, port):
|
||||
"""
|
||||
Queries the server for a list of ports on a given network
|
||||
Fetches the details of a certain port
|
||||
"""
|
||||
return self.do_request("GET", self.port_path % (network, port))
|
||||
|
||||
@api_call
|
||||
def create_port(self, network):
|
||||
def create_port(self, network, body=None):
|
||||
"""
|
||||
Creates a new port on a network on the server
|
||||
Creates a new port on a given network
|
||||
"""
|
||||
return self.do_request("POST", self.ports_path % (network))
|
||||
body = self.serialize(body)
|
||||
return self.do_request("POST", self.ports_path % (network), body=body)
|
||||
|
||||
@api_call
|
||||
def delete_port(self, network, port):
|
||||
"""
|
||||
Deletes a port from a network on the server
|
||||
Deletes the specified port from a network
|
||||
"""
|
||||
return self.do_request("DELETE", self.port_path % (network, port))
|
||||
|
||||
@api_call
|
||||
def set_port_state(self, network, port, body=None):
|
||||
"""
|
||||
Sets the state of a port on the server
|
||||
Sets the state of the specified port
|
||||
"""
|
||||
body = self.serialize(body)
|
||||
return self.do_request("PUT",
|
||||
self.port_path % (network, port), body=body)
|
||||
|
||||
@api_call
|
||||
def list_port_attachments(self, network, port):
|
||||
def show_port_attachment(self, network, port):
|
||||
"""
|
||||
Deletes a port from a network on the server
|
||||
Fetches the attachment-id associated with the specified port
|
||||
"""
|
||||
return self.do_request("GET", self.attachment_path % (network, port))
|
||||
|
||||
@api_call
|
||||
def attach_resource(self, network, port, body=None):
|
||||
"""
|
||||
Deletes a port from a network on the server
|
||||
Sets the attachment-id of the specified port
|
||||
"""
|
||||
body = self.serialize(body)
|
||||
return self.do_request("PUT",
|
||||
self.attachment_path % (network, port), body=body)
|
||||
|
||||
@api_call
|
||||
def detach_resource(self, network, port):
|
||||
"""
|
||||
Deletes a port from a network on the server
|
||||
Removes the attachment-id of the specified port
|
||||
"""
|
||||
return self.do_request("DELETE",
|
||||
self.attachment_path % (network, port))
|
||||
|
||||
@@ -21,6 +21,9 @@ Quantum-type exceptions. SHOULD include dedicated exception logging.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import gettext
|
||||
|
||||
gettext.install('quantum', unicode=1)
|
||||
|
||||
|
||||
class QuantumException(Exception):
|
||||
@@ -108,6 +111,12 @@ class AlreadyAttached(QuantumException):
|
||||
"already plugged into port %(att_port_id)s")
|
||||
|
||||
|
||||
class NetworkNameExists(QuantumException):
|
||||
message = _("Unable to set network name to %(net_name). " \
|
||||
"Network with id %(net_id) already has this name for " \
|
||||
"tenant %(tenant_id)")
|
||||
|
||||
|
||||
class Duplicate(Error):
|
||||
pass
|
||||
|
||||
|
||||
518
quantum/common/extensions.py
Normal file
518
quantum/common/extensions.py
Normal file
@@ -0,0 +1,518 @@
|
||||
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2011 OpenStack LLC.
|
||||
# Copyright 2011 Justin Santa Barbara
|
||||
# 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 imp
|
||||
import logging
|
||||
import os
|
||||
import routes
|
||||
import webob.dec
|
||||
import webob.exc
|
||||
|
||||
from gettext import gettext as _
|
||||
from abc import ABCMeta
|
||||
from quantum.common import exceptions
|
||||
from quantum.manager import QuantumManager
|
||||
from quantum.common import wsgi
|
||||
|
||||
LOG = logging.getLogger('quantum.common.extensions')
|
||||
|
||||
|
||||
class PluginInterface(object):
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
@classmethod
|
||||
def __subclasshook__(cls, klass):
|
||||
"""
|
||||
The __subclasshook__ method is a class method
|
||||
that will be called everytime a class is tested
|
||||
using issubclass(klass, PluginInterface).
|
||||
In that case, it will check that every method
|
||||
marked with the abstractmethod decorator is
|
||||
provided by the plugin class.
|
||||
"""
|
||||
for method in cls.__abstractmethods__:
|
||||
if any(method in base.__dict__ for base in klass.__mro__):
|
||||
continue
|
||||
return NotImplemented
|
||||
return True
|
||||
|
||||
|
||||
class ExtensionDescriptor(object):
|
||||
"""Base class that defines the contract for extensions.
|
||||
|
||||
Note that you don't have to derive from this class to have a valid
|
||||
extension; it is purely a convenience.
|
||||
|
||||
"""
|
||||
|
||||
def get_name(self):
|
||||
"""The name of the extension.
|
||||
|
||||
e.g. 'Fox In Socks'
|
||||
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_alias(self):
|
||||
"""The alias for the extension.
|
||||
|
||||
e.g. 'FOXNSOX'
|
||||
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_description(self):
|
||||
"""Friendly description for the extension.
|
||||
|
||||
e.g. 'The Fox In Socks Extension'
|
||||
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_namespace(self):
|
||||
"""The XML namespace for the extension.
|
||||
|
||||
e.g. 'http://www.fox.in.socks/api/ext/pie/v1.0'
|
||||
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_updated(self):
|
||||
"""The timestamp when the extension was last updated.
|
||||
|
||||
e.g. '2011-01-22T13:25:27-06:00'
|
||||
|
||||
"""
|
||||
# NOTE(justinsb): Not sure of the purpose of this is, vs the XML NS
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_resources(self):
|
||||
"""List of extensions.ResourceExtension extension objects.
|
||||
|
||||
Resources define new nouns, and are accessible through URLs.
|
||||
|
||||
"""
|
||||
resources = []
|
||||
return resources
|
||||
|
||||
def get_actions(self):
|
||||
"""List of extensions.ActionExtension extension objects.
|
||||
|
||||
Actions are verbs callable from the API.
|
||||
|
||||
"""
|
||||
actions = []
|
||||
return actions
|
||||
|
||||
def get_request_extensions(self):
|
||||
"""List of extensions.RequestException extension objects.
|
||||
|
||||
Request extensions are used to handle custom request data.
|
||||
|
||||
"""
|
||||
request_exts = []
|
||||
return request_exts
|
||||
|
||||
def get_plugin_interface(self):
|
||||
"""
|
||||
Returns an abstract class which defines contract for the plugin.
|
||||
The abstract class should inherit from extesnions.PluginInterface,
|
||||
Methods in this abstract class should be decorated as abstractmethod
|
||||
"""
|
||||
return None
|
||||
|
||||
|
||||
class ActionExtensionController(wsgi.Controller):
|
||||
|
||||
def __init__(self, application):
|
||||
|
||||
self.application = application
|
||||
self.action_handlers = {}
|
||||
|
||||
def add_action(self, action_name, handler):
|
||||
self.action_handlers[action_name] = handler
|
||||
|
||||
def action(self, request, id):
|
||||
|
||||
input_dict = self._deserialize(request.body,
|
||||
request.get_content_type())
|
||||
for action_name, handler in self.action_handlers.iteritems():
|
||||
if action_name in input_dict:
|
||||
return handler(input_dict, request, id)
|
||||
# no action handler found (bump to downstream application)
|
||||
response = self.application
|
||||
return response
|
||||
|
||||
|
||||
class RequestExtensionController(wsgi.Controller):
|
||||
|
||||
def __init__(self, application):
|
||||
self.application = application
|
||||
self.handlers = []
|
||||
|
||||
def add_handler(self, handler):
|
||||
self.handlers.append(handler)
|
||||
|
||||
def process(self, request, *args, **kwargs):
|
||||
res = request.get_response(self.application)
|
||||
# currently request handlers are un-ordered
|
||||
for handler in self.handlers:
|
||||
response = handler(request, res)
|
||||
return response
|
||||
|
||||
|
||||
class ExtensionController(wsgi.Controller):
|
||||
|
||||
def __init__(self, extension_manager):
|
||||
self.extension_manager = extension_manager
|
||||
|
||||
def _translate(self, ext):
|
||||
ext_data = {}
|
||||
ext_data['name'] = ext.get_name()
|
||||
ext_data['alias'] = ext.get_alias()
|
||||
ext_data['description'] = ext.get_description()
|
||||
ext_data['namespace'] = ext.get_namespace()
|
||||
ext_data['updated'] = ext.get_updated()
|
||||
ext_data['links'] = [] # TODO(dprince): implement extension links
|
||||
return ext_data
|
||||
|
||||
def index(self, request):
|
||||
extensions = []
|
||||
for _alias, ext in self.extension_manager.extensions.iteritems():
|
||||
extensions.append(self._translate(ext))
|
||||
return dict(extensions=extensions)
|
||||
|
||||
def show(self, request, id):
|
||||
# NOTE(dprince): the extensions alias is used as the 'id' for show
|
||||
ext = self.extension_manager.extensions.get(id, None)
|
||||
if not ext:
|
||||
raise webob.exc.HTTPNotFound(
|
||||
_("Extension with alias %s does not exist") % id)
|
||||
return self._translate(ext)
|
||||
|
||||
def delete(self, request, id):
|
||||
raise webob.exc.HTTPNotFound()
|
||||
|
||||
def create(self, request):
|
||||
raise webob.exc.HTTPNotFound()
|
||||
|
||||
|
||||
class ExtensionMiddleware(wsgi.Middleware):
|
||||
"""Extensions middleware for WSGI."""
|
||||
def __init__(self, application, config_params,
|
||||
ext_mgr=None):
|
||||
|
||||
self.ext_mgr = (ext_mgr
|
||||
or ExtensionManager(
|
||||
config_params.get('api_extensions_path', '')))
|
||||
mapper = routes.Mapper()
|
||||
|
||||
# extended resources
|
||||
for resource in self.ext_mgr.get_resources():
|
||||
LOG.debug(_('Extended resource: %s'),
|
||||
resource.collection)
|
||||
mapper.resource(resource.collection, resource.collection,
|
||||
controller=resource.controller,
|
||||
collection=resource.collection_actions,
|
||||
member=resource.member_actions,
|
||||
parent_resource=resource.parent)
|
||||
|
||||
# extended actions
|
||||
action_controllers = self._action_ext_controllers(application,
|
||||
self.ext_mgr, mapper)
|
||||
for action in self.ext_mgr.get_actions():
|
||||
LOG.debug(_('Extended action: %s'), action.action_name)
|
||||
controller = action_controllers[action.collection]
|
||||
controller.add_action(action.action_name, action.handler)
|
||||
|
||||
# extended requests
|
||||
req_controllers = self._request_ext_controllers(application,
|
||||
self.ext_mgr, mapper)
|
||||
for request_ext in self.ext_mgr.get_request_extensions():
|
||||
LOG.debug(_('Extended request: %s'), request_ext.key)
|
||||
controller = req_controllers[request_ext.key]
|
||||
controller.add_handler(request_ext.handler)
|
||||
|
||||
self._router = routes.middleware.RoutesMiddleware(self._dispatch,
|
||||
mapper)
|
||||
|
||||
super(ExtensionMiddleware, self).__init__(application)
|
||||
|
||||
@classmethod
|
||||
def factory(cls, global_config, **local_config):
|
||||
"""Paste factory."""
|
||||
def _factory(app):
|
||||
return cls(app, global_config, **local_config)
|
||||
return _factory
|
||||
|
||||
def _action_ext_controllers(self, application, ext_mgr, mapper):
|
||||
"""Return a dict of ActionExtensionController-s by collection."""
|
||||
action_controllers = {}
|
||||
for action in ext_mgr.get_actions():
|
||||
if not action.collection in action_controllers.keys():
|
||||
controller = ActionExtensionController(application)
|
||||
mapper.connect("/%s/:(id)/action.:(format)" %
|
||||
action.collection,
|
||||
action='action',
|
||||
controller=controller,
|
||||
conditions=dict(method=['POST']))
|
||||
mapper.connect("/%s/:(id)/action" % action.collection,
|
||||
action='action',
|
||||
controller=controller,
|
||||
conditions=dict(method=['POST']))
|
||||
action_controllers[action.collection] = controller
|
||||
|
||||
return action_controllers
|
||||
|
||||
def _request_ext_controllers(self, application, ext_mgr, mapper):
|
||||
"""Returns a dict of RequestExtensionController-s by collection."""
|
||||
request_ext_controllers = {}
|
||||
for req_ext in ext_mgr.get_request_extensions():
|
||||
if not req_ext.key in request_ext_controllers.keys():
|
||||
controller = RequestExtensionController(application)
|
||||
mapper.connect(req_ext.url_route + '.:(format)',
|
||||
action='process',
|
||||
controller=controller,
|
||||
conditions=req_ext.conditions)
|
||||
|
||||
mapper.connect(req_ext.url_route,
|
||||
action='process',
|
||||
controller=controller,
|
||||
conditions=req_ext.conditions)
|
||||
request_ext_controllers[req_ext.key] = controller
|
||||
|
||||
return request_ext_controllers
|
||||
|
||||
@webob.dec.wsgify(RequestClass=wsgi.Request)
|
||||
def __call__(self, req):
|
||||
"""Route the incoming request with router."""
|
||||
req.environ['extended.app'] = self.application
|
||||
return self._router
|
||||
|
||||
@staticmethod
|
||||
@webob.dec.wsgify(RequestClass=wsgi.Request)
|
||||
def _dispatch(req):
|
||||
"""Dispatch the request.
|
||||
|
||||
Returns the routed WSGI app's response or defers to the extended
|
||||
application.
|
||||
|
||||
"""
|
||||
match = req.environ['wsgiorg.routing_args'][1]
|
||||
if not match:
|
||||
return req.environ['extended.app']
|
||||
app = match['controller']
|
||||
return app
|
||||
|
||||
|
||||
def plugin_aware_extension_middleware_factory(global_config, **local_config):
|
||||
"""Paste factory."""
|
||||
def _factory(app):
|
||||
extensions_path = global_config.get('api_extensions_path', '')
|
||||
ext_mgr = PluginAwareExtensionManager(extensions_path,
|
||||
QuantumManager().get_plugin())
|
||||
return ExtensionMiddleware(app, global_config, ext_mgr=ext_mgr)
|
||||
return _factory
|
||||
|
||||
|
||||
class ExtensionManager(object):
|
||||
"""Load extensions from the configured extension path.
|
||||
|
||||
See tests/unit/extensions/foxinsocks.py for an
|
||||
example extension implementation.
|
||||
|
||||
"""
|
||||
def __init__(self, path):
|
||||
LOG.info(_('Initializing extension manager.'))
|
||||
self.path = path
|
||||
self.extensions = {}
|
||||
self._load_all_extensions()
|
||||
|
||||
def get_resources(self):
|
||||
"""Returns a list of ResourceExtension objects."""
|
||||
resources = []
|
||||
resources.append(ResourceExtension('extensions',
|
||||
ExtensionController(self)))
|
||||
for alias, ext in self.extensions.iteritems():
|
||||
try:
|
||||
resources.extend(ext.get_resources())
|
||||
except AttributeError:
|
||||
# NOTE(dprince): Extension aren't required to have resource
|
||||
# extensions
|
||||
pass
|
||||
return resources
|
||||
|
||||
def get_actions(self):
|
||||
"""Returns a list of ActionExtension objects."""
|
||||
actions = []
|
||||
for alias, ext in self.extensions.iteritems():
|
||||
try:
|
||||
actions.extend(ext.get_actions())
|
||||
except AttributeError:
|
||||
# NOTE(dprince): Extension aren't required to have action
|
||||
# extensions
|
||||
pass
|
||||
return actions
|
||||
|
||||
def get_request_extensions(self):
|
||||
"""Returns a list of RequestExtension objects."""
|
||||
request_exts = []
|
||||
for alias, ext in self.extensions.iteritems():
|
||||
try:
|
||||
request_exts.extend(ext.get_request_extensions())
|
||||
except AttributeError:
|
||||
# NOTE(dprince): Extension aren't required to have request
|
||||
# extensions
|
||||
pass
|
||||
return request_exts
|
||||
|
||||
def _check_extension(self, extension):
|
||||
"""Checks for required methods in extension objects."""
|
||||
try:
|
||||
LOG.debug(_('Ext name: %s'), extension.get_name())
|
||||
LOG.debug(_('Ext alias: %s'), extension.get_alias())
|
||||
LOG.debug(_('Ext description: %s'), extension.get_description())
|
||||
LOG.debug(_('Ext namespace: %s'), extension.get_namespace())
|
||||
LOG.debug(_('Ext updated: %s'), extension.get_updated())
|
||||
except AttributeError as ex:
|
||||
LOG.exception(_("Exception loading extension: %s"), unicode(ex))
|
||||
return False
|
||||
return True
|
||||
|
||||
def _load_all_extensions(self):
|
||||
"""Load extensions from the configured path.
|
||||
|
||||
Load extensions from the configured path. The extension name is
|
||||
constructed from the module_name. If your extension module was named
|
||||
widgets.py the extension class within that module should be
|
||||
'Widgets'.
|
||||
|
||||
See tests/unit/extensions/foxinsocks.py for an example
|
||||
extension implementation.
|
||||
|
||||
"""
|
||||
if os.path.exists(self.path):
|
||||
self._load_all_extensions_from_path(self.path)
|
||||
|
||||
def _load_all_extensions_from_path(self, path):
|
||||
for f in os.listdir(path):
|
||||
try:
|
||||
LOG.info(_('Loading extension file: %s'), f)
|
||||
mod_name, file_ext = os.path.splitext(os.path.split(f)[-1])
|
||||
ext_path = os.path.join(path, f)
|
||||
if file_ext.lower() == '.py' and not mod_name.startswith('_'):
|
||||
mod = imp.load_source(mod_name, ext_path)
|
||||
ext_name = mod_name[0].upper() + mod_name[1:]
|
||||
new_ext_class = getattr(mod, ext_name, None)
|
||||
if not new_ext_class:
|
||||
LOG.warn(_('Did not find expected name '
|
||||
'"%(ext_name)s" in %(file)s'),
|
||||
{'ext_name': ext_name,
|
||||
'file': ext_path})
|
||||
continue
|
||||
new_ext = new_ext_class()
|
||||
self.add_extension(new_ext)
|
||||
except Exception as exception:
|
||||
LOG.warn("extension file %s wasnt loaded due to %s",
|
||||
f, exception)
|
||||
|
||||
def add_extension(self, ext):
|
||||
# Do nothing if the extension doesn't check out
|
||||
if not self._check_extension(ext):
|
||||
return
|
||||
|
||||
alias = ext.get_alias()
|
||||
LOG.warn(_('Loaded extension: %s'), alias)
|
||||
|
||||
if alias in self.extensions:
|
||||
raise exceptions.Error("Found duplicate extension: %s"
|
||||
% alias)
|
||||
self.extensions[alias] = ext
|
||||
|
||||
|
||||
class PluginAwareExtensionManager(ExtensionManager):
|
||||
|
||||
def __init__(self, path, plugin):
|
||||
self.plugin = plugin
|
||||
super(PluginAwareExtensionManager, self).__init__(path)
|
||||
|
||||
def _check_extension(self, extension):
|
||||
"""Checks if plugin supports extension and implements the
|
||||
extension contract."""
|
||||
extension_is_valid = super(PluginAwareExtensionManager,
|
||||
self)._check_extension(extension)
|
||||
return (extension_is_valid and
|
||||
self._plugin_supports(extension) and
|
||||
self._plugin_implements_interface(extension))
|
||||
|
||||
def _plugin_supports(self, extension):
|
||||
alias = extension.get_alias()
|
||||
supports_extension = (hasattr(self.plugin,
|
||||
"supported_extension_aliases") and
|
||||
alias in self.plugin.supported_extension_aliases)
|
||||
if not supports_extension:
|
||||
LOG.warn("extension %s not supported by plugin %s",
|
||||
alias, self.plugin)
|
||||
return supports_extension
|
||||
|
||||
def _plugin_implements_interface(self, extension):
|
||||
if(not hasattr(extension, "get_plugin_interface") or
|
||||
extension.get_plugin_interface() is None):
|
||||
return True
|
||||
plugin_has_interface = isinstance(self.plugin,
|
||||
extension.get_plugin_interface())
|
||||
if not plugin_has_interface:
|
||||
LOG.warn("plugin %s does not implement extension's"
|
||||
"plugin interface %s" % (self.plugin,
|
||||
extension.get_alias()))
|
||||
return plugin_has_interface
|
||||
|
||||
|
||||
class RequestExtension(object):
|
||||
"""Extend requests and responses of core Quantum OpenStack API controllers.
|
||||
|
||||
Provide a way to add data to responses and handle custom request data
|
||||
that is sent to core Quantum OpenStack API controllers.
|
||||
|
||||
"""
|
||||
def __init__(self, method, url_route, handler):
|
||||
self.url_route = url_route
|
||||
self.handler = handler
|
||||
self.conditions = dict(method=[method])
|
||||
self.key = "%s-%s" % (method, url_route)
|
||||
|
||||
|
||||
class ActionExtension(object):
|
||||
"""Add custom actions to core Quantum OpenStack API controllers."""
|
||||
|
||||
def __init__(self, collection, action_name, handler):
|
||||
self.collection = collection
|
||||
self.action_name = action_name
|
||||
self.handler = handler
|
||||
|
||||
|
||||
class ResourceExtension(object):
|
||||
"""Add top level resources to the OpenStack API in Quantum."""
|
||||
|
||||
def __init__(self, collection, controller, parent=None,
|
||||
collection_actions={}, member_actions={}):
|
||||
self.collection = collection
|
||||
self.controller = controller
|
||||
self.parent = parent
|
||||
self.collection_actions = collection_actions
|
||||
self.member_actions = member_actions
|
||||
@@ -21,14 +21,12 @@ Wraps gflags.
|
||||
Global flags should be defined here, the rest are defined where they're used.
|
||||
|
||||
"""
|
||||
|
||||
import getopt
|
||||
import gflags
|
||||
import os
|
||||
import string
|
||||
import sys
|
||||
|
||||
import gflags
|
||||
|
||||
|
||||
class FlagValues(gflags.FlagValues):
|
||||
"""Extension of gflags.FlagValues that allows undefined and runtime flags.
|
||||
|
||||
@@ -18,8 +18,10 @@
|
||||
"""
|
||||
System-level utilities and helper functions.
|
||||
"""
|
||||
|
||||
import ConfigParser
|
||||
import datetime
|
||||
import exceptions as exception
|
||||
import flags
|
||||
import inspect
|
||||
import logging
|
||||
import os
|
||||
@@ -27,10 +29,8 @@ import random
|
||||
import subprocess
|
||||
import socket
|
||||
import sys
|
||||
import ConfigParser
|
||||
|
||||
import exceptions as exception
|
||||
import flags
|
||||
|
||||
from exceptions import ProcessExecutionError
|
||||
|
||||
|
||||
|
||||
@@ -22,17 +22,17 @@ Utility methods for working with WSGI servers
|
||||
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from xml.dom import minidom
|
||||
|
||||
import eventlet.wsgi
|
||||
eventlet.patcher.monkey_patch(all=False, socket=True)
|
||||
import routes.middleware
|
||||
import webob.dec
|
||||
import webob.exc
|
||||
from xml.dom import minidom
|
||||
|
||||
|
||||
from quantum import utils
|
||||
from quantum.common import exceptions as exception
|
||||
from quantum import utils
|
||||
|
||||
|
||||
LOG = logging.getLogger('quantum.common.wsgi')
|
||||
|
||||
|
||||
@@ -20,8 +20,10 @@
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker, exc
|
||||
|
||||
from quantum.common import exceptions as q_exc
|
||||
from quantum.db import models
|
||||
|
||||
|
||||
_ENGINE = None
|
||||
_MAKER = None
|
||||
BASE = models.BASE
|
||||
@@ -75,21 +77,29 @@ def unregister_models():
|
||||
BASE.metadata.drop_all(_ENGINE)
|
||||
|
||||
|
||||
def network_create(tenant_id, name):
|
||||
def _check_duplicate_net_name(tenant_id, net_name):
|
||||
session = get_session()
|
||||
net = None
|
||||
try:
|
||||
net = session.query(models.Network).\
|
||||
filter_by(tenant_id=tenant_id, name=name).\
|
||||
filter_by(tenant_id=tenant_id, name=net_name).\
|
||||
one()
|
||||
raise Exception("Network with name %(name)s already " \
|
||||
"exists for tenant %(tenant_id)s" % locals())
|
||||
raise q_exc.NetworkNameExists(tenant_id=tenant_id,
|
||||
net_name=net_name, net_id=net.uuid)
|
||||
except exc.NoResultFound:
|
||||
with session.begin():
|
||||
net = models.Network(tenant_id, name)
|
||||
session.add(net)
|
||||
session.flush()
|
||||
return net
|
||||
# this is the "normal" path, as API spec specifies
|
||||
# that net-names are unique within a tenant
|
||||
pass
|
||||
|
||||
|
||||
def network_create(tenant_id, name):
|
||||
session = get_session()
|
||||
|
||||
_check_duplicate_net_name(tenant_id, name)
|
||||
with session.begin():
|
||||
net = models.Network(tenant_id, name)
|
||||
session.add(net)
|
||||
session.flush()
|
||||
return net
|
||||
|
||||
|
||||
def network_list(tenant_id):
|
||||
@@ -105,23 +115,18 @@ def network_get(net_id):
|
||||
return session.query(models.Network).\
|
||||
filter_by(uuid=net_id).\
|
||||
one()
|
||||
except exc.NoResultFound:
|
||||
raise Exception("No net found with id = %s" % net_id)
|
||||
except exc.NoResultFound, e:
|
||||
raise q_exc.NetworkNotFound(net_id=net_id)
|
||||
|
||||
|
||||
def network_rename(net_id, tenant_id, new_name):
|
||||
session = get_session()
|
||||
try:
|
||||
res = session.query(models.Network).\
|
||||
filter_by(tenant_id=tenant_id, name=new_name).\
|
||||
one()
|
||||
except exc.NoResultFound:
|
||||
net = network_get(net_id)
|
||||
net.name = new_name
|
||||
session.merge(net)
|
||||
session.flush()
|
||||
return net
|
||||
raise Exception("A network with name \"%s\" already exists" % new_name)
|
||||
net = network_get(net_id)
|
||||
_check_duplicate_net_name(tenant_id, new_name)
|
||||
net.name = new_name
|
||||
session.merge(net)
|
||||
session.flush()
|
||||
return net
|
||||
|
||||
|
||||
def network_destroy(net_id):
|
||||
@@ -134,10 +139,13 @@ def network_destroy(net_id):
|
||||
session.flush()
|
||||
return net
|
||||
except exc.NoResultFound:
|
||||
raise Exception("No network found with id = %s" % net_id)
|
||||
raise q_exc.NetworkNotFound(net_id=net_id)
|
||||
|
||||
|
||||
def port_create(net_id, state=None):
|
||||
# confirm network exists
|
||||
network_get(net_id)
|
||||
|
||||
session = get_session()
|
||||
with session.begin():
|
||||
port = models.Port(net_id)
|
||||
@@ -154,63 +162,90 @@ def port_list(net_id):
|
||||
all()
|
||||
|
||||
|
||||
def port_get(port_id):
|
||||
def port_get(port_id, net_id):
|
||||
# confirm network exists
|
||||
network_get(net_id)
|
||||
session = get_session()
|
||||
try:
|
||||
return session.query(models.Port).\
|
||||
filter_by(uuid=port_id).\
|
||||
filter_by(network_id=net_id).\
|
||||
one()
|
||||
except exc.NoResultFound:
|
||||
raise Exception("No port found with id = %s " % port_id)
|
||||
raise q_exc.PortNotFound(net_id=net_id, port_id=port_id)
|
||||
|
||||
|
||||
def port_set_state(port_id, new_state):
|
||||
port = port_get(port_id)
|
||||
if port:
|
||||
session = get_session()
|
||||
port.state = new_state
|
||||
session.merge(port)
|
||||
session.flush()
|
||||
return port
|
||||
def port_set_state(port_id, net_id, new_state):
|
||||
if new_state not in ('ACTIVE', 'DOWN'):
|
||||
raise q_exc.StateInvalid(port_state=new_state)
|
||||
|
||||
# confirm network exists
|
||||
network_get(net_id)
|
||||
|
||||
def port_set_attachment(port_id, new_interface_id):
|
||||
port = port_get(port_id, net_id)
|
||||
session = get_session()
|
||||
ports = []
|
||||
port.state = new_state
|
||||
session.merge(port)
|
||||
session.flush()
|
||||
return port
|
||||
|
||||
|
||||
def port_set_attachment(port_id, net_id, new_interface_id):
|
||||
# confirm network exists
|
||||
network_get(net_id)
|
||||
|
||||
session = get_session()
|
||||
port = port_get(port_id, net_id)
|
||||
|
||||
if new_interface_id != "":
|
||||
# We are setting, not clearing, the attachment-id
|
||||
if port['interface_id']:
|
||||
raise q_exc.PortInUse(net_id=net_id, port_id=port_id,
|
||||
att_id=port['interface_id'])
|
||||
|
||||
try:
|
||||
ports = session.query(models.Port).\
|
||||
port = session.query(models.Port).\
|
||||
filter_by(interface_id=new_interface_id).\
|
||||
all()
|
||||
one()
|
||||
raise q_exc.AlreadyAttached(net_id=net_id,
|
||||
port_id=port_id,
|
||||
att_id=new_interface_id,
|
||||
att_port_id=port['uuid'])
|
||||
except exc.NoResultFound:
|
||||
# this is what should happen
|
||||
pass
|
||||
if len(ports) == 0:
|
||||
port = port_get(port_id)
|
||||
port.interface_id = new_interface_id
|
||||
session.merge(port)
|
||||
session.flush()
|
||||
return port
|
||||
else:
|
||||
raise Exception("Port with attachment \"%s\" already exists"
|
||||
% (new_interface_id))
|
||||
port.interface_id = new_interface_id
|
||||
session.merge(port)
|
||||
session.flush()
|
||||
return port
|
||||
|
||||
|
||||
def port_unset_attachment(port_id):
|
||||
def port_unset_attachment(port_id, net_id):
|
||||
# confirm network exists
|
||||
network_get(net_id)
|
||||
|
||||
session = get_session()
|
||||
port = port_get(port_id)
|
||||
port = port_get(port_id, net_id)
|
||||
port.interface_id = None
|
||||
session.merge(port)
|
||||
session.flush
|
||||
session.flush()
|
||||
|
||||
|
||||
def port_destroy(port_id):
|
||||
def port_destroy(port_id, net_id):
|
||||
# confirm network exists
|
||||
network_get(net_id)
|
||||
|
||||
session = get_session()
|
||||
try:
|
||||
port = session.query(models.Port).\
|
||||
filter_by(uuid=port_id).\
|
||||
one()
|
||||
filter_by(uuid=port_id).\
|
||||
filter_by(network_id=net_id).\
|
||||
one()
|
||||
if port['interface_id']:
|
||||
raise q_exc.PortInUse(net_id=net_id, port_id=port_id,
|
||||
att_id=port['interface_id'])
|
||||
session.delete(port)
|
||||
session.flush()
|
||||
return port
|
||||
except exc.NoResultFound:
|
||||
raise Exception("No port found with id = %s " % port_id)
|
||||
raise q_exc.PortNotFound(port_id=port_id)
|
||||
|
||||
@@ -19,10 +19,12 @@
|
||||
|
||||
import uuid
|
||||
|
||||
|
||||
from sqlalchemy import Column, String, ForeignKey
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import relation, object_mapper
|
||||
|
||||
|
||||
BASE = declarative_base()
|
||||
|
||||
|
||||
|
||||
@@ -26,12 +26,13 @@ The caller should make sure that QuantumManager is a singleton.
|
||||
import gettext
|
||||
import logging
|
||||
import os
|
||||
|
||||
import logging
|
||||
gettext.install('quantum', unicode=1)
|
||||
|
||||
from common import utils
|
||||
from quantum_plugin_base import QuantumPluginBase
|
||||
|
||||
LOG = logging.getLogger('quantum.manager')
|
||||
CONFIG_FILE = "plugins.ini"
|
||||
LOG = logging.getLogger('quantum.manager')
|
||||
|
||||
@@ -44,6 +45,9 @@ def find_config(basepath):
|
||||
|
||||
|
||||
class QuantumManager(object):
|
||||
|
||||
_instance = None
|
||||
|
||||
def __init__(self, options=None, config_file=None):
|
||||
if config_file == None:
|
||||
self.configuration_file = find_config(
|
||||
@@ -66,5 +70,8 @@ class QuantumManager(object):
|
||||
"All compatibility tests passed")
|
||||
self.plugin = plugin_klass()
|
||||
|
||||
def get_plugin(self):
|
||||
return self.plugin
|
||||
@classmethod
|
||||
def get_plugin(cls, options=None, config_file=None):
|
||||
if cls._instance is None:
|
||||
cls._instance = cls(options, config_file)
|
||||
return cls._instance.plugin
|
||||
|
||||
@@ -119,6 +119,11 @@ class QuantumEchoPlugin(object):
|
||||
"""
|
||||
print("unplug_interface() called\n")
|
||||
|
||||
supported_extension_aliases = ["FOXNSOX"]
|
||||
|
||||
def method_to_support_foxnsox_extension(self):
|
||||
print("method_to_support_foxnsox_extension() called\n")
|
||||
|
||||
|
||||
class DummyDataPlugin(object):
|
||||
|
||||
@@ -240,7 +245,7 @@ class FakePlugin(object):
|
||||
def _get_port(self, tenant_id, network_id, port_id):
|
||||
net = self._get_network(tenant_id, network_id)
|
||||
try:
|
||||
port = db.port_get(port_id)
|
||||
port = db.port_get(port_id, network_id)
|
||||
except:
|
||||
raise exc.PortNotFound(net_id=network_id, port_id=port_id)
|
||||
# Port must exist and belong to the appropriate network.
|
||||
@@ -322,10 +327,7 @@ class FakePlugin(object):
|
||||
Virtual Network.
|
||||
"""
|
||||
LOG.debug("FakePlugin.rename_network() called")
|
||||
try:
|
||||
db.network_rename(net_id, tenant_id, new_name)
|
||||
except:
|
||||
raise exc.NetworkNotFound(net_id=net_id)
|
||||
db.network_rename(net_id, tenant_id, new_name)
|
||||
net = self._get_network(tenant_id, net_id)
|
||||
return net
|
||||
|
||||
@@ -373,7 +375,7 @@ class FakePlugin(object):
|
||||
self._get_network(tenant_id, net_id)
|
||||
self._get_port(tenant_id, net_id, port_id)
|
||||
self._validate_port_state(new_state)
|
||||
db.port_set_state(port_id, new_state)
|
||||
db.port_set_state(port_id, net_id, new_state)
|
||||
port_item = {'port-id': port_id,
|
||||
'port-state': new_state}
|
||||
return port_item
|
||||
@@ -392,7 +394,7 @@ class FakePlugin(object):
|
||||
raise exc.PortInUse(net_id=net_id, port_id=port_id,
|
||||
att_id=port['interface_id'])
|
||||
try:
|
||||
port = db.port_destroy(port_id)
|
||||
port = db.port_destroy(port_id, net_id)
|
||||
except Exception, e:
|
||||
raise Exception("Failed to delete port: %s" % str(e))
|
||||
d = {}
|
||||
@@ -412,7 +414,7 @@ class FakePlugin(object):
|
||||
if port['interface_id']:
|
||||
raise exc.PortInUse(net_id=net_id, port_id=port_id,
|
||||
att_id=port['interface_id'])
|
||||
db.port_set_attachment(port_id, remote_interface_id)
|
||||
db.port_set_attachment(port_id, net_id, remote_interface_id)
|
||||
|
||||
def unplug_interface(self, tenant_id, net_id, port_id):
|
||||
"""
|
||||
@@ -423,4 +425,4 @@ class FakePlugin(object):
|
||||
self._get_port(tenant_id, net_id, port_id)
|
||||
# TODO(salvatore-orlando):
|
||||
# Should unplug on port without attachment raise an Error?
|
||||
db.port_unset_attachment(port_id)
|
||||
db.port_unset_attachment(port_id, net_id)
|
||||
|
||||
236
quantum/plugins/cisco/README
Executable file
236
quantum/plugins/cisco/README
Executable file
@@ -0,0 +1,236 @@
|
||||
=====================================================================
|
||||
README: A Framework for a Quantum Plugin Supporting Multiple Switches
|
||||
=====================================================================
|
||||
|
||||
:Author: Sumit Naiksatam, Ram Durairaj, Mark Voelker, Edgar Magana, Shweta Padubidri, Rohit Agarwalla, Ying Liu
|
||||
:Contact: netstack@lists.launchpad.net
|
||||
:Web site: https://launchpad.net/~cisco-openstack
|
||||
:Copyright: 2011 Cisco Systems, Inc.
|
||||
|
||||
.. contents::
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
This plugin implementation provides the following capabilities
|
||||
to help you take your Layer 2 network for a Quantum leap:
|
||||
|
||||
* A reference implementation a framework for a Quantum Plugin
|
||||
to use multiple devices/switches in a L2 network
|
||||
* Supports multiple models of switches concurrently
|
||||
* Supports Cisco UCS blade servers with M81KR Virtual Interface Cards
|
||||
(aka "Palo adapters") via 802.1Qbh.
|
||||
* Supports the Cisco Nexus family of switches.
|
||||
|
||||
It does not provide:
|
||||
|
||||
* A hologram of Al that only you can see.
|
||||
* A map to help you find your way through time.
|
||||
* A cure for amnesia or your swiss-cheesed brain.
|
||||
|
||||
Let's leap in!
|
||||
|
||||
Pre-requisites
|
||||
--------------
|
||||
(The following are necessary only when using the UCS and/or Nexus devices in your system.
|
||||
If you plan to just leverage the plugin framework, you do not need these.)
|
||||
* One or more UCS B200 series blade servers with M81KR VIC (aka
|
||||
Palo adapters) installed.
|
||||
* UCSM 2.0 (Capitola) Build 230 or above.
|
||||
* OpenStack Cactus release installation (additional patch is required,
|
||||
details follow in this document)
|
||||
* RHEL 6.1 (as of this writing, UCS only officially supports RHEL, but
|
||||
it should be noted that Ubuntu support is planned in coming releases as well)
|
||||
** Package: python-configobj-4.6.0-3.el6.noarch (or newer)
|
||||
** Package: python-routes-1.12.3-2.el6.noarch (or newer)
|
||||
|
||||
If you are using a Nexus switch in your topology, you'll need the following
|
||||
NX-OS version and packages to enable Nexus support:
|
||||
* NX-OS 5.2.1 (Delhi) Build 69 or above.
|
||||
* paramiko library - SSHv2 protocol library for python
|
||||
** To install on RHEL 6.1, run: yum install python-paramiko
|
||||
* ncclient v0.3.1 - Python library for NETCONF clients
|
||||
** RedHat does not provide a package for ncclient in RHEL 6.1. Here is how
|
||||
to get it, from your shell prompt do:
|
||||
|
||||
git clone git@github.com:ddutta/ncclient.git
|
||||
sudo python ./setup.py install
|
||||
|
||||
** For more information of ncclient, see:
|
||||
http://schmizz.net/ncclient/
|
||||
|
||||
To verify the version of any package you have installed on your system,
|
||||
run "rpm -qav | grep <package name>", where <package name> is the
|
||||
package you want to query (for example: python-routes).
|
||||
|
||||
Note that you can get access to recent versions of the packages above
|
||||
and other OpenStack software packages by adding a new repository to
|
||||
your yum configuration. To do so, edit or create
|
||||
/etc/yum.repos.d/openstack.repo and add the following:
|
||||
|
||||
[openstack-deps]
|
||||
name=OpenStack Nova Compute Dependencies
|
||||
baseurl=http://yum.griddynamics.net/yum/cactus/deps
|
||||
enabled=1
|
||||
gpgcheck=1
|
||||
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-OPENSTACK
|
||||
|
||||
Then run "yum install python-routes".
|
||||
|
||||
|
||||
Module Structure:
|
||||
-----------------
|
||||
* quantum/plugins/cisco/ - Contains the L2-Network Plugin Framework
|
||||
/common - Modules common to the entire plugin
|
||||
/conf - All configuration files
|
||||
/db - Persistence framework
|
||||
/nexus - Nexus-specific modules
|
||||
/tests - Tests specific to this plugin
|
||||
/ucs - UCS-specific modules
|
||||
|
||||
|
||||
Plugin Installation Instructions
|
||||
----------------------------------
|
||||
1. Make a backup copy of quantum/quantum/plugins.ini.
|
||||
|
||||
2. Edit quantum/quantum/plugins.ini and edit the "provider" entry to point
|
||||
to the L2Network-plugin:
|
||||
|
||||
provider = quantum.plugins.cisco.l2network_plugin.L2Network
|
||||
|
||||
3. If you are not running Quantum on the same host as the OpenStack Cloud
|
||||
Controller, you will need to change the db_ip_address configuration
|
||||
in nova.ini.
|
||||
|
||||
4. If you want to turn on support for Cisco Nexus switches:
|
||||
4a. Uncomment the nexus_plugin property in
|
||||
quantum/plugins/cisco/conf/plugins.ini to read:
|
||||
|
||||
nexus_plugin=quantum.plugins.cisco.nexus.cisco_nexus_plugin.NexusPlugin
|
||||
|
||||
4b. Enter the relevant configuration in the
|
||||
quantum/plugins/cisco/conf/nexus.ini file. Example:
|
||||
|
||||
[SWITCH]
|
||||
# Change the following to reflect the IP address of the Nexus switch.
|
||||
# This will be the address at which Quantum sends and receives configuration
|
||||
# information via SSHv2.
|
||||
nexus_ip_address=10.0.0.1
|
||||
# Port number on the Nexus switch to which the UCSM 6120 is connected
|
||||
# Use shortened interface syntax, e.g. "3/23" not "Ethernet3/23".
|
||||
nexus_port=3/23
|
||||
|
||||
[DRIVER]
|
||||
name=quantum.plugins.cisco.nexus.cisco_nexus_network_driver.CiscoNEXUSDriver
|
||||
|
||||
4c. Make sure that SSH host key of the Nexus switch is known to the
|
||||
host on which you are running the Quantum service. You can do
|
||||
this simply by logging in to your Quantum host as the user that
|
||||
Quantum runs as and SSHing to the switch at least once. If the
|
||||
host key changes (e.g. due to replacement of the supervisor or
|
||||
clearing of the SSH config on the switch), you may need to repeat
|
||||
this step and remove the old hostkey from ~/.ssh/known_hosts.
|
||||
|
||||
5. Verify that you have the correct credentials for each IP address listed
|
||||
in quantum/plugins/cisco/conf/credentials.ini. Example:
|
||||
|
||||
# Provide the UCSM credentials
|
||||
# UCSM IP address, username and password.
|
||||
[10.0.0.2]
|
||||
username=admin
|
||||
password=mySecretPasswordForUCSM
|
||||
|
||||
# Provide the Nova DB credentials.
|
||||
# The IP address should be the same as in nova.ini.
|
||||
[10.0.0.3]
|
||||
username=nova
|
||||
password=mySecretPasswordForNova
|
||||
|
||||
# Provide the Nexus credentials, if you are using Nexus switches.
|
||||
# If not this will be ignored.
|
||||
[10.0.0.1]
|
||||
username=admin
|
||||
password=mySecretPasswordForNexus
|
||||
|
||||
6. Start the Quantum service. If something doesn't work, verify that
|
||||
your configuration of each of the above files hasn't gone a little kaka.
|
||||
Once you've put right what once went wrong, leap on.
|
||||
|
||||
|
||||
How to test the installation
|
||||
----------------------------
|
||||
The unit tests are located at quantum/plugins/cisco/tests/unit. They can be
|
||||
executed from quantum/plugins/cisco/ using the run_tests.py script.
|
||||
|
||||
1. Testing the core API (without UCS/Nexus/RHEL hardware, and can be run on
|
||||
Ubuntu):
|
||||
First disable all device-specific plugins by commenting out the entries in:
|
||||
quantum/plugins/cisco/conf/plugins.ini
|
||||
Then run the test script:
|
||||
|
||||
python run_tests.py unit.test_l2networkApi
|
||||
|
||||
2. Specific Plugin unit test (needs environment setup as indicated in the
|
||||
pre-requisites):
|
||||
python run_tests.py unit.<name_of_the file>
|
||||
E.g.:
|
||||
|
||||
python run_tests.py unit.test_ucs_plugin.py
|
||||
|
||||
3. All unit tests (needs environment setup as indicated in the pre-requisites):
|
||||
|
||||
python run_tests.py unit
|
||||
|
||||
|
||||
Additional installation required on Nova Compute
|
||||
------------------------------------------------
|
||||
1. Create a table in the "nova" database for ports. This can be
|
||||
accomplished with the following SQL statement:
|
||||
|
||||
CREATE TABLE ports (
|
||||
port_id VARCHAR(255) PRIMARY KEY,
|
||||
profile_name VARCHAR(255),
|
||||
dynamic_vnic VARCHAR(255),
|
||||
host VARCHAR(255),
|
||||
instance_name VARCHAR(255),
|
||||
instance_nic_name VARCHAR(255),
|
||||
used TINYINT(1)
|
||||
);
|
||||
|
||||
Assuming you're using MySQL, you can run the following command from a
|
||||
shell prompt on the Cloud Controller node to create the table:
|
||||
|
||||
mysql -uroot -p nova -e 'create table ports (port_id VARCHAR(255) primary key, profile_name VARCHAR(255), dynamic_vnic VARCHAR(255), host VARCHAR(255), instance_name VARCHAR(255), instance_nic_name VARCHAR(255), used tinyint(1));'
|
||||
|
||||
You'll be prompted for a password.
|
||||
|
||||
2. A patch is available for the Cactus release in this branch:
|
||||
https://code.launchpad.net/~snaiksat/quantum/cactus-ucs-support
|
||||
replace the following file in your installation:
|
||||
/usr/lib/python2.6/site-packages/nova/virt/libvirt_conn.py
|
||||
with the file from the branch:
|
||||
nova/virt/libvirt_conn.py
|
||||
|
||||
3. Add the following file from the Cisco Nova branch:
|
||||
nova/virt/cisco_ucs.py
|
||||
to:
|
||||
/usr/lib/python2.6/site-packages/nova/virt/cisco_ucs.py
|
||||
|
||||
4. Add the 802.1Qbh specific libvirt template file, from:
|
||||
nova/virt/libvirt-qbh.xml.template
|
||||
to:
|
||||
/usr/share/nova/libvirt-qbh.xml.template
|
||||
|
||||
5. Edit /etc/nova.conf to set the libvirt XML template to the above template:
|
||||
--libvirt_xml_template=/usr/share/nova/libvirt-qbh.xml.template
|
||||
|
||||
6. Restart the nova-compute service.
|
||||
|
||||
(Note that the requirement for the above patch is temporary and will go away
|
||||
with the integration with OpenStack Diablo. A 802.1Qbh-specific VIF driver
|
||||
will be made available as per the specification here:
|
||||
http://wiki.openstack.org/network-refactoring#VIF_driver)
|
||||
|
||||
Bingo bango bongo! That's it! Thanks for taking the leap into Quantum.
|
||||
|
||||
...Oh, boy!
|
||||
18
quantum/plugins/cisco/__init__.py
Normal file
18
quantum/plugins/cisco/__init__.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2011 Cisco Systems, 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.
|
||||
#
|
||||
# @author: Sumit Naiksatam, Cisco Systems, Inc.
|
||||
#
|
||||
18
quantum/plugins/cisco/common/__init__.py
Normal file
18
quantum/plugins/cisco/common/__init__.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2011 Cisco Systems, 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.
|
||||
#
|
||||
# @author: Sumit Naiksatam, Cisco Systems, Inc.
|
||||
#
|
||||
40
quantum/plugins/cisco/common/cisco_configparser.py
Normal file
40
quantum/plugins/cisco/common/cisco_configparser.py
Normal file
@@ -0,0 +1,40 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2011 Cisco Systems, 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.
|
||||
#
|
||||
# @author: Sumit Naiksatam, Cisco Systems, Inc.
|
||||
#
|
||||
|
||||
import logging as LOG
|
||||
import os
|
||||
|
||||
from configobj import ConfigObj
|
||||
from validate import Validator
|
||||
|
||||
from quantum.plugins.cisco.common import cisco_constants as const
|
||||
from quantum.plugins.cisco.common import cisco_exceptions as cexc
|
||||
|
||||
LOG.basicConfig(level=LOG.WARN)
|
||||
LOG.getLogger(const.LOGGER_COMPONENT_NAME)
|
||||
|
||||
|
||||
class CiscoConfigParser(ConfigObj):
|
||||
|
||||
def __init__(self, filename):
|
||||
super(CiscoConfigParser, self).__init__(filename, raise_errors=True,
|
||||
file_error=True)
|
||||
|
||||
def dummy(self, section, key):
|
||||
return section[key]
|
||||
103
quantum/plugins/cisco/common/cisco_constants.py
Normal file
103
quantum/plugins/cisco/common/cisco_constants.py
Normal file
@@ -0,0 +1,103 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2011 Cisco Systems, 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.
|
||||
#
|
||||
# @author: Sumit Naiksatam, Cisco Systems, Inc.
|
||||
#
|
||||
|
||||
PLUGINS = 'PLUGINS'
|
||||
|
||||
PORT_STATE = 'port-state'
|
||||
PORT_UP = "UP"
|
||||
PORT_DOWN = "DOWN"
|
||||
|
||||
ATTACHMENT = 'attachment'
|
||||
PORT_ID = 'port-id'
|
||||
|
||||
NET_ID = 'net-id'
|
||||
NET_NAME = 'net-name'
|
||||
NET_PORTS = 'net-ports'
|
||||
NET_VLAN_NAME = 'net-vlan-name'
|
||||
NET_VLAN_ID = 'net-vlan-id'
|
||||
NET_TENANTS = 'net-tenants'
|
||||
|
||||
TENANT_ID = 'tenant-id'
|
||||
TENANT_NETWORKS = 'tenant-networks'
|
||||
TENANT_NAME = 'tenant-name'
|
||||
TENANT_PORTPROFILES = 'tenant-portprofiles'
|
||||
TENANT_QOS_LEVELS = 'tenant-qos-levels'
|
||||
TENANT_CREDENTIALS = 'tenant-credentials'
|
||||
|
||||
PORT_PROFILE = 'port-profile'
|
||||
PROFILE_ID = 'profile-id'
|
||||
PROFILE_NAME = 'profile-name'
|
||||
PROFILE_VLAN_NAME = 'profile-vlan-name'
|
||||
PROFILE_VLAN_ID = 'vlan-id'
|
||||
PROFILE_QOS = 'profile-qos'
|
||||
PROFILE_ASSOCIATIONS = 'assignment'
|
||||
|
||||
QOS_LEVEL_ID = 'qos-id'
|
||||
QOS_LEVEL_NAME = 'qos-name'
|
||||
QOS_LEVEL_ASSOCIATIONS = 'qos-level-associations'
|
||||
QOS_LEVEL_DESCRIPTION = 'qos-desc'
|
||||
|
||||
CREDENTIAL_ID = 'credential-id'
|
||||
CREDENTIAL_NAME = 'credential-name'
|
||||
CREDENTIAL_USERNAME = 'credential-username'
|
||||
CREDENTIAL_PASSWORD = 'credential-password'
|
||||
MASKED_PASSWORD = '********'
|
||||
|
||||
USERNAME = 'username'
|
||||
PASSWORD = 'password'
|
||||
|
||||
LOGGER_COMPONENT_NAME = "cisco_plugin"
|
||||
|
||||
BLADE_INTF_DN = "blade-intf-distinguished-name"
|
||||
BLADE_INTF_ORDER = "blade-intf-order"
|
||||
BLADE_INTF_LINK_STATE = "blade-intf-link-state"
|
||||
BLADE_INTF_OPER_STATE = "blade-intf-operational-state"
|
||||
BLADE_INTF_INST_TYPE = "blade-intf-inst-type"
|
||||
BLADE_INTF_RHEL_DEVICE_NAME = "blade-intf-rhel-device-name"
|
||||
BLADE_INTF_DYNAMIC = "dynamic"
|
||||
BLADE_INTF_STATE_UNKNOWN = "unknown"
|
||||
BLADE_INTF_STATE_UNALLOCATED = "unallocated"
|
||||
BLADE_INTF_RESERVED = "blade-intf-reserved"
|
||||
BLADE_INTF_UNRESERVED = "blade-intf-unreserved"
|
||||
BLADE_INTF_RESERVATION = "blade-intf-reservation-status"
|
||||
BLADE_UNRESERVED_INTF_COUNT = "blade-unreserved-interfaces-count"
|
||||
BLADE_INTF_DATA = "blade-intf-data"
|
||||
|
||||
LEAST_RSVD_BLADE_UCSM = "least-reserved-blade-ucsm"
|
||||
LEAST_RSVD_BLADE_CHASSIS = "least-reserved-blade-chassis"
|
||||
LEAST_RSVD_BLADE_ID = "least-reserved-blade-id"
|
||||
LEAST_RSVD_BLADE_DATA = "least-reserved-blade-data"
|
||||
|
||||
RESERVED_NIC_HOSTNAME = "reserved-dynamic-nic-hostname"
|
||||
RESERVED_NIC_NAME = "reserved-dynamic-nic-device-name"
|
||||
|
||||
RESERVED_INTERFACE_UCSM = "reserved-interface-ucsm-ip"
|
||||
RESERVED_INTERFACE_CHASSIS = "reserved-interface-chassis"
|
||||
RESERVED_INTERFACE_BLADE = "reserved-interface-blade"
|
||||
RESERVED_INTERFACE_DN = "reserved-interface-dn"
|
||||
|
||||
RHEL_DEVICE_NAME_REPFIX = "eth"
|
||||
|
||||
UCS_PLUGIN = 'ucs_plugin'
|
||||
NEXUS_PLUGIN = 'nexus_plugin'
|
||||
|
||||
PLUGIN_OBJ_REF = 'plugin-obj-ref'
|
||||
PARAM_LIST = 'param-list'
|
||||
|
||||
DEVICE_IP = 'device-ip'
|
||||
60
quantum/plugins/cisco/common/cisco_credentials.py
Normal file
60
quantum/plugins/cisco/common/cisco_credentials.py
Normal file
@@ -0,0 +1,60 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2011 Cisco Systems, 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.
|
||||
#
|
||||
# @author: Sumit Naiksatam, Cisco Systems, Inc.
|
||||
#
|
||||
|
||||
import logging as LOG
|
||||
import os
|
||||
|
||||
from quantum.plugins.cisco.common import cisco_constants as const
|
||||
from quantum.plugins.cisco.common import cisco_configparser as confp
|
||||
|
||||
LOG.basicConfig(level=LOG.WARN)
|
||||
LOG.getLogger(const.LOGGER_COMPONENT_NAME)
|
||||
|
||||
CREDENTIALS_FILE = "../conf/credentials.ini"
|
||||
|
||||
cp = confp.CiscoConfigParser(os.path.dirname(os.path.realpath(__file__)) \
|
||||
+ "/" + CREDENTIALS_FILE)
|
||||
_creds_dictionary = cp.walk(cp.dummy)
|
||||
|
||||
|
||||
class Store(object):
|
||||
@staticmethod
|
||||
def putCredential(id, username, password):
|
||||
_creds_dictionary[id] = {const.USERNAME: username,
|
||||
const.PASSWORD: password}
|
||||
|
||||
@staticmethod
|
||||
def getUsername(id):
|
||||
return _creds_dictionary[id][const.USERNAME]
|
||||
|
||||
@staticmethod
|
||||
def getPassword(id):
|
||||
return _creds_dictionary[id][const.PASSWORD]
|
||||
|
||||
@staticmethod
|
||||
def getCredential(id):
|
||||
return _creds_dictionary[id]
|
||||
|
||||
@staticmethod
|
||||
def getCredentials():
|
||||
return _creds_dictionary
|
||||
|
||||
@staticmethod
|
||||
def deleteCredential(id):
|
||||
return _creds_dictionary.pop(id)
|
||||
57
quantum/plugins/cisco/common/cisco_exceptions.py
Normal file
57
quantum/plugins/cisco/common/cisco_exceptions.py
Normal file
@@ -0,0 +1,57 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2011 Cisco Systems, 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.
|
||||
#
|
||||
# @author: Sumit Naiksatam, Cisco Systems, Inc.
|
||||
#
|
||||
|
||||
"""
|
||||
Exceptions used by the Cisco plugin
|
||||
"""
|
||||
|
||||
from quantum.common import exceptions
|
||||
|
||||
|
||||
class NoMoreNics(exceptions.QuantumException):
|
||||
message = _("Unable to complete operation on port %(port_id)s " \
|
||||
"for network %(net_id)s. No more dynamic nics are available" \
|
||||
"in the system.")
|
||||
|
||||
|
||||
class PortProfileLimit(exceptions.QuantumException):
|
||||
message = _("Unable to complete operation on port %(port_id)s " \
|
||||
"for network %(net_id)s. The system has reached the maximum" \
|
||||
"limit of allowed port profiles.")
|
||||
|
||||
|
||||
class UCSMPortProfileLimit(exceptions.QuantumException):
|
||||
message = _("Unable to complete operation on port %(port_id)s " \
|
||||
"for network %(net_id)s. The system has reached the maximum" \
|
||||
"limit of allowed UCSM port profiles.")
|
||||
|
||||
|
||||
class NetworksLimit(exceptions.QuantumException):
|
||||
message = _("Unable to create new network. Number of networks" \
|
||||
"for the system has exceeded the limit")
|
||||
|
||||
|
||||
class PortProfileNotFound(exceptions.QuantumException):
|
||||
message = _("Port profile %(portprofile_id)s could not be found " \
|
||||
"for tenant %(tenant_id)s")
|
||||
|
||||
|
||||
class PortProfileInvalidDelete(exceptions.QuantumException):
|
||||
message = _("Port profile %(profile_id)s could not be deleted " \
|
||||
"for tenant %(tenant_id)s since port associations exist")
|
||||
35
quantum/plugins/cisco/common/cisco_nova_configuration.py
Normal file
35
quantum/plugins/cisco/common/cisco_nova_configuration.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2011 Cisco Systems, 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.
|
||||
#
|
||||
# @author: Sumit Naiksatam, Cisco Systems, Inc.
|
||||
#
|
||||
|
||||
import os
|
||||
|
||||
from quantum.plugins.cisco.common import cisco_configparser as confp
|
||||
|
||||
CONF_FILE = "../conf/nova.ini"
|
||||
|
||||
cp = confp.CiscoConfigParser(os.path.dirname(os.path.realpath(__file__)) \
|
||||
+ "/" + CONF_FILE)
|
||||
|
||||
section = cp['NOVA']
|
||||
DB_SERVER_IP = section['db_server_ip']
|
||||
DB_NAME = section['db_name']
|
||||
DB_USERNAME = section['db_username']
|
||||
DB_PASSWORD = section['db_password']
|
||||
NOVA_HOST_NAME = section['nova_host_name']
|
||||
NOVA_PROJ_NAME = section['nova_proj_name']
|
||||
59
quantum/plugins/cisco/common/cisco_utils.py
Normal file
59
quantum/plugins/cisco/common/cisco_utils.py
Normal file
@@ -0,0 +1,59 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2011 Cisco Systems, 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.
|
||||
#
|
||||
# @author: Sumit Naiksatam, Cisco Systems, Inc.
|
||||
#
|
||||
|
||||
import MySQLdb
|
||||
import logging as LOG
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from quantum.common import exceptions as exc
|
||||
from quantum.plugins.cisco.common import cisco_constants as const
|
||||
from quantum.plugins.cisco.common import cisco_credentials as cred
|
||||
from quantum.plugins.cisco.common import cisco_nova_configuration as conf
|
||||
|
||||
LOG.basicConfig(level=LOG.WARN)
|
||||
LOG.getLogger(const.LOGGER_COMPONENT_NAME)
|
||||
|
||||
|
||||
class DBUtils(object):
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def _get_db_connection(self):
|
||||
db_ip = conf.DB_SERVER_IP
|
||||
db_username = conf.DB_USERNAME
|
||||
db_password = conf.DB_PASSWORD
|
||||
self.db = MySQLdb.connect(db_ip, db_username, db_password,
|
||||
conf.DB_NAME)
|
||||
return self.db
|
||||
|
||||
def execute_db_query(self, sql_query):
|
||||
db = self._get_db_connection()
|
||||
cursor = db.cursor()
|
||||
try:
|
||||
cursor.execute(sql_query)
|
||||
results = cursor.fetchall()
|
||||
db.commit()
|
||||
LOG.debug("DB query execution succeeded: %s" % sql_query)
|
||||
except:
|
||||
db.rollback()
|
||||
LOG.debug("DB query execution failed: %s" % sql_query)
|
||||
traceback.print_exc()
|
||||
db.close()
|
||||
15
quantum/plugins/cisco/conf/credentials.ini
Normal file
15
quantum/plugins/cisco/conf/credentials.ini
Normal file
@@ -0,0 +1,15 @@
|
||||
#Provide the UCSM credentials
|
||||
[<put_ucsm_ip_address_here>]
|
||||
username=<put_user_name_here>
|
||||
password=<put_password_here>
|
||||
|
||||
#Provide the Nova DB credentials, the IP address should be the same as in nova.ini
|
||||
[<put_nova_db_ip_here>]
|
||||
username=<put_user_name_here>
|
||||
password=<put_password_here>
|
||||
|
||||
#Provide the Nexus credentials, if you are using Nexus
|
||||
[<put_nexus_ip_address_here>]
|
||||
username=<put_user_name_here>
|
||||
password=<put_password_here>
|
||||
|
||||
16
quantum/plugins/cisco/conf/l2network_plugin.ini
Normal file
16
quantum/plugins/cisco/conf/l2network_plugin.ini
Normal file
@@ -0,0 +1,16 @@
|
||||
[VLANS]
|
||||
vlan_start=<put_vlan_id_range_start_here>
|
||||
vlan_end=<put_vlan_id_range_end_here>
|
||||
vlan_name_prefix=q-
|
||||
|
||||
[PORTS]
|
||||
max_ports=100
|
||||
|
||||
[PORTPROFILES]
|
||||
max_port_profiles=65568
|
||||
|
||||
[NETWORKS]
|
||||
max_networks=65568
|
||||
|
||||
[MODEL]
|
||||
model_class=quantum.plugins.cisco.l2network_model.L2NetworkModel
|
||||
8
quantum/plugins/cisco/conf/nexus.ini
Normal file
8
quantum/plugins/cisco/conf/nexus.ini
Normal file
@@ -0,0 +1,8 @@
|
||||
[SWITCH]
|
||||
# Change the following to reflect the Nexus switch details
|
||||
nexus_ip_address=<put_nexus_switch_ip_address_here>
|
||||
#Port number of the Interface connected from the Nexus 7K Switch to UCSM 6120, e.g.: 3/23
|
||||
nexus_port=<put_interface_name_here>
|
||||
|
||||
[DRIVER]
|
||||
name=quantum.plugins.cisco.nexus.cisco_nexus_network_driver.CiscoNEXUSDriver
|
||||
8
quantum/plugins/cisco/conf/nova.ini
Normal file
8
quantum/plugins/cisco/conf/nova.ini
Normal file
@@ -0,0 +1,8 @@
|
||||
[NOVA]
|
||||
#Change the following details to reflect your OpenStack Nova configuration. If you are running this service on the same machine as the Nova DB, you do not have to change the IP address.
|
||||
db_server_ip=127.0.0.1
|
||||
db_name=nova
|
||||
db_username=<put_db_user_name_here>
|
||||
db_password=<put_db_password_here>
|
||||
nova_host_name=<put_openstack_cloud_controller_hostname_here>
|
||||
nova_proj_name=<put_openstack_project_name_here>
|
||||
3
quantum/plugins/cisco/conf/plugins.ini
Normal file
3
quantum/plugins/cisco/conf/plugins.ini
Normal file
@@ -0,0 +1,3 @@
|
||||
[PLUGINS]
|
||||
ucs_plugin=quantum.plugins.cisco.ucs.cisco_ucs_plugin.UCSVICPlugin
|
||||
#nexus_plugin=quantum.plugins.cisco.nexus.cisco_nexus_plugin.NexusPlugin
|
||||
10
quantum/plugins/cisco/conf/ucs.ini
Normal file
10
quantum/plugins/cisco/conf/ucs.ini
Normal file
@@ -0,0 +1,10 @@
|
||||
[UCSM]
|
||||
#change the following to the appropriate UCSM IP address
|
||||
ip_address=<put_ucsm_ip_address_here>
|
||||
default_vlan_name=default
|
||||
default_vlan_id=1
|
||||
max_ucsm_port_profiles=1024
|
||||
profile_name_prefix=q-
|
||||
|
||||
[DRIVER]
|
||||
name=quantum.plugins.cisco.ucs.cisco_ucs_network_driver.CiscoUCSMDriver
|
||||
18
quantum/plugins/cisco/db/__init__.py
Normal file
18
quantum/plugins/cisco/db/__init__.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2011 Cisco Systems, 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.
|
||||
#
|
||||
# @author: Sumit Naiksatam, Cisco Systems, Inc.
|
||||
#
|
||||
152
quantum/plugins/cisco/l2device_plugin_base.py
Normal file
152
quantum/plugins/cisco/l2device_plugin_base.py
Normal file
@@ -0,0 +1,152 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2011 Cisco Systems, 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.
|
||||
#
|
||||
# @author: Sumit Naiksatam, Cisco Systems, Inc.
|
||||
#
|
||||
|
||||
import inspect
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
|
||||
class L2DevicePluginBase(object):
|
||||
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
@abstractmethod
|
||||
def get_all_networks(self, tenant_id, **kwargs):
|
||||
"""
|
||||
:returns:
|
||||
:raises:
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def create_network(self, tenant_id, net_name, net_id, vlan_name, vlan_id,
|
||||
**kwargs):
|
||||
"""
|
||||
:returns:
|
||||
:raises:
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def delete_network(self, tenant_id, net_id, **kwargs):
|
||||
"""
|
||||
:returns:
|
||||
:raises:
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_network_details(self, tenant_id, net_id, **kwargs):
|
||||
"""
|
||||
:returns:
|
||||
:raises:
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def rename_network(self, tenant_id, net_id, new_name, **kwargs):
|
||||
"""
|
||||
:returns:
|
||||
:raises:
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_all_ports(self, tenant_id, net_id, **kwargs):
|
||||
"""
|
||||
:returns:
|
||||
:raises:
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def create_port(self, tenant_id, net_id, port_state, port_id, **kwargs):
|
||||
"""
|
||||
:returns:
|
||||
:raises:
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def delete_port(self, tenant_id, net_id, port_id, **kwargs):
|
||||
"""
|
||||
:returns:
|
||||
:raises:
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def update_port(self, tenant_id, net_id, port_id, port_state, **kwargs):
|
||||
"""
|
||||
:returns:
|
||||
:raises:
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_port_details(self, tenant_id, net_id, port_id, **kwargs):
|
||||
"""
|
||||
:returns:
|
||||
:raises:
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def plug_interface(self, tenant_id, net_id, port_id, remote_interface_id,
|
||||
**kwargs):
|
||||
"""
|
||||
:returns:
|
||||
:raises:
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def unplug_interface(self, tenant_id, net_id, port_id, **kwargs):
|
||||
"""
|
||||
:returns:
|
||||
:raises:
|
||||
"""
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def __subclasshook__(cls, klass):
|
||||
"""
|
||||
The __subclasshook__ method is a class method
|
||||
that will be called everytime a class is tested
|
||||
using issubclass(klass, Plugin).
|
||||
In that case, it will check that every method
|
||||
marked with the abstractmethod decorator is
|
||||
provided by the plugin class.
|
||||
"""
|
||||
if cls is QuantumPluginBase:
|
||||
for method in cls.__abstractmethods__:
|
||||
method_ok = False
|
||||
for base in klass.__mro__:
|
||||
if method in base.__dict__:
|
||||
fn_obj = base.__dict__[method]
|
||||
if inspect.isfunction(fn_obj):
|
||||
abstract_fn_obj = cls.__dict__[method]
|
||||
arg_count = fn_obj.func_code.co_argcount
|
||||
expected_arg_count = \
|
||||
abstract_fn_obj.func_code.co_argcount
|
||||
method_ok = arg_count == expected_arg_count
|
||||
if method_ok:
|
||||
continue
|
||||
return NotImplemented
|
||||
return True
|
||||
return NotImplemented
|
||||
100
quantum/plugins/cisco/l2network_model.py
Normal file
100
quantum/plugins/cisco/l2network_model.py
Normal file
@@ -0,0 +1,100 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2011 Cisco Systems, 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.
|
||||
#
|
||||
# @author: Sumit Naiksatam, Cisco Systems, Inc.
|
||||
#
|
||||
|
||||
import inspect
|
||||
import logging as LOG
|
||||
|
||||
from quantum.common import utils
|
||||
from quantum.plugins.cisco.l2network_model_base import L2NetworkModelBase
|
||||
from quantum.plugins.cisco import l2network_plugin_configuration as conf
|
||||
from quantum.plugins.cisco.common import cisco_constants as const
|
||||
|
||||
LOG.basicConfig(level=LOG.WARN)
|
||||
LOG.getLogger(const.LOGGER_COMPONENT_NAME)
|
||||
|
||||
|
||||
class L2NetworkModel(L2NetworkModelBase):
|
||||
_plugins = {}
|
||||
|
||||
def __init__(self):
|
||||
for key in conf.plugins[const.PLUGINS].keys():
|
||||
self._plugins[key] = utils.import_object(
|
||||
conf.plugins[const.PLUGINS][key])
|
||||
LOG.debug("Loaded device plugin %s\n" % \
|
||||
conf.plugins[const.PLUGINS][key])
|
||||
|
||||
def _funcName(self, offset=0):
|
||||
return inspect.stack()[1 + offset][3]
|
||||
|
||||
def _invokeAllDevicePlugins(self, function_name, args, kwargs):
|
||||
for pluginObjRef in self._plugins.values():
|
||||
getattr(pluginObjRef, function_name)(*args, **kwargs)
|
||||
|
||||
def _invokeUCSPlugin(self, function_name, args, kwargs):
|
||||
if const.UCS_PLUGIN in self._plugins.keys():
|
||||
getattr(self._plugins[const.UCS_PLUGIN],
|
||||
function_name)(*args, **kwargs)
|
||||
|
||||
def _invokeNexusPlugin(self, function_name, args, kwargs):
|
||||
if const.NEXUS_PLUGIN in self._plugins.keys():
|
||||
getattr(self._plugins[const.NEXUS_PLUGIN],
|
||||
function_name)(*args, **kwargs)
|
||||
|
||||
def get_all_networks(self, args):
|
||||
pass
|
||||
|
||||
def create_network(self, args):
|
||||
deviceParams = {const.DEVICE_IP: ""}
|
||||
self._invokeAllDevicePlugins(self._funcName(), args, deviceParams)
|
||||
|
||||
def delete_network(self, args):
|
||||
deviceParams = {const.DEVICE_IP: ""}
|
||||
self._invokeAllDevicePlugins(self._funcName(), args, deviceParams)
|
||||
|
||||
def get_network_details(self, args):
|
||||
pass
|
||||
|
||||
def rename_network(self, args):
|
||||
deviceParams = {const.DEVICE_IP: ""}
|
||||
self._invokeAllDevicePlugins(self._funcName(), args, deviceParams)
|
||||
|
||||
def get_all_ports(self, args):
|
||||
pass
|
||||
|
||||
def create_port(self, args):
|
||||
deviceParams = {const.DEVICE_IP: ""}
|
||||
self._invokeUCSPlugin(self._funcName(), args, deviceParams)
|
||||
|
||||
def delete_port(self, args):
|
||||
deviceParams = {const.DEVICE_IP: ""}
|
||||
self._invokeUCSPlugin(self._funcName(), args, deviceParams)
|
||||
|
||||
def update_port(self, args):
|
||||
pass
|
||||
|
||||
def get_port_details(self, args):
|
||||
pass
|
||||
|
||||
def plug_interface(self, args):
|
||||
deviceParams = {const.DEVICE_IP: ""}
|
||||
self._invokeUCSPlugin(self._funcName(), args, deviceParams)
|
||||
|
||||
def unplug_interface(self, args):
|
||||
deviceParams = {const.DEVICE_IP: ""}
|
||||
self._invokeUCSPlugin(self._funcName(), args, deviceParams)
|
||||
150
quantum/plugins/cisco/l2network_model_base.py
Normal file
150
quantum/plugins/cisco/l2network_model_base.py
Normal file
@@ -0,0 +1,150 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2011 Cisco Systems, 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.
|
||||
#
|
||||
# @author: Sumit Naiksatam, Cisco Systems, Inc.
|
||||
#
|
||||
|
||||
import inspect
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
|
||||
class L2NetworkModelBase(object):
|
||||
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
@abstractmethod
|
||||
def get_all_networks(self, args):
|
||||
"""
|
||||
:returns:
|
||||
:raises:
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def create_network(self, args):
|
||||
"""
|
||||
:returns:
|
||||
:raises:
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def delete_network(self, args):
|
||||
"""
|
||||
:returns:
|
||||
:raises:
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_network_details(self, args):
|
||||
"""
|
||||
:returns:
|
||||
:raises:
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def rename_network(self, args):
|
||||
"""
|
||||
:returns:
|
||||
:raises:
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_all_ports(self, args):
|
||||
"""
|
||||
:returns:
|
||||
:raises:
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def create_port(self, args):
|
||||
"""
|
||||
:returns:
|
||||
:raises:
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def delete_port(self, args):
|
||||
"""
|
||||
:returns:
|
||||
:raises:
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def update_port(self, args):
|
||||
"""
|
||||
:returns:
|
||||
:raises:
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_port_details(self, args):
|
||||
"""
|
||||
:returns:
|
||||
:raises:
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def plug_interface(self, args):
|
||||
"""
|
||||
:returns:
|
||||
:raises:
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def unplug_interface(self, args):
|
||||
"""
|
||||
:returns:
|
||||
:raises:
|
||||
"""
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def __subclasshook__(cls, klass):
|
||||
"""
|
||||
The __subclasshook__ method is a class method
|
||||
that will be called everytime a class is tested
|
||||
using issubclass(klass, Plugin).
|
||||
In that case, it will check that every method
|
||||
marked with the abstractmethod decorator is
|
||||
provided by the plugin class.
|
||||
"""
|
||||
if cls is QuantumPluginBase:
|
||||
for method in cls.__abstractmethods__:
|
||||
method_ok = False
|
||||
for base in klass.__mro__:
|
||||
if method in base.__dict__:
|
||||
fn_obj = base.__dict__[method]
|
||||
if inspect.isfunction(fn_obj):
|
||||
abstract_fn_obj = cls.__dict__[method]
|
||||
arg_count = fn_obj.func_code.co_argcount
|
||||
expected_arg_count = \
|
||||
abstract_fn_obj.func_code.co_argcount
|
||||
method_ok = arg_count == expected_arg_count
|
||||
if method_ok:
|
||||
continue
|
||||
return NotImplemented
|
||||
return True
|
||||
return NotImplemented
|
||||
394
quantum/plugins/cisco/l2network_plugin.py
Normal file
394
quantum/plugins/cisco/l2network_plugin.py
Normal file
@@ -0,0 +1,394 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2011 Cisco Systems, 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.
|
||||
#
|
||||
# @author: Sumit Naiksatam, Cisco Systems, Inc.
|
||||
#
|
||||
|
||||
import inspect
|
||||
import logging as LOG
|
||||
|
||||
from quantum.common import exceptions as exc
|
||||
from quantum.common import utils
|
||||
from quantum.quantum_plugin_base import QuantumPluginBase
|
||||
from quantum.plugins.cisco import l2network_plugin_configuration as conf
|
||||
from quantum.plugins.cisco.common import cisco_constants as const
|
||||
from quantum.plugins.cisco.common import cisco_exceptions as cexc
|
||||
|
||||
LOG.basicConfig(level=LOG.WARN)
|
||||
LOG.getLogger(const.LOGGER_COMPONENT_NAME)
|
||||
|
||||
|
||||
class L2Network(QuantumPluginBase):
|
||||
_networks = {}
|
||||
_tenants = {}
|
||||
_portprofiles = {}
|
||||
|
||||
def __init__(self):
|
||||
self._net_counter = 0
|
||||
self._portprofile_counter = 0
|
||||
self._port_counter = 0
|
||||
self._vlan_counter = int(conf.VLAN_START) - 1
|
||||
self._model = utils.import_object(conf.MODEL_CLASS)
|
||||
|
||||
"""
|
||||
Core API implementation
|
||||
"""
|
||||
def get_all_networks(self, tenant_id):
|
||||
"""
|
||||
Returns a dictionary containing all
|
||||
<network_uuid, network_name> for
|
||||
the specified tenant.
|
||||
"""
|
||||
LOG.debug("get_all_networks() called\n")
|
||||
self._invokeDevicePlugins(self._funcName(), [tenant_id])
|
||||
return self._networks.values()
|
||||
|
||||
def create_network(self, tenant_id, net_name):
|
||||
"""
|
||||
Creates a new Virtual Network, and assigns it
|
||||
a symbolic name.
|
||||
"""
|
||||
LOG.debug("create_network() called\n")
|
||||
new_net_id = self._get_unique_net_id(tenant_id)
|
||||
vlan_id = self._get_vlan_for_tenant(tenant_id, net_name)
|
||||
vlan_name = self._get_vlan_name(new_net_id, str(vlan_id))
|
||||
self._invokeDevicePlugins(self._funcName(), [tenant_id, net_name,
|
||||
new_net_id, vlan_name,
|
||||
vlan_id])
|
||||
new_net_dict = {const.NET_ID: new_net_id,
|
||||
const.NET_NAME: net_name,
|
||||
const.NET_PORTS: {},
|
||||
const.NET_VLAN_NAME: vlan_name,
|
||||
const.NET_VLAN_ID: vlan_id,
|
||||
const.NET_TENANTS: [tenant_id]}
|
||||
self._networks[new_net_id] = new_net_dict
|
||||
tenant = self._get_tenant(tenant_id)
|
||||
tenant_networks = tenant[const.TENANT_NETWORKS]
|
||||
tenant_networks[new_net_id] = new_net_dict
|
||||
return new_net_dict
|
||||
|
||||
def delete_network(self, tenant_id, net_id):
|
||||
"""
|
||||
Deletes the network with the specified network identifier
|
||||
belonging to the specified tenant.
|
||||
"""
|
||||
LOG.debug("delete_network() called\n")
|
||||
net = self._networks.get(net_id)
|
||||
if net:
|
||||
if len(net[const.NET_PORTS].values()) > 0:
|
||||
ports_on_net = net[const.NET_PORTS].values()
|
||||
for port in ports_on_net:
|
||||
if port[const.ATTACHMENT]:
|
||||
raise exc.NetworkInUse(net_id=net_id)
|
||||
for port in ports_on_net:
|
||||
self.delete_port(tenant_id, net_id, port[const.PORT_ID])
|
||||
|
||||
self._invokeDevicePlugins(self._funcName(), [tenant_id, net_id])
|
||||
self._networks.pop(net_id)
|
||||
tenant = self._get_tenant(tenant_id)
|
||||
tenant_networks = tenant[const.TENANT_NETWORKS]
|
||||
tenant_networks.pop(net_id)
|
||||
return net
|
||||
# Network not found
|
||||
raise exc.NetworkNotFound(net_id=net_id)
|
||||
|
||||
def get_network_details(self, tenant_id, net_id):
|
||||
"""
|
||||
Gets the details of a particular network
|
||||
"""
|
||||
LOG.debug("get_network_details() called\n")
|
||||
self._invokeDevicePlugins(self._funcName(), [tenant_id, net_id])
|
||||
network = self._get_network(tenant_id, net_id)
|
||||
ports_on_net = network[const.NET_PORTS].values()
|
||||
return {const.NET_ID: network[const.NET_ID],
|
||||
const.NET_NAME: network[const.NET_NAME],
|
||||
const.NET_PORTS: ports_on_net}
|
||||
|
||||
def rename_network(self, tenant_id, net_id, new_name):
|
||||
"""
|
||||
Updates the symbolic name belonging to a particular
|
||||
Virtual Network.
|
||||
"""
|
||||
LOG.debug("rename_network() called\n")
|
||||
self._invokeDevicePlugins(self._funcName(), [tenant_id, net_id,
|
||||
new_name])
|
||||
network = self._get_network(tenant_id, net_id)
|
||||
network[const.NET_NAME] = new_name
|
||||
return network
|
||||
|
||||
def get_all_ports(self, tenant_id, net_id):
|
||||
"""
|
||||
Retrieves all port identifiers belonging to the
|
||||
specified Virtual Network.
|
||||
"""
|
||||
LOG.debug("get_all_ports() called\n")
|
||||
self._invokeDevicePlugins(self._funcName(), [tenant_id, net_id])
|
||||
network = self._get_network(tenant_id, net_id)
|
||||
ports_on_net = network[const.NET_PORTS].values()
|
||||
return ports_on_net
|
||||
|
||||
def create_port(self, tenant_id, net_id, port_state=None):
|
||||
"""
|
||||
Creates a port on the specified Virtual Network.
|
||||
"""
|
||||
LOG.debug("create_port() called\n")
|
||||
net = self._get_network(tenant_id, net_id)
|
||||
ports = net[const.NET_PORTS]
|
||||
unique_port_id_string = self._get_unique_port_id(tenant_id, net_id)
|
||||
self._invokeDevicePlugins(self._funcName(), [tenant_id, net_id,
|
||||
port_state,
|
||||
unique_port_id_string])
|
||||
new_port_dict = {const.PORT_ID: unique_port_id_string,
|
||||
const.PORT_STATE: const.PORT_UP,
|
||||
const.ATTACHMENT: None}
|
||||
ports[unique_port_id_string] = new_port_dict
|
||||
return new_port_dict
|
||||
|
||||
def delete_port(self, tenant_id, net_id, port_id):
|
||||
"""
|
||||
Deletes a port on a specified Virtual Network,
|
||||
if the port contains a remote interface attachment,
|
||||
the remote interface should first be un-plugged and
|
||||
then the port can be deleted.
|
||||
"""
|
||||
LOG.debug("delete_port() called\n")
|
||||
port = self._get_port(tenant_id, net_id, port_id)
|
||||
if port[const.ATTACHMENT]:
|
||||
raise exc.PortInUse(net_id=net_id, port_id=port_id,
|
||||
att_id=port[const.ATTACHMENT])
|
||||
try:
|
||||
#TODO (Sumit): Before deleting port profile make sure that there
|
||||
# is no VM using this port profile
|
||||
self._invokeDevicePlugins(self._funcName(), [tenant_id, net_id,
|
||||
port_id])
|
||||
net = self._get_network(tenant_id, net_id)
|
||||
net[const.NET_PORTS].pop(port_id)
|
||||
except KeyError:
|
||||
raise exc.PortNotFound(net_id=net_id, port_id=port_id)
|
||||
|
||||
def update_port(self, tenant_id, net_id, port_id, port_state):
|
||||
"""
|
||||
Updates the state of a port on the specified Virtual Network.
|
||||
"""
|
||||
LOG.debug("update_port() called\n")
|
||||
self._invokeDevicePlugins(self._funcName(), [tenant_id, net_id,
|
||||
port_id, port_state])
|
||||
port = self._get_port(tenant_id, net_id, port_id)
|
||||
self._validate_port_state(port_state)
|
||||
port[const.PORT_STATE] = port_state
|
||||
return port
|
||||
|
||||
def get_port_details(self, tenant_id, net_id, port_id):
|
||||
"""
|
||||
This method allows the user to retrieve a remote interface
|
||||
that is attached to this particular port.
|
||||
"""
|
||||
LOG.debug("get_port_details() called\n")
|
||||
self._invokeDevicePlugins(self._funcName(), [tenant_id, net_id,
|
||||
port_id])
|
||||
return self._get_port(tenant_id, net_id, port_id)
|
||||
|
||||
def plug_interface(self, tenant_id, net_id, port_id,
|
||||
remote_interface_id):
|
||||
"""
|
||||
Attaches a remote interface to the specified port on the
|
||||
specified Virtual Network.
|
||||
"""
|
||||
LOG.debug("plug_interface() called\n")
|
||||
self._validate_attachment(tenant_id, net_id, port_id,
|
||||
remote_interface_id)
|
||||
port = self._get_port(tenant_id, net_id, port_id)
|
||||
if port[const.ATTACHMENT]:
|
||||
raise exc.PortInUse(net_id=net_id, port_id=port_id,
|
||||
att_id=port[const.ATTACHMENT])
|
||||
self._invokeDevicePlugins(self._funcName(), [tenant_id, net_id,
|
||||
port_id,
|
||||
remote_interface_id])
|
||||
port[const.ATTACHMENT] = remote_interface_id
|
||||
|
||||
def unplug_interface(self, tenant_id, net_id, port_id):
|
||||
"""
|
||||
Detaches a remote interface from the specified port on the
|
||||
specified Virtual Network.
|
||||
"""
|
||||
LOG.debug("unplug_interface() called\n")
|
||||
port = self._get_port(tenant_id, net_id, port_id)
|
||||
self._invokeDevicePlugins(self._funcName(), [tenant_id, net_id,
|
||||
port_id])
|
||||
port[const.ATTACHMENT] = None
|
||||
|
||||
"""
|
||||
Extension API implementation
|
||||
"""
|
||||
def get_all_portprofiles(self, tenant_id):
|
||||
return self._portprofiles.values()
|
||||
|
||||
def get_portprofile_details(self, tenant_id, profile_id):
|
||||
return self._get_portprofile(tenant_id, profile_id)
|
||||
|
||||
def create_portprofile(self, tenant_id, profile_name, vlan_id):
|
||||
profile_id = self._get_unique_profile_id(tenant_id)
|
||||
new_port_profile_dict = {const.PROFILE_ID: profile_id,
|
||||
const.PROFILE_NAME: profile_name,
|
||||
const.PROFILE_ASSOCIATIONS: [],
|
||||
const.PROFILE_VLAN_ID: vlan_id,
|
||||
const.PROFILE_QOS: None}
|
||||
self._portprofiles[profile_id] = new_port_profile_dict
|
||||
tenant = self._get_tenant(tenant_id)
|
||||
portprofiles = tenant[const.TENANT_PORTPROFILES]
|
||||
portprofiles[profile_id] = new_port_profile_dict
|
||||
return new_port_profile_dict
|
||||
|
||||
def delete_portprofile(self, tenant_id, profile_id):
|
||||
portprofile = self._get_portprofile(tenant_id, profile_id)
|
||||
associations = portprofile[const.PROFILE_ASSOCIATIONS]
|
||||
if len(associations) > 0:
|
||||
raise cexc.PortProfileInvalidDelete(tenant_id=tenant_id,
|
||||
profile_id=profile_id)
|
||||
else:
|
||||
self._portprofiles.pop(profile_id)
|
||||
tenant = self._get_tenant(tenant_id)
|
||||
tenant[const.TENANT_PORTPROFILES].pop(profile_id)
|
||||
|
||||
def rename_portprofile(self, tenant_id, profile_id, new_name):
|
||||
portprofile = self._get_portprofile(tenant_id, profile_id)
|
||||
portprofile[const.PROFILE_NAME] = new_name
|
||||
return portprofile
|
||||
|
||||
def associate_portprofile(self, tenant_id, net_id,
|
||||
port_id, portprofile_id):
|
||||
portprofile = self._get_portprofile(tenant_id, portprofile_id)
|
||||
associations = portprofile[const.PROFILE_ASSOCIATIONS]
|
||||
associations.append(port_id)
|
||||
|
||||
def disassociate_portprofile(self, tenant_id, net_id,
|
||||
port_id, portprofile_id):
|
||||
portprofile = self._get_portprofile(tenant_id, portprofile_id)
|
||||
associations = portprofile[const.PROFILE_ASSOCIATIONS]
|
||||
associations.remove(port_id)
|
||||
|
||||
def create_defaultPProfile(self, tenant_id, network_id, profile_name,
|
||||
vlan_id):
|
||||
pass
|
||||
|
||||
"""
|
||||
Private functions
|
||||
"""
|
||||
def _invokeDevicePlugins(self, function_name, args):
|
||||
"""
|
||||
All device-specific calls are delegate to the model
|
||||
"""
|
||||
getattr(self._model, function_name)(args)
|
||||
|
||||
def _get_vlan_for_tenant(self, tenant_id, net_name):
|
||||
# TODO (Sumit):
|
||||
# The VLAN ID for a tenant might need to be obtained from
|
||||
# somewhere (from Donabe/Melange?)
|
||||
# Also need to make sure that the VLAN ID is not being used already
|
||||
# Currently, just a wrap-around counter ranging from VLAN_START to
|
||||
# VLAN_END
|
||||
self._vlan_counter += 1
|
||||
self._vlan_counter %= int(conf.VLAN_END)
|
||||
if self._vlan_counter < int(conf.VLAN_START):
|
||||
self._vlan_counter = int(conf.VLAN_START)
|
||||
return self._vlan_counter
|
||||
|
||||
def _get_vlan_name(self, net_id, vlan):
|
||||
vlan_name = conf.VLAN_NAME_PREFIX + net_id + "-" + vlan
|
||||
return vlan_name
|
||||
|
||||
def _validate_port_state(self, port_state):
|
||||
if port_state.upper() not in (const.PORT_UP, const.PORT_DOWN):
|
||||
raise exc.StateInvalid(port_state=port_state)
|
||||
return True
|
||||
|
||||
def _validate_attachment(self, tenant_id, network_id, port_id,
|
||||
remote_interface_id):
|
||||
network = self._get_network(tenant_id, network_id)
|
||||
for port in network[const.NET_PORTS].values():
|
||||
if port[const.ATTACHMENT] == remote_interface_id:
|
||||
raise exc.AlreadyAttached(net_id=network_id,
|
||||
port_id=port_id,
|
||||
att_id=port[const.ATTACHMENT],
|
||||
att_port_id=port[const.PORT_ID])
|
||||
|
||||
def _get_network(self, tenant_id, network_id):
|
||||
network = self._networks.get(network_id)
|
||||
if not network:
|
||||
raise exc.NetworkNotFound(net_id=network_id)
|
||||
return network
|
||||
|
||||
def _get_tenant(self, tenant_id):
|
||||
tenant = self._tenants.get(tenant_id)
|
||||
if not tenant:
|
||||
LOG.debug("Creating new tenant record with tenant id %s\n" %
|
||||
tenant_id)
|
||||
tenant = {const.TENANT_ID: tenant_id,
|
||||
const.TENANT_NAME: tenant_id,
|
||||
const.TENANT_NETWORKS: {},
|
||||
const.TENANT_PORTPROFILES: {}}
|
||||
self._tenants[tenant_id] = tenant
|
||||
return tenant
|
||||
|
||||
def _get_port(self, tenant_id, network_id, port_id):
|
||||
net = self._get_network(tenant_id, network_id)
|
||||
port = net[const.NET_PORTS].get(port_id)
|
||||
if not port:
|
||||
raise exc.PortNotFound(net_id=network_id, port_id=port_id)
|
||||
return port
|
||||
|
||||
def _get_portprofile(self, tenant_id, portprofile_id):
|
||||
portprofile = self._portprofiles.get(portprofile_id)
|
||||
if not portprofile:
|
||||
raise cexc.PortProfileNotFound(tenant_id=tenant_id,
|
||||
portprofile_id=portprofile_id)
|
||||
return portprofile
|
||||
|
||||
def _get_unique_net_id(self, tenant_id):
|
||||
self._net_counter += 1
|
||||
self._net_counter %= int(conf.MAX_NETWORKS)
|
||||
id = tenant_id[:3] + \
|
||||
"-n-" + ("0" * (6 - len(str(self._net_counter)))) + \
|
||||
str(self._net_counter)
|
||||
# TODO (Sumit): Need to check if the ID has already been allocated
|
||||
# ID will be generated by DB
|
||||
return id
|
||||
|
||||
def _get_unique_port_id(self, tenant_id, net_id):
|
||||
self._port_counter += 1
|
||||
self._port_counter %= int(conf.MAX_PORTS)
|
||||
id = net_id + "-p-" + str(self._port_counter)
|
||||
# TODO (Sumit): Need to check if the ID has already been allocated
|
||||
# ID will be generated by DB
|
||||
return id
|
||||
|
||||
def _get_unique_profile_id(self, tenant_id):
|
||||
self._portprofile_counter += 1
|
||||
self._portprofile_counter %= int(conf.MAX_PORT_PROFILES)
|
||||
id = tenant_id[:3] + "-pp-" + \
|
||||
("0" * (6 - len(str(self._net_counter)))) \
|
||||
+ str(self._portprofile_counter)
|
||||
# TODO (Sumit): Need to check if the ID has already been allocated
|
||||
# ID will be generated by DB
|
||||
return id
|
||||
|
||||
def _funcName(self, offset=0):
|
||||
return inspect.stack()[1 + offset][3]
|
||||
|
||||
"""
|
||||
TODO (Sumit):
|
||||
(1) Persistent storage
|
||||
"""
|
||||
50
quantum/plugins/cisco/l2network_plugin_configuration.py
Normal file
50
quantum/plugins/cisco/l2network_plugin_configuration.py
Normal file
@@ -0,0 +1,50 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2011 Cisco Systems, 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.
|
||||
#
|
||||
# @author: Sumit Naiksatam, Cisco Systems, Inc.
|
||||
#
|
||||
|
||||
import os
|
||||
|
||||
from quantum.plugins.cisco.common import cisco_configparser as confp
|
||||
|
||||
CONF_FILE = "conf/l2network_plugin.ini"
|
||||
|
||||
cp = confp.CiscoConfigParser(os.path.dirname(os.path.realpath(__file__)) \
|
||||
+ "/" + CONF_FILE)
|
||||
|
||||
section = cp['VLANS']
|
||||
VLAN_NAME_PREFIX = section['vlan_name_prefix']
|
||||
VLAN_START = section['vlan_start']
|
||||
VLAN_END = section['vlan_end']
|
||||
|
||||
section = cp['PORTS']
|
||||
MAX_PORTS = section['max_ports']
|
||||
|
||||
section = cp['PORTPROFILES']
|
||||
MAX_PORT_PROFILES = section['max_port_profiles']
|
||||
|
||||
section = cp['NETWORKS']
|
||||
MAX_NETWORKS = section['max_networks']
|
||||
|
||||
section = cp['MODEL']
|
||||
MODEL_CLASS = section['model_class']
|
||||
|
||||
CONF_FILE = "conf/plugins.ini"
|
||||
|
||||
cp = confp.CiscoConfigParser(os.path.dirname(os.path.realpath(__file__)) \
|
||||
+ "/" + CONF_FILE)
|
||||
plugins = cp.walk(cp.dummy)
|
||||
18
quantum/plugins/cisco/nexus/__init__.py
Normal file
18
quantum/plugins/cisco/nexus/__init__.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2011 Cisco Systems, 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.
|
||||
#
|
||||
# @author: Sumit Naiksatam, Cisco Systems, Inc.
|
||||
#
|
||||
35
quantum/plugins/cisco/nexus/cisco_nexus_configuration.py
Normal file
35
quantum/plugins/cisco/nexus/cisco_nexus_configuration.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2011 Cisco Systems, 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.
|
||||
#
|
||||
# @author: Sumit Naiksatam, Cisco Systems, Inc.
|
||||
# @author: Edgar Magana, Cisco Systems, Inc.
|
||||
#
|
||||
|
||||
import os
|
||||
|
||||
from quantum.plugins.cisco.common import cisco_configparser as confp
|
||||
|
||||
CONF_FILE = "../conf/nexus.ini"
|
||||
|
||||
cp = confp.CiscoConfigParser(os.path.dirname(os.path.realpath(__file__)) \
|
||||
+ "/" + CONF_FILE)
|
||||
|
||||
section = cp['SWITCH']
|
||||
NEXUS_IP_ADDRESS = section['nexus_ip_address']
|
||||
NEXUS_PORT = section['nexus_port']
|
||||
|
||||
section = cp['DRIVER']
|
||||
NEXUS_DRIVER = section['name']
|
||||
226
quantum/plugins/cisco/nexus/cisco_nexus_network_driver.py
Normal file
226
quantum/plugins/cisco/nexus/cisco_nexus_network_driver.py
Normal file
@@ -0,0 +1,226 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2011 Cisco Systems, 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.
|
||||
#
|
||||
# @author: Debojyoti Dutta, Cisco Systems, Inc.
|
||||
# @author: Edgar Magana, Cisco Systems Inc.
|
||||
#
|
||||
"""
|
||||
Implements a Nexus-OS NETCONF over SSHv2 API Client
|
||||
"""
|
||||
|
||||
import logging as LOG
|
||||
import string
|
||||
import subprocess
|
||||
|
||||
from quantum.plugins.cisco.common import cisco_constants as const
|
||||
from quantum.plugins.cisco.common import cisco_exceptions as cexc
|
||||
|
||||
from ncclient import manager
|
||||
|
||||
LOG.basicConfig(level=LOG.WARN)
|
||||
LOG.getLogger(const.LOGGER_COMPONENT_NAME)
|
||||
|
||||
|
||||
# The following are standard strings, messages used to communicate with Nexus,
|
||||
#only place holder values change for each message
|
||||
exec_conf_prefix = """
|
||||
<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
|
||||
<configure xmlns="http://www.cisco.com/nxos:1.0:vlan_mgr_cli">
|
||||
<__XML__MODE__exec_configure>
|
||||
"""
|
||||
|
||||
|
||||
exec_conf_postfix = """
|
||||
</__XML__MODE__exec_configure>
|
||||
</configure>
|
||||
</config>
|
||||
"""
|
||||
|
||||
|
||||
cmd_vlan_conf_snippet = """
|
||||
<vlan>
|
||||
<vlan-id-create-delete>
|
||||
<__XML__PARAM_value>%s</__XML__PARAM_value>
|
||||
<__XML__MODE_vlan>
|
||||
<name>
|
||||
<vlan-name>%s</vlan-name>
|
||||
</name>
|
||||
<state>
|
||||
<vstate>active</vstate>
|
||||
</state>
|
||||
<no>
|
||||
<shutdown/>
|
||||
</no>
|
||||
</__XML__MODE_vlan>
|
||||
</vlan-id-create-delete>
|
||||
</vlan>
|
||||
"""
|
||||
|
||||
cmd_no_vlan_conf_snippet = """
|
||||
<no>
|
||||
<vlan>
|
||||
<vlan-id-create-delete>
|
||||
<__XML__PARAM_value>%s</__XML__PARAM_value>
|
||||
</vlan-id-create-delete>
|
||||
</vlan>
|
||||
</no>
|
||||
"""
|
||||
|
||||
cmd_vlan_int_snippet = """
|
||||
<interface>
|
||||
<ethernet>
|
||||
<interface>%s</interface>
|
||||
<__XML__MODE_if-ethernet-switch>
|
||||
<switchport></switchport>
|
||||
<switchport>
|
||||
<trunk>
|
||||
<allowed>
|
||||
<vlan>
|
||||
<__XML__BLK_Cmd_switchport_trunk_allowed_allow-vlans>
|
||||
<allow-vlans>%s</allow-vlans>
|
||||
</__XML__BLK_Cmd_switchport_trunk_allowed_allow-vlans>
|
||||
</vlan>
|
||||
</allowed>
|
||||
</trunk>
|
||||
</switchport>
|
||||
</__XML__MODE_if-ethernet-switch>
|
||||
</ethernet>
|
||||
</interface>
|
||||
"""
|
||||
|
||||
cmd_port_trunk = """
|
||||
<interface>
|
||||
<ethernet>
|
||||
<interface>%s</interface>
|
||||
<__XML__MODE_if-ethernet-switch>
|
||||
<switchport></switchport>
|
||||
<switchport>
|
||||
<mode>
|
||||
<trunk>
|
||||
</trunk>
|
||||
</mode>
|
||||
</switchport>
|
||||
</__XML__MODE_if-ethernet-switch>
|
||||
</ethernet>
|
||||
</interface>
|
||||
"""
|
||||
|
||||
cmd_no_switchport = """
|
||||
<interface>
|
||||
<ethernet>
|
||||
<interface>%s</interface>
|
||||
<__XML__MODE_if-ethernet-switch>
|
||||
<no>
|
||||
<switchport>
|
||||
</switchport>
|
||||
</no>
|
||||
</__XML__MODE_if-ethernet-switch>
|
||||
</ethernet>
|
||||
</interface>
|
||||
"""
|
||||
|
||||
|
||||
cmd_no_vlan_int_snippet = """
|
||||
<interface>
|
||||
<ethernet>
|
||||
<interface>%s</interface>
|
||||
<__XML__MODE_if-ethernet-switch>
|
||||
<switchport></switchport>
|
||||
<no>
|
||||
<switchport>
|
||||
<trunk>
|
||||
<allowed>
|
||||
<vlan>
|
||||
<__XML__BLK_Cmd_switchport_trunk_allowed_allow-vlans>
|
||||
<allow-vlans>%s</allow-vlans>
|
||||
</__XML__BLK_Cmd_switchport_trunk_allowed_allow-vlans>
|
||||
</vlan>
|
||||
</allowed>
|
||||
</trunk>
|
||||
</switchport>
|
||||
</no>
|
||||
</__XML__MODE_if-ethernet-switch>
|
||||
</ethernet>
|
||||
</interface>
|
||||
"""
|
||||
|
||||
|
||||
filter_show_vlan_brief_snippet = """
|
||||
<show xmlns="http://www.cisco.com/nxos:1.0:vlan_mgr_cli">
|
||||
<vlan>
|
||||
<brief/>
|
||||
</vlan>
|
||||
</show> """
|
||||
|
||||
|
||||
class CiscoNEXUSDriver():
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def nxos_connect(self, nexus_host, port, nexus_user, nexus_password):
|
||||
m = manager.connect(host=nexus_host, port=22, username=nexus_user,
|
||||
password=nexus_password)
|
||||
return m
|
||||
|
||||
def enable_vlan(self, mgr, vlanid, vlanname):
|
||||
confstr = cmd_vlan_conf_snippet % (vlanid, vlanname)
|
||||
confstr = exec_conf_prefix + confstr + exec_conf_postfix
|
||||
mgr.edit_config(target='running', config=confstr)
|
||||
|
||||
def disable_vlan(self, mgr, vlanid):
|
||||
confstr = cmd_no_vlan_conf_snippet % vlanid
|
||||
confstr = exec_conf_prefix + confstr + exec_conf_postfix
|
||||
mgr.edit_config(target='running', config=confstr)
|
||||
|
||||
def enable_port_trunk(self, mgr, interface):
|
||||
confstr = cmd_port_trunk % (interface)
|
||||
confstr = exec_conf_prefix + confstr + exec_conf_postfix
|
||||
print confstr
|
||||
mgr.edit_config(target='running', config=confstr)
|
||||
|
||||
def disable_switch_port(self, mgr, interface):
|
||||
confstr = cmd_no_switchport % (interface)
|
||||
confstr = exec_conf_prefix + confstr + exec_conf_postfix
|
||||
print confstr
|
||||
mgr.edit_config(target='running', config=confstr)
|
||||
|
||||
def enable_vlan_on_trunk_int(self, mgr, interface, vlanid):
|
||||
confstr = cmd_vlan_int_snippet % (interface, vlanid)
|
||||
confstr = exec_conf_prefix + confstr + exec_conf_postfix
|
||||
print confstr
|
||||
mgr.edit_config(target='running', config=confstr)
|
||||
|
||||
def disable_vlan_on_trunk_int(self, mgr, interface, vlanid):
|
||||
confstr = cmd_no_vlan_int_snippet % (interface, vlanid)
|
||||
confstr = exec_conf_prefix + confstr + exec_conf_postfix
|
||||
print confstr
|
||||
mgr.edit_config(target='running', config=confstr)
|
||||
|
||||
def create_vlan(self, vlan_name, vlan_id, nexus_host, nexus_user,
|
||||
nexus_password, nexus_interface):
|
||||
#TODO (Edgar) Move the SSH port to the configuration file
|
||||
with self.nxos_connect(nexus_host, 22, nexus_user,
|
||||
nexus_password) as m:
|
||||
self.enable_vlan(m, vlan_id, vlan_name)
|
||||
self.enable_vlan_on_trunk_int(m, nexus_interface, vlan_id)
|
||||
|
||||
def delete_vlan(self, vlan_id, nexus_host, nexus_user,
|
||||
nexus_password, nexus_interface):
|
||||
with self.nxos_connect(nexus_host, 22, nexus_user,
|
||||
nexus_password) as m:
|
||||
self.disable_vlan(m, vlan_id)
|
||||
self.disable_switch_port(m, nexus_interface)
|
||||
168
quantum/plugins/cisco/nexus/cisco_nexus_plugin.py
Normal file
168
quantum/plugins/cisco/nexus/cisco_nexus_plugin.py
Normal file
@@ -0,0 +1,168 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2011 Cisco Systems, 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.
|
||||
#
|
||||
# @author: Sumit Naiksatam, Cisco Systems, Inc.
|
||||
# @author: Edgar Magana, Cisco Systems, Inc.
|
||||
#
|
||||
import logging as LOG
|
||||
|
||||
from quantum.common import exceptions as exc
|
||||
from quantum.common import utils
|
||||
from quantum.plugins.cisco.common import cisco_constants as const
|
||||
from quantum.plugins.cisco.common import cisco_credentials as cred
|
||||
from quantum.plugins.cisco.common import cisco_exceptions as cexc
|
||||
from quantum.plugins.cisco.common import cisco_utils as cutil
|
||||
from quantum.plugins.cisco.l2device_plugin_base import L2DevicePluginBase
|
||||
from quantum.plugins.cisco.nexus import cisco_nexus_configuration as conf
|
||||
|
||||
LOG.basicConfig(level=LOG.WARN)
|
||||
LOG.getLogger(const.LOGGER_COMPONENT_NAME)
|
||||
|
||||
|
||||
class NexusPlugin(L2DevicePluginBase):
|
||||
_networks = {}
|
||||
|
||||
def __init__(self):
|
||||
self._client = utils.import_object(conf.NEXUS_DRIVER)
|
||||
LOG.debug("Loaded driver %s\n" % conf.NEXUS_DRIVER)
|
||||
#TODO (Edgar) Using just one Nexus 7K Switch and Port
|
||||
self._nexus_ip = conf.NEXUS_IP_ADDRESS
|
||||
self._nexus_username = cred.Store.getUsername(conf.NEXUS_IP_ADDRESS)
|
||||
self._nexus_password = cred.Store.getPassword(conf.NEXUS_IP_ADDRESS)
|
||||
self._nexus_port = conf.NEXUS_PORT
|
||||
|
||||
def get_all_networks(self, tenant_id):
|
||||
"""
|
||||
Returns a dictionary containing all
|
||||
<network_uuid, network_name> for
|
||||
the specified tenant.
|
||||
"""
|
||||
LOG.debug("NexusPlugin:get_all_networks() called\n")
|
||||
return self._networks.values()
|
||||
|
||||
def create_network(self, tenant_id, net_name, net_id, vlan_name, vlan_id,
|
||||
**kwargs):
|
||||
"""
|
||||
Create a VLAN in the switch, and configure the appropriate interfaces
|
||||
for this VLAN
|
||||
"""
|
||||
LOG.debug("NexusPlugin:create_network() called\n")
|
||||
self._client.create_vlan(vlan_name, str(vlan_id), self._nexus_ip,
|
||||
self._nexus_username, self._nexus_password, self._nexus_port)
|
||||
|
||||
new_net_dict = {const.NET_ID: net_id,
|
||||
const.NET_NAME: net_name,
|
||||
const.NET_PORTS: {},
|
||||
const.NET_VLAN_NAME: vlan_name,
|
||||
const.NET_VLAN_ID: vlan_id}
|
||||
self._networks[net_id] = new_net_dict
|
||||
return new_net_dict
|
||||
|
||||
def delete_network(self, tenant_id, net_id, **kwargs):
|
||||
"""
|
||||
Deletes a VLAN in the switch, and removes the VLAN configuration
|
||||
from the relevant interfaces
|
||||
"""
|
||||
LOG.debug("NexusPlugin:delete_network() called\n")
|
||||
net = self._networks.get(net_id)
|
||||
vlan_id = self._get_vlan_id_for_network(tenant_id, net_id)
|
||||
if net:
|
||||
self._client.delete_vlan(str(vlan_id), self._nexus_ip,
|
||||
self._nexus_username, self._nexus_password, self._nexus_port)
|
||||
self._networks.pop(net_id)
|
||||
return net
|
||||
# Network not found
|
||||
raise exc.NetworkNotFound(net_id=net_id)
|
||||
|
||||
def get_network_details(self, tenant_id, net_id, **kwargs):
|
||||
"""
|
||||
Returns the details of a particular network
|
||||
"""
|
||||
LOG.debug("NexusPlugin:get_network_details() called\n")
|
||||
network = self._get_network(tenant_id, net_id)
|
||||
return network
|
||||
|
||||
def rename_network(self, tenant_id, net_id, new_name, **kwargs):
|
||||
"""
|
||||
Updates the symbolic name belonging to a particular
|
||||
Virtual Network.
|
||||
"""
|
||||
#TODO (Edgar) We need to add an update method in the Nexus Driver
|
||||
LOG.debug("NexusPlugin:rename_network() called\n")
|
||||
network = self._get_network(tenant_id, net_id)
|
||||
network[const.NET_NAME] = new_name
|
||||
return network
|
||||
|
||||
def get_all_ports(self, tenant_id, net_id, **kwargs):
|
||||
"""
|
||||
This is probably not applicable to the Nexus plugin.
|
||||
Delete if not required.
|
||||
"""
|
||||
LOG.debug("NexusPlugin:get_all_ports() called\n")
|
||||
|
||||
def create_port(self, tenant_id, net_id, port_state, port_id, **kwargs):
|
||||
"""
|
||||
This is probably not applicable to the Nexus plugin.
|
||||
Delete if not required.
|
||||
"""
|
||||
LOG.debug("NexusPlugin:create_port() called\n")
|
||||
|
||||
def delete_port(self, tenant_id, net_id, port_id, **kwargs):
|
||||
"""
|
||||
This is probably not applicable to the Nexus plugin.
|
||||
Delete if not required.
|
||||
"""
|
||||
LOG.debug("NexusPlugin:delete_port() called\n")
|
||||
|
||||
def update_port(self, tenant_id, net_id, port_id, port_state, **kwargs):
|
||||
"""
|
||||
This is probably not applicable to the Nexus plugin.
|
||||
Delete if not required.
|
||||
"""
|
||||
LOG.debug("NexusPlugin:update_port() called\n")
|
||||
|
||||
def get_port_details(self, tenant_id, net_id, port_id, **kwargs):
|
||||
"""
|
||||
This is probably not applicable to the Nexus plugin.
|
||||
Delete if not required.
|
||||
"""
|
||||
LOG.debug("NexusPlugin:get_port_details() called\n")
|
||||
|
||||
def plug_interface(self, tenant_id, net_id, port_id, remote_interface_id,
|
||||
**kwargs):
|
||||
"""
|
||||
This is probably not applicable to the Nexus plugin.
|
||||
Delete if not required.
|
||||
"""
|
||||
LOG.debug("NexusPlugin:plug_interface() called\n")
|
||||
|
||||
def unplug_interface(self, tenant_id, net_id, port_id, **kwargs):
|
||||
"""
|
||||
This is probably not applicable to the Nexus plugin.
|
||||
Delete if not required.
|
||||
"""
|
||||
LOG.debug("NexusPlugin:unplug_interface() called\n")
|
||||
|
||||
def _get_vlan_id_for_network(self, tenant_id, network_id):
|
||||
net = self._get_network(tenant_id, network_id)
|
||||
vlan_id = net[const.NET_VLAN_ID]
|
||||
return vlan_id
|
||||
|
||||
def _get_network(self, tenant_id, network_id):
|
||||
network = self._networks.get(network_id)
|
||||
if not network:
|
||||
raise exc.NetworkNotFound(net_id=network_id)
|
||||
return network
|
||||
301
quantum/plugins/cisco/run_tests.py
Normal file
301
quantum/plugins/cisco/run_tests.py
Normal file
@@ -0,0 +1,301 @@
|
||||
#!/usr/bin/env python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 OpenStack, LLC
|
||||
# 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.
|
||||
|
||||
# Colorizer Code is borrowed from Twisted:
|
||||
# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
"""Unittest runner for quantum
|
||||
|
||||
To run all test::
|
||||
python run_tests.py
|
||||
|
||||
To run all unit tests::
|
||||
python run_tests.py unit
|
||||
|
||||
To run all functional tests::
|
||||
python run_tests.py functional
|
||||
|
||||
To run a single unit test::
|
||||
python run_tests.py unit.test_stores:TestSwiftBackend.test_get
|
||||
|
||||
To run a single functional test::
|
||||
python run_tests.py functional.test_service:TestController.test_create
|
||||
|
||||
To run a single unit test module::
|
||||
python run_tests.py unit.test_stores
|
||||
|
||||
To run a single functional test module::
|
||||
python run_tests.py functional.test_stores
|
||||
"""
|
||||
|
||||
import gettext
|
||||
import logging
|
||||
import os
|
||||
import unittest
|
||||
import sys
|
||||
|
||||
from nose import config
|
||||
from nose import result
|
||||
from nose import core
|
||||
|
||||
|
||||
class _AnsiColorizer(object):
|
||||
"""
|
||||
A colorizer is an object that loosely wraps around a stream, allowing
|
||||
callers to write text to the stream in a particular color.
|
||||
|
||||
Colorizer classes must implement C{supported()} and C{write(text, color)}.
|
||||
"""
|
||||
_colors = dict(black=30, red=31, green=32, yellow=33,
|
||||
blue=34, magenta=35, cyan=36, white=37)
|
||||
|
||||
def __init__(self, stream):
|
||||
self.stream = stream
|
||||
|
||||
def supported(cls, stream=sys.stdout):
|
||||
"""
|
||||
A class method that returns True if the current platform supports
|
||||
coloring terminal output using this method. Returns False otherwise.
|
||||
"""
|
||||
if not stream.isatty():
|
||||
return False # auto color only on TTYs
|
||||
try:
|
||||
import curses
|
||||
except ImportError:
|
||||
return False
|
||||
else:
|
||||
try:
|
||||
try:
|
||||
return curses.tigetnum("colors") > 2
|
||||
except curses.error:
|
||||
curses.setupterm()
|
||||
return curses.tigetnum("colors") > 2
|
||||
except:
|
||||
raise
|
||||
# guess false in case of error
|
||||
return False
|
||||
supported = classmethod(supported)
|
||||
|
||||
def write(self, text, color):
|
||||
"""
|
||||
Write the given text to the stream in the given color.
|
||||
|
||||
@param text: Text to be written to the stream.
|
||||
|
||||
@param color: A string label for a color. e.g. 'red', 'white'.
|
||||
"""
|
||||
color = self._colors[color]
|
||||
self.stream.write('\x1b[%s;1m%s\x1b[0m' % (color, text))
|
||||
|
||||
|
||||
class _Win32Colorizer(object):
|
||||
"""
|
||||
See _AnsiColorizer docstring.
|
||||
"""
|
||||
def __init__(self, stream):
|
||||
from win32console import GetStdHandle, STD_OUT_HANDLE, \
|
||||
FOREGROUND_RED, FOREGROUND_BLUE, FOREGROUND_GREEN, \
|
||||
FOREGROUND_INTENSITY
|
||||
red, green, blue, bold = (FOREGROUND_RED, FOREGROUND_GREEN,
|
||||
FOREGROUND_BLUE, FOREGROUND_INTENSITY)
|
||||
self.stream = stream
|
||||
self.screenBuffer = GetStdHandle(STD_OUT_HANDLE)
|
||||
self._colors = {
|
||||
'normal': red | green | blue,
|
||||
'red': red | bold,
|
||||
'green': green | bold,
|
||||
'blue': blue | bold,
|
||||
'yellow': red | green | bold,
|
||||
'magenta': red | blue | bold,
|
||||
'cyan': green | blue | bold,
|
||||
'white': red | green | blue | bold}
|
||||
|
||||
def supported(cls, stream=sys.stdout):
|
||||
try:
|
||||
import win32console
|
||||
screenBuffer = win32console.GetStdHandle(
|
||||
win32console.STD_OUT_HANDLE)
|
||||
except ImportError:
|
||||
return False
|
||||
import pywintypes
|
||||
try:
|
||||
screenBuffer.SetConsoleTextAttribute(
|
||||
win32console.FOREGROUND_RED |
|
||||
win32console.FOREGROUND_GREEN |
|
||||
win32console.FOREGROUND_BLUE)
|
||||
except pywintypes.error:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
supported = classmethod(supported)
|
||||
|
||||
def write(self, text, color):
|
||||
color = self._colors[color]
|
||||
self.screenBuffer.SetConsoleTextAttribute(color)
|
||||
self.stream.write(text)
|
||||
self.screenBuffer.SetConsoleTextAttribute(self._colors['normal'])
|
||||
|
||||
|
||||
class _NullColorizer(object):
|
||||
"""
|
||||
See _AnsiColorizer docstring.
|
||||
"""
|
||||
def __init__(self, stream):
|
||||
self.stream = stream
|
||||
|
||||
def supported(cls, stream=sys.stdout):
|
||||
return True
|
||||
supported = classmethod(supported)
|
||||
|
||||
def write(self, text, color):
|
||||
self.stream.write(text)
|
||||
|
||||
|
||||
class QuantumTestResult(result.TextTestResult):
|
||||
def __init__(self, *args, **kw):
|
||||
result.TextTestResult.__init__(self, *args, **kw)
|
||||
self._last_case = None
|
||||
self.colorizer = None
|
||||
# NOTE(vish, tfukushima): reset stdout for the terminal check
|
||||
stdout = sys.__stdout__
|
||||
for colorizer in [_Win32Colorizer, _AnsiColorizer, _NullColorizer]:
|
||||
if colorizer.supported():
|
||||
self.colorizer = colorizer(self.stream)
|
||||
break
|
||||
sys.stdout = stdout
|
||||
|
||||
def getDescription(self, test):
|
||||
return str(test)
|
||||
|
||||
# NOTE(vish, tfukushima): copied from unittest with edit to add color
|
||||
def addSuccess(self, test):
|
||||
unittest.TestResult.addSuccess(self, test)
|
||||
if self.showAll:
|
||||
self.colorizer.write("OK", 'green')
|
||||
self.stream.writeln()
|
||||
elif self.dots:
|
||||
self.stream.write('.')
|
||||
self.stream.flush()
|
||||
|
||||
# NOTE(vish, tfukushima): copied from unittest with edit to add color
|
||||
def addFailure(self, test, err):
|
||||
unittest.TestResult.addFailure(self, test, err)
|
||||
if self.showAll:
|
||||
self.colorizer.write("FAIL", 'red')
|
||||
self.stream.writeln()
|
||||
elif self.dots:
|
||||
self.stream.write('F')
|
||||
self.stream.flush()
|
||||
|
||||
# NOTE(vish, tfukushima): copied from unittest with edit to add color
|
||||
def addError(self, test, err):
|
||||
"""Overrides normal addError to add support for errorClasses.
|
||||
If the exception is a registered class, the error will be added
|
||||
to the list for that class, not errors.
|
||||
"""
|
||||
stream = getattr(self, 'stream', None)
|
||||
ec, ev, tb = err
|
||||
try:
|
||||
exc_info = self._exc_info_to_string(err, test)
|
||||
except TypeError:
|
||||
# This is for compatibility with Python 2.3.
|
||||
exc_info = self._exc_info_to_string(err)
|
||||
for cls, (storage, label, isfail) in self.errorClasses.items():
|
||||
if result.isclass(ec) and issubclass(ec, cls):
|
||||
if isfail:
|
||||
test.passwd = False
|
||||
storage.append((test, exc_info))
|
||||
# Might get patched into a streamless result
|
||||
if stream is not None:
|
||||
if self.showAll:
|
||||
message = [label]
|
||||
detail = result._exception_details(err[1])
|
||||
if detail:
|
||||
message.append(detail)
|
||||
stream.writeln(": ".join(message))
|
||||
elif self.dots:
|
||||
stream.write(label[:1])
|
||||
return
|
||||
self.errors.append((test, exc_info))
|
||||
test.passed = False
|
||||
if stream is not None:
|
||||
if self.showAll:
|
||||
self.colorizer.write("ERROR", 'red')
|
||||
self.stream.writeln()
|
||||
elif self.dots:
|
||||
stream.write('E')
|
||||
|
||||
def startTest(self, test):
|
||||
unittest.TestResult.startTest(self, test)
|
||||
current_case = test.test.__class__.__name__
|
||||
|
||||
if self.showAll:
|
||||
if current_case != self._last_case:
|
||||
self.stream.writeln(current_case)
|
||||
self._last_case = current_case
|
||||
|
||||
self.stream.write(
|
||||
' %s' % str(test.test._testMethodName).ljust(60))
|
||||
self.stream.flush()
|
||||
|
||||
|
||||
class QuantumTestRunner(core.TextTestRunner):
|
||||
def _makeResult(self):
|
||||
return QuantumTestResult(self.stream,
|
||||
self.descriptions,
|
||||
self.verbosity,
|
||||
self.config)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Set up test logger.
|
||||
logger = logging.getLogger()
|
||||
hdlr = logging.StreamHandler()
|
||||
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
|
||||
hdlr.setFormatter(formatter)
|
||||
logger.addHandler(hdlr)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
working_dir = os.path.abspath("tests")
|
||||
c = config.Config(stream=sys.stdout,
|
||||
env=os.environ,
|
||||
verbosity=3,
|
||||
workingDir=working_dir)
|
||||
runner = QuantumTestRunner(stream=c.stream,
|
||||
verbosity=c.verbosity,
|
||||
config=c)
|
||||
sys.exit(not core.run(config=c, testRunner=runner))
|
||||
18
quantum/plugins/cisco/tests/__init__.py
Normal file
18
quantum/plugins/cisco/tests/__init__.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2011 Cisco Systems, 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.
|
||||
#
|
||||
# @author: Sumit Naiksatam, Cisco Systems, Inc.
|
||||
#
|
||||
32
quantum/plugins/cisco/tests/unit/__init__.py
Normal file
32
quantum/plugins/cisco/tests/unit/__init__.py
Normal file
@@ -0,0 +1,32 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2011 OpenStack LLC.
|
||||
# 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.
|
||||
|
||||
# See http://code.google.com/p/python-nose/issues/detail?id=373
|
||||
# The code below enables nosetests to work with i18n _() blocks
|
||||
import __builtin__
|
||||
import unittest
|
||||
setattr(__builtin__, '_', lambda x: x)
|
||||
|
||||
|
||||
class BaseTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
|
||||
def setUp():
|
||||
pass
|
||||
892
quantum/plugins/cisco/tests/unit/test_l2networkApi.py
Normal file
892
quantum/plugins/cisco/tests/unit/test_l2networkApi.py
Normal file
@@ -0,0 +1,892 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2011 Cisco Systems, 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.
|
||||
#
|
||||
# @author: Shweta Padubidri, Cisco Systems, Inc.
|
||||
#
|
||||
|
||||
import logging
|
||||
import unittest
|
||||
from quantum.common import exceptions as exc
|
||||
from quantum.plugins.cisco.common import cisco_constants as const
|
||||
from quantum.plugins.cisco.common import cisco_exceptions as cexc
|
||||
from quantum.plugins.cisco import l2network_plugin
|
||||
from quantum.plugins.cisco import l2network_plugin_configuration as conf
|
||||
|
||||
LOG = logging.getLogger('quantum.tests.test_core_api_func')
|
||||
|
||||
|
||||
class CoreAPITestFunc(unittest.TestCase):
|
||||
|
||||
def test_create_network(self, net_tenant_id=None, net_name=None):
|
||||
|
||||
"""
|
||||
Tests creation of new Virtual Network.
|
||||
"""
|
||||
|
||||
LOG.debug("test_create_network - START")
|
||||
if net_tenant_id:
|
||||
tenant_id = net_tenant_id
|
||||
else:
|
||||
tenant_id = self.tenant_id
|
||||
if net_name:
|
||||
network_name = net_name
|
||||
else:
|
||||
network_name = self.network_name
|
||||
new_net_dict = self._l2network_plugin.create_network(
|
||||
tenant_id, network_name)
|
||||
self.assertEqual(new_net_dict[const.NET_NAME], network_name)
|
||||
self.tearDownNetwork(tenant_id, new_net_dict[const.NET_ID])
|
||||
LOG.debug("test_create_network - END")
|
||||
|
||||
def test_delete_network(self, net_tenant_id=None):
|
||||
"""
|
||||
Tests deletion of a Virtual Network.
|
||||
"""
|
||||
LOG.debug("test_delete_network - START")
|
||||
if net_tenant_id:
|
||||
tenant_id = net_tenant_id
|
||||
else:
|
||||
tenant_id = self.tenant_id
|
||||
new_net_dict = self._l2network_plugin.create_network(
|
||||
tenant_id, self.network_name)
|
||||
delete_net_dict = self._l2network_plugin.delete_network(
|
||||
tenant_id, new_net_dict[const.NET_ID])
|
||||
self.assertEqual(
|
||||
new_net_dict[const.NET_ID], delete_net_dict[const.NET_ID])
|
||||
LOG.debug("test_delete_network - END")
|
||||
|
||||
def test_delete_networkDNE(self, net_tenant_id=None, net_id='0005'):
|
||||
"""
|
||||
Tests deletion of a Virtual Network when Network does not exist.
|
||||
"""
|
||||
LOG.debug("test_delete_network_not_found - START")
|
||||
if net_tenant_id:
|
||||
tenant_id = net_tenant_id
|
||||
else:
|
||||
tenant_id = self.tenant_id
|
||||
self.assertRaises(
|
||||
exc.NetworkNotFound, self._l2network_plugin.delete_network,
|
||||
tenant_id, net_id)
|
||||
LOG.debug("test_delete_network_not_found - END")
|
||||
|
||||
def test_delete_networkInUse(self, tenant_id='test_network'):
|
||||
"""
|
||||
Tests deletion of a Virtual Network when Network is in Use.
|
||||
"""
|
||||
LOG.debug("test_delete_networkInUse - START")
|
||||
|
||||
new_net_dict = self._l2network_plugin.create_network(
|
||||
tenant_id, self.network_name)
|
||||
port_dict = self._l2network_plugin.create_port(
|
||||
tenant_id, new_net_dict[const.NET_ID], self.port_state)
|
||||
self._l2network_plugin.plug_interface(
|
||||
tenant_id, new_net_dict[const.NET_ID],
|
||||
port_dict[const.PORT_ID], self.remote_interface)
|
||||
self.assertRaises(exc.NetworkInUse,
|
||||
self._l2network_plugin.delete_network, tenant_id,
|
||||
new_net_dict[const.NET_ID])
|
||||
self.tearDownNetworkPortInterface(
|
||||
tenant_id, new_net_dict[const.NET_ID],
|
||||
port_dict[const.PORT_ID])
|
||||
LOG.debug("test_delete_networkInUse - END")
|
||||
|
||||
def test_show_network(self, net_tenant_id=None):
|
||||
"""
|
||||
Tests display of details of a Virtual Network .
|
||||
"""
|
||||
|
||||
LOG.debug("test_show_network - START")
|
||||
if net_tenant_id:
|
||||
tenant_id = net_tenant_id
|
||||
else:
|
||||
tenant_id = self.tenant_id
|
||||
new_net_dict = self._l2network_plugin.create_network(
|
||||
tenant_id, self.network_name)
|
||||
result_net_dict = self._l2network_plugin.get_network_details(
|
||||
tenant_id, new_net_dict[const.NET_ID])
|
||||
self.assertEqual(
|
||||
new_net_dict[const.NET_ID], result_net_dict[const.NET_ID])
|
||||
self.tearDownNetwork(tenant_id, new_net_dict[const.NET_ID])
|
||||
LOG.debug("test_show_network - END")
|
||||
|
||||
def test_show_networkDNE(self, net_tenant_id=None, net_id='0005'):
|
||||
"""
|
||||
Tests display of a Virtual Network when Network does not exist.
|
||||
"""
|
||||
|
||||
LOG.debug("test_show_network_not_found - START")
|
||||
if net_tenant_id:
|
||||
tenant_id = net_tenant_id
|
||||
else:
|
||||
tenant_id = self.tenant_id
|
||||
self.assertRaises(exc.NetworkNotFound,
|
||||
self._l2network_plugin.get_network_details,
|
||||
tenant_id, net_id)
|
||||
LOG.debug("test_show_network_not_found - END")
|
||||
|
||||
def test_rename_network(self, net_tenant_id=None,
|
||||
new_name='new_test_network'):
|
||||
"""
|
||||
Tests rename of a Virtual Network .
|
||||
"""
|
||||
|
||||
LOG.debug("test_rename_network - START")
|
||||
if net_tenant_id:
|
||||
tenant_id = net_tenant_id
|
||||
else:
|
||||
tenant_id = self.tenant_id
|
||||
new_net_dict = self._l2network_plugin.create_network(
|
||||
tenant_id, self.network_name)
|
||||
rename_net_dict = self._l2network_plugin.rename_network(
|
||||
tenant_id, new_net_dict[const.NET_ID], new_name)
|
||||
self.assertEqual(new_name, rename_net_dict[const.NET_NAME])
|
||||
self.tearDownNetwork(tenant_id, new_net_dict[const.NET_ID])
|
||||
LOG.debug("test_rename_network - END")
|
||||
|
||||
def test_rename_networkDNE(self, net_tenant_id=None,
|
||||
net_id='0005', new_name='new_test_network'):
|
||||
"""
|
||||
Tests rename of a Virtual Network when Network does not exist.
|
||||
"""
|
||||
|
||||
LOG.debug("test_rename_network_not_found - START")
|
||||
if net_tenant_id:
|
||||
tenant_id = net_tenant_id
|
||||
else:
|
||||
tenant_id = self.tenant_id
|
||||
self.assertRaises(exc.NetworkNotFound,
|
||||
self._l2network_plugin.rename_network,
|
||||
tenant_id, net_id, new_name)
|
||||
LOG.debug("test_rename_network_not_found - END")
|
||||
|
||||
def test_list_networks(self, tenant_id='test_network'):
|
||||
"""
|
||||
Tests listing of all the Virtual Networks .
|
||||
"""
|
||||
|
||||
LOG.debug("test_list_networks - START")
|
||||
new_net_dict = self._l2network_plugin.create_network(
|
||||
tenant_id, self.network_name)
|
||||
new_net_dict2 = self._l2network_plugin.create_network(
|
||||
tenant_id, 'test_net2')
|
||||
net_list = self._l2network_plugin.get_all_networks(tenant_id)
|
||||
net_temp_list = [new_net_dict, new_net_dict2]
|
||||
self.assertEqual(len(net_list), 2)
|
||||
self.assertTrue(net_list[0] in net_temp_list)
|
||||
self.assertTrue(net_list[1] in net_temp_list)
|
||||
self.tearDownNetwork(tenant_id, new_net_dict[const.NET_ID])
|
||||
self.tearDownNetwork(tenant_id, new_net_dict2[const.NET_ID])
|
||||
LOG.debug("test_list_networks - END")
|
||||
|
||||
def test_list_ports(self, tenant_id='test_network'):
|
||||
"""
|
||||
Tests listing of all the Ports.
|
||||
"""
|
||||
|
||||
LOG.debug("test_list_ports - START")
|
||||
new_net_dict = self._l2network_plugin.create_network(
|
||||
tenant_id, self.network_name)
|
||||
port_dict = self._l2network_plugin.create_port(
|
||||
tenant_id, new_net_dict[const.NET_ID], self.port_state)
|
||||
port_dict2 = self._l2network_plugin.create_port(
|
||||
tenant_id, new_net_dict[const.NET_ID], self.port_state)
|
||||
port_list = self._l2network_plugin.get_all_ports(
|
||||
tenant_id, new_net_dict[const.NET_ID])
|
||||
port_temp_list = [port_dict, port_dict2]
|
||||
self.assertEqual(len(port_list), 2)
|
||||
self.assertTrue(port_list[0] in port_temp_list)
|
||||
self.assertTrue(port_list[1] in port_temp_list)
|
||||
|
||||
self.tearDownPortOnly(tenant_id, new_net_dict[const.NET_ID],
|
||||
port_dict[const.PORT_ID])
|
||||
self.tearDownNetworkPort(tenant_id, new_net_dict[const.NET_ID],
|
||||
port_dict2[const.PORT_ID])
|
||||
LOG.debug("test_list_ports - END")
|
||||
|
||||
def test_create_port(self, tenant_id='test_network',
|
||||
port_state=const.PORT_UP):
|
||||
"""
|
||||
Tests creation of Ports.
|
||||
"""
|
||||
|
||||
LOG.debug("test_create_port - START")
|
||||
new_net_dict = self._l2network_plugin.create_network(
|
||||
tenant_id, self.network_name)
|
||||
port_dict = self._l2network_plugin.create_port(
|
||||
tenant_id, new_net_dict[const.NET_ID], port_state)
|
||||
self.assertEqual(port_dict[const.PORT_STATE], port_state)
|
||||
self.tearDownNetworkPort(tenant_id, new_net_dict[const.NET_ID],
|
||||
port_dict[const.PORT_ID])
|
||||
LOG.debug("test_create_port - END")
|
||||
|
||||
def test_create_port_network_DNE(
|
||||
self, net_tenant_id=None, net_id='0005', port_state=const.PORT_UP):
|
||||
|
||||
"""
|
||||
Tests creation of Ports when network does not exist.
|
||||
"""
|
||||
|
||||
LOG.debug("test_create_port_network_DNE - START")
|
||||
if net_tenant_id:
|
||||
tenant_id = net_tenant_id
|
||||
else:
|
||||
tenant_id = self.tenant_id
|
||||
self.assertRaises(exc.NetworkNotFound,
|
||||
self._l2network_plugin.create_port,
|
||||
tenant_id, net_id, port_state)
|
||||
LOG.debug("test_create_port_network_DNE - END:")
|
||||
|
||||
def test_delete_port(self, tenant_id='test_tenant',
|
||||
port_state=const.PORT_UP):
|
||||
"""
|
||||
Tests deletion of Ports
|
||||
"""
|
||||
|
||||
LOG.debug("test_delete_port - START")
|
||||
new_net_dict = self._l2network_plugin.create_network(
|
||||
tenant_id, self.network_name)
|
||||
port_dict = self._l2network_plugin.create_port(
|
||||
tenant_id, new_net_dict[const.NET_ID], port_state)
|
||||
delete_port_dict = self._l2network_plugin.delete_port(
|
||||
tenant_id, new_net_dict[const.NET_ID],
|
||||
port_dict[const.PORT_ID])
|
||||
self.tearDownNetwork(tenant_id, new_net_dict[const.NET_ID])
|
||||
self.assertEqual(delete_port_dict, None)
|
||||
LOG.debug("test_delete_port - END")
|
||||
|
||||
def test_delete_port_networkDNE(self, tenant_id='test_tenant',
|
||||
net_id='0005', port_id='p0005'):
|
||||
"""
|
||||
Tests deletion of Ports when network does not exist.
|
||||
"""
|
||||
|
||||
LOG.debug("test_delete_port_networkDNE - START")
|
||||
self.assertRaises(exc.NetworkNotFound,
|
||||
self._l2network_plugin.delete_port, tenant_id,
|
||||
net_id, port_id)
|
||||
LOG.debug("test_delete_port_networkDNE - END")
|
||||
|
||||
def test_delete_portDNE(self, tenant_id='test_tenant', port_id='p0005'):
|
||||
"""
|
||||
Tests deletion of Ports when port does not exist.
|
||||
"""
|
||||
|
||||
LOG.debug("test_delete_portDNE - START")
|
||||
new_net_dict = self._l2network_plugin.create_network(
|
||||
tenant_id, self.network_name)
|
||||
self.assertRaises(exc.PortNotFound, self._l2network_plugin.delete_port,
|
||||
tenant_id, new_net_dict[const.NET_ID], port_id)
|
||||
self.tearDownNetwork(tenant_id, new_net_dict[const.NET_ID])
|
||||
LOG.debug("test_delete_portDNE - END")
|
||||
|
||||
def test_delete_portInUse(self, tenant_id='test_tenant'):
|
||||
"""
|
||||
Tests deletion of Ports when port is in Use.
|
||||
"""
|
||||
|
||||
LOG.debug("test_delete_portInUse - START")
|
||||
new_net_dict = self._l2network_plugin.create_network(
|
||||
tenant_id, self.network_name)
|
||||
port_dict = self._l2network_plugin.create_port(
|
||||
tenant_id, new_net_dict[const.NET_ID],
|
||||
self.port_state)
|
||||
self._l2network_plugin.plug_interface(
|
||||
tenant_id, new_net_dict[const.NET_ID],
|
||||
port_dict[const.PORT_ID], self.remote_interface)
|
||||
self.assertRaises(exc.PortInUse,
|
||||
self._l2network_plugin.delete_port, tenant_id,
|
||||
new_net_dict[const.NET_ID], port_dict[const.PORT_ID])
|
||||
self.tearDownNetworkPortInterface(
|
||||
tenant_id, new_net_dict[const.NET_ID], port_dict[const.PORT_ID])
|
||||
LOG.debug("test_delete_portInUse - END")
|
||||
|
||||
def test_update_port(self, tenant_id='test_tenant',
|
||||
port_state=const.PORT_DOWN):
|
||||
"""
|
||||
Tests updation of Ports.
|
||||
"""
|
||||
|
||||
LOG.debug("test_update_port - START")
|
||||
new_net_dict = self._l2network_plugin.create_network(
|
||||
tenant_id, self.network_name)
|
||||
port_dict = self._l2network_plugin.create_port(
|
||||
tenant_id, new_net_dict[const.NET_ID], self.port_state)
|
||||
update_port_dict = self._l2network_plugin.update_port(
|
||||
tenant_id, new_net_dict[const.NET_ID],
|
||||
port_dict[const.PORT_ID], port_state)
|
||||
self.assertEqual(update_port_dict[const.PORT_STATE], port_state)
|
||||
self.tearDownNetworkPort(tenant_id, new_net_dict[const.NET_ID],
|
||||
port_dict[const.PORT_ID])
|
||||
LOG.debug("test_update_port - END")
|
||||
|
||||
def test_update_port_networkDNE(self, tenant_id='test_tenant',
|
||||
net_id='0005', port_id='p0005'):
|
||||
"""
|
||||
Tests updation of Ports when network does not exist.
|
||||
"""
|
||||
|
||||
LOG.debug("test_update_port_networkDNE - START")
|
||||
self.assertRaises(exc.NetworkNotFound,
|
||||
self._l2network_plugin.update_port, tenant_id,
|
||||
net_id, port_id, self.port_state)
|
||||
LOG.debug("test_update_port_networkDNE - END")
|
||||
|
||||
def test_update_portDNE(self, tenant_id='test_tenant', port_id='p0005'):
|
||||
"""
|
||||
Tests updation of Ports when port does not exist.
|
||||
"""
|
||||
|
||||
LOG.debug("test_update_portDNE - START")
|
||||
new_net_dict = self._l2network_plugin.create_network(
|
||||
tenant_id, self.network_name)
|
||||
self.assertRaises(
|
||||
exc.PortNotFound, self._l2network_plugin.update_port, tenant_id,
|
||||
new_net_dict[const.NET_ID], port_id, self.port_state)
|
||||
self.tearDownNetwork(tenant_id, new_net_dict[const.NET_ID])
|
||||
LOG.debug("test_update_portDNE - END")
|
||||
|
||||
def test_show_port(self, tenant_id='test_tenant'):
|
||||
"""
|
||||
Tests display of Ports
|
||||
"""
|
||||
|
||||
LOG.debug("test_show_port - START")
|
||||
new_net_dict = self._l2network_plugin.create_network(
|
||||
tenant_id, self.network_name)
|
||||
port_dict = self._l2network_plugin.create_port(
|
||||
tenant_id, new_net_dict[const.NET_ID], self.port_state)
|
||||
get_port_dict = self._l2network_plugin.get_port_details(
|
||||
tenant_id, new_net_dict[const.NET_ID],
|
||||
port_dict[const.PORT_ID])
|
||||
self.assertEqual(get_port_dict[const.PORT_STATE], self.port_state)
|
||||
self.tearDownNetworkPort(tenant_id, new_net_dict[const.NET_ID],
|
||||
port_dict[const.PORT_ID])
|
||||
LOG.debug("test_show_port - END")
|
||||
|
||||
def test_show_port_networkDNE(self, tenant_id='test_tenant',
|
||||
net_id='0005', port_id='p0005'):
|
||||
"""
|
||||
Tests display of Ports when network does not exist
|
||||
"""
|
||||
|
||||
LOG.debug("test_show_port_networkDNE - START")
|
||||
self.assertRaises(exc.NetworkNotFound,
|
||||
self._l2network_plugin.get_port_details,
|
||||
tenant_id, net_id, port_id)
|
||||
LOG.debug("test_show_port_networkDNE - END")
|
||||
|
||||
def test_show_portDNE(self, tenant_id='test_tenant', port_id='p0005'):
|
||||
"""
|
||||
Tests display of Ports when port does not exist
|
||||
"""
|
||||
|
||||
LOG.debug("test_show_portDNE - START")
|
||||
new_net_dict = self._l2network_plugin.create_network(
|
||||
tenant_id, self.network_name)
|
||||
self.assertRaises(exc.PortNotFound,
|
||||
self._l2network_plugin.get_port_details, tenant_id,
|
||||
new_net_dict[const.NET_ID], port_id)
|
||||
self.tearDownNetwork(tenant_id, new_net_dict[const.NET_ID])
|
||||
LOG.debug("test_show_portDNE - END")
|
||||
|
||||
def test_plug_interface(self, tenant_id='test_tenant',
|
||||
remote_interface='new_interface'):
|
||||
"""
|
||||
Tests attachment of interface to the port
|
||||
"""
|
||||
|
||||
LOG.debug("test_plug_interface - START")
|
||||
new_net_dict = self._l2network_plugin.create_network(
|
||||
tenant_id, self.network_name)
|
||||
port_dict = self._l2network_plugin.create_port(
|
||||
tenant_id, new_net_dict[const.NET_ID], self.port_state)
|
||||
self._l2network_plugin.plug_interface(
|
||||
tenant_id, new_net_dict[const.NET_ID],
|
||||
port_dict[const.PORT_ID], remote_interface)
|
||||
self.assertEqual(
|
||||
self._l2network_plugin._networks[new_net_dict[const.NET_ID]]
|
||||
[const.NET_PORTS][port_dict[const.PORT_ID]]
|
||||
[const.ATTACHMENT], remote_interface)
|
||||
self.tearDownNetworkPortInterface(
|
||||
tenant_id, new_net_dict[const.NET_ID],
|
||||
port_dict[const.PORT_ID])
|
||||
LOG.debug("test_plug_interface - END")
|
||||
|
||||
def test_plug_interface_networkDNE(
|
||||
self, tenant_id='test_tenant', net_id='0005',
|
||||
port_id='p0005', remote_interface='new_interface'):
|
||||
"""
|
||||
Tests attachment of interface network does not exist
|
||||
"""
|
||||
|
||||
LOG.debug("test_plug_interface_networkDNE - START")
|
||||
self.assertRaises(exc.NetworkNotFound,
|
||||
self._l2network_plugin.plug_interface, tenant_id,
|
||||
net_id, port_id, remote_interface)
|
||||
LOG.debug("test_plug_interface_networkDNE - END")
|
||||
|
||||
def test_plug_interface_portDNE(self, tenant_id='test_tenant',
|
||||
port_id='p0005',
|
||||
remote_interface='new_interface'):
|
||||
"""
|
||||
Tests attachment of interface port does not exist
|
||||
"""
|
||||
|
||||
LOG.debug("test_plug_interface_portDNE - START")
|
||||
new_net_dict = self._l2network_plugin.create_network(
|
||||
tenant_id, self.network_name)
|
||||
self.assertRaises(
|
||||
exc.PortNotFound, self._l2network_plugin.plug_interface, tenant_id,
|
||||
new_net_dict[const.NET_ID], port_id, remote_interface)
|
||||
self.tearDownNetwork(tenant_id, new_net_dict[const.NET_ID])
|
||||
LOG.debug("test_plug_interface_portDNE - END")
|
||||
|
||||
def test_plug_interface_portInUse(self, tenant_id='test_tenant',
|
||||
remote_interface='new_interface'):
|
||||
|
||||
"""
|
||||
Tests attachment of new interface to the port when there is an
|
||||
existing attachment
|
||||
"""
|
||||
|
||||
LOG.debug("test_plug_interface_portInUse - START")
|
||||
new_net_dict = self._l2network_plugin.create_network(
|
||||
tenant_id, self.network_name)
|
||||
port_dict = self._l2network_plugin.create_port(
|
||||
tenant_id, new_net_dict[const.NET_ID], self.port_state)
|
||||
self._l2network_plugin.plug_interface(
|
||||
tenant_id, new_net_dict[const.NET_ID],
|
||||
port_dict[const.PORT_ID], remote_interface)
|
||||
self.assertRaises(exc.AlreadyAttached,
|
||||
self._l2network_plugin.plug_interface, tenant_id,
|
||||
new_net_dict[const.NET_ID],
|
||||
port_dict[const.PORT_ID], remote_interface)
|
||||
self.tearDownNetworkPortInterface(
|
||||
tenant_id, new_net_dict[const.NET_ID],
|
||||
port_dict[const.PORT_ID])
|
||||
LOG.debug("test_plug_interface_portInUse - END")
|
||||
|
||||
def test_unplug_interface(self, tenant_id='test_tenant'):
|
||||
"""
|
||||
Tests detaachment of an interface to a port
|
||||
"""
|
||||
|
||||
LOG.debug("test_unplug_interface - START")
|
||||
new_net_dict = self._l2network_plugin.create_network(
|
||||
tenant_id, self.network_name)
|
||||
port_dict = self._l2network_plugin.create_port(
|
||||
tenant_id, new_net_dict[const.NET_ID],
|
||||
self.port_state)
|
||||
self._l2network_plugin.plug_interface(
|
||||
tenant_id, new_net_dict[const.NET_ID],
|
||||
port_dict[const.PORT_ID], self.remote_interface)
|
||||
self._l2network_plugin.unplug_interface(
|
||||
tenant_id, new_net_dict[const.NET_ID],
|
||||
port_dict[const.PORT_ID])
|
||||
self.assertEqual(self._l2network_plugin._networks
|
||||
[new_net_dict[const.NET_ID]][const.NET_PORTS]
|
||||
[port_dict[const.PORT_ID]][const.ATTACHMENT], None)
|
||||
self.tearDownNetworkPort(tenant_id, new_net_dict[const.NET_ID],
|
||||
port_dict[const.PORT_ID])
|
||||
LOG.debug("test_unplug_interface - END")
|
||||
|
||||
def test_unplug_interface_networkDNE(self, tenant_id='test_tenant',
|
||||
net_id='0005', port_id='p0005'):
|
||||
"""
|
||||
Tests detaachment of an interface to a port, when the network does
|
||||
not exist
|
||||
"""
|
||||
|
||||
LOG.debug("test_unplug_interface_networkDNE - START")
|
||||
self.assertRaises(exc.NetworkNotFound,
|
||||
self._l2network_plugin.unplug_interface,
|
||||
tenant_id, net_id, port_id)
|
||||
LOG.debug("test_unplug_interface_networkDNE - END")
|
||||
|
||||
def test_unplug_interface_portDNE(self, tenant_id='test_tenant',
|
||||
port_id='p0005'):
|
||||
"""
|
||||
Tests detaachment of an interface to a port, when the port does
|
||||
not exist
|
||||
"""
|
||||
|
||||
LOG.debug("test_unplug_interface_portDNE - START")
|
||||
new_net_dict = self._l2network_plugin.create_network(tenant_id,
|
||||
self.network_name)
|
||||
self.assertRaises(exc.PortNotFound,
|
||||
self._l2network_plugin.unplug_interface, tenant_id,
|
||||
new_net_dict[const.NET_ID], port_id)
|
||||
self.tearDownNetwork(tenant_id, new_net_dict[const.NET_ID])
|
||||
LOG.debug("test_unplug_interface_portDNE - END")
|
||||
|
||||
def test_create_portprofile(self, net_tenant_id=None,
|
||||
net_profile_name=None, net_vlan_id=None):
|
||||
"""
|
||||
Tests creation of a port-profile
|
||||
"""
|
||||
|
||||
LOG.debug("test_create_portprofile - tenant id: %s - START",
|
||||
net_tenant_id)
|
||||
if net_tenant_id:
|
||||
tenant_id = net_tenant_id
|
||||
else:
|
||||
tenant_id = self.tenant_id
|
||||
if net_profile_name:
|
||||
profile_name = net_profile_name
|
||||
else:
|
||||
profile_name = self.profile_name
|
||||
if net_vlan_id:
|
||||
vlan_id = net_vlan_id
|
||||
else:
|
||||
vlan_id = self.vlan_id
|
||||
port_profile_dict = self._l2network_plugin.create_portprofile(
|
||||
tenant_id, profile_name, vlan_id)
|
||||
port_profile_id = port_profile_dict['profile-id']
|
||||
self.assertEqual(
|
||||
self._l2network_plugin._portprofiles[port_profile_id]['vlan-id'],
|
||||
vlan_id)
|
||||
self.assertEqual(
|
||||
self._l2network_plugin._portprofiles[port_profile_id]
|
||||
['profile-name'], profile_name)
|
||||
self.tearDownPortProfile(tenant_id, port_profile_id)
|
||||
LOG.debug("test_create_portprofile - tenant id: %s - END",
|
||||
net_tenant_id)
|
||||
|
||||
def test_delete_portprofile(self, net_tenant_id=None):
|
||||
"""
|
||||
Tests deletion of a port-profile
|
||||
"""
|
||||
|
||||
LOG.debug("test_delete_portprofile - tenant id: %s - START",
|
||||
net_tenant_id)
|
||||
if net_tenant_id:
|
||||
tenant_id = net_tenant_id
|
||||
else:
|
||||
tenant_id = self.tenant_id
|
||||
port_profile_dict = self._l2network_plugin.create_portprofile(
|
||||
tenant_id, self.profile_name, self.vlan_id)
|
||||
port_profile_id = port_profile_dict['profile-id']
|
||||
self._l2network_plugin.delete_portprofile(tenant_id, port_profile_id)
|
||||
self.assertEqual(self._l2network_plugin._portprofiles, {})
|
||||
LOG.debug("test_delete_portprofile - tenant id: %s - END",
|
||||
net_tenant_id)
|
||||
|
||||
def test_delete_portprofileDNE(self, tenant_id='test_tenant',
|
||||
profile_id='pr0005'):
|
||||
"""
|
||||
Tests deletion of a port-profile when netowrk does not exist
|
||||
"""
|
||||
|
||||
LOG.debug("test_delete_portprofileDNE - START")
|
||||
self.assertRaises(cexc.PortProfileNotFound,
|
||||
self._l2network_plugin.delete_portprofile,
|
||||
tenant_id, profile_id)
|
||||
LOG.debug("test_delete_portprofileDNE - END")
|
||||
|
||||
def test_delete_portprofileAssociated(self, tenant_id='test_tenant'):
|
||||
|
||||
"""
|
||||
Tests deletion of an associatedport-profile
|
||||
"""
|
||||
|
||||
LOG.debug("test_delete_portprofileAssociated - START")
|
||||
port_profile_dict = self._l2network_plugin.create_portprofile(
|
||||
tenant_id, self.profile_name, self.vlan_id)
|
||||
port_profile_id = port_profile_dict['profile-id']
|
||||
self._l2network_plugin.associate_portprofile(
|
||||
tenant_id, self.net_id, self.port_id, port_profile_id)
|
||||
self.assertRaises(cexc.PortProfileInvalidDelete,
|
||||
self._l2network_plugin.delete_portprofile,
|
||||
tenant_id, port_profile_id)
|
||||
self.tearDownAssociatePortProfile(tenant_id, self.net_id,
|
||||
self.port_id, port_profile_id)
|
||||
LOG.debug("test_delete_portprofileAssociated - END")
|
||||
|
||||
def test_list_portprofile(self, tenant_id='test_tenant'):
|
||||
"""
|
||||
Tests listing of port-profiles
|
||||
"""
|
||||
|
||||
LOG.debug("test_list_portprofile - tenant id: %s - START", tenant_id)
|
||||
profile_name2 = tenant_id + '_port_profile2'
|
||||
vlan_id2 = tenant_id + '201'
|
||||
port_profile_dict1 = self._l2network_plugin.create_portprofile(
|
||||
tenant_id, self.profile_name, self.vlan_id)
|
||||
port_profile_dict2 = self._l2network_plugin.create_portprofile(
|
||||
tenant_id, profile_name2, vlan_id2)
|
||||
port_profile_id1 = port_profile_dict1['profile-id']
|
||||
port_profile_id2 = port_profile_dict2['profile-id']
|
||||
list_all_portprofiles = self._l2network_plugin.get_all_portprofiles(
|
||||
tenant_id)
|
||||
self.assertEqual(self._l2network_plugin._portprofiles
|
||||
[port_profile_id1]['vlan-id'], self.vlan_id)
|
||||
self.assertEqual(self._l2network_plugin._portprofiles
|
||||
[port_profile_id1]['profile-name'], self.profile_name)
|
||||
self.assertEqual(self._l2network_plugin._portprofiles
|
||||
[port_profile_id2]['vlan-id'], vlan_id2)
|
||||
self.assertEqual(self._l2network_plugin._portprofiles
|
||||
[port_profile_id2]['profile-name'], profile_name2)
|
||||
LOG.debug("test_create_portprofile - tenant id: %s - END", tenant_id)
|
||||
|
||||
def test_show_portprofile(self, net_tenant_id=None):
|
||||
"""
|
||||
Tests display of a port-profile
|
||||
"""
|
||||
|
||||
LOG.debug("test_show_portprofile - START")
|
||||
if net_tenant_id:
|
||||
tenant_id = net_tenant_id
|
||||
else:
|
||||
tenant_id = self.tenant_id
|
||||
port_profile_dict = self._l2network_plugin.create_portprofile(
|
||||
tenant_id, self.profile_name, self.vlan_id)
|
||||
port_profile_id = port_profile_dict['profile-id']
|
||||
result_port_profile = self._l2network_plugin.get_portprofile_details(
|
||||
tenant_id, port_profile_id)
|
||||
self.assertEqual(result_port_profile[const.PROFILE_VLAN_ID],
|
||||
self.vlan_id)
|
||||
self.assertEqual(result_port_profile[const.PROFILE_NAME],
|
||||
self.profile_name)
|
||||
self.tearDownPortProfile(tenant_id, port_profile_id)
|
||||
LOG.debug("test_show_portprofile - tenant id: %s - END", net_tenant_id)
|
||||
|
||||
def test_show_portprofileDNE(self, tenant_id='test_tenant',
|
||||
profile_id='pr0005'):
|
||||
"""
|
||||
Tests display of a port-profile when network does not exist
|
||||
"""
|
||||
|
||||
LOG.debug("test_show_portprofileDNE - START")
|
||||
self.assertRaises(cexc.PortProfileNotFound,
|
||||
self._l2network_plugin.get_portprofile_details,
|
||||
tenant_id, profile_id)
|
||||
LOG.debug("test_show_portprofileDNE - END")
|
||||
|
||||
def test_rename_portprofile(self, tenant_id='test_tenant',
|
||||
new_profile_name='new_profile_name'):
|
||||
"""
|
||||
Tests rename of a port-profile
|
||||
"""
|
||||
|
||||
LOG.debug("test_rename_portprofile - START")
|
||||
port_profile_dict = self._l2network_plugin.create_portprofile(
|
||||
tenant_id, self.profile_name, self.vlan_id)
|
||||
port_profile_id = port_profile_dict['profile-id']
|
||||
result_port_profile_dict = self._l2network_plugin.rename_portprofile(
|
||||
tenant_id, port_profile_id, new_profile_name)
|
||||
self.assertEqual(result_port_profile_dict[const.PROFILE_NAME],
|
||||
new_profile_name)
|
||||
self.tearDownPortProfile(tenant_id, port_profile_id)
|
||||
LOG.debug("test_show_portprofile - tenant id: %s - END")
|
||||
|
||||
def test_rename_portprofileDNE(self, tenant_id='test_tenant',
|
||||
profile_id='pr0005',
|
||||
new_profile_name='new_profile_name'):
|
||||
"""
|
||||
Tests rename of a port-profile when network does not exist
|
||||
"""
|
||||
|
||||
LOG.debug("test_rename_portprofileDNE - START")
|
||||
self.assertRaises(cexc.PortProfileNotFound,
|
||||
self._l2network_plugin.rename_portprofile,
|
||||
tenant_id, profile_id, new_profile_name)
|
||||
LOG.debug("test_rename_portprofileDNE - END")
|
||||
|
||||
def test_associate_portprofile(self, tenant_id='test_tenant',
|
||||
net_id='0005', port_id='p00005'):
|
||||
"""
|
||||
Tests association of a port-profile
|
||||
"""
|
||||
|
||||
LOG.debug("test_associate_portprofile - START")
|
||||
port_profile_dict = self._l2network_plugin.create_portprofile(
|
||||
tenant_id, self.profile_name, self.vlan_id)
|
||||
port_profile_id = port_profile_dict['profile-id']
|
||||
self._l2network_plugin.associate_portprofile(
|
||||
tenant_id, net_id, port_id, port_profile_id)
|
||||
self.assertEqual(
|
||||
self._l2network_plugin._portprofiles[port_profile_id]
|
||||
[const.PROFILE_ASSOCIATIONS][0], port_id)
|
||||
self.tearDownAssociatePortProfile(tenant_id, net_id,
|
||||
port_id, port_profile_id)
|
||||
LOG.debug("test_associate_portprofile - END")
|
||||
|
||||
def test_associate_portprofileDNE(self, tenant_id='test_tenant',
|
||||
net_id='0005', port_id='p00005',
|
||||
profile_id='pr0005'):
|
||||
"""
|
||||
Tests association of a port-profile when a network does not exist
|
||||
"""
|
||||
|
||||
LOG.debug("test_associate_portprofileDNE - START")
|
||||
self.assertRaises(cexc.PortProfileNotFound,
|
||||
self._l2network_plugin.associate_portprofile,
|
||||
tenant_id, net_id, port_id, profile_id)
|
||||
LOG.debug("test_associate_portprofileDNE - END")
|
||||
|
||||
def test_disassociate_portprofile(self, tenant_id='test_tenant',
|
||||
net_id='0005', port_id='p00005'):
|
||||
"""
|
||||
Tests disassociation of a port-profile
|
||||
"""
|
||||
|
||||
LOG.debug("test_disassociate_portprofile - START")
|
||||
port_profile_dict = self._l2network_plugin.create_portprofile(
|
||||
tenant_id, self.profile_name, self.vlan_id)
|
||||
port_profile_id = port_profile_dict['profile-id']
|
||||
self._l2network_plugin.associate_portprofile(tenant_id, net_id,
|
||||
port_id, port_profile_id)
|
||||
self._l2network_plugin.disassociate_portprofile(
|
||||
tenant_id, net_id, port_id, port_profile_id)
|
||||
self.assertEqual(self._l2network_plugin._portprofiles
|
||||
[port_profile_id][const.PROFILE_ASSOCIATIONS], [])
|
||||
self.tearDownPortProfile(tenant_id, port_profile_id)
|
||||
LOG.debug("test_disassociate_portprofile - END")
|
||||
|
||||
def test_disassociate_portprofileDNE(self, tenant_id='test_tenant',
|
||||
net_id='0005', port_id='p00005', profile_id='pr0005'):
|
||||
"""
|
||||
Tests disassociation of a port-profile when network does not exist
|
||||
"""
|
||||
|
||||
LOG.debug("test_disassociate_portprofileDNE - START")
|
||||
self.assertRaises(cexc.PortProfileNotFound,
|
||||
self._l2network_plugin.disassociate_portprofile,
|
||||
tenant_id, net_id, port_id, profile_id)
|
||||
LOG.debug("test_disassociate_portprofileDNE - END")
|
||||
|
||||
# def test_disassociate_portprofile_Unassociated
|
||||
|
||||
def test_get_tenant(self, net_tenant_id=None):
|
||||
"""
|
||||
Tests get tenant
|
||||
"""
|
||||
|
||||
LOG.debug("test_get_tenant - START")
|
||||
if net_tenant_id:
|
||||
tenant_id = net_tenant_id
|
||||
else:
|
||||
tenant_id = self.tenant_id
|
||||
tenant_dict = self._l2network_plugin._get_tenant(tenant_id)
|
||||
self.assertEqual(tenant_dict[const.TENANT_ID], tenant_id)
|
||||
self.assertEqual(tenant_dict[const.TENANT_NAME], tenant_id)
|
||||
LOG.debug("test_get_tenant - END")
|
||||
|
||||
def test_get_vlan_name(self, net_tenant_id=None, vlan_name="NewVlan",
|
||||
vlan_prefix=conf.VLAN_NAME_PREFIX):
|
||||
"""
|
||||
Tests get vlan name
|
||||
"""
|
||||
|
||||
LOG.debug("test_get_vlan_name - START")
|
||||
if net_tenant_id:
|
||||
tenant_id = net_tenant_id
|
||||
else:
|
||||
tenant_id = self.tenant_id
|
||||
result_vlan_name = self._l2network_plugin._get_vlan_name(tenant_id,
|
||||
vlan_name)
|
||||
expected_output = vlan_prefix + tenant_id + "-" + vlan_name
|
||||
self.assertEqual(result_vlan_name, expected_output)
|
||||
LOG.debug("test_get_vlan_name - END")
|
||||
|
||||
def test_validate_port_state(self, port_state=const.PORT_UP):
|
||||
"""
|
||||
Tests validate port state
|
||||
"""
|
||||
|
||||
LOG.debug("test_validate_port_state - START")
|
||||
result = self._l2network_plugin._validate_port_state(port_state)
|
||||
self.assertEqual(result, True)
|
||||
LOG.debug("test_validate_port_state - END")
|
||||
|
||||
def test_invalid_port_state(self, port_state="BADSTATE"):
|
||||
"""
|
||||
Tests invalidate port state
|
||||
"""
|
||||
|
||||
LOG.debug("test_validate_port_state - START")
|
||||
self.assertRaises(exc.StateInvalid,
|
||||
self._l2network_plugin._validate_port_state,
|
||||
port_state)
|
||||
LOG.debug("test_validate_port_state - END")
|
||||
|
||||
def test_validate_attachment(self, net_tenant_id=None,
|
||||
remote_interface_id="new_interface"):
|
||||
"""
|
||||
Tests validate attachment
|
||||
"""
|
||||
|
||||
LOG.debug("test_validate_attachment - START")
|
||||
if net_tenant_id:
|
||||
tenant_id = net_tenant_id
|
||||
else:
|
||||
tenant_id = self.tenant_id
|
||||
net_name = self.network_name
|
||||
new_network_dict = self._l2network_plugin.create_network(tenant_id,
|
||||
net_name)
|
||||
network_id = new_network_dict[const.NET_ID]
|
||||
new_port_dict = self._l2network_plugin.create_port(tenant_id,
|
||||
network_id)
|
||||
port_id = new_port_dict[const.PORT_ID]
|
||||
self._l2network_plugin.plug_interface(
|
||||
tenant_id, new_network_dict[const.NET_ID], port_id,
|
||||
remote_interface_id)
|
||||
self.assertRaises(exc.AlreadyAttached,
|
||||
self._l2network_plugin._validate_attachment,
|
||||
tenant_id, network_id, port_id, remote_interface_id)
|
||||
self.tearDownNetworkPortInterface(
|
||||
tenant_id, new_network_dict[const.NET_ID], port_id)
|
||||
LOG.debug("test_validate_attachment - END")
|
||||
|
||||
def setUp(self):
|
||||
self.tenant_id = "test_tenant"
|
||||
self.network_name = "test_network"
|
||||
self.profile_name = "test_tenant_port_profile"
|
||||
self.vlan_id = "test_tenant_vlanid300"
|
||||
self.port_state = const.PORT_UP
|
||||
self.net_id = '00005'
|
||||
self.port_id = 'p0005'
|
||||
self.remote_interface = 'new_interface'
|
||||
self._l2network_plugin = l2network_plugin.L2Network()
|
||||
|
||||
"""
|
||||
Clean up functions after the tests
|
||||
"""
|
||||
|
||||
def tearDownNetwork(self, tenant_id, network_dict_id):
|
||||
self._l2network_plugin.delete_network(tenant_id, network_dict_id)
|
||||
|
||||
def tearDownPortOnly(self, tenant_id, network_dict_id, port_id):
|
||||
self._l2network_plugin.delete_port(tenant_id, network_dict_id, port_id)
|
||||
|
||||
def tearDownNetworkPort(self, tenant_id, network_dict_id, port_id):
|
||||
self._l2network_plugin.delete_port(tenant_id, network_dict_id, port_id)
|
||||
self.tearDownNetwork(tenant_id, network_dict_id)
|
||||
|
||||
def tearDownNetworkPortInterface(self, tenant_id, network_dict_id,
|
||||
port_id):
|
||||
self._l2network_plugin.unplug_interface(tenant_id,
|
||||
network_dict_id, port_id)
|
||||
self.tearDownNetworkPort(tenant_id, network_dict_id, port_id)
|
||||
|
||||
def tearDownPortProfile(self, tenant_id, port_profile_id):
|
||||
self._l2network_plugin.delete_portprofile(tenant_id, port_profile_id)
|
||||
|
||||
def tearDownAssociatePortProfile(self, tenant_id, net_id, port_id,
|
||||
port_profile_id):
|
||||
self._l2network_plugin.disassociate_portprofile(
|
||||
tenant_id, net_id, port_id, port_profile_id)
|
||||
self.tearDownPortProfile(tenant_id, port_profile_id)
|
||||
282
quantum/plugins/cisco/tests/unit/test_nexus_plugin.py
Normal file
282
quantum/plugins/cisco/tests/unit/test_nexus_plugin.py
Normal file
@@ -0,0 +1,282 @@
|
||||
# copyright 2011 Cisco Systems, 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.
|
||||
#
|
||||
# @author: Shweta Padubidri, Peter Strunk, Cisco Systems, Inc.
|
||||
#
|
||||
import unittest
|
||||
import logging
|
||||
from quantum.common import exceptions as exc
|
||||
from quantum.plugins.cisco.common import cisco_constants as const
|
||||
from quantum.plugins.cisco.nexus import cisco_nexus_plugin
|
||||
|
||||
LOG = logging.getLogger('quantum.tests.test_nexus')
|
||||
|
||||
|
||||
class TestNexusPlugin(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
self.tenant_id = "test_tenant_cisco1"
|
||||
self.net_name = "test_network_cisco1"
|
||||
self.net_id = 000007
|
||||
self.vlan_name = "q-" + str(self.net_id) + "vlan"
|
||||
self.vlan_id = 267
|
||||
self.port_id = "9"
|
||||
self._cisco_nexus_plugin = cisco_nexus_plugin.NexusPlugin()
|
||||
|
||||
def test_create_network(self, net_tenant_id=None, network_name=None,
|
||||
network_id=None, net_vlan_name=None,
|
||||
net_vlan_id=None):
|
||||
"""
|
||||
Tests creation of new Virtual Network.
|
||||
"""
|
||||
|
||||
LOG.debug("test_create_network - START")
|
||||
if net_tenant_id:
|
||||
tenant_id = net_tenant_id
|
||||
else:
|
||||
tenant_id = self.tenant_id
|
||||
if network_name:
|
||||
net_name = network_name
|
||||
else:
|
||||
net_name = self.net_name
|
||||
if network_id:
|
||||
net_id = network_id
|
||||
else:
|
||||
net_id = self.net_id
|
||||
if net_vlan_name:
|
||||
vlan_name = net_vlan_name
|
||||
else:
|
||||
vlan_name = self.vlan_name
|
||||
if net_vlan_id:
|
||||
vlan_id = net_vlan_id
|
||||
else:
|
||||
vlan_id = self.vlan_id
|
||||
|
||||
new_net_dict = self._cisco_nexus_plugin.create_network(
|
||||
tenant_id, net_name, net_id, vlan_name, vlan_id)
|
||||
|
||||
self.assertEqual(new_net_dict[const.NET_ID], self.net_id)
|
||||
self.assertEqual(new_net_dict[const.NET_NAME], self.net_name)
|
||||
self.assertEqual(new_net_dict[const.NET_VLAN_NAME], self.vlan_name)
|
||||
self.assertEqual(new_net_dict[const.NET_VLAN_ID], self.vlan_id)
|
||||
self.tearDownNetwork(tenant_id, new_net_dict[const.NET_ID])
|
||||
LOG.debug("test_create_network - END")
|
||||
|
||||
def test_delete_network(self, net_tenant_id=None, network_id=None):
|
||||
"""
|
||||
Tests deletion of a Virtual Network.
|
||||
"""
|
||||
|
||||
LOG.debug("test_delete_network - START")
|
||||
|
||||
if net_tenant_id:
|
||||
tenant_id = net_tenant_id
|
||||
else:
|
||||
tenant_id = self.tenant_id
|
||||
if network_id:
|
||||
net_id = network_id
|
||||
else:
|
||||
net_id = self.net_id
|
||||
|
||||
new_net_dict = self._cisco_nexus_plugin.create_network(
|
||||
tenant_id, self.net_name, net_id, self.vlan_name, self.vlan_id)
|
||||
deleted_net_dict = self._cisco_nexus_plugin.delete_network(
|
||||
tenant_id, new_net_dict[const.NET_ID])
|
||||
self.assertEqual(deleted_net_dict[const.NET_ID], net_id)
|
||||
LOG.debug("test_delete_network - END")
|
||||
|
||||
def test_delete_network_DNE(self, net_tenant_id=None, net_id='0005'):
|
||||
"""
|
||||
Tests deletion of a Virtual Network when Network does not exist.
|
||||
"""
|
||||
|
||||
LOG.debug("test_delete_network_DNE - START")
|
||||
|
||||
if net_tenant_id:
|
||||
tenant_id = net_tenant_id
|
||||
else:
|
||||
tenant_id = self.tenant_id
|
||||
|
||||
self.assertRaises(exc.NetworkNotFound,
|
||||
self._cisco_nexus_plugin.delete_network,
|
||||
tenant_id, net_id)
|
||||
|
||||
LOG.debug("test_delete_network_DNE - END")
|
||||
|
||||
def test_get_network_details(self, net_tenant_id=None, network_id=None):
|
||||
"""
|
||||
Tests displays details of a Virtual Network .
|
||||
"""
|
||||
|
||||
LOG.debug("test_get_network_details - START")
|
||||
|
||||
if net_tenant_id:
|
||||
tenant_id = net_tenant_id
|
||||
else:
|
||||
tenant_id = self.tenant_id
|
||||
if network_id:
|
||||
net_id = network_id
|
||||
else:
|
||||
net_id = self.net_id
|
||||
|
||||
new_net_dict = self._cisco_nexus_plugin.create_network(
|
||||
tenant_id, self.net_name, net_id, self.vlan_name, self.vlan_id)
|
||||
check_net_dict = self._cisco_nexus_plugin.get_network_details(
|
||||
tenant_id, net_id)
|
||||
|
||||
self.assertEqual(check_net_dict[const.NET_ID], net_id)
|
||||
self.assertEqual(check_net_dict[const.NET_NAME], self.net_name)
|
||||
self.assertEqual(check_net_dict[const.NET_VLAN_NAME], self.vlan_name)
|
||||
self.assertEqual(check_net_dict[const.NET_VLAN_ID], self.vlan_id)
|
||||
self.tearDownNetwork(tenant_id, new_net_dict[const.NET_ID])
|
||||
LOG.debug("test_get_network_details - END")
|
||||
|
||||
def test_get_networkDNE(self, net_tenant_id=None, net_id='0005'):
|
||||
"""
|
||||
Tests display of a Virtual Network when Network does not exist.
|
||||
"""
|
||||
|
||||
LOG.debug("test_get_network_details_network_does_not_exist - START")
|
||||
|
||||
if net_tenant_id:
|
||||
tenant_id = net_tenant_id
|
||||
else:
|
||||
tenant_id = self.tenant_id
|
||||
|
||||
self.assertRaises(exc.NetworkNotFound,
|
||||
self._cisco_nexus_plugin.get_network_details,
|
||||
tenant_id, net_id)
|
||||
|
||||
LOG.debug("test_get_network_details_network_does_not_exist - END")
|
||||
|
||||
def test_rename_network(self, new_name="new_network_name",
|
||||
net_tenant_id=None, network_id=None):
|
||||
"""
|
||||
Tests rename of a Virtual Network .
|
||||
"""
|
||||
|
||||
LOG.debug("test_rename_network - START")
|
||||
|
||||
if net_tenant_id:
|
||||
tenant_id = net_tenant_id
|
||||
else:
|
||||
tenant_id = self.tenant_id
|
||||
if network_id:
|
||||
net_id = network_id
|
||||
else:
|
||||
net_id = self.net_id
|
||||
|
||||
new_net_dict = self._cisco_nexus_plugin.create_network(
|
||||
tenant_id, self.net_name, net_id, self.vlan_name,
|
||||
self.vlan_id)
|
||||
rename_net_dict = self._cisco_nexus_plugin.rename_network(
|
||||
tenant_id, new_net_dict[const.NET_ID], new_name)
|
||||
self.assertEqual(rename_net_dict[const.NET_NAME], new_name)
|
||||
self.tearDownNetwork(tenant_id, new_net_dict[const.NET_ID])
|
||||
LOG.debug("test_rename_network - END")
|
||||
|
||||
def test_rename_network_DNE(self, new_name="new_network_name",
|
||||
net_tenant_id=None, network_id='0005'):
|
||||
"""
|
||||
Tests rename of a Virtual Network when Network does not exist.
|
||||
"""
|
||||
|
||||
LOG.debug("test_rename_network_DNE - START")
|
||||
|
||||
if net_tenant_id:
|
||||
tenant_id = net_tenant_id
|
||||
else:
|
||||
tenant_id = self.tenant_id
|
||||
if network_id:
|
||||
net_id = network_id
|
||||
else:
|
||||
net_id = self.net_id
|
||||
|
||||
self.assertRaises(exc.NetworkNotFound,
|
||||
self._cisco_nexus_plugin.rename_network,
|
||||
new_name, tenant_id, net_id)
|
||||
|
||||
LOG.debug("test_rename_network_DNE - END")
|
||||
|
||||
def test_list_all_networks(self, net_tenant_id=None):
|
||||
"""
|
||||
Tests listing of all the Virtual Networks .
|
||||
"""
|
||||
|
||||
LOG.debug("test_list_all_networks - START")
|
||||
if net_tenant_id:
|
||||
tenant_id = net_tenant_id
|
||||
else:
|
||||
tenant_id = self.tenant_id
|
||||
new_net_dict1 = self._cisco_nexus_plugin.create_network(
|
||||
tenant_id, self.net_name, self.net_id,
|
||||
self.vlan_name, self.vlan_id)
|
||||
new_net_dict2 = self._cisco_nexus_plugin.create_network(
|
||||
tenant_id, "New_Network2", "0011",
|
||||
"second_vlan", "2003")
|
||||
list_net_dict = self._cisco_nexus_plugin.get_all_networks(tenant_id)
|
||||
net_temp_list = [new_net_dict1, new_net_dict2]
|
||||
self.assertEqual(len(list_net_dict), 2)
|
||||
self.assertTrue(list_net_dict[0] in net_temp_list)
|
||||
self.assertTrue(list_net_dict[1] in net_temp_list)
|
||||
self.tearDownNetwork(tenant_id, new_net_dict1[const.NET_ID])
|
||||
self.tearDownNetwork(tenant_id, new_net_dict2[const.NET_ID])
|
||||
LOG.debug("test_list_all_networks - END")
|
||||
|
||||
def test_get_vlan_id_for_network(self, net_tenant_id=None,
|
||||
network_id=None):
|
||||
"""
|
||||
Tests retrieval of vlan id for a Virtual Networks .
|
||||
"""
|
||||
|
||||
LOG.debug("test_get_vlan_id_for_network - START")
|
||||
if net_tenant_id:
|
||||
tenant_id = net_tenant_id
|
||||
else:
|
||||
tenant_id = self.tenant_id
|
||||
if network_id:
|
||||
net_id = network_id
|
||||
else:
|
||||
net_id = self.net_id
|
||||
new_net_dict = self._cisco_nexus_plugin.create_network(
|
||||
tenant_id, self.net_name, net_id, self.vlan_name,
|
||||
self.vlan_id)
|
||||
result_vlan_id = self._cisco_nexus_plugin._get_vlan_id_for_network(
|
||||
tenant_id, net_id)
|
||||
self.assertEqual(result_vlan_id, self.vlan_id)
|
||||
self.tearDownNetwork(tenant_id, new_net_dict[const.NET_ID])
|
||||
LOG.debug("test_get_vlan_id_for_network - END")
|
||||
|
||||
"""
|
||||
Clean up functions after the tests
|
||||
"""
|
||||
|
||||
def tearDownNetwork(self, tenant_id, network_dict_id):
|
||||
self._cisco_nexus_plugin.delete_network(tenant_id, network_dict_id)
|
||||
|
||||
# def test_create_network(self):
|
||||
# _test_create_network(self._cisco_nexus_plugin)
|
||||
|
||||
# def test_delete_network(self):
|
||||
# _test_delete_network(self._cisco_nexus_plugin)
|
||||
|
||||
# def test_rename_network(self):
|
||||
# _test_rename_network(self._cisco_nexus_plugin)
|
||||
|
||||
# def test_show_network(self):
|
||||
# _test_get_network_details(self._cisco_nexus_plugin)
|
||||
|
||||
# def test_list_networks(self):
|
||||
# _test_list_all_networks(self._cisco_nexus_plugin)
|
||||
165
quantum/plugins/cisco/tests/unit/test_ucs_driver.py
Normal file
165
quantum/plugins/cisco/tests/unit/test_ucs_driver.py
Normal file
@@ -0,0 +1,165 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2011 Cisco Systems, 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.
|
||||
#
|
||||
# @author: Shweta Padubidri, Cisco Systems, Inc.
|
||||
#
|
||||
|
||||
import logging
|
||||
import unittest
|
||||
|
||||
from quantum.plugins.cisco.ucs import cisco_ucs_network_driver
|
||||
|
||||
LOG = logging.getLogger('quantum.tests.test_ucs_driver')
|
||||
|
||||
create_vlan_output = "<configConfMos cookie=\"cookie_placeholder\" "\
|
||||
"inHierarchical=\"true\"> <inConfigs><pair key=\"fabric/lan/net-New Vlan\"> "\
|
||||
"<fabricVlan defaultNet=\"no\" dn=\"fabric/lan/net-New Vlan\" id=\"200\" "\
|
||||
"name=\"New Vlan\" status=\"created\"></fabricVlan> </pair> </inConfigs> "\
|
||||
"</configConfMos>"
|
||||
|
||||
create_profile_output = "<configConfMos cookie=\"cookie_placeholder\" "\
|
||||
"inHierarchical=\"true\"> <inConfigs><pair key=\"fabric/lan/profiles/vnic-"\
|
||||
"New Profile\"> <vnicProfile descr=\"Profile created by Cisco OpenStack "\
|
||||
"Quantum Plugin\" dn=\"fabric/lan/profiles/vnic-New Profile\" maxPorts="\
|
||||
"\"64\" name=\"New Profile\" nwCtrlPolicyName=\"\" pinToGroupName=\"\" "\
|
||||
"qosPolicyName=\"\" status=\"created\"> <vnicEtherIf defaultNet=\"yes\" "\
|
||||
"name=\"New Vlan\" rn=\"if-New Vlan\" > </vnicEtherIf> </vnicProfile> "\
|
||||
"</pair> </inConfigs> </configConfMos>"
|
||||
|
||||
change_vlan_output = "<configConfMos cookie=\"cookie_placeholder\" "\
|
||||
"inHierarchical=\"true\"> <inConfigs><pair key=\""\
|
||||
"fabric/lan/profiles/vnic-New Profile\"> <vnicProfile descr=\"Profile "\
|
||||
"created by Cisco OpenStack Quantum Plugin\" "\
|
||||
"dn=\"fabric/lan/profiles/vnic-New Profile\" maxPorts=\"64\" "\
|
||||
"name=\"New Profile\" nwCtrlPolicyName=\"\" pinToGroupName=\"\" "\
|
||||
"qosPolicyName=\"\" status=\"created,modified\"><vnicEtherIf "\
|
||||
"rn=\"if-Old Vlan\" status=\"deleted\"> </vnicEtherIf> "\
|
||||
"<vnicEtherIf defaultNet=\"yes\" name=\"New Vlan\" rn=\"if-New Vlan\" > "\
|
||||
"</vnicEtherIf> </vnicProfile> </pair></inConfigs> </configConfMos>"
|
||||
|
||||
delete_vlan_output = "<configConfMos cookie=\"cookie_placeholder\" "\
|
||||
"inHierarchical=\"true\"> <inConfigs><pair key=\"fabric/lan/net-New Vlan\"> "\
|
||||
"<fabricVlan dn=\"fabric/lan/net-New Vlan\" status=\"deleted\"> "\
|
||||
"</fabricVlan> </pair> </inConfigs></configConfMos>"
|
||||
|
||||
delete_profile_output = "<configConfMos cookie=\"cookie_placeholder\" "\
|
||||
"inHierarchical=\"false\"> <inConfigs><pair key=\""\
|
||||
"fabric/lan/profiles/vnic-New Profile\"> <vnicProfile "\
|
||||
"dn=\"fabric/lan/profiles/vnic-New Profile\" status=\"deleted\"> "\
|
||||
"</vnicProfile></pair> </inConfigs> </configConfMos>"
|
||||
|
||||
associate_profile_output = "<configConfMos cookie=\"cookie_placeholder\" "\
|
||||
"inHierarchical=\"true\"> <inConfigs> <pair key="\
|
||||
"\"fabric/lan/profiles/vnic-New Profile/cl-New Profile Client\">"\
|
||||
" <vmVnicProfCl dcName=\".*\" descr=\"\" dn=\"fabric/lan/profiles/vnic-"\
|
||||
"New Profile/cl-New Profile Client\"name=\"New Profile Client\" "\
|
||||
"orgPath=\".*\" status=\"created\" swName=\"default$\"> </vmVnicProfCl>" \
|
||||
"</pair> </inConfigs> </configConfMos>"
|
||||
|
||||
|
||||
class TestUCSDriver(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self._ucsmDriver = cisco_ucs_network_driver.CiscoUCSMDriver()
|
||||
self.vlan_name = 'New Vlan'
|
||||
self.vlan_id = '200'
|
||||
self.profile_name = 'New Profile'
|
||||
self.old_vlan_name = 'Old Vlan'
|
||||
self.profile_client_name = 'New Profile Client'
|
||||
|
||||
def test_create_vlan_post_data(self, expected_output=create_vlan_output):
|
||||
"""
|
||||
Tests creation of vlan post Data
|
||||
"""
|
||||
|
||||
LOG.debug("test_create_vlan")
|
||||
vlan_details = self._ucsmDriver._create_vlan_post_data(
|
||||
self.vlan_name, self.vlan_id)
|
||||
self.assertEqual(vlan_details, expected_output)
|
||||
LOG.debug("test_create_vlan - END")
|
||||
|
||||
def test_create_profile_post_data(
|
||||
self, expected_output=create_profile_output):
|
||||
"""
|
||||
Tests creation of profile post Data
|
||||
"""
|
||||
|
||||
LOG.debug("test_create_profile_post_data - START")
|
||||
profile_details = self._ucsmDriver._create_profile_post_data(
|
||||
self.profile_name, self.vlan_name)
|
||||
self.assertEqual(profile_details, expected_output)
|
||||
LOG.debug("test_create_profile_post - END")
|
||||
|
||||
def test_change_vlan_in_profile_post_data(
|
||||
self, expected_output=change_vlan_output):
|
||||
"""
|
||||
Tests creation of change vlan in profile post Data
|
||||
"""
|
||||
|
||||
LOG.debug("test_create_profile_post_data - START")
|
||||
profile_details = self._ucsmDriver._change_vlan_in_profile_post_data(
|
||||
self.profile_name, self.old_vlan_name, self.vlan_name)
|
||||
self.assertEqual(profile_details, expected_output)
|
||||
LOG.debug("test_create_profile_post - END")
|
||||
|
||||
def test_delete_vlan_post_data(self, expected_output=delete_vlan_output):
|
||||
LOG.debug("test_create_profile_post_data - START")
|
||||
"""
|
||||
Tests deletion of vlan post Data
|
||||
"""
|
||||
|
||||
vlan_details = self._ucsmDriver._create_vlan_post_data(
|
||||
self.vlan_name, self.vlan_id)
|
||||
vlan_delete_details = self._ucsmDriver._delete_vlan_post_data(
|
||||
self.vlan_name)
|
||||
self.assertEqual(vlan_delete_details, expected_output)
|
||||
LOG.debug("test_create_profile_post - END")
|
||||
|
||||
def test_delete_profile_post_data(
|
||||
self, expected_output=delete_profile_output):
|
||||
"""
|
||||
Tests deletion of profile post Data
|
||||
"""
|
||||
|
||||
LOG.debug("test_create_profile_post_data - START")
|
||||
profile_details = self._ucsmDriver._create_profile_post_data(
|
||||
self.profile_name, self.vlan_name)
|
||||
profile_delete_details = self._ucsmDriver._delete_profile_post_data(
|
||||
self.profile_name)
|
||||
self.assertEqual(profile_delete_details, expected_output)
|
||||
LOG.debug("test_create_profile_post - END")
|
||||
|
||||
def test_create_profile_client_post_data(
|
||||
self, expected_output=associate_profile_output):
|
||||
"""
|
||||
Tests creation of profile client post Data
|
||||
"""
|
||||
|
||||
LOG.debug("test_create_profile_client_post_data - START")
|
||||
profile_details = self._ucsmDriver._create_profile_client_post_data(
|
||||
self.profile_name, self.profile_client_name)
|
||||
self.assertEqual(profile_details, expected_output)
|
||||
LOG.debug("test_create_profile_post - END")
|
||||
|
||||
def test_get_next_dynamic_nic(self):
|
||||
"""
|
||||
Tests get next dynamic nic
|
||||
"""
|
||||
|
||||
LOG.debug("test_get_next_dynamic_nic - START")
|
||||
dynamic_nic_id = self._ucsmDriver._get_next_dynamic_nic()
|
||||
self.assertTrue(len(dynamic_nic_id) > 0)
|
||||
LOG.debug("test_get_next_dynamic_nic - END")
|
||||
480
quantum/plugins/cisco/tests/unit/test_ucs_plugin.py
Normal file
480
quantum/plugins/cisco/tests/unit/test_ucs_plugin.py
Normal file
@@ -0,0 +1,480 @@
|
||||
#vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#copyright 2011 Cisco Systems, 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.
|
||||
#
|
||||
# @author: Shubhangi Satras, Cisco Systems, Inc.
|
||||
#
|
||||
import unittest
|
||||
import logging as LOG
|
||||
from quantum.common import exceptions as exc
|
||||
from quantum.plugins.cisco.common import cisco_constants as const
|
||||
from quantum.plugins.cisco.ucs import cisco_ucs_plugin
|
||||
from quantum.plugins.cisco.ucs import cisco_ucs_configuration as conf
|
||||
|
||||
LOG.basicConfig(level=LOG.WARN)
|
||||
LOG.getLogger("cisco_plugin")
|
||||
|
||||
|
||||
class UCSVICTestPlugin(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
self.tenant_id = "test_tenant_cisco12"
|
||||
self.net_name = "test_network_cisco12"
|
||||
self.net_id = 000007
|
||||
self.vlan_name = "q-" + str(self.net_id) + "vlan"
|
||||
self.vlan_id = 266
|
||||
self.port_id = "4"
|
||||
self._cisco_ucs_plugin = cisco_ucs_plugin.UCSVICPlugin()
|
||||
|
||||
def test_create_network(self):
|
||||
"""
|
||||
Tests creation of new Virtual Network.
|
||||
"""
|
||||
LOG.debug("UCSVICTestPlugin:_test_create_network() called\n")
|
||||
new_net_dict = self._cisco_ucs_plugin.create_network(
|
||||
self.tenant_id, self.net_name, self.net_id,
|
||||
self.vlan_name, self.vlan_id)
|
||||
self.assertEqual(new_net_dict[const.NET_ID], self.net_id)
|
||||
self.assertEqual(new_net_dict[const.NET_NAME], self.net_name)
|
||||
self.assertEqual(new_net_dict[const.NET_VLAN_NAME], self.vlan_name)
|
||||
self.assertEqual(new_net_dict[const.NET_VLAN_ID], self.vlan_id)
|
||||
self.tearDownNetwork(self.tenant_id, self.net_id)
|
||||
|
||||
def test_delete_network(self):
|
||||
"""
|
||||
Tests deletion of the network with the specified network identifier
|
||||
belonging to the specified tenant.
|
||||
"""
|
||||
LOG.debug("UCSVICTestPlugin:test_delete_network() called\n")
|
||||
self._cisco_ucs_plugin.create_network(
|
||||
self.tenant_id, self.net_name, self.net_id,
|
||||
self.vlan_name, self.vlan_id)
|
||||
new_net_dict = self._cisco_ucs_plugin.delete_network(
|
||||
self.tenant_id, self.net_id)
|
||||
self.assertEqual(new_net_dict[const.NET_ID], self.net_id)
|
||||
|
||||
def test_get_network_details(self):
|
||||
"""
|
||||
Tests the deletion the Virtual Network belonging to a the
|
||||
spec
|
||||
"""
|
||||
LOG.debug("UCSVICTestPlugin:test_get_network_details() called\n")
|
||||
self._cisco_ucs_plugin.create_network(
|
||||
self.tenant_id, self.net_name, self.net_id,
|
||||
self.vlan_name, self.vlan_id)
|
||||
new_net_dict = self._cisco_ucs_plugin.get_network_details(
|
||||
self.tenant_id, self.net_id)
|
||||
self.assertEqual(new_net_dict[const.NET_ID], self.net_id)
|
||||
self.assertEqual(new_net_dict[const.NET_VLAN_NAME], self.vlan_name)
|
||||
self.assertEqual(new_net_dict[const.NET_VLAN_ID], self.vlan_id)
|
||||
self.tearDownNetwork(self.tenant_id, self.net_id)
|
||||
|
||||
def test_get_all_networks(self):
|
||||
"""
|
||||
Tests whether dictionary is returned containing all
|
||||
<network_uuid, network_name> for
|
||||
the specified tenant.
|
||||
"""
|
||||
LOG.debug("UCSVICTestPlugin:test_get_all_networks() called\n")
|
||||
new_net_dict1 = self._cisco_ucs_plugin.create_network(
|
||||
self.tenant_id, self.net_name, self.net_id,
|
||||
self.vlan_name, self.vlan_id)
|
||||
new_net_dict2 = self._cisco_ucs_plugin.create_network(
|
||||
self.tenant_id, "test_network2",
|
||||
000006, "q-000006vlan", "6")
|
||||
net_list = self._cisco_ucs_plugin.get_all_networks(self.tenant_id)
|
||||
net_id_list = [new_net_dict1, new_net_dict2]
|
||||
self.assertTrue(net_list[0] in net_id_list)
|
||||
self.assertTrue(net_list[1] in net_id_list)
|
||||
self.tearDownNetwork(self.tenant_id, new_net_dict1[const.NET_ID])
|
||||
self.tearDownNetwork(self.tenant_id, new_net_dict2[const.NET_ID])
|
||||
|
||||
def test_get_all_ports(self):
|
||||
"""
|
||||
Retrieves all port identifiers belonging to the
|
||||
specified Virtual Network.
|
||||
"""
|
||||
LOG.debug("UCSVICPlugin:get_all_ports() called\n")
|
||||
new_net_dict = self._cisco_ucs_plugin.create_network(
|
||||
self.tenant_id, self.net_name, self.net_id,
|
||||
self.vlan_name, self.vlan_id)
|
||||
port_dict1 = self._cisco_ucs_plugin.create_port(
|
||||
self.tenant_id, self.net_id, const.PORT_UP,
|
||||
self.port_id)
|
||||
port_dict2 = self._cisco_ucs_plugin.create_port(
|
||||
self.tenant_id, self.net_id,
|
||||
const.PORT_UP, "10")
|
||||
ports_on_net = self._cisco_ucs_plugin.get_all_ports(
|
||||
self.tenant_id, self.net_id)
|
||||
port_list = [port_dict1, port_dict2]
|
||||
self.assertTrue(port_list[0] in ports_on_net)
|
||||
self.assertTrue(port_list[1] in ports_on_net)
|
||||
self._cisco_ucs_plugin.delete_port(self.tenant_id, self.net_id,
|
||||
self.port_id)
|
||||
self.tearDownNetworkPort(self.tenant_id, new_net_dict[const.NET_ID],
|
||||
port_dict2[const.PORT_ID])
|
||||
|
||||
def _test_rename_network(self, new_name):
|
||||
"""
|
||||
Tests whether symbolic name is updated for the particular
|
||||
Virtual Network.
|
||||
"""
|
||||
LOG.debug("UCSVICTestPlugin:_test_rename_network() called\n")
|
||||
self._cisco_ucs_plugin.create_network(self.tenant_id, self.net_name,
|
||||
self.net_id, self.vlan_name,
|
||||
self.vlan_id)
|
||||
new_net_dict = self._cisco_ucs_plugin.rename_network(
|
||||
self.tenant_id, self.net_id, new_name)
|
||||
self.assertEqual(new_net_dict[const.NET_NAME], new_name)
|
||||
self.tearDownNetwork(self.tenant_id, self.net_id)
|
||||
|
||||
def test_rename_network(self):
|
||||
self._test_rename_network("new_test_network1")
|
||||
|
||||
def _test_create_port(self, port_state):
|
||||
"""
|
||||
Tests creation of a port on the specified Virtual Network.
|
||||
"""
|
||||
LOG.debug("UCSVICTestPlugin:_test_create_port() called\n")
|
||||
self._cisco_ucs_plugin.create_network(self.tenant_id, self.net_name,
|
||||
self.net_id, self.vlan_name,
|
||||
self.vlan_id)
|
||||
new_port_dict = self._cisco_ucs_plugin.create_port(
|
||||
self.tenant_id, self.net_id, port_state, self.port_id)
|
||||
self.assertEqual(new_port_dict[const.PORT_ID], self.port_id)
|
||||
self.assertEqual(new_port_dict[const.PORT_STATE], port_state)
|
||||
self.assertEqual(new_port_dict[const.ATTACHMENT], None)
|
||||
profile_name = self._cisco_ucs_plugin._get_profile_name(self.port_id)
|
||||
new_port_profile = new_port_dict[const.PORT_PROFILE]
|
||||
self.assertEqual(new_port_profile[const.PROFILE_NAME], profile_name)
|
||||
self.assertEqual(new_port_profile[const.PROFILE_VLAN_NAME],
|
||||
conf.DEFAULT_VLAN_NAME)
|
||||
self.assertEqual(new_port_profile[const.PROFILE_VLAN_ID],
|
||||
conf.DEFAULT_VLAN_ID)
|
||||
self.tearDownNetworkPort(self.tenant_id, self.net_id, self.port_id)
|
||||
|
||||
def test_create_port(self):
|
||||
self._test_create_port(const.PORT_UP)
|
||||
|
||||
def _test_delete_port(self, port_state):
|
||||
"""
|
||||
Tests Deletion of a port on a specified Virtual Network,
|
||||
if the port contains a remote interface attachment,
|
||||
the remote interface should first be un-plugged and
|
||||
then the port can be deleted.
|
||||
"""
|
||||
LOG.debug("UCSVICTestPlugin:_test_delete_port() called\n")
|
||||
self._cisco_ucs_plugin.create_network(self.tenant_id, self.net_name,
|
||||
self.net_id, self.vlan_name,
|
||||
self.vlan_id)
|
||||
self._cisco_ucs_plugin.create_port(self.tenant_id, self.net_id,
|
||||
port_state, self.port_id)
|
||||
self._cisco_ucs_plugin.delete_port(self.tenant_id, self.net_id,
|
||||
self.port_id)
|
||||
net = self._cisco_ucs_plugin._get_network(self.tenant_id, self.net_id)
|
||||
self.assertEqual(net[const.NET_PORTS], {})
|
||||
self.tearDownNetwork(self.tenant_id, self.net_id)
|
||||
|
||||
def test_delete_port(self):
|
||||
self._test_delete_port(const.PORT_UP)
|
||||
|
||||
def _test_update_port(self, port_state):
|
||||
"""
|
||||
Tests Updation of the state of a port on the specified Virtual Network.
|
||||
"""
|
||||
LOG.debug("UCSVICTestPlugin:_test_update_port() called\n")
|
||||
self._cisco_ucs_plugin.create_network(self.tenant_id, self.net_name,
|
||||
self.net_id, self.vlan_name,
|
||||
self.vlan_id)
|
||||
self._cisco_ucs_plugin.create_port(self.tenant_id, self.net_id,
|
||||
port_state, self.port_id)
|
||||
port = self._cisco_ucs_plugin.update_port(
|
||||
self.tenant_id, self.net_id,
|
||||
self.port_id, port_state)
|
||||
self.assertEqual(port[const.PORT_STATE], port_state)
|
||||
self.tearDownNetworkPort(self.tenant_id, self.net_id, self.port_id)
|
||||
|
||||
def test_update_port_state_up(self):
|
||||
self._test_update_port(const.PORT_UP)
|
||||
|
||||
def test_update_port_state_down(self):
|
||||
self._test_update_port(const.PORT_DOWN)
|
||||
|
||||
def _test_get_port_details_state_up(self, port_state):
|
||||
"""
|
||||
Tests whether user is able to retrieve a remote interface
|
||||
that is attached to this particular port when port state is Up.
|
||||
"""
|
||||
LOG.debug("UCSVICTestPlugin:_test_get_port_details_state_up()" +
|
||||
"called\n")
|
||||
self._cisco_ucs_plugin.create_network(self.tenant_id, self.net_name,
|
||||
self.net_id, self.vlan_name,
|
||||
self.vlan_id)
|
||||
self._cisco_ucs_plugin.create_port(self.tenant_id, self.net_id,
|
||||
port_state, self.port_id)
|
||||
port = self._cisco_ucs_plugin.get_port_details(
|
||||
self.tenant_id, self.net_id, self.port_id)
|
||||
self.assertEqual(port[const.PORT_ID], self.port_id)
|
||||
self.assertEqual(port[const.PORT_STATE], port_state)
|
||||
self.assertEqual(port[const.ATTACHMENT], None)
|
||||
new_port_profile = port[const.PORT_PROFILE]
|
||||
profile_name = self._cisco_ucs_plugin._get_profile_name(self.port_id)
|
||||
self.assertEqual(new_port_profile[const.PROFILE_VLAN_NAME],
|
||||
conf.DEFAULT_VLAN_NAME)
|
||||
self.assertEqual(new_port_profile[const.PROFILE_VLAN_ID],
|
||||
conf.DEFAULT_VLAN_ID)
|
||||
self.assertEqual(new_port_profile[const.PROFILE_NAME], profile_name)
|
||||
self.tearDownNetworkPort(self.tenant_id, self.net_id, self.port_id)
|
||||
|
||||
def _test_get_port_details_state_down(self, port_state):
|
||||
"""
|
||||
Tests whether user is able to retrieve a remote interface
|
||||
that is attached to this particular port when port state is down.
|
||||
"""
|
||||
LOG.debug("UCSVICTestPlugin:_test_get_port_details_state_down()" +
|
||||
"called\n")
|
||||
self._cisco_ucs_plugin.create_network(self.tenant_id, self.net_name,
|
||||
self.net_id, self.vlan_name,
|
||||
self.vlan_id)
|
||||
self._cisco_ucs_plugin.create_port(self.tenant_id, self.net_id,
|
||||
port_state, self.port_id)
|
||||
port = self._cisco_ucs_plugin.get_port_details(self.tenant_id,
|
||||
self.net_id,
|
||||
self.port_id)
|
||||
self.assertEqual(port[const.PORT_ID], self.port_id)
|
||||
self.assertNotEqual(port[const.PORT_STATE], port_state)
|
||||
self.assertEqual(port[const.ATTACHMENT], None)
|
||||
new_port_profile = port[const.PORT_PROFILE]
|
||||
profile_name = self._cisco_ucs_plugin._get_profile_name(self.port_id)
|
||||
self.assertEqual(new_port_profile[const.PROFILE_VLAN_NAME],
|
||||
conf.DEFAULT_VLAN_NAME)
|
||||
self.assertEqual(new_port_profile[const.PROFILE_VLAN_ID],
|
||||
conf.DEFAULT_VLAN_ID)
|
||||
self.assertEqual(new_port_profile[const.PROFILE_NAME], profile_name)
|
||||
self.tearDownNetworkPort(self.tenant_id, self.net_id, self.port_id)
|
||||
|
||||
def test_get_port_details_state_up(self):
|
||||
self._test_get_port_details_state_up(const.PORT_UP)
|
||||
|
||||
def test_get_port_details_state_down(self):
|
||||
self._test_get_port_details_state_down(const.PORT_DOWN)
|
||||
|
||||
def test_create_port_profile(self):
|
||||
LOG.debug("UCSVICTestPlugin:test_create_port_profile() called\n")
|
||||
new_port_profile = self._cisco_ucs_plugin._create_port_profile(
|
||||
self.tenant_id, self.net_id, self.port_id,
|
||||
self.vlan_name, self.vlan_id)
|
||||
profile_name = self._cisco_ucs_plugin._get_profile_name(self.port_id)
|
||||
self.assertEqual(new_port_profile[const.PROFILE_NAME], profile_name)
|
||||
self.assertEqual(new_port_profile[const.PROFILE_VLAN_NAME],
|
||||
self.vlan_name)
|
||||
self.assertEqual(new_port_profile[const.PROFILE_VLAN_ID], self.vlan_id)
|
||||
self._cisco_ucs_plugin._delete_port_profile(self.port_id, profile_name)
|
||||
|
||||
def test_delete_port_profile(self):
|
||||
LOG.debug("UCSVICTestPlugin:test_delete_port_profile() called\n")
|
||||
self._cisco_ucs_plugin._create_port_profile(
|
||||
self.tenant_id, self.net_id, self.port_id, self.vlan_name,
|
||||
self.vlan_id)
|
||||
profile_name = self._cisco_ucs_plugin._get_profile_name(self.port_id)
|
||||
counter1 = self._cisco_ucs_plugin._port_profile_counter
|
||||
self._cisco_ucs_plugin._delete_port_profile(self.port_id,
|
||||
profile_name)
|
||||
counter2 = self._cisco_ucs_plugin._port_profile_counter
|
||||
self.assertNotEqual(counter1, counter2)
|
||||
|
||||
def _test_plug_interface(self, remote_interface_id):
|
||||
"""
|
||||
Attaches a remote interface to the specified port on the
|
||||
specified Virtual Network.
|
||||
"""
|
||||
LOG.debug("UCSVICTestPlugin:_test_plug_interface() called\n")
|
||||
self._cisco_ucs_plugin.create_network(self.tenant_id, self.net_name,
|
||||
self.net_id, self.vlan_name,
|
||||
self.vlan_id)
|
||||
self._cisco_ucs_plugin.create_port(self.tenant_id, self.net_id,
|
||||
const.PORT_UP, self.port_id)
|
||||
self._cisco_ucs_plugin.plug_interface(self.tenant_id, self.net_id,
|
||||
self.port_id,
|
||||
remote_interface_id)
|
||||
port = self._cisco_ucs_plugin._get_port(
|
||||
self.tenant_id, self.net_id, self.port_id)
|
||||
self.assertEqual(port[const.ATTACHMENT], remote_interface_id)
|
||||
port_profile = port[const.PORT_PROFILE]
|
||||
profile_name = port_profile[const.PROFILE_NAME]
|
||||
new_vlan_name = self._cisco_ucs_plugin._get_vlan_name_for_network(
|
||||
self.tenant_id, self.net_id)
|
||||
new_vlan_id = self._cisco_ucs_plugin._get_vlan_id_for_network(
|
||||
self.tenant_id, self.net_id)
|
||||
self.assertEqual(port_profile[const.PROFILE_VLAN_NAME], new_vlan_name)
|
||||
self.assertEqual(port_profile[const.PROFILE_VLAN_ID], new_vlan_id)
|
||||
self.tearDownNetworkPortInterface(self.tenant_id, self.net_id,
|
||||
self.port_id)
|
||||
|
||||
def test_plug_interface(self):
|
||||
self._test_plug_interface("4")
|
||||
|
||||
def _test_unplug_interface(self, remote_interface_id):
|
||||
"""
|
||||
Tests whether remote interface detaches from the specified port on the
|
||||
specified Virtual Network.
|
||||
"""
|
||||
LOG.debug("UCSVICTestPlugin:_test_unplug_interface() called\n")
|
||||
self._cisco_ucs_plugin.create_network(self.tenant_id, self.net_name,
|
||||
self.net_id, self.vlan_name,
|
||||
self.vlan_id)
|
||||
self._cisco_ucs_plugin.create_port(self.tenant_id, self.net_id,
|
||||
const.PORT_UP, self.port_id)
|
||||
self._cisco_ucs_plugin.plug_interface(self.tenant_id, self.net_id,
|
||||
self.port_id,
|
||||
remote_interface_id)
|
||||
self._cisco_ucs_plugin.unplug_interface(self.tenant_id, self.net_id,
|
||||
self.port_id)
|
||||
port = self._cisco_ucs_plugin._get_port(
|
||||
self.tenant_id, self.net_id, self.port_id)
|
||||
self.assertEqual(port[const.ATTACHMENT], None)
|
||||
port_profile = port[const.PORT_PROFILE]
|
||||
profile_name = port_profile[const.PROFILE_NAME]
|
||||
self.assertEqual(port_profile[const.PROFILE_VLAN_NAME],
|
||||
conf.DEFAULT_VLAN_NAME)
|
||||
self.assertEqual(port_profile[const.PROFILE_VLAN_ID],
|
||||
conf.DEFAULT_VLAN_ID)
|
||||
self.tearDownNetworkPort(self.tenant_id, self.net_id, self.port_id)
|
||||
|
||||
def test_unplug_interface(self):
|
||||
self._test_unplug_interface("4")
|
||||
|
||||
def test_get_vlan_name_for_network(self):
|
||||
LOG.debug("UCSVICTestPlugin:test_get_vlan_name_for_network() called\n")
|
||||
net = self._cisco_ucs_plugin.create_network(
|
||||
self.tenant_id, self.net_name, self.net_id,
|
||||
self.vlan_name, self.vlan_id)
|
||||
self.assertEqual(net[const.NET_VLAN_NAME], self.vlan_name)
|
||||
self.tearDownNetwork(self.tenant_id, self.net_id)
|
||||
|
||||
def test_get_vlan_id_for_network(self):
|
||||
LOG.debug("UCSVICTestPlugin:test_get_vlan_id_for_network() called\n")
|
||||
net = self._cisco_ucs_plugin.create_network(
|
||||
self.tenant_id, self.net_name, self.net_id, self.vlan_name,
|
||||
self.vlan_id)
|
||||
self.assertEqual(net[const.NET_VLAN_ID], self.vlan_id)
|
||||
self.tearDownNetwork(self.tenant_id, self.net_id)
|
||||
|
||||
def test_get_network(self):
|
||||
LOG.debug("UCSVICTestPlugin:test_get_network() called\n")
|
||||
net = self._cisco_ucs_plugin.create_network(
|
||||
self.tenant_id, self.net_name, self.net_id, self.vlan_name,
|
||||
self.vlan_id)
|
||||
self.assertEqual(net[const.NET_ID], self.net_id)
|
||||
self.tearDownNetwork(self.tenant_id, self.net_id)
|
||||
|
||||
def test_get_port(self):
|
||||
LOG.debug("UCSVICTestPlugin:test_get_port() called\n")
|
||||
self._cisco_ucs_plugin.create_network(self.tenant_id, self.net_name,
|
||||
self.net_id, self.vlan_name,
|
||||
self.vlan_id)
|
||||
new_port_dict = self._cisco_ucs_plugin.create_port(
|
||||
self.tenant_id, self.net_id,
|
||||
const.PORT_UP, self.port_id)
|
||||
self.assertEqual(new_port_dict[const.PORT_ID], self.port_id)
|
||||
self.tearDownNetworkPort(self.tenant_id, self.net_id, self.port_id)
|
||||
|
||||
def test_get_network_NetworkNotFound(self):
|
||||
self.assertRaises(exc.NetworkNotFound,
|
||||
self._cisco_ucs_plugin._get_network,
|
||||
*(self.tenant_id, self.net_id))
|
||||
|
||||
def test_delete_network_NetworkNotFound(self):
|
||||
self.assertRaises(exc.NetworkNotFound,
|
||||
self._cisco_ucs_plugin.delete_network,
|
||||
*(self.tenant_id, self.net_id))
|
||||
|
||||
def test_delete_port_PortInUse(self):
|
||||
self._test_delete_port_PortInUse("4")
|
||||
|
||||
def _test_delete_port_PortInUse(self, remote_interface_id):
|
||||
self._cisco_ucs_plugin.create_network(self.tenant_id, self.net_name,
|
||||
self.net_id, self.vlan_name,
|
||||
self.vlan_id)
|
||||
self._cisco_ucs_plugin.create_port(self.tenant_id, self.net_id,
|
||||
const.PORT_UP, self.port_id)
|
||||
self._cisco_ucs_plugin.plug_interface(self.tenant_id, self.net_id,
|
||||
self.port_id,
|
||||
remote_interface_id)
|
||||
self.assertRaises(exc.PortInUse, self._cisco_ucs_plugin.delete_port,
|
||||
*(self.tenant_id, self.net_id, self.port_id))
|
||||
self.tearDownNetworkPortInterface(self.tenant_id, self.net_id,
|
||||
self.port_id)
|
||||
|
||||
def test_delete_port_PortNotFound(self):
|
||||
self._cisco_ucs_plugin.create_network(self.tenant_id, self.net_name,
|
||||
self.net_id, self.vlan_name,
|
||||
self.vlan_id)
|
||||
self.assertRaises(exc.PortNotFound, self._cisco_ucs_plugin.delete_port,
|
||||
*(self.tenant_id, self.net_id, self.port_id))
|
||||
self.tearDownNetwork(self.tenant_id, self.net_id)
|
||||
|
||||
def test_plug_interface_PortInUse(self):
|
||||
self._test_plug_interface_PortInUse("6", "5")
|
||||
|
||||
def _test_plug_interface_PortInUse(self, remote_interface_id1,
|
||||
remote_interface_id2):
|
||||
LOG.debug("UCSVICTestPlugin:_test_plug_interface_PortInUse() called\n")
|
||||
self._cisco_ucs_plugin.create_network(self.tenant_id, self.net_name,
|
||||
self.net_id, self.vlan_name,
|
||||
self.vlan_id)
|
||||
self._cisco_ucs_plugin.create_port(self.tenant_id, self.net_id,
|
||||
const.PORT_UP, self.port_id)
|
||||
self._cisco_ucs_plugin.plug_interface(self.tenant_id, self.net_id,
|
||||
self.port_id,
|
||||
remote_interface_id1)
|
||||
self.assertRaises(exc.PortInUse, self._cisco_ucs_plugin.plug_interface,
|
||||
*(self.tenant_id, self.net_id, self.port_id,
|
||||
remote_interface_id2))
|
||||
self.tearDownNetworkPortInterface(self.tenant_id, self.net_id,
|
||||
self.port_id)
|
||||
|
||||
def test_validate_attachment_AlreadyAttached(self):
|
||||
LOG.debug("UCSVICTestPlugin:testValidateAttachmentAlreadyAttached")
|
||||
self._test_validate_attachment_AlreadyAttached("4")
|
||||
|
||||
def _test_validate_attachment_AlreadyAttached(self, remote_interface_id):
|
||||
LOG.debug("UCSVICTestPlugin:_test_validate_attachmentAlreadyAttached")
|
||||
self._cisco_ucs_plugin.create_network(self.tenant_id, self.net_name,
|
||||
self.net_id, self.vlan_name,
|
||||
self.vlan_id)
|
||||
self._cisco_ucs_plugin.create_port(self.tenant_id, self.net_id,
|
||||
const.PORT_UP, self.port_id)
|
||||
self._cisco_ucs_plugin.plug_interface(self.tenant_id, self.net_id,
|
||||
self.port_id,
|
||||
remote_interface_id)
|
||||
self.assertRaises(
|
||||
exc.AlreadyAttached, self._cisco_ucs_plugin._validate_attachment,
|
||||
*(self.tenant_id, self.net_id, self.port_id, remote_interface_id))
|
||||
self.tearDownNetworkPortInterface(self.tenant_id, self.net_id,
|
||||
self.port_id)
|
||||
|
||||
def tearDownNetwork(self, tenant_id, net_id):
|
||||
self._cisco_ucs_plugin.delete_network(tenant_id, net_id)
|
||||
|
||||
def tearDownNetworkPort(self, tenant_id, net_id, port_id):
|
||||
self._cisco_ucs_plugin.delete_port(tenant_id, net_id,
|
||||
port_id)
|
||||
self.tearDownNetwork(tenant_id, net_id)
|
||||
|
||||
def tearDownNetworkPortInterface(self, tenant_id, net_id, port_id):
|
||||
self._cisco_ucs_plugin.unplug_interface(tenant_id, net_id,
|
||||
port_id)
|
||||
self.tearDownNetworkPort(tenant_id, net_id, port_id)
|
||||
18
quantum/plugins/cisco/ucs/__init__.py
Normal file
18
quantum/plugins/cisco/ucs/__init__.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2011 Cisco Systems, 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.
|
||||
#
|
||||
# @author: Sumit Naiksatam, Cisco Systems, Inc.
|
||||
#
|
||||
51
quantum/plugins/cisco/ucs/cisco_getvif.py
Normal file
51
quantum/plugins/cisco/ucs/cisco_getvif.py
Normal file
@@ -0,0 +1,51 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2011 Cisco Systems, 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.
|
||||
#
|
||||
# @author: Rohit Agarwalla, Cisco Systems Inc.
|
||||
#
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
|
||||
def get_next_dynic(argv=[]):
|
||||
cmd = ["ifconfig", "-a"]
|
||||
f_cmd_output = subprocess.Popen(cmd, stdout=subprocess.PIPE).\
|
||||
communicate()[0]
|
||||
eths = [lines.split(' ')[0] for lines in f_cmd_output.splitlines() \
|
||||
if "eth" in lines]
|
||||
#print eths
|
||||
for eth in eths:
|
||||
cmd = ["ethtool", "-i", eth]
|
||||
f_cmd_output = subprocess.Popen(cmd, stdout=subprocess.PIPE).\
|
||||
communicate()[0]
|
||||
bdf = [lines.split(' ')[1] for lines in f_cmd_output.splitlines() \
|
||||
if "bus-info" in lines]
|
||||
#print bdf
|
||||
cmd = ["lspci", "-n", "-s", bdf[0]]
|
||||
f_cmd_output = subprocess.Popen(cmd, stdout=subprocess.PIPE).\
|
||||
communicate()[0]
|
||||
deviceid = [(lines.split(':')[3]).split(' ')[0] \
|
||||
for lines in f_cmd_output.splitlines()]
|
||||
#print deviceid
|
||||
if deviceid[0] == "0044":
|
||||
cmd = ["/sbin/ip", "link", "show", eth]
|
||||
f_cmd_output = subprocess.Popen(cmd, stdout=subprocess.PIPE).\
|
||||
communicate()[0]
|
||||
used = [lines for lines in f_cmd_output.splitlines() \
|
||||
if "UP" in lines]
|
||||
if not used:
|
||||
break
|
||||
return eth
|
||||
37
quantum/plugins/cisco/ucs/cisco_ucs_configuration.py
Normal file
37
quantum/plugins/cisco/ucs/cisco_ucs_configuration.py
Normal file
@@ -0,0 +1,37 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2011 Cisco Systems, 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.
|
||||
#
|
||||
# @author: Sumit Naiksatam, Cisco Systems, Inc.
|
||||
#
|
||||
|
||||
import os
|
||||
|
||||
from quantum.plugins.cisco.common import cisco_configparser as confp
|
||||
|
||||
CONF_FILE = "../conf/ucs.ini"
|
||||
|
||||
cp = confp.CiscoConfigParser(os.path.dirname(os.path.realpath(__file__)) \
|
||||
+ "/" + CONF_FILE)
|
||||
|
||||
section = cp['UCSM']
|
||||
UCSM_IP_ADDRESS = section['ip_address']
|
||||
DEFAULT_VLAN_NAME = section['default_vlan_name']
|
||||
DEFAULT_VLAN_ID = section['default_vlan_id']
|
||||
MAX_UCSM_PORT_PROFILES = section['max_ucsm_port_profiles']
|
||||
PROFILE_NAME_PREFIX = section['profile_name_prefix']
|
||||
|
||||
section = cp['DRIVER']
|
||||
UCSM_DRIVER = section['name']
|
||||
235
quantum/plugins/cisco/ucs/cisco_ucs_network_driver.py
Normal file
235
quantum/plugins/cisco/ucs/cisco_ucs_network_driver.py
Normal file
@@ -0,0 +1,235 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2011 Cisco Systems, 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.
|
||||
#
|
||||
# @author: Sumit Naiksatam, Cisco Systems Inc.
|
||||
#
|
||||
"""
|
||||
Implements a UCSM XML API Client
|
||||
"""
|
||||
|
||||
import httplib
|
||||
import logging as LOG
|
||||
import string
|
||||
import subprocess
|
||||
from xml.etree import ElementTree as et
|
||||
import urllib
|
||||
|
||||
from quantum.plugins.cisco.common import cisco_constants as const
|
||||
from quantum.plugins.cisco.common import cisco_exceptions as cexc
|
||||
from quantum.plugins.cisco.ucs import cisco_getvif as gvif
|
||||
|
||||
|
||||
LOG.basicConfig(level=LOG.WARN)
|
||||
LOG.getLogger(const.LOGGER_COMPONENT_NAME)
|
||||
|
||||
COOKIE_VALUE = "cookie_placeholder"
|
||||
PROFILE_NAME = "profilename_placeholder"
|
||||
PROFILE_CLIENT = "profileclient_placeholder"
|
||||
VLAN_NAME = "vlanname_placeholder"
|
||||
VLAN_ID = "vlanid_placeholder"
|
||||
OLD_VLAN_NAME = "old_vlanname_placeholder"
|
||||
DYNAMIC_NIC_PREFIX = "eth"
|
||||
|
||||
# The following are standard strings, messages used to communicate with UCSM,
|
||||
#only place holder values change for each message
|
||||
HEADERS = {"Content-Type": "text/xml"}
|
||||
METHOD = "POST"
|
||||
URL = "/nuova"
|
||||
|
||||
CREATE_VLAN = "<configConfMos cookie=\"" + COOKIE_VALUE + \
|
||||
"\" inHierarchical=\"true\"> <inConfigs>" \
|
||||
"<pair key=\"fabric/lan/net-" + VLAN_NAME + \
|
||||
"\"> <fabricVlan defaultNet=\"no\" " \
|
||||
"dn=\"fabric/lan/net-" + VLAN_NAME + \
|
||||
"\" id=\"" + VLAN_ID + "\" name=\"" + \
|
||||
VLAN_NAME + "\" status=\"created\">" \
|
||||
"</fabricVlan> </pair> </inConfigs> </configConfMos>"
|
||||
|
||||
CREATE_PROFILE = "<configConfMos cookie=\"" + COOKIE_VALUE + \
|
||||
"\" inHierarchical=\"true\"> <inConfigs>" \
|
||||
"<pair key=\"fabric/lan/profiles/vnic-" + PROFILE_NAME + \
|
||||
"\"> <vnicProfile descr=\"Profile created by " \
|
||||
"Cisco OpenStack Quantum Plugin\" " \
|
||||
"dn=\"fabric/lan/profiles/vnic-" + PROFILE_NAME + \
|
||||
"\" maxPorts=\"64\" name=\"" + PROFILE_NAME + \
|
||||
"\" nwCtrlPolicyName=\"\" pinToGroupName=\"\" " \
|
||||
"qosPolicyName=\"\" status=\"created\"> " \
|
||||
"<vnicEtherIf defaultNet=\"yes\" name=\"" + VLAN_NAME + \
|
||||
"\" rn=\"if-" + VLAN_NAME + "\" > </vnicEtherIf> " \
|
||||
"</vnicProfile> </pair> </inConfigs> </configConfMos>"
|
||||
|
||||
ASSOCIATE_PROFILE = "<configConfMos cookie=\"" + COOKIE_VALUE + \
|
||||
"\" inHierarchical=\"true\"> <inConfigs> <pair " \
|
||||
"key=\"fabric/lan/profiles/vnic-" + PROFILE_NAME + \
|
||||
"/cl-" + PROFILE_CLIENT + "\"> <vmVnicProfCl dcName=\".*\" " \
|
||||
"descr=\"\" dn=\"fabric/lan/profiles/vnic-" + \
|
||||
PROFILE_NAME + "/cl-" + PROFILE_CLIENT + \
|
||||
"\"name=\"" + PROFILE_CLIENT + "\" orgPath=\".*\" " \
|
||||
"status=\"created\" swName=\"default$\"> </vmVnicProfCl>" \
|
||||
"</pair> </inConfigs> </configConfMos>"
|
||||
|
||||
CHANGE_VLAN_IN_PROFILE = "<configConfMos cookie=\"" + COOKIE_VALUE + \
|
||||
"\" inHierarchical=\"true\"> <inConfigs>" \
|
||||
"<pair key=\"fabric/lan/profiles/vnic-" + \
|
||||
PROFILE_NAME + "\"> <vnicProfile descr=\"Profile " \
|
||||
"created by Cisco OpenStack Quantum Plugin\" " \
|
||||
"dn=\"fabric/lan/profiles/vnic-" + \
|
||||
PROFILE_NAME + "\" maxPorts=\"64\" name=\"" + \
|
||||
PROFILE_NAME + "\" nwCtrlPolicyName=\"\" " \
|
||||
"pinToGroupName=\"\" qosPolicyName=\"\" " \
|
||||
"status=\"created,modified\">" \
|
||||
"<vnicEtherIf rn=\"if-" + OLD_VLAN_NAME + \
|
||||
"\" status=\"deleted\"> </vnicEtherIf> <vnicEtherIf " \
|
||||
"defaultNet=\"yes\" name=\"" + \
|
||||
VLAN_NAME + "\" rn=\"if-" + VLAN_NAME + \
|
||||
"\" > </vnicEtherIf> </vnicProfile> </pair>" \
|
||||
"</inConfigs> </configConfMos>"
|
||||
|
||||
DELETE_VLAN = "<configConfMos cookie=\"" + COOKIE_VALUE + \
|
||||
"\" inHierarchical=\"true\"> <inConfigs>" \
|
||||
"<pair key=\"fabric/lan/net-" + VLAN_NAME + \
|
||||
"\"> <fabricVlan dn=\"fabric/lan/net-" + VLAN_NAME + \
|
||||
"\" status=\"deleted\"> </fabricVlan> </pair> </inConfigs>" \
|
||||
"</configConfMos>"
|
||||
|
||||
DELETE_PROFILE = "<configConfMos cookie=\"" + COOKIE_VALUE + \
|
||||
"\" inHierarchical=\"false\"> <inConfigs>" \
|
||||
"<pair key=\"fabric/lan/profiles/vnic-" + PROFILE_NAME + \
|
||||
"\"> <vnicProfile dn=\"fabric/lan/profiles/vnic-" + \
|
||||
PROFILE_NAME + "\" status=\"deleted\"> </vnicProfile>" \
|
||||
"</pair> </inConfigs> </configConfMos>"
|
||||
|
||||
|
||||
class CiscoUCSMDriver():
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def _post_data(self, ucsm_ip, ucsm_username, ucsm_password, data):
|
||||
conn = httplib.HTTPConnection(ucsm_ip)
|
||||
login_data = "<aaaLogin inName=\"" + ucsm_username + \
|
||||
"\" inPassword=\"" + ucsm_password + "\" />"
|
||||
conn.request(METHOD, URL, login_data, HEADERS)
|
||||
response = conn.getresponse()
|
||||
response_data = response.read()
|
||||
LOG.debug(response.status)
|
||||
LOG.debug(response.reason)
|
||||
LOG.debug(response_data)
|
||||
# TODO (Sumit): If login is not successful, throw exception
|
||||
xmlTree = et.XML(response_data)
|
||||
cookie = xmlTree.attrib["outCookie"]
|
||||
|
||||
data = data.replace(COOKIE_VALUE, cookie)
|
||||
LOG.debug("POST: %s" % data)
|
||||
conn.request(METHOD, URL, data, HEADERS)
|
||||
response = conn.getresponse()
|
||||
response_data = response.read()
|
||||
LOG.debug(response.status)
|
||||
LOG.debug(response.reason)
|
||||
LOG.debug("UCSM Response: %s" % response_data)
|
||||
|
||||
logout_data = "<aaaLogout inCookie=\"" + cookie + "\" />"
|
||||
conn.request(METHOD, URL, logout_data, HEADERS)
|
||||
response = conn.getresponse()
|
||||
response_data = response.read()
|
||||
LOG.debug(response.status)
|
||||
LOG.debug(response.reason)
|
||||
LOG.debug(response_data)
|
||||
|
||||
def _create_vlan_post_data(self, vlan_name, vlan_id):
|
||||
data = CREATE_VLAN.replace(VLAN_NAME, vlan_name)
|
||||
data = data.replace(VLAN_ID, vlan_id)
|
||||
return data
|
||||
|
||||
def _create_profile_post_data(self, profile_name, vlan_name):
|
||||
data = CREATE_PROFILE.replace(PROFILE_NAME, profile_name)
|
||||
data = data.replace(VLAN_NAME, vlan_name)
|
||||
return data
|
||||
|
||||
def _create_profile_client_post_data(self, profile_name,
|
||||
profile_client_name):
|
||||
data = ASSOCIATE_PROFILE.replace(PROFILE_NAME, profile_name)
|
||||
data = data.replace(PROFILE_CLIENT, profile_client_name)
|
||||
return data
|
||||
|
||||
def _change_vlan_in_profile_post_data(self, profile_name, old_vlan_name,
|
||||
new_vlan_name):
|
||||
data = CHANGE_VLAN_IN_PROFILE.replace(PROFILE_NAME, profile_name)
|
||||
data = data.replace(OLD_VLAN_NAME, old_vlan_name)
|
||||
data = data.replace(VLAN_NAME, new_vlan_name)
|
||||
return data
|
||||
|
||||
def _delete_vlan_post_data(self, vlan_name):
|
||||
data = DELETE_VLAN.replace(VLAN_NAME, vlan_name)
|
||||
return data
|
||||
|
||||
def _delete_profile_post_data(self, profile_name):
|
||||
data = DELETE_PROFILE.replace(PROFILE_NAME, profile_name)
|
||||
return data
|
||||
|
||||
def _get_next_dynamic_nic(self):
|
||||
dynamic_nic_id = gvif.get_next_dynic()
|
||||
if len(dynamic_nic_id) > 0:
|
||||
return dynamic_nic_id
|
||||
else:
|
||||
raise cisco_exceptions.NoMoreNics(net_id=net_id, port_id=port_id)
|
||||
|
||||
def create_vlan(self, vlan_name, vlan_id, ucsm_ip, ucsm_username,
|
||||
ucsm_password):
|
||||
data = self._create_vlan_post_data(vlan_name, vlan_id)
|
||||
self._post_data(ucsm_ip, ucsm_username, ucsm_password, data)
|
||||
|
||||
def create_profile(self, profile_name, vlan_name, ucsm_ip, ucsm_username,
|
||||
ucsm_password):
|
||||
data = self._create_profile_post_data(profile_name, vlan_name)
|
||||
self._post_data(ucsm_ip, ucsm_username, ucsm_password, data)
|
||||
data = self._create_profile_client_post_data(profile_name,
|
||||
profile_name[-16:])
|
||||
self._post_data(ucsm_ip, ucsm_username, ucsm_password, data)
|
||||
|
||||
def change_vlan_in_profile(self, profile_name, old_vlan_name,
|
||||
new_vlan_name, ucsm_ip, ucsm_username,
|
||||
ucsm_password):
|
||||
data = self._change_vlan_in_profile_post_data(profile_name,
|
||||
old_vlan_name,
|
||||
new_vlan_name)
|
||||
self._post_data(ucsm_ip, ucsm_username, ucsm_password, data)
|
||||
|
||||
def get_dynamic_nic(self, host):
|
||||
# TODO (Sumit): Check availability per host
|
||||
# TODO (Sumit): If not available raise exception
|
||||
# TODO (Sumit): This simple logic assumes that create-port and
|
||||
# spawn-VM happens in lock-step
|
||||
# But we should support multiple create-port calls,
|
||||
# followed by spawn-VM calls
|
||||
# That would require managing a pool of available
|
||||
# dynamic vnics per host
|
||||
dynamic_nic_name = self._get_next_dynamic_nic()
|
||||
LOG.debug("Reserving dynamic nic %s" % dynamic_nic_name)
|
||||
return dynamic_nic_name
|
||||
|
||||
def delete_vlan(self, vlan_name, ucsm_ip, ucsm_username, ucsm_password):
|
||||
data = self._delete_vlan_post_data(vlan_name)
|
||||
self._post_data(ucsm_ip, ucsm_username, ucsm_password, data)
|
||||
|
||||
def delete_profile(self, profile_name, ucsm_ip, ucsm_username,
|
||||
ucsm_password):
|
||||
data = self._delete_profile_post_data(profile_name)
|
||||
self._post_data(ucsm_ip, ucsm_username, ucsm_password, data)
|
||||
|
||||
def release_dynamic_nic(self, host):
|
||||
# TODO (Sumit): Release on a specific host
|
||||
pass
|
||||
298
quantum/plugins/cisco/ucs/cisco_ucs_plugin.py
Normal file
298
quantum/plugins/cisco/ucs/cisco_ucs_plugin.py
Normal file
@@ -0,0 +1,298 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2011 Cisco Systems, 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.
|
||||
#
|
||||
# @author: Sumit Naiksatam, Cisco Systems, Inc.
|
||||
#
|
||||
|
||||
import logging as LOG
|
||||
|
||||
from quantum.common import exceptions as exc
|
||||
from quantum.common import utils
|
||||
from quantum.plugins.cisco.common import cisco_constants as const
|
||||
from quantum.plugins.cisco.common import cisco_credentials as cred
|
||||
from quantum.plugins.cisco.common import cisco_exceptions as cexc
|
||||
from quantum.plugins.cisco.common import cisco_utils as cutil
|
||||
from quantum.plugins.cisco.l2device_plugin_base import L2DevicePluginBase
|
||||
from quantum.plugins.cisco.ucs import cisco_ucs_configuration as conf
|
||||
|
||||
LOG.basicConfig(level=LOG.WARN)
|
||||
LOG.getLogger(const.LOGGER_COMPONENT_NAME)
|
||||
|
||||
|
||||
class UCSVICPlugin(L2DevicePluginBase):
|
||||
_networks = {}
|
||||
|
||||
def __init__(self):
|
||||
self._client = utils.import_object(conf.UCSM_DRIVER)
|
||||
LOG.debug("Loaded driver %s\n" % conf.UCSM_DRIVER)
|
||||
self._utils = cutil.DBUtils()
|
||||
# TODO (Sumit) This is for now, when using only one chassis
|
||||
self._ucsm_ip = conf.UCSM_IP_ADDRESS
|
||||
self._ucsm_username = cred.Store.getUsername(conf.UCSM_IP_ADDRESS)
|
||||
self._ucsm_password = cred.Store.getPassword(conf.UCSM_IP_ADDRESS)
|
||||
# TODO (Sumit) Make the counter per UCSM
|
||||
self._port_profile_counter = 0
|
||||
|
||||
def get_all_networks(self, tenant_id, **kwargs):
|
||||
"""
|
||||
Returns a dictionary containing all
|
||||
<network_uuid, network_name> for
|
||||
the specified tenant.
|
||||
"""
|
||||
LOG.debug("UCSVICPlugin:get_all_networks() called\n")
|
||||
return self._networks.values()
|
||||
|
||||
def create_network(self, tenant_id, net_name, net_id, vlan_name, vlan_id,
|
||||
**kwargs):
|
||||
"""
|
||||
Creates a new Virtual Network, and assigns it
|
||||
a symbolic name.
|
||||
"""
|
||||
LOG.debug("UCSVICPlugin:create_network() called\n")
|
||||
self._client.create_vlan(vlan_name, str(vlan_id), self._ucsm_ip,
|
||||
self._ucsm_username, self._ucsm_password)
|
||||
new_net_dict = {const.NET_ID: net_id,
|
||||
const.NET_NAME: net_name,
|
||||
const.NET_PORTS: {},
|
||||
const.NET_VLAN_NAME: vlan_name,
|
||||
const.NET_VLAN_ID: vlan_id}
|
||||
self._networks[net_id] = new_net_dict
|
||||
return new_net_dict
|
||||
|
||||
def delete_network(self, tenant_id, net_id, **kwargs):
|
||||
"""
|
||||
Deletes the network with the specified network identifier
|
||||
belonging to the specified tenant.
|
||||
"""
|
||||
LOG.debug("UCSVICPlugin:delete_network() called\n")
|
||||
net = self._networks.get(net_id)
|
||||
# TODO (Sumit) : Verify that no attachments are plugged into the
|
||||
# network
|
||||
if net:
|
||||
# TODO (Sumit) : Before deleting the network, make sure all the
|
||||
# ports associated with this network are also deleted
|
||||
self._client.delete_vlan(net[const.NET_VLAN_NAME], self._ucsm_ip,
|
||||
self._ucsm_username, self._ucsm_password)
|
||||
self._networks.pop(net_id)
|
||||
return net
|
||||
raise exc.NetworkNotFound(net_id=net_id)
|
||||
|
||||
def get_network_details(self, tenant_id, net_id, **kwargs):
|
||||
"""
|
||||
Deletes the Virtual Network belonging to a the
|
||||
spec
|
||||
"""
|
||||
LOG.debug("UCSVICPlugin:get_network_details() called\n")
|
||||
network = self._get_network(tenant_id, net_id)
|
||||
return network
|
||||
|
||||
def rename_network(self, tenant_id, net_id, new_name, **kwargs):
|
||||
"""
|
||||
Updates the symbolic name belonging to a particular
|
||||
Virtual Network.
|
||||
"""
|
||||
LOG.debug("UCSVICPlugin:rename_network() called\n")
|
||||
network = self._get_network(tenant_id, net_id)
|
||||
network[const.NET_NAME] = new_name
|
||||
return network
|
||||
|
||||
def get_all_ports(self, tenant_id, net_id, **kwargs):
|
||||
"""
|
||||
Retrieves all port identifiers belonging to the
|
||||
specified Virtual Network.
|
||||
"""
|
||||
LOG.debug("UCSVICPlugin:get_all_ports() called\n")
|
||||
network = self._get_network(tenant_id, net_id)
|
||||
ports_on_net = network[const.NET_PORTS].values()
|
||||
return ports_on_net
|
||||
|
||||
def create_port(self, tenant_id, net_id, port_state, port_id, **kwargs):
|
||||
"""
|
||||
Creates a port on the specified Virtual Network.
|
||||
"""
|
||||
LOG.debug("UCSVICPlugin:create_port() called\n")
|
||||
net = self._get_network(tenant_id, net_id)
|
||||
ports = net[const.NET_PORTS]
|
||||
# TODO (Sumit): This works on a single host deployment,
|
||||
# in multi-host environment, dummy needs to be replaced with the
|
||||
# hostname
|
||||
dynamic_nic_name = self._client.get_dynamic_nic("dummy")
|
||||
new_port_profile = self._create_port_profile(tenant_id, net_id,
|
||||
port_id,
|
||||
conf.DEFAULT_VLAN_NAME,
|
||||
conf.DEFAULT_VLAN_ID)
|
||||
profile_name = new_port_profile[const.PROFILE_NAME]
|
||||
sql_query = "INSERT INTO ports (port_id, profile_name, dynamic_vnic," \
|
||||
"host, instance_name, instance_nic_name, used) VALUES" \
|
||||
"('%s', '%s', '%s', 'dummy', NULL, NULL, 0)" % \
|
||||
(port_id, profile_name, dynamic_nic_name)
|
||||
self._utils.execute_db_query(sql_query)
|
||||
new_port_dict = {const.PORT_ID: port_id,
|
||||
const.PORT_STATE: const.PORT_UP,
|
||||
const.ATTACHMENT: None,
|
||||
const.PORT_PROFILE: new_port_profile}
|
||||
ports[port_id] = new_port_dict
|
||||
return new_port_dict
|
||||
|
||||
def delete_port(self, tenant_id, net_id, port_id, **kwargs):
|
||||
"""
|
||||
Deletes a port on a specified Virtual Network,
|
||||
if the port contains a remote interface attachment,
|
||||
the remote interface should first be un-plugged and
|
||||
then the port can be deleted.
|
||||
"""
|
||||
LOG.debug("UCSVICPlugin:delete_port() called\n")
|
||||
port = self._get_port(tenant_id, net_id, port_id)
|
||||
if port[const.ATTACHMENT]:
|
||||
raise exc.PortInUse(net_id=net_id, port_id=port_id,
|
||||
att_id=port[const.ATTACHMENT])
|
||||
try:
|
||||
#TODO (Sumit): Before deleting port profile make sure that there
|
||||
# is no VM using this port profile
|
||||
self._client.release_dynamic_nic("dummy")
|
||||
port_profile = port[const.PORT_PROFILE]
|
||||
self._delete_port_profile(port_id,
|
||||
port_profile[const.PROFILE_NAME])
|
||||
sql_query = "delete from ports where port_id = \"%s\"" % \
|
||||
(port[const.PORT_ID])
|
||||
self._utils.execute_db_query(sql_query)
|
||||
net = self._get_network(tenant_id, net_id)
|
||||
net[const.NET_PORTS].pop(port_id)
|
||||
except KeyError:
|
||||
raise exc.PortNotFound(net_id=net_id, port_id=port_id)
|
||||
|
||||
def update_port(self, tenant_id, net_id, port_id, port_state, **kwargs):
|
||||
"""
|
||||
Updates the state of a port on the specified Virtual Network.
|
||||
"""
|
||||
LOG.debug("UCSVICPlugin:update_port() called\n")
|
||||
port = self._get_port(tenant_id, net_id, port_id)
|
||||
self._validate_port_state(port_state)
|
||||
port[const.PORT_STATE] = port_state
|
||||
return port
|
||||
|
||||
def get_port_details(self, tenant_id, net_id, port_id, **kwargs):
|
||||
"""
|
||||
This method allows the user to retrieve a remote interface
|
||||
that is attached to this particular port.
|
||||
"""
|
||||
LOG.debug("UCSVICPlugin:get_port_details() called\n")
|
||||
return self._get_port(tenant_id, net_id, port_id)
|
||||
|
||||
def plug_interface(self, tenant_id, net_id, port_id, remote_interface_id,
|
||||
**kwargs):
|
||||
"""
|
||||
Attaches a remote interface to the specified port on the
|
||||
specified Virtual Network.
|
||||
"""
|
||||
LOG.debug("UCSVICPlugin:plug_interface() called\n")
|
||||
self._validate_attachment(tenant_id, net_id, port_id,
|
||||
remote_interface_id)
|
||||
port = self._get_port(tenant_id, net_id, port_id)
|
||||
if port[const.ATTACHMENT]:
|
||||
raise exc.PortInUse(net_id=net_id, port_id=port_id,
|
||||
att_id=port[const.ATTACHMENT])
|
||||
port[const.ATTACHMENT] = remote_interface_id
|
||||
port_profile = port[const.PORT_PROFILE]
|
||||
profile_name = port_profile[const.PROFILE_NAME]
|
||||
old_vlan_name = port_profile[const.PROFILE_VLAN_NAME]
|
||||
new_vlan_name = self._get_vlan_name_for_network(tenant_id, net_id)
|
||||
new_vlan_id = self._get_vlan_id_for_network(tenant_id, net_id)
|
||||
self._client.change_vlan_in_profile(profile_name, old_vlan_name,
|
||||
new_vlan_name, self._ucsm_ip,
|
||||
self._ucsm_username,
|
||||
self._ucsm_password)
|
||||
port_profile[const.PROFILE_VLAN_NAME] = new_vlan_name
|
||||
port_profile[const.PROFILE_VLAN_ID] = new_vlan_id
|
||||
|
||||
def unplug_interface(self, tenant_id, net_id, port_id, **kwargs):
|
||||
"""
|
||||
Detaches a remote interface from the specified port on the
|
||||
specified Virtual Network.
|
||||
"""
|
||||
LOG.debug("UCSVICPlugin:unplug_interface() called\n")
|
||||
port = self._get_port(tenant_id, net_id, port_id)
|
||||
port[const.ATTACHMENT] = None
|
||||
port_profile = port[const.PORT_PROFILE]
|
||||
profile_name = port_profile[const.PROFILE_NAME]
|
||||
old_vlan_name = port_profile[const.PROFILE_VLAN_NAME]
|
||||
new_vlan_name = conf.DEFAULT_VLAN_NAME
|
||||
self._client.change_vlan_in_profile(profile_name, old_vlan_name,
|
||||
new_vlan_name, self._ucsm_ip,
|
||||
self._ucsm_username,
|
||||
self._ucsm_password)
|
||||
port_profile[const.PROFILE_VLAN_NAME] = conf.DEFAULT_VLAN_NAME
|
||||
port_profile[const.PROFILE_VLAN_ID] = conf.DEFAULT_VLAN_ID
|
||||
|
||||
def _get_profile_name(self, port_id):
|
||||
profile_name = conf.PROFILE_NAME_PREFIX + port_id
|
||||
return profile_name
|
||||
|
||||
def _validate_port_state(self, port_state):
|
||||
if port_state.upper() not in (const.PORT_UP, const.PORT_DOWN):
|
||||
raise exc.StateInvalid(port_state=port_state)
|
||||
return True
|
||||
|
||||
def _validate_attachment(self, tenant_id, network_id, port_id,
|
||||
remote_interface_id):
|
||||
network = self._get_network(tenant_id, network_id)
|
||||
for port in network[const.NET_PORTS].values():
|
||||
if port[const.ATTACHMENT] == remote_interface_id:
|
||||
raise exc.AlreadyAttached(net_id=network_id,
|
||||
port_id=port_id,
|
||||
att_id=port[const.ATTACHMENT],
|
||||
att_port_id=port[const.PORT_ID])
|
||||
|
||||
def _get_network(self, tenant_id, network_id):
|
||||
network = self._networks.get(network_id)
|
||||
if not network:
|
||||
raise exc.NetworkNotFound(net_id=network_id)
|
||||
return network
|
||||
|
||||
def _get_vlan_name_for_network(self, tenant_id, network_id):
|
||||
net = self._get_network(tenant_id, network_id)
|
||||
vlan_name = net[const.NET_VLAN_NAME]
|
||||
return vlan_name
|
||||
|
||||
def _get_vlan_id_for_network(self, tenant_id, network_id):
|
||||
net = self._get_network(tenant_id, network_id)
|
||||
vlan_id = net[const.NET_VLAN_ID]
|
||||
return vlan_id
|
||||
|
||||
def _get_port(self, tenant_id, network_id, port_id):
|
||||
net = self._get_network(tenant_id, network_id)
|
||||
port = net[const.NET_PORTS].get(port_id)
|
||||
if not port:
|
||||
raise exc.PortNotFound(net_id=network_id, port_id=port_id)
|
||||
return port
|
||||
|
||||
def _create_port_profile(self, tenant_id, net_id, port_id, vlan_name,
|
||||
vlan_id):
|
||||
if self._port_profile_counter >= int(conf.MAX_UCSM_PORT_PROFILES):
|
||||
raise cexc.UCSMPortProfileLimit(net_id=net_id, port_id=port_id)
|
||||
profile_name = self._get_profile_name(port_id)
|
||||
self._client.create_profile(profile_name, vlan_name, self._ucsm_ip,
|
||||
self._ucsm_username, self._ucsm_password)
|
||||
self._port_profile_counter += 1
|
||||
new_port_profile = {const.PROFILE_NAME: profile_name,
|
||||
const.PROFILE_VLAN_NAME: vlan_name,
|
||||
const.PROFILE_VLAN_ID: vlan_id}
|
||||
return new_port_profile
|
||||
|
||||
def _delete_port_profile(self, port_id, profile_name):
|
||||
self._client.delete_profile(profile_name, self._ucsm_ip,
|
||||
self._ucsm_username, self._ucsm_password)
|
||||
self._port_profile_counter -= 1
|
||||
@@ -198,7 +198,7 @@ class OVSQuantumAgent:
|
||||
|
||||
while True:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT * FROM ports")
|
||||
cursor.execute("SELECT * FROM ports where state = 'ACTIVE'")
|
||||
rows = cursor.fetchall()
|
||||
cursor.close()
|
||||
all_bindings = {}
|
||||
|
||||
@@ -19,11 +19,12 @@
|
||||
|
||||
import ConfigParser
|
||||
import logging as LOG
|
||||
from optparse import OptionParser
|
||||
import os
|
||||
import sys
|
||||
|
||||
from quantum.common import exceptions as q_exc
|
||||
from quantum.quantum_plugin_base import QuantumPluginBase
|
||||
from optparse import OptionParser
|
||||
|
||||
import quantum.db.api as db
|
||||
import ovs_db
|
||||
@@ -110,99 +111,91 @@ class OVSQuantumPlugin(QuantumPluginBase):
|
||||
nets = []
|
||||
for x in db.network_list(tenant_id):
|
||||
LOG.debug("Adding network: %s" % x.uuid)
|
||||
d = {}
|
||||
d["net-id"] = str(x.uuid)
|
||||
d["net-name"] = x.name
|
||||
nets.append(d)
|
||||
nets.append(self._make_net_dict(str(x.uuid), x.name, None))
|
||||
return nets
|
||||
|
||||
def _make_net_dict(self, net_id, net_name, ports):
|
||||
res = {'net-id': net_id,
|
||||
'net-name': net_name}
|
||||
if ports:
|
||||
res['net-ports'] = ports
|
||||
return res
|
||||
|
||||
def create_network(self, tenant_id, net_name):
|
||||
d = {}
|
||||
try:
|
||||
res = db.network_create(tenant_id, net_name)
|
||||
LOG.debug("Created newtork: %s" % res)
|
||||
except Exception, e:
|
||||
LOG.error("Error: %s" % str(e))
|
||||
return d
|
||||
d["net-id"] = str(res.uuid)
|
||||
d["net-name"] = res.name
|
||||
vlan_id = self.vmap.acquire(str(res.uuid))
|
||||
ovs_db.add_vlan_binding(vlan_id, str(res.uuid))
|
||||
return d
|
||||
net = db.network_create(tenant_id, net_name)
|
||||
LOG.debug("Created network: %s" % net)
|
||||
vlan_id = self.vmap.acquire(str(net.uuid))
|
||||
ovs_db.add_vlan_binding(vlan_id, str(net.uuid))
|
||||
return self._make_net_dict(str(net.uuid), net.name, [])
|
||||
|
||||
def delete_network(self, tenant_id, net_id):
|
||||
net = db.network_get(net_id)
|
||||
|
||||
# Verify that no attachments are plugged into the network
|
||||
for port in db.port_list(net_id):
|
||||
if port['interface_id']:
|
||||
raise q_exc.NetworkInUse(net_id=net_id)
|
||||
net = db.network_destroy(net_id)
|
||||
d = {}
|
||||
d["net-id"] = str(net.uuid)
|
||||
ovs_db.remove_vlan_binding(net_id)
|
||||
self.vmap.release(net_id)
|
||||
return d
|
||||
return self._make_net_dict(str(net.uuid), net.name, [])
|
||||
|
||||
def get_network_details(self, tenant_id, net_id):
|
||||
ports = db.port_list(net_id)
|
||||
ifaces = []
|
||||
for p in ports:
|
||||
ifaces.append(p.interface_id)
|
||||
return ifaces
|
||||
net = db.network_get(net_id)
|
||||
ports = self.get_all_ports(tenant_id, net_id)
|
||||
return self._make_net_dict(str(net.uuid), net.name, ports)
|
||||
|
||||
def rename_network(self, tenant_id, net_id, new_name):
|
||||
try:
|
||||
net = db.network_rename(net_id, tenant_id, new_name)
|
||||
except Exception, e:
|
||||
raise Exception("Failed to rename network: %s" % str(e))
|
||||
d = {}
|
||||
d["net-id"] = str(net.uuid)
|
||||
d["net-name"] = net.name
|
||||
return d
|
||||
net = db.network_rename(net_id, tenant_id, new_name)
|
||||
return self._make_net_dict(str(net.uuid), net.name, None)
|
||||
|
||||
def _make_port_dict(self, port_id, port_state, net_id, attachment):
|
||||
res = {'port-id': port_id,
|
||||
'port-state': port_state}
|
||||
if net_id:
|
||||
res['net-id'] = net_id
|
||||
if attachment:
|
||||
res['attachment-id'] = attachment
|
||||
return res
|
||||
|
||||
def get_all_ports(self, tenant_id, net_id):
|
||||
ids = []
|
||||
ports = db.port_list(net_id)
|
||||
for x in ports:
|
||||
LOG.debug("Appending port: %s" % x.uuid)
|
||||
d = {}
|
||||
d["port-id"] = str(x.uuid)
|
||||
for p in ports:
|
||||
LOG.debug("Appending port: %s" % p.uuid)
|
||||
d = self._make_port_dict(str(p.uuid), p.state, None, None)
|
||||
ids.append(d)
|
||||
return ids
|
||||
|
||||
def create_port(self, tenant_id, net_id, port_state=None):
|
||||
LOG.debug("Creating port with network_id: %s" % net_id)
|
||||
port = db.port_create(net_id)
|
||||
d = {}
|
||||
d["port-id"] = str(port.uuid)
|
||||
LOG.debug("-> %s" % (port.uuid))
|
||||
return d
|
||||
port = db.port_create(net_id, port_state)
|
||||
return self._make_port_dict(str(port.uuid), port.state, None, None)
|
||||
|
||||
def delete_port(self, tenant_id, net_id, port_id):
|
||||
try:
|
||||
port = db.port_destroy(port_id)
|
||||
except Exception, e:
|
||||
raise Exception("Failed to delete port: %s" % str(e))
|
||||
d = {}
|
||||
d["port-id"] = str(port.uuid)
|
||||
return d
|
||||
port = db.port_destroy(port_id, net_id)
|
||||
return self._make_port_dict(str(port.uuid), port.state, None, None)
|
||||
|
||||
def update_port(self, tenant_id, net_id, port_id, port_state):
|
||||
"""
|
||||
Updates the state of a port on the specified Virtual Network.
|
||||
"""
|
||||
LOG.debug("update_port() called\n")
|
||||
port = db.port_get(port_id)
|
||||
port['port-state'] = port_state
|
||||
return port
|
||||
port = db.port_get(port_id, net_id)
|
||||
db.port_set_state(port_id, net_id, port_state)
|
||||
return self._make_port_dict(str(port.uuid), port.state, None, None)
|
||||
|
||||
def get_port_details(self, tenant_id, net_id, port_id):
|
||||
port = db.port_get(port_id)
|
||||
rv = {"port-id": port.uuid, "attachment": port.interface_id,
|
||||
"net-id": port.network_id, "port-state": "UP"}
|
||||
return rv
|
||||
port = db.port_get(port_id, net_id)
|
||||
return self._make_port_dict(str(port.uuid), port.state,
|
||||
port.network_id, port.interface_id)
|
||||
|
||||
def plug_interface(self, tenant_id, net_id, port_id, remote_iface_id):
|
||||
db.port_set_attachment(port_id, remote_iface_id)
|
||||
db.port_set_attachment(port_id, net_id, remote_iface_id)
|
||||
|
||||
def unplug_interface(self, tenant_id, net_id, port_id):
|
||||
db.port_set_attachment(port_id, "")
|
||||
db.port_set_attachment(port_id, net_id, "")
|
||||
|
||||
def get_interface_details(self, tenant_id, net_id, port_id):
|
||||
res = db.port_get(port_id)
|
||||
res = db.port_get(port_id, net_id)
|
||||
return res.interface_id
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2011 OpenStack LLC.
|
||||
# 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 logging
|
||||
logging.basicConfig()
|
||||
|
||||
75
tests/unit/extension_stubs.py
Normal file
75
tests/unit/extension_stubs.py
Normal file
@@ -0,0 +1,75 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
# Copyright 2011 OpenStack LLC.
|
||||
# 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 abc import abstractmethod
|
||||
|
||||
from quantum.common import extensions
|
||||
from quantum.common import wsgi
|
||||
|
||||
|
||||
class StubExtension(object):
|
||||
|
||||
def __init__(self, alias="stub_extension"):
|
||||
self.alias = alias
|
||||
|
||||
def get_name(self):
|
||||
return "Stub Extension"
|
||||
|
||||
def get_alias(self):
|
||||
return self.alias
|
||||
|
||||
def get_description(self):
|
||||
return ""
|
||||
|
||||
def get_namespace(self):
|
||||
return ""
|
||||
|
||||
def get_updated(self):
|
||||
return ""
|
||||
|
||||
|
||||
class StubPlugin(object):
|
||||
|
||||
def __init__(self, supported_extensions=[]):
|
||||
self.supported_extension_aliases = supported_extensions
|
||||
|
||||
|
||||
class ExtensionExpectingPluginInterface(StubExtension):
|
||||
"""
|
||||
This extension expects plugin to implement all the methods defined
|
||||
in StubPluginInterface
|
||||
"""
|
||||
|
||||
def get_plugin_interface(self):
|
||||
return StubPluginInterface
|
||||
|
||||
|
||||
class StubPluginInterface(extensions.PluginInterface):
|
||||
|
||||
@abstractmethod
|
||||
def get_foo(self, bar=None):
|
||||
pass
|
||||
|
||||
|
||||
class StubBaseAppController(wsgi.Controller):
|
||||
|
||||
def index(self, request):
|
||||
return "base app index"
|
||||
|
||||
def show(self, request, id):
|
||||
return {'fort': 'knox'}
|
||||
|
||||
def update(self, request, id):
|
||||
return {'uneditable': 'original_value'}
|
||||
15
tests/unit/extensions/__init__.py
Normal file
15
tests/unit/extensions/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2011 OpenStack LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
110
tests/unit/extensions/foxinsocks.py
Normal file
110
tests/unit/extensions/foxinsocks.py
Normal file
@@ -0,0 +1,110 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2011 OpenStack LLC.
|
||||
# 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 json
|
||||
|
||||
from quantum.common import wsgi
|
||||
from quantum.common import extensions
|
||||
from abc import abstractmethod
|
||||
|
||||
|
||||
class FoxInSocksController(wsgi.Controller):
|
||||
|
||||
def index(self, request):
|
||||
return "Try to say this Mr. Knox, sir..."
|
||||
|
||||
|
||||
class FoxInSocksPluginInterface(extensions.PluginInterface):
|
||||
|
||||
@abstractmethod
|
||||
def method_to_support_foxnsox_extension(self):
|
||||
pass
|
||||
|
||||
|
||||
class Foxinsocks(object):
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def get_plugin_interface(self):
|
||||
return FoxInSocksPluginInterface
|
||||
|
||||
def get_name(self):
|
||||
return "Fox In Socks"
|
||||
|
||||
def get_alias(self):
|
||||
return "FOXNSOX"
|
||||
|
||||
def get_description(self):
|
||||
return "The Fox In Socks Extension"
|
||||
|
||||
def get_namespace(self):
|
||||
return "http://www.fox.in.socks/api/ext/pie/v1.0"
|
||||
|
||||
def get_updated(self):
|
||||
return "2011-01-22T13:25:27-06:00"
|
||||
|
||||
def get_resources(self):
|
||||
resources = []
|
||||
resource = extensions.ResourceExtension('foxnsocks',
|
||||
FoxInSocksController())
|
||||
resources.append(resource)
|
||||
return resources
|
||||
|
||||
def get_actions(self):
|
||||
return [extensions.ActionExtension('dummy_resources',
|
||||
'FOXNSOX:add_tweedle',
|
||||
self._add_tweedle_handler),
|
||||
extensions.ActionExtension('dummy_resources',
|
||||
'FOXNSOX:delete_tweedle',
|
||||
self._delete_tweedle_handler)]
|
||||
|
||||
def get_request_extensions(self):
|
||||
request_exts = []
|
||||
|
||||
def _goose_handler(req, res):
|
||||
#NOTE: This only handles JSON responses.
|
||||
# You can use content type header to test for XML.
|
||||
data = json.loads(res.body)
|
||||
data['FOXNSOX:googoose'] = req.GET.get('chewing')
|
||||
res.body = json.dumps(data)
|
||||
return res
|
||||
|
||||
req_ext1 = extensions.RequestExtension('GET', '/dummy_resources/:(id)',
|
||||
_goose_handler)
|
||||
request_exts.append(req_ext1)
|
||||
|
||||
def _bands_handler(req, res):
|
||||
#NOTE: This only handles JSON responses.
|
||||
# You can use content type header to test for XML.
|
||||
data = json.loads(res.body)
|
||||
data['FOXNSOX:big_bands'] = 'Pig Bands!'
|
||||
res.body = json.dumps(data)
|
||||
return res
|
||||
|
||||
req_ext2 = extensions.RequestExtension('GET', '/dummy_resources/:(id)',
|
||||
_bands_handler)
|
||||
request_exts.append(req_ext2)
|
||||
return request_exts
|
||||
|
||||
def _add_tweedle_handler(self, input_dict, req, id):
|
||||
return "Tweedle {0} Added.".format(
|
||||
input_dict['FOXNSOX:add_tweedle']['name'])
|
||||
|
||||
def _delete_tweedle_handler(self, input_dict, req, id):
|
||||
return "Tweedle {0} Deleted.".format(
|
||||
input_dict['FOXNSOX:delete_tweedle']['name'])
|
||||
@@ -20,8 +20,8 @@
|
||||
import logging
|
||||
import unittest
|
||||
|
||||
import tests.unit.testlib_api as testlib
|
||||
|
||||
import tests.unit.testlib_api as testlib
|
||||
from quantum import api as server
|
||||
from quantum.db import api as db
|
||||
from quantum.common.test_lib import test_config
|
||||
@@ -150,6 +150,19 @@ class APITest(unittest.TestCase):
|
||||
network_data['network'])
|
||||
LOG.debug("_test_rename_network - format:%s - END", format)
|
||||
|
||||
def _test_rename_network_duplicate(self, format):
|
||||
LOG.debug("_test_rename_network_duplicate - format:%s - START", format)
|
||||
content_type = "application/%s" % format
|
||||
network_id1 = self._create_network(format, name="net1")
|
||||
network_id2 = self._create_network(format, name="net2")
|
||||
update_network_req = testlib.update_network_request(self.tenant_id,
|
||||
network_id2,
|
||||
"net1",
|
||||
format)
|
||||
update_network_res = update_network_req.get_response(self.api)
|
||||
self.assertEqual(update_network_res.status_int, 422)
|
||||
LOG.debug("_test_rename_network_duplicate - format:%s - END", format)
|
||||
|
||||
def _test_rename_network_badrequest(self, format):
|
||||
LOG.debug("_test_rename_network_badrequest - format:%s - START",
|
||||
format)
|
||||
@@ -427,6 +440,23 @@ class APITest(unittest.TestCase):
|
||||
show_port_res.body, content_type)
|
||||
self.assertEqual({'id': port_id, 'state': new_port_state},
|
||||
port_data['port'])
|
||||
|
||||
# now set it back to the original value
|
||||
update_port_req = testlib.update_port_request(self.tenant_id,
|
||||
network_id, port_id,
|
||||
port_state,
|
||||
format)
|
||||
update_port_res = update_port_req.get_response(self.api)
|
||||
self.assertEqual(update_port_res.status_int, 200)
|
||||
show_port_req = testlib.show_port_request(self.tenant_id,
|
||||
network_id, port_id,
|
||||
format)
|
||||
show_port_res = show_port_req.get_response(self.api)
|
||||
self.assertEqual(show_port_res.status_int, 200)
|
||||
port_data = self._port_serializer.deserialize(
|
||||
show_port_res.body, content_type)
|
||||
self.assertEqual({'id': port_id, 'state': port_state},
|
||||
port_data['port'])
|
||||
LOG.debug("_test_set_port_state - format:%s - END", format)
|
||||
|
||||
def _test_set_port_state_networknotfound(self, format):
|
||||
@@ -706,6 +736,12 @@ class APITest(unittest.TestCase):
|
||||
def test_rename_network_xml(self):
|
||||
self._test_rename_network('xml')
|
||||
|
||||
def test_rename_network_duplicate_json(self):
|
||||
self._test_rename_network_duplicate('json')
|
||||
|
||||
def test_rename_network_duplicate_xml(self):
|
||||
self._test_rename_network_duplicate('xml')
|
||||
|
||||
def test_rename_network_badrequest_json(self):
|
||||
self._test_rename_network_badrequest('json')
|
||||
|
||||
|
||||
@@ -131,19 +131,19 @@ class APITest(unittest.TestCase):
|
||||
LOG.debug("_test_list_networks - tenant:%s "\
|
||||
"- format:%s - END", format, tenant)
|
||||
|
||||
def _test_list_network_details(self,
|
||||
def _test_show_network_details(self,
|
||||
tenant=TENANT_1, format='json', status=200):
|
||||
LOG.debug("_test_list_network_details - tenant:%s "\
|
||||
LOG.debug("_test_show_network_details - tenant:%s "\
|
||||
"- format:%s - START", format, tenant)
|
||||
|
||||
self._assert_sanity(self.client.list_network_details,
|
||||
self._assert_sanity(self.client.show_network_details,
|
||||
status,
|
||||
"GET",
|
||||
"networks/001",
|
||||
data=["001"],
|
||||
params={'tenant': tenant, 'format': format})
|
||||
|
||||
LOG.debug("_test_list_network_details - tenant:%s "\
|
||||
LOG.debug("_test_show_network_details - tenant:%s "\
|
||||
"- format:%s - END", format, tenant)
|
||||
|
||||
def _test_create_network(self, tenant=TENANT_1, format='json', status=200):
|
||||
@@ -203,19 +203,19 @@ class APITest(unittest.TestCase):
|
||||
LOG.debug("_test_list_ports - tenant:%s "\
|
||||
"- format:%s - END", format, tenant)
|
||||
|
||||
def _test_list_port_details(self,
|
||||
def _test_show_port_details(self,
|
||||
tenant=TENANT_1, format='json', status=200):
|
||||
LOG.debug("_test_list_port_details - tenant:%s "\
|
||||
LOG.debug("_test_show_port_details - tenant:%s "\
|
||||
"- format:%s - START", format, tenant)
|
||||
|
||||
self._assert_sanity(self.client.list_port_details,
|
||||
self._assert_sanity(self.client.show_port_details,
|
||||
status,
|
||||
"GET",
|
||||
"networks/001/ports/001",
|
||||
data=["001", "001"],
|
||||
params={'tenant': tenant, 'format': format})
|
||||
|
||||
LOG.debug("_test_list_port_details - tenant:%s "\
|
||||
LOG.debug("_test_show_port_details - tenant:%s "\
|
||||
"- format:%s - END", format, tenant)
|
||||
|
||||
def _test_create_port(self, tenant=TENANT_1, format='json', status=200):
|
||||
@@ -261,19 +261,19 @@ class APITest(unittest.TestCase):
|
||||
LOG.debug("_test_set_port_state - tenant:%s "\
|
||||
"- format:%s - END", format, tenant)
|
||||
|
||||
def _test_list_port_attachments(self,
|
||||
def _test_show_port_attachment(self,
|
||||
tenant=TENANT_1, format='json', status=200):
|
||||
LOG.debug("_test_list_port_attachments - tenant:%s "\
|
||||
LOG.debug("_test_show_port_attachment - tenant:%s "\
|
||||
"- format:%s - START", format, tenant)
|
||||
|
||||
self._assert_sanity(self.client.list_port_attachments,
|
||||
self._assert_sanity(self.client.show_port_attachment,
|
||||
status,
|
||||
"GET",
|
||||
"networks/001/ports/001/attachment",
|
||||
data=["001", "001"],
|
||||
params={'tenant': tenant, 'format': format})
|
||||
|
||||
LOG.debug("_test_list_port_attachments - tenant:%s "\
|
||||
LOG.debug("_test_show_port_attachment - tenant:%s "\
|
||||
"- format:%s - END", format, tenant)
|
||||
|
||||
def _test_attach_resource(self, tenant=TENANT_1,
|
||||
@@ -345,23 +345,23 @@ class APITest(unittest.TestCase):
|
||||
def test_list_networks_error_401(self):
|
||||
self._test_list_networks(status=401)
|
||||
|
||||
def test_list_network_details_json(self):
|
||||
self._test_list_network_details(format='json')
|
||||
def test_show_network_details_json(self):
|
||||
self._test_show_network_details(format='json')
|
||||
|
||||
def test_list_network_details_xml(self):
|
||||
self._test_list_network_details(format='xml')
|
||||
def test_show_network_details_xml(self):
|
||||
self._test_show_network_details(format='xml')
|
||||
|
||||
def test_list_network_details_alt_tenant(self):
|
||||
self._test_list_network_details(tenant=TENANT_2)
|
||||
def test_show_network_details_alt_tenant(self):
|
||||
self._test_show_network_details(tenant=TENANT_2)
|
||||
|
||||
def test_list_network_details_error_470(self):
|
||||
self._test_list_network_details(status=470)
|
||||
def test_show_network_details_error_470(self):
|
||||
self._test_show_network_details(status=470)
|
||||
|
||||
def test_list_network_details_error_401(self):
|
||||
self._test_list_network_details(status=401)
|
||||
def test_show_network_details_error_401(self):
|
||||
self._test_show_network_details(status=401)
|
||||
|
||||
def test_list_network_details_error_420(self):
|
||||
self._test_list_network_details(status=420)
|
||||
def test_show_network_details_error_420(self):
|
||||
self._test_show_network_details(status=420)
|
||||
|
||||
def test_create_network_json(self):
|
||||
self._test_create_network(format='json')
|
||||
@@ -447,26 +447,26 @@ class APITest(unittest.TestCase):
|
||||
def test_list_ports_error_420(self):
|
||||
self._test_list_ports(status=420)
|
||||
|
||||
def test_list_port_details_json(self):
|
||||
def test_show_port_details_json(self):
|
||||
self._test_list_ports(format='json')
|
||||
|
||||
def test_list_port_details_xml(self):
|
||||
def test_show_port_details_xml(self):
|
||||
self._test_list_ports(format='xml')
|
||||
|
||||
def test_list_port_details_alt_tenant(self):
|
||||
def test_show_port_details_alt_tenant(self):
|
||||
self._test_list_ports(tenant=TENANT_2)
|
||||
|
||||
def test_list_port_details_error_470(self):
|
||||
self._test_list_port_details(status=470)
|
||||
def test_show_port_details_error_470(self):
|
||||
self._test_show_port_details(status=470)
|
||||
|
||||
def test_list_port_details_error_401(self):
|
||||
self._test_list_ports(status=401)
|
||||
def test_show_port_details_error_401(self):
|
||||
self._test_show_port_details(status=401)
|
||||
|
||||
def test_list_port_details_error_420(self):
|
||||
self._test_list_ports(status=420)
|
||||
def test_show_port_details_error_420(self):
|
||||
self._test_show_port_details(status=420)
|
||||
|
||||
def test_list_port_details_error_430(self):
|
||||
self._test_list_ports(status=430)
|
||||
def test_show_port_details_error_430(self):
|
||||
self._test_show_port_details(status=430)
|
||||
|
||||
def test_create_port_json(self):
|
||||
self._test_create_port(format='json')
|
||||
@@ -546,29 +546,29 @@ class APITest(unittest.TestCase):
|
||||
def test_set_port_state_error_431(self):
|
||||
self._test_set_port_state(status=431)
|
||||
|
||||
def test_list_port_attachments_json(self):
|
||||
self._test_list_port_attachments(format='json')
|
||||
def test_show_port_attachment_json(self):
|
||||
self._test_show_port_attachment(format='json')
|
||||
|
||||
def test_list_port_attachments_xml(self):
|
||||
self._test_list_port_attachments(format='xml')
|
||||
def test_show_port_attachment_xml(self):
|
||||
self._test_show_port_attachment(format='xml')
|
||||
|
||||
def test_list_port_attachments_alt_tenant(self):
|
||||
self._test_list_port_attachments(tenant=TENANT_2)
|
||||
def test_show_port_attachment_alt_tenant(self):
|
||||
self._test_show_port_attachment(tenant=TENANT_2)
|
||||
|
||||
def test_list_port_attachments_error_470(self):
|
||||
self._test_list_port_attachments(status=470)
|
||||
def test_show_port_attachment_error_470(self):
|
||||
self._test_show_port_attachment(status=470)
|
||||
|
||||
def test_list_port_attachments_error_401(self):
|
||||
self._test_list_port_attachments(status=401)
|
||||
def test_show_port_attachment_error_401(self):
|
||||
self._test_show_port_attachment(status=401)
|
||||
|
||||
def test_list_port_attachments_error_400(self):
|
||||
self._test_list_port_attachments(status=400)
|
||||
def test_show_port_attachment_error_400(self):
|
||||
self._test_show_port_attachment(status=400)
|
||||
|
||||
def test_list_port_attachments_error_420(self):
|
||||
self._test_list_port_attachments(status=420)
|
||||
def test_show_port_attachment_error_420(self):
|
||||
self._test_show_port_attachment(status=420)
|
||||
|
||||
def test_list_port_attachments_error_430(self):
|
||||
self._test_list_port_attachments(status=430)
|
||||
def test_show_port_attachment_error_430(self):
|
||||
self._test_show_port_attachment(status=430)
|
||||
|
||||
def test_attach_resource_json(self):
|
||||
self._test_attach_resource(format='json')
|
||||
402
tests/unit/test_extensions.py
Normal file
402
tests/unit/test_extensions.py
Normal file
@@ -0,0 +1,402 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
# Copyright 2011 OpenStack LLC.
|
||||
# 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 json
|
||||
import os.path
|
||||
import routes
|
||||
import unittest
|
||||
from tests.unit import BaseTest
|
||||
from webtest import TestApp
|
||||
|
||||
|
||||
from quantum.common import wsgi
|
||||
from quantum.common import config
|
||||
from quantum.common import extensions
|
||||
from quantum.plugins.SamplePlugin import QuantumEchoPlugin
|
||||
from tests.unit.extension_stubs import (StubExtension, StubPlugin,
|
||||
StubPluginInterface,
|
||||
StubBaseAppController,
|
||||
ExtensionExpectingPluginInterface)
|
||||
from quantum.common.extensions import (ExtensionManager,
|
||||
PluginAwareExtensionManager,
|
||||
ExtensionMiddleware)
|
||||
|
||||
|
||||
test_conf_file = os.path.join(os.path.dirname(__file__), os.pardir,
|
||||
os.pardir, 'etc', 'quantum.conf.test')
|
||||
extensions_path = os.path.join(os.path.dirname(__file__), "extensions")
|
||||
|
||||
|
||||
class ExtensionsTestApp(wsgi.Router):
|
||||
|
||||
def __init__(self, options={}):
|
||||
mapper = routes.Mapper()
|
||||
controller = StubBaseAppController()
|
||||
mapper.resource("dummy_resource", "/dummy_resources",
|
||||
controller=controller)
|
||||
super(ExtensionsTestApp, self).__init__(mapper)
|
||||
|
||||
|
||||
class ResourceExtensionTest(unittest.TestCase):
|
||||
|
||||
class ResourceExtensionController(wsgi.Controller):
|
||||
|
||||
def index(self, request):
|
||||
return "resource index"
|
||||
|
||||
def show(self, request, id):
|
||||
return {'data': {'id': id}}
|
||||
|
||||
def custom_member_action(self, request, id):
|
||||
return {'member_action': 'value'}
|
||||
|
||||
def custom_collection_action(self, request):
|
||||
return {'collection': 'value'}
|
||||
|
||||
def test_resource_can_be_added_as_extension(self):
|
||||
res_ext = extensions.ResourceExtension('tweedles',
|
||||
self.ResourceExtensionController())
|
||||
test_app = setup_extensions_test_app(SimpleExtensionManager(res_ext))
|
||||
|
||||
index_response = test_app.get("/tweedles")
|
||||
self.assertEqual(200, index_response.status_int)
|
||||
self.assertEqual("resource index", index_response.body)
|
||||
|
||||
show_response = test_app.get("/tweedles/25266")
|
||||
self.assertEqual({'data': {'id': "25266"}}, show_response.json)
|
||||
|
||||
def test_resource_extension_with_custom_member_action(self):
|
||||
controller = self.ResourceExtensionController()
|
||||
member = {'custom_member_action': "GET"}
|
||||
res_ext = extensions.ResourceExtension('tweedles', controller,
|
||||
member_actions=member)
|
||||
test_app = setup_extensions_test_app(SimpleExtensionManager(res_ext))
|
||||
|
||||
response = test_app.get("/tweedles/some_id/custom_member_action")
|
||||
self.assertEqual(200, response.status_int)
|
||||
self.assertEqual(json.loads(response.body)['member_action'], "value")
|
||||
|
||||
def test_resource_extension_with_custom_collection_action(self):
|
||||
controller = self.ResourceExtensionController()
|
||||
collections = {'custom_collection_action': "GET"}
|
||||
res_ext = extensions.ResourceExtension('tweedles', controller,
|
||||
collection_actions=collections)
|
||||
test_app = setup_extensions_test_app(SimpleExtensionManager(res_ext))
|
||||
|
||||
response = test_app.get("/tweedles/custom_collection_action")
|
||||
self.assertEqual(200, response.status_int)
|
||||
self.assertEqual(json.loads(response.body)['collection'], "value")
|
||||
|
||||
def test_returns_404_for_non_existant_extension(self):
|
||||
test_app = setup_extensions_test_app(SimpleExtensionManager(None))
|
||||
|
||||
response = test_app.get("/non_extistant_extension", status='*')
|
||||
|
||||
self.assertEqual(404, response.status_int)
|
||||
|
||||
|
||||
class ActionExtensionTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ActionExtensionTest, self).setUp()
|
||||
self.extension_app = setup_extensions_test_app()
|
||||
|
||||
def test_extended_action_for_adding_extra_data(self):
|
||||
action_name = 'FOXNSOX:add_tweedle'
|
||||
action_params = dict(name='Beetle')
|
||||
req_body = json.dumps({action_name: action_params})
|
||||
response = self.extension_app.post('/dummy_resources/1/action',
|
||||
req_body, content_type='application/json')
|
||||
self.assertEqual("Tweedle Beetle Added.", response.body)
|
||||
|
||||
def test_extended_action_for_deleting_extra_data(self):
|
||||
action_name = 'FOXNSOX:delete_tweedle'
|
||||
action_params = dict(name='Bailey')
|
||||
req_body = json.dumps({action_name: action_params})
|
||||
response = self.extension_app.post("/dummy_resources/1/action",
|
||||
req_body, content_type='application/json')
|
||||
self.assertEqual("Tweedle Bailey Deleted.", response.body)
|
||||
|
||||
def test_returns_404_for_non_existant_action(self):
|
||||
non_existant_action = 'blah_action'
|
||||
action_params = dict(name="test")
|
||||
req_body = json.dumps({non_existant_action: action_params})
|
||||
|
||||
response = self.extension_app.post("/dummy_resources/1/action",
|
||||
req_body, content_type='application/json',
|
||||
status='*')
|
||||
|
||||
self.assertEqual(404, response.status_int)
|
||||
|
||||
def test_returns_404_for_non_existant_resource(self):
|
||||
action_name = 'add_tweedle'
|
||||
action_params = dict(name='Beetle')
|
||||
req_body = json.dumps({action_name: action_params})
|
||||
|
||||
response = self.extension_app.post("/asdf/1/action", req_body,
|
||||
content_type='application/json', status='*')
|
||||
self.assertEqual(404, response.status_int)
|
||||
|
||||
|
||||
class RequestExtensionTest(BaseTest):
|
||||
|
||||
def test_headers_can_be_extended(self):
|
||||
def extend_headers(req, res):
|
||||
assert req.headers['X-NEW-REQUEST-HEADER'] == "sox"
|
||||
res.headers['X-NEW-RESPONSE-HEADER'] = "response_header_data"
|
||||
return res
|
||||
|
||||
app = self._setup_app_with_request_handler(extend_headers, 'GET')
|
||||
response = app.get("/dummy_resources/1",
|
||||
headers={'X-NEW-REQUEST-HEADER': "sox"})
|
||||
|
||||
self.assertEqual(response.headers['X-NEW-RESPONSE-HEADER'],
|
||||
"response_header_data")
|
||||
|
||||
def test_extend_get_resource_response(self):
|
||||
def extend_response_data(req, res):
|
||||
data = json.loads(res.body)
|
||||
data['FOXNSOX:extended_key'] = req.GET.get('extended_key')
|
||||
res.body = json.dumps(data)
|
||||
return res
|
||||
|
||||
app = self._setup_app_with_request_handler(extend_response_data, 'GET')
|
||||
response = app.get("/dummy_resources/1?extended_key=extended_data")
|
||||
|
||||
self.assertEqual(200, response.status_int)
|
||||
response_data = json.loads(response.body)
|
||||
self.assertEqual('extended_data',
|
||||
response_data['FOXNSOX:extended_key'])
|
||||
self.assertEqual('knox', response_data['fort'])
|
||||
|
||||
def test_get_resources(self):
|
||||
app = setup_extensions_test_app()
|
||||
|
||||
response = app.get("/dummy_resources/1?chewing=newblue")
|
||||
|
||||
response_data = json.loads(response.body)
|
||||
self.assertEqual('newblue', response_data['FOXNSOX:googoose'])
|
||||
self.assertEqual("Pig Bands!", response_data['FOXNSOX:big_bands'])
|
||||
|
||||
def test_edit_previously_uneditable_field(self):
|
||||
|
||||
def _update_handler(req, res):
|
||||
data = json.loads(res.body)
|
||||
data['uneditable'] = req.params['uneditable']
|
||||
res.body = json.dumps(data)
|
||||
return res
|
||||
|
||||
base_app = TestApp(setup_base_app())
|
||||
response = base_app.put("/dummy_resources/1",
|
||||
{'uneditable': "new_value"})
|
||||
self.assertEqual(response.json['uneditable'], "original_value")
|
||||
|
||||
ext_app = self._setup_app_with_request_handler(_update_handler,
|
||||
'PUT')
|
||||
ext_response = ext_app.put("/dummy_resources/1",
|
||||
{'uneditable': "new_value"})
|
||||
self.assertEqual(ext_response.json['uneditable'], "new_value")
|
||||
|
||||
def _setup_app_with_request_handler(self, handler, verb):
|
||||
req_ext = extensions.RequestExtension(verb,
|
||||
'/dummy_resources/:(id)', handler)
|
||||
manager = SimpleExtensionManager(None, None, req_ext)
|
||||
return setup_extensions_test_app(manager)
|
||||
|
||||
|
||||
class ExtensionManagerTest(unittest.TestCase):
|
||||
|
||||
def test_invalid_extensions_are_not_registered(self):
|
||||
|
||||
class InvalidExtension(object):
|
||||
"""
|
||||
This Extension doesn't implement extension methods :
|
||||
get_name, get_description, get_namespace and get_updated
|
||||
"""
|
||||
def get_alias(self):
|
||||
return "invalid_extension"
|
||||
|
||||
ext_mgr = ExtensionManager('')
|
||||
ext_mgr.add_extension(InvalidExtension())
|
||||
ext_mgr.add_extension(StubExtension("valid_extension"))
|
||||
|
||||
self.assertTrue('valid_extension' in ext_mgr.extensions)
|
||||
self.assertFalse('invalid_extension' in ext_mgr.extensions)
|
||||
|
||||
|
||||
class PluginAwareExtensionManagerTest(unittest.TestCase):
|
||||
|
||||
def test_unsupported_extensions_are_not_loaded(self):
|
||||
stub_plugin = StubPlugin(supported_extensions=["e1", "e3"])
|
||||
ext_mgr = PluginAwareExtensionManager('', stub_plugin)
|
||||
|
||||
ext_mgr.add_extension(StubExtension("e1"))
|
||||
ext_mgr.add_extension(StubExtension("e2"))
|
||||
ext_mgr.add_extension(StubExtension("e3"))
|
||||
|
||||
self.assertTrue("e1" in ext_mgr.extensions)
|
||||
self.assertFalse("e2" in ext_mgr.extensions)
|
||||
self.assertTrue("e3" in ext_mgr.extensions)
|
||||
|
||||
def test_extensions_are_not_loaded_for_plugins_unaware_of_extensions(self):
|
||||
class ExtensionUnawarePlugin(object):
|
||||
"""
|
||||
This plugin does not implement supports_extension method.
|
||||
Extensions will not be loaded when this plugin is used.
|
||||
"""
|
||||
pass
|
||||
|
||||
ext_mgr = PluginAwareExtensionManager('', ExtensionUnawarePlugin())
|
||||
ext_mgr.add_extension(StubExtension("e1"))
|
||||
|
||||
self.assertFalse("e1" in ext_mgr.extensions)
|
||||
|
||||
def test_extensions_not_loaded_for_plugin_without_expected_interface(self):
|
||||
|
||||
class PluginWithoutExpectedInterface(object):
|
||||
"""
|
||||
Plugin does not implement get_foo method as expected by extension
|
||||
"""
|
||||
supported_extension_aliases = ["supported_extension"]
|
||||
|
||||
ext_mgr = PluginAwareExtensionManager('',
|
||||
PluginWithoutExpectedInterface())
|
||||
ext_mgr.add_extension(
|
||||
ExtensionExpectingPluginInterface("supported_extension"))
|
||||
|
||||
self.assertFalse("e1" in ext_mgr.extensions)
|
||||
|
||||
def test_extensions_are_loaded_for_plugin_with_expected_interface(self):
|
||||
|
||||
class PluginWithExpectedInterface(object):
|
||||
"""
|
||||
This Plugin implements get_foo method as expected by extension
|
||||
"""
|
||||
supported_extension_aliases = ["supported_extension"]
|
||||
|
||||
def get_foo(self, bar=None):
|
||||
pass
|
||||
ext_mgr = PluginAwareExtensionManager('',
|
||||
PluginWithExpectedInterface())
|
||||
ext_mgr.add_extension(
|
||||
ExtensionExpectingPluginInterface("supported_extension"))
|
||||
|
||||
self.assertTrue("supported_extension" in ext_mgr.extensions)
|
||||
|
||||
def test_extensions_expecting_quantum_plugin_interface_are_loaded(self):
|
||||
class ExtensionForQuamtumPluginInterface(StubExtension):
|
||||
"""
|
||||
This Extension does not implement get_plugin_interface method.
|
||||
This will work with any plugin implementing QuantumPluginBase
|
||||
"""
|
||||
pass
|
||||
stub_plugin = StubPlugin(supported_extensions=["e1"])
|
||||
ext_mgr = PluginAwareExtensionManager('', stub_plugin)
|
||||
ext_mgr.add_extension(ExtensionForQuamtumPluginInterface("e1"))
|
||||
|
||||
self.assertTrue("e1" in ext_mgr.extensions)
|
||||
|
||||
def test_extensions_without_need_for__plugin_interface_are_loaded(self):
|
||||
class ExtensionWithNoNeedForPluginInterface(StubExtension):
|
||||
"""
|
||||
This Extension does not need any plugin interface.
|
||||
This will work with any plugin implementing QuantumPluginBase
|
||||
"""
|
||||
def get_plugin_interface(self):
|
||||
return None
|
||||
|
||||
stub_plugin = StubPlugin(supported_extensions=["e1"])
|
||||
ext_mgr = PluginAwareExtensionManager('', stub_plugin)
|
||||
ext_mgr.add_extension(ExtensionWithNoNeedForPluginInterface("e1"))
|
||||
|
||||
self.assertTrue("e1" in ext_mgr.extensions)
|
||||
|
||||
|
||||
class ExtensionControllerTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ExtensionControllerTest, self).setUp()
|
||||
self.test_app = setup_extensions_test_app()
|
||||
|
||||
def test_index_gets_all_registerd_extensions(self):
|
||||
response = self.test_app.get("/extensions")
|
||||
foxnsox = response.json["extensions"][0]
|
||||
|
||||
self.assertEqual(foxnsox["alias"], "FOXNSOX")
|
||||
self.assertEqual(foxnsox["namespace"],
|
||||
"http://www.fox.in.socks/api/ext/pie/v1.0")
|
||||
|
||||
def test_extension_can_be_accessed_by_alias(self):
|
||||
foxnsox_extension = self.test_app.get("/extensions/FOXNSOX").json
|
||||
|
||||
self.assertEqual(foxnsox_extension["alias"], "FOXNSOX")
|
||||
self.assertEqual(foxnsox_extension["namespace"],
|
||||
"http://www.fox.in.socks/api/ext/pie/v1.0")
|
||||
|
||||
def test_show_returns_not_found_for_non_existant_extension(self):
|
||||
response = self.test_app.get("/extensions/non_existant", status="*")
|
||||
|
||||
self.assertEqual(response.status_int, 404)
|
||||
|
||||
|
||||
def app_factory(global_conf, **local_conf):
|
||||
conf = global_conf.copy()
|
||||
conf.update(local_conf)
|
||||
return ExtensionsTestApp(conf)
|
||||
|
||||
|
||||
def setup_base_app():
|
||||
options = {'config_file': test_conf_file}
|
||||
conf, app = config.load_paste_app('extensions_test_app', options, None)
|
||||
return app
|
||||
|
||||
|
||||
def setup_extensions_middleware(extension_manager=None):
|
||||
extension_manager = (extension_manager or
|
||||
PluginAwareExtensionManager(extensions_path,
|
||||
QuantumEchoPlugin()))
|
||||
options = {'config_file': test_conf_file}
|
||||
conf, app = config.load_paste_app('extensions_test_app', options, None)
|
||||
return ExtensionMiddleware(app, conf, ext_mgr=extension_manager)
|
||||
|
||||
|
||||
def setup_extensions_test_app(extension_manager=None):
|
||||
return TestApp(setup_extensions_middleware(extension_manager))
|
||||
|
||||
|
||||
class SimpleExtensionManager(object):
|
||||
|
||||
def __init__(self, resource_ext=None, action_ext=None, request_ext=None):
|
||||
self.resource_ext = resource_ext
|
||||
self.action_ext = action_ext
|
||||
self.request_ext = request_ext
|
||||
|
||||
def get_resources(self):
|
||||
resource_exts = []
|
||||
if self.resource_ext:
|
||||
resource_exts.append(self.resource_ext)
|
||||
return resource_exts
|
||||
|
||||
def get_actions(self):
|
||||
action_exts = []
|
||||
if self.action_ext:
|
||||
action_exts.append(self.action_ext)
|
||||
return action_exts
|
||||
|
||||
def get_request_extensions(self):
|
||||
request_extensions = []
|
||||
if self.request_ext:
|
||||
request_extensions.append(self.request_ext)
|
||||
return request_extensions
|
||||
@@ -5,7 +5,7 @@ Paste
|
||||
PasteDeploy
|
||||
pep8>=0.5.0
|
||||
python-gflags
|
||||
sqlalchemy
|
||||
simplejson
|
||||
sqlalchemy
|
||||
webob
|
||||
webtest
|
||||
|
||||
Reference in New Issue
Block a user