blueprint quantum-packaging

Change-Id: Ica19170540b06ecddb0fbb6d340ee7a6819c1708
This commit is contained in:
Tyler Smith 2011-02-22 20:42:24 -05:00
parent e767b8f463
commit 24b0207cbe
185 changed files with 1681 additions and 441 deletions

79
README
View File

@ -20,6 +20,16 @@
implementation and enables them to switch out a plug-in by simple editing a implementation and enables them to switch out a plug-in by simple editing a
config file - plugins.ini config file - plugins.ini
# -- Layout
The Quantum project includes 3 core packages:
quantum-common (General utils for Quantum and its plugins)
quantum-server (The actual Quantum service itself)
quantum-client (The Quantum CLI and API Python library)
As well as some plugins.
# -- Dependencies # -- Dependencies
The following python packages are required to run quantum. These can be The following python packages are required to run quantum. These can be
@ -44,6 +54,24 @@ this)
3) Install packages with pip: 3) Install packages with pip:
$ pip install <package name> $ pip install <package name>
# -- Running from the source code
bin/quantum-server #Server
bin/quantum #CLI
sh run_tests.sh #Tests
# -- Installing from the source code
You have 3 options:
a) sudo python setup.py install
# Installs to /usr/lib, /usr/bin, /etc, etc
b) python setup.py install --user
# Install into $HOME/.local/...
c) python setup.py install --venv
# Creates and installs into a virtual-env at ~/.quantum-venv
# -- Configuring Quantum plug-in # -- Configuring Quantum plug-in
1) Identify your desired plug-in. Choose a plugin from one of he options in 1) Identify your desired plug-in. Choose a plugin from one of he options in
@ -60,29 +88,30 @@ this)
# -- Launching the Quantum Service # -- Launching the Quantum Service
1) Start quantum using the following command [on the quantum service host]: # If you're running from the source
~/src/quantum$ PYTHONPATH=.:$PYTHONPATH python bin/quantum etc/quantum.conf bin/quantum-server
# If you installed Quantum
quantum-server
# -- Making requests against the Quantum Service # -- Making requests against the Quantum Service
Please refer to sample Web Service client code in: Quantum comes with a programmatic CLI that is driven by the Quantum Web
Service. You can use the CLI by issuing the following command:
../quantum/test_scripts/miniclient.py # If you're running from the source
bin/quantum
# -- CLI tools to program the Quantum-managed Cloud networking fabric # If you installed Quantum
quantum
Quantum comes with a programmatic CLI that is driven by the Quantum Web This will show help all of the available commands.
Service. You can use the CLI by issuing the following command:
~/src/quantum$ PYTHONPATH=.:$PYTHONPATH python quantum/cli.py An example session looks like this:
This will show help all of the available commands. $ export TENANT=t1
$ quantum -v create_net $TENANT network1
An example session looks like this: Created a new Virtual Network with ID:e754e7c0-a8eb-40e5-861a-b182d30c3441
$ export TENANT=t1
$ PYTHONPATH=. python quantum/cli.py -v create_net $TENANT network1
Created a new Virtual Network with ID:e754e7c0-a8eb-40e5-861a-b182d30c3441
# -- Authentication and Authorization # -- Authentication and Authorization
@ -102,12 +131,12 @@ pipeline = authN authZ extensions quantumapiapp
The final step concerns configuring access to Keystone. The following attributes The final step concerns configuring access to Keystone. The following attributes
must be specified in the [filter:authN] section of quantum.conf: must be specified in the [filter:authN] section of quantum.conf:
auth_host IP address or host name of the server where Keystone is running auth_host IP address or host name of the server where Keystone is running
auth_port Port where the Keystone Admin API is listening auth_port Port where the Keystone Admin API is listening
auth_protocol Protocol used for communicating with Keystone (http/https) auth_protocol Protocol used for communicating with Keystone (http/https)
auth_version Keystone API version (default: 2.0) auth_version Keystone API version (default: 2.0)
auth_admin_token Keystone token for administrative access auth_admin_token Keystone token for administrative access
auth_admin_user Keystone user with administrative rights auth_admin_user Keystone user with administrative rights
auth_admin_password Password for the user specified with auth_admin_user auth_admin_password Password for the user specified with auth_admin_user
NOTE: aut_admin_token and auth_admin_user/password are exclusive. NOTE: aut_admin_token and auth_admin_user/password are exclusive.
@ -200,3 +229,11 @@ There are a few requirements to writing your own plugin:
The QuantumEchoPlugin lists foxinsox in its supported_extension_aliases The QuantumEchoPlugin lists foxinsox in its supported_extension_aliases
and implements the method from FoxInSocksPluginInterface. and implements the method from FoxInSocksPluginInterface.
# -- Building packages
rpms:
python setup.py build rpm
debs:
python setup.py build deb

View File

@ -1,5 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4 # vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 Nicira Networks, Inc.
# Copyright 2011 Cisco Systems
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -13,4 +14,8 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
# @author: Somik Behera, Nicira Networks, Inc. # @author: Tyler Smith, Cisco Systems
import os
import sys
sys.path.append(os.path.join(os.getcwd(), 'tools'))

View File

@ -19,43 +19,8 @@
# If ../quantum/__init__.py exists, add ../ to Python search path, so that # If ../quantum/__init__.py exists, add ../ to Python search path, so that
# it will override what happens to be installed in /usr/(local/)lib/python... # it will override what happens to be installed in /usr/(local/)lib/python...
import gettext import __init__
import optparse import source_environment
import os from quantum.cli import main as cli
import sys
cli()
possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
os.pardir,
os.pardir))
if os.path.exists(os.path.join(possible_topdir, 'quantum', '__init__.py')):
sys.path.insert(0, possible_topdir)
gettext.install('quantum', unicode=1)
from quantum import service
from quantum.common import config
def create_options(parser):
"""
Sets up the CLI and config-file options that may be
parsed and program commands.
:param parser: The option parser
"""
config.add_common_options(parser)
config.add_log_options(parser)
if __name__ == '__main__':
oparser = optparse.OptionParser(version='%%prog VERSION')
create_options(oparser)
(options, args) = config.parse_options(oparser)
try:
service = service.serve_wsgi(service.QuantumApiService,
options=options,
args=args)
service.wait()
except RuntimeError, e:
sys.exit("ERROR: %s" % e)

26
bin/quantum-server Executable file
View File

@ -0,0 +1,26 @@
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 Nicira Neworks, 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.
# If ../quantum/__init__.py exists, add ../ to Python search path, so that
# it will override what happens to be installed in /usr/(local/)lib/python...
import __init__
import source_environment
from quantum.server import main as server
server()

View File

@ -0,0 +1,5 @@
try:
__import__('pkg_resources').declare_namespace(__name__)
except ImportError:
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)

View File

@ -112,7 +112,7 @@ def build_args(cmd, cmdargs, arglist):
return args return args
if __name__ == "__main__": def main():
usagestr = "Usage: %prog [OPTIONS] <command> [args]" usagestr = "Usage: %prog [OPTIONS] <command> [args]"
parser = OptionParser(usage=usagestr) parser = OptionParser(usage=usagestr)
parser.add_option("-H", "--host", dest="host", parser.add_option("-H", "--host", dest="host",

View File

@ -22,7 +22,7 @@ import socket
import urllib import urllib
from quantum.common import exceptions from quantum.common import exceptions
from quantum.common.wsgi import Serializer from quantum.common.serializer import Serializer
LOG = logging.getLogger('quantum.client') LOG = logging.getLogger('quantum.client')
EXCEPTIONS = { EXCEPTIONS = {

View File

@ -17,6 +17,12 @@
# See http://code.google.com/p/python-nose/issues/detail?id=373 # See http://code.google.com/p/python-nose/issues/detail?id=373
# The code below enables nosetests to work with i18n _() blocks # The code below enables nosetests to work with i18n _() blocks
try:
__import__('pkg_resources').declare_namespace(__name__)
except ImportError:
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)
import __builtin__ import __builtin__
import unittest import unittest
setattr(__builtin__, '_', lambda x: x) setattr(__builtin__, '_', lambda x: x)

View File

@ -20,7 +20,7 @@ import logging
import unittest import unittest
import re import re
from quantum.common.wsgi import Serializer from quantum.common.serializer import Serializer
from quantum.client import Client from quantum.client import Client
LOG = logging.getLogger('quantum.tests.test_api') LOG = logging.getLogger('quantum.tests.test_api')

57
client/setup.py Normal file
View File

@ -0,0 +1,57 @@
try:
from setuptools import setup, find_packages
except ImportError:
from ez_setup import use_setuptools
use_setuptools()
from setuptools import setup, find_packages
Name = 'quantum-client'
Url = "https://launchpad.net/quantum"
Version = '2012.1-dev'
License = 'Apache License 2.0'
# Change as required
Author = 'Netstack'
AuthorEmail = 'netstack@lists.launchpad.net'
Maintainer = ''
Summary = 'Client functionalities for Quantum'
ShortDescription = Summary
Description = Summary
requires = [
'quantum-common'
]
EagerResources = [
'quantum',
]
ProjectScripts = [
]
PackageData = {
}
setup(
name=Name,
version=Version,
url=Url,
author=Author,
author_email=AuthorEmail,
description=ShortDescription,
long_description=Description,
license=License,
scripts=ProjectScripts,
install_requires=requires,
include_package_data=True,
packages=find_packages('lib'),
package_data=PackageData,
package_dir={'': 'lib'},
eager_resources=EagerResources,
namespace_packages=['quantum'],
entry_points={
'console_scripts': [
'quantum = quantum.cli:main'
]
},
)

239
common/README Normal file
View File

@ -0,0 +1,239 @@
# -- Welcome!
You have come across a cloud computing network fabric controller. It has
identified itself as "Quantum." It aims to tame your (cloud) networking!
# -- Basics:
1) Quantum REST API: Quantum supports a REST-ful programmatic interface to
manage your cloud networking fabric.
2) Quantum Plugins: Quantum sports a plug-able architecture that allows
Quantum's REST API to be backed by various entities that can create a
cloud-class virtual networking fabric. The advantages of this plug-able
architecture is two-folds:
a) Allows for ANY open-source project or commercial vendor to write a
Quantum plug-in.
b) Allows Quantum users to not be tied down to a single Quantum
implementation and enables them to switch out a plug-in by simple editing a
config file - plugins.ini
# -- Layout
The Quantum project includes 3 core packages:
quantum-common (General utils for Quantum and its plugins)
quantum-server (The actual Quantum service itself)
quantum-client (The Quantum CLI and API Python library)
As well as some plugins.
# -- Dependencies
The following python packages are required to run quantum. These can be
installed using pip:
eventlet>=0.9.12
nose
Paste
PasteDeploy
pep8==0.5.0
python-gflags
routes
simplejson
webob
webtest
1) Install easy_install (there is probably a distribution specific package for
this)
2) Install pip:
$ easy_install pip==dev
3) Install packages with pip:
$ pip install <package name>
# -- Running from the source code
bin/quantum-server #Server
bin/quantum #CLI
python run_tests.py #Tests
# -- Installing from the source code
You have 3 options:
a) sudo python setup.py install
# Installs to /usr/lib, /usr/bin, /etc, etc
b) python setup.py install --user
# Install into $HOME/.local/...
c) python setup.py install --venv
# Creates and installs into a virtual-env at ~/.quantum-venv
# -- Configuring Quantum plug-in
1) Identify your desired plug-in. Choose a plugin from one of he options in
the quantum/plugins directory.
2) Update plug-in configuration by editing the quantum/plugins.ini file and
modify "provider" property to point to the location of the Quantum plug-in.
It should specify the class path to the plugin and the class name (i.e. for
a plugin class MyPlugin in quantum/plugins/myplugin/myplugin.py the
provider would be: quantum.plugins.myplugin.myplugin.MyPlugin)
3) Read the plugin specific README, this is usually found in the same
directory as your Quantum plug-in, and follow configuration instructions.
# -- Launching the Quantum Service
# If you're running from the source
bin/quantum-server
# If you installed Quantum
quantum-server
# -- Making requests against the Quantum Service
Quantum comes with a programmatic CLI that is driven by the Quantum Web
Service. You can use the CLI by issuing the following command:
# If you're running from the source
bin/quantum
# If you installed Quantum
quantum
This will show help all of the available commands.
An example session looks like this:
$ export TENANT=t1
$ quantum -v create_net $TENANT network1
Created a new Virtual Network with ID:e754e7c0-a8eb-40e5-861a-b182d30c3441
# -- Authentication and Authorization
Requests to Quantum API are authenticated with the Keystone identity service
using a token-based authentication protocol.
1) Enabling Authentication and Authorization
The Keystone identity service is a requirement. It must be installed, although
not necessarily on the same machine where Quantum is running; both Keystone's
admin API and service API should be running
Authentication and Authorization middleware should be enabled in the Quantum
pipeline. To this aim, uncomment the following line in /etc/quantum.conf:
pipeline = authN authZ extensions quantumapiapp
The final step concerns configuring access to Keystone. The following attributes
must be specified in the [filter:authN] section of quantum.conf:
auth_host IP address or host name of the server where Keystone is running
auth_port Port where the Keystone Admin API is listening
auth_protocol Protocol used for communicating with Keystone (http/https)
auth_version Keystone API version (default: 2.0)
auth_admin_token Keystone token for administrative access
auth_admin_user Keystone user with administrative rights
auth_admin_password Password for the user specified with auth_admin_user
NOTE: aut_admin_token and auth_admin_user/password are exclusive.
If both are specified, auth_admin_token has priority.
2) Authenticating and Authorizing request for Quantum API
A user should first authenticate with Keystone, supplying user credentials;
the Keystone service will return an authentication token, together with
informations concerning token expirations and endpoint where that token can
be used.
The authentication token must be included in every request for the Quantum
API, in the 'X_AUTH_TOKEN' header. Quantum will look for the authentication
token in this header, and validate it with the Keystone service.
In order to validate authentication tokens, Quantum uses Keystone's
administrative API. It therefore requires credentials for an administrative
user, which can be specified in Quantum's configuration file
(etc/quantum.conf)
Either username and password, or an authentication token for an administrative
user can be specified in the configuration file:
- Credentials:
auth_admin_user = admin
auth_admin_password = secrete
- Admin token:
auth_admin_token = 9a82c95a-99e9-4c3a-b5ee-199f6ba7ff04
As of the current release, any user for a tenant is allowed to perform
every operation on the networks owned by the tenant itself, except for
plugging interfaces. In order to perform such operation, the user must have
the Quantum:NetworkAdmin roles. Roles can be configured in Keystone using
the administrative API.
# -- Writing your own Quantum plug-in
If you wish the write your own Quantum plugin, please refer to some concrete as
well as sample plugins available in:
../quantum/quantum/plugins/.. directory.
There are a few requirements to writing your own plugin:
1) Your plugin should implement all methods defined in the
quantum/quantum_plugin_base.QuantumPluginBase class
2) Copy your Quantum plug-in over to the quantum/quantum/plugins/.. directory
3) The next step is to edit the plugins.ini file in the same directory
as QuantumPluginBase class and specify the location of your custom plugin
as the "provider"
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.
# -- Building packages
rpms:
python setup.py build rpm
debs:
python setup.py build deb

View File

@ -0,0 +1,5 @@
try:
__import__('pkg_resources').declare_namespace(__name__)
except ImportError:
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)

View File

@ -38,7 +38,7 @@ DEFAULT_LOG_FORMAT = "%(asctime)s %(levelname)8s [%(name)s] %(message)s"
DEFAULT_LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S" DEFAULT_LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
LOG = logging.getLogger('quantum.common.wsgi') LOG = logging.getLogger('quantum.wsgi')
def parse_options(parser, cli_args=None): def parse_options(parser, cli_args=None):
@ -179,14 +179,14 @@ def setup_logging(options, conf):
root_logger.addHandler(handler) root_logger.addHandler(handler)
def find_config_file(options, args): def find_config_file(options, args, config_file='quantum.conf'):
""" """
Return the first config file found. Return the first config file found.
We search for the paste config file in the following order: We search for the paste config file in the following order:
* If --config-file option is used, use that * If --config-file option is used, use that
* If args[0] is a file, use that * If args[0] is a file, use that
* Search for quantum.conf in standard directories: * Search for the configuration file in standard directories:
* . * .
* ~.quantum/ * ~.quantum/
* ~ * ~
@ -204,16 +204,31 @@ def find_config_file(options, args):
if os.path.exists(args[0]): if os.path.exists(args[0]):
return fix_path(args[0]) return fix_path(args[0])
# Handle standard directory search for quantum.conf dir_to_common = os.path.dirname(os.path.abspath(__file__))
config_file_dirs = [fix_path(os.getcwd()), root = os.path.join(dir_to_common, '..', '..', '..', '..')
fix_path(os.path.join('~', '.quantum')), # Handle standard directory search for the config file
config_file_dirs = [fix_path(os.path.join(os.getcwd(), 'server', 'etc')),
fix_path(os.path.join('~', '.quantum-venv', 'etc',
'quantum')),
fix_path('~'), fix_path('~'),
os.path.join(FLAGS.state_path, 'etc'), os.path.join(FLAGS.state_path, 'etc'),
os.path.join(FLAGS.state_path, 'etc', 'quantum'), os.path.join(FLAGS.state_path, 'etc', 'quantum'),
os.path.join(root, 'server', 'etc'),
fix_path(os.path.join('~', '.local',
'etc', 'quantum')),
'/usr/etc/quantum',
'/usr/local/etc/quantum',
'/etc/quantum/', '/etc/quantum/',
'/etc'] '/etc']
if os.path.exists(os.path.join(root, 'plugins')):
plugins = [fix_path(os.path.join(root, 'plugins', p, 'etc'))
for p in os.listdir(os.path.join(root, 'plugins'))]
plugins = [p for p in plugins if os.path.isdir(p)]
config_file_dirs.extend(plugins)
for cfg_dir in config_file_dirs: for cfg_dir in config_file_dirs:
cfg_file = os.path.join(cfg_dir, 'quantum.conf') cfg_file = os.path.join(cfg_dir, config_file)
if os.path.exists(cfg_file): if os.path.exists(cfg_file):
return cfg_file return cfg_file

View File

@ -27,7 +27,7 @@ from gettext import gettext as _
from abc import ABCMeta from abc import ABCMeta
from quantum.common import exceptions from quantum.common import exceptions
from quantum.manager import QuantumManager from quantum.manager import QuantumManager
from quantum.common import wsgi from quantum import wsgi
LOG = logging.getLogger('quantum.common.extensions') LOG = logging.getLogger('quantum.common.extensions')

View File

@ -0,0 +1,153 @@
from xml.dom import minidom
import webob.exc
from quantum.common import utils
class Serializer(object):
"""Serializes and deserializes dictionaries to certain MIME types."""
def __init__(self, metadata=None, default_xmlns=None):
"""Create a serializer based on the given WSGI environment.
'metadata' is an optional dict mapping MIME types to information
needed to serialize a dictionary to that type.
"""
self.metadata = metadata or {}
self.default_xmlns = default_xmlns
def _get_serialize_handler(self, content_type):
handlers = {
'application/json': self._to_json,
'application/xml': self._to_xml,
}
try:
return handlers[content_type]
except Exception:
raise exception.InvalidContentType(content_type=content_type)
def serialize(self, data, content_type):
"""Serialize a dictionary into the specified content type."""
return self._get_serialize_handler(content_type)(data)
def deserialize(self, datastring, content_type):
"""Deserialize a string to a dictionary.
The string must be in the format of a supported MIME type.
"""
try:
return self.get_deserialize_handler(content_type)(datastring)
except Exception:
raise webob.exc.HTTPBadRequest("Could not deserialize data")
def get_deserialize_handler(self, content_type):
handlers = {
'application/json': self._from_json,
'application/xml': self._from_xml,
}
try:
return handlers[content_type]
except Exception:
raise exception.InvalidContentType(content_type=content_type)
def _from_json(self, datastring):
return utils.loads(datastring)
def _from_xml(self, datastring):
xmldata = self.metadata.get('application/xml', {})
plurals = set(xmldata.get('plurals', {}))
node = minidom.parseString(datastring).childNodes[0]
return {node.nodeName: self._from_xml_node(node, plurals)}
def _from_xml_node(self, node, listnames):
"""Convert a minidom node to a simple Python type.
listnames is a collection of names of XML nodes whose subnodes should
be considered list items.
"""
if len(node.childNodes) == 1 and node.childNodes[0].nodeType == 3:
return node.childNodes[0].nodeValue
elif node.nodeName in listnames:
return [self._from_xml_node(n, listnames)
for n in node.childNodes if n.nodeType != node.TEXT_NODE]
else:
result = dict()
for attr in node.attributes.keys():
result[attr] = node.attributes[attr].nodeValue
for child in node.childNodes:
if child.nodeType != node.TEXT_NODE:
result[child.nodeName] = self._from_xml_node(child,
listnames)
return result
def _to_json(self, data):
return utils.dumps(data)
def _to_xml(self, data):
metadata = self.metadata.get('application/xml', {})
# We expect data to contain a single key which is the XML root.
root_key = data.keys()[0]
doc = minidom.Document()
node = self._to_xml_node(doc, metadata, root_key, data[root_key])
xmlns = node.getAttribute('xmlns')
if not xmlns and self.default_xmlns:
node.setAttribute('xmlns', self.default_xmlns)
return node.toprettyxml(indent='', newl='')
def _to_xml_node(self, doc, metadata, nodename, data):
"""Recursive method to convert data members to XML nodes."""
result = doc.createElement(nodename)
# Set the xml namespace if one is specified
# TODO(justinsb): We could also use prefixes on the keys
xmlns = metadata.get('xmlns', None)
if xmlns:
result.setAttribute('xmlns', xmlns)
if type(data) is list:
collections = metadata.get('list_collections', {})
if nodename in collections:
metadata = collections[nodename]
for item in data:
node = doc.createElement(metadata['item_name'])
node.setAttribute(metadata['item_key'], str(item))
result.appendChild(node)
return result
singular = metadata.get('plurals', {}).get(nodename, None)
if singular is None:
if nodename.endswith('s'):
singular = nodename[:-1]
else:
singular = 'item'
for item in data:
node = self._to_xml_node(doc, metadata, singular, item)
result.appendChild(node)
elif type(data) is dict:
collections = metadata.get('dict_collections', {})
if nodename in collections:
metadata = collections[nodename]
for k, v in data.items():
node = doc.createElement(metadata['item_name'])
node.setAttribute(metadata['item_key'], str(k))
text = doc.createTextNode(str(v))
node.appendChild(text)
result.appendChild(node)
return result
attrs = metadata.get('attributes', {}).get(nodename, {})
for k, v in data.items():
if k in attrs:
result.setAttribute(k, str(v))
else:
node = self._to_xml_node(doc, metadata, k, v)
result.appendChild(node)
else:
# Type is atom.
node = doc.createTextNode(str(data))
result.appendChild(node)
return result

View File

@ -1,7 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4 # vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 Nicira Networks, Inc # Copyright 2011, Nicira Networks, Inc.
# All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # 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 # not use this file except in compliance with the License. You may obtain
@ -14,14 +13,16 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
#
# Borrowed from nova code base, more utilities will be added/borrowed as and
# when needed.
# @author: Somik Behera, Nicira Networks, Inc.
"""Utilities and helper functions."""
"""
System-level utilities and helper functions.
"""
import ConfigParser import ConfigParser
import datetime import datetime
import exceptions as exception import exceptions as exception
import flags
import inspect import inspect
import logging import logging
import os import os
@ -29,11 +30,73 @@ import random
import subprocess import subprocess
import socket import socket
import sys import sys
import base64
import functools
import json
import re
import string
import struct
import time
import types
from quantum.common import flags
from quantum.common import exceptions as exception
from quantum.common.exceptions import ProcessExecutionError
from exceptions import ProcessExecutionError def import_class(import_str):
"""Returns a class from a string including module and class."""
mod_str, _sep, class_str = import_str.rpartition('.')
try:
__import__(mod_str)
return getattr(sys.modules[mod_str], class_str)
except (ImportError, ValueError, AttributeError), exc:
print(('Inner Exception: %s'), exc)
raise exception.ClassNotFound(class_name=class_str)
def import_object(import_str):
"""Returns an object including a module or module and class."""
try:
__import__(import_str)
return sys.modules[import_str]
except ImportError:
cls = import_class(import_str)
return cls()
def to_primitive(value):
if type(value) is type([]) or type(value) is type((None,)):
o = []
for v in value:
o.append(to_primitive(v))
return o
elif type(value) is type({}):
o = {}
for k, v in value.iteritems():
o[k] = to_primitive(v)
return o
elif isinstance(value, datetime.datetime):
return str(value)
elif hasattr(value, 'iteritems'):
return to_primitive(dict(value.iteritems()))
elif hasattr(value, '__iter__'):
return to_primitive(list(value))
else:
return value
def dumps(value):
try:
return json.dumps(value)
except TypeError:
pass
return json.dumps(to_primitive(value))
def loads(s):
return json.loads(s)
TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ" TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
@ -78,7 +141,7 @@ def import_class(import_str):
return getattr(sys.modules[mod_str], class_str) return getattr(sys.modules[mod_str], class_str)
except (ImportError, ValueError, AttributeError) as e: except (ImportError, ValueError, AttributeError) as e:
print e print e
raise exception.NotFound('Class %s cannot be found' % class_str) raise exception.ClassNotFound(class_name=class_str)
def import_object(import_str): def import_object(import_str):

View File

@ -0,0 +1,67 @@
#!/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.
"""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 os
import unittest
import sys
from quantum.common.test_lib import run_tests
from nose import config
from nose import core
import quantum.tests.unit
def main():
c = config.Config(stream=sys.stdout,
env=os.environ,
verbosity=3,
includeExe=True,
traverseNamespace=True,
plugins=core.DefaultPluginManager())
c.configureWhere(quantum.tests.unit.__path__)
sys.exit(run_tests(c))
if __name__ == "__main__":
main()

67
common/setup.py Normal file
View File

@ -0,0 +1,67 @@
try:
from setuptools import setup, find_packages
except ImportError:
from ez_setup import use_setuptools
use_setuptools()
from setuptools import setup, find_packages
Name = 'quantum-common'
Url = "https://launchpad.net/quantum"
Version = '2012.1-dev'
License = 'Apache License 2.0'
# Change as required
Author = 'Netstack'
AuthorEmail = 'netstack@lists.launchpad.net'
Maintainer = ''
Summary = 'Common functionalities for Quantum'
ShortDescription = Summary
Description = Summary
requires = [
'eventlet>=0.9.12',
'Routes>=1.12.3',
'nose',
'Paste',
'PasteDeploy',
'pep8>=0.6.1',
'python-gflags',
'simplejson',
'sqlalchemy',
'webob',
'webtest'
]
EagerResources = [
'quantum',
]
ProjectScripts = [
]
PackageData = {
}
setup(
name=Name,
version=Version,
url=Url,
author=Author,
author_email=AuthorEmail,
description=ShortDescription,
long_description=Description,
license=License,
scripts=ProjectScripts,
install_requires=requires,
include_package_data=True,
packages=find_packages('lib'),
package_data=PackageData,
package_dir={'': 'lib'},
eager_resources=EagerResources,
namespace_packages=['quantum'],
entry_points={
'console_scripts': [
'quantum-tests = quantum.run_tests:main'
]
},
)

View File

@ -0,0 +1 @@
include etc/*

View File

@ -0,0 +1,5 @@
try:
__import__('pkg_resources').declare_namespace(__name__)
except ImportError:
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)

View File

@ -0,0 +1,5 @@
try:
__import__('pkg_resources').declare_namespace(__name__)
except ImportError:
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)

View File

@ -20,18 +20,21 @@
""" """
import os import os
import logging as LOG
from quantum.common.config import find_config_file
from quantum.plugins.cisco.common import cisco_configparser as confp from quantum.plugins.cisco.common import cisco_configparser as confp
from quantum.plugins.cisco.common import cisco_constants as const from quantum.plugins.cisco.common import cisco_constants as const
from quantum.plugins.cisco.common import cisco_exceptions as cexc from quantum.plugins.cisco.common import cisco_exceptions as cexc
from quantum.plugins.cisco.db import l2network_db as cdb from quantum.plugins.cisco.db import l2network_db as cdb
LOG.basicConfig(level=LOG.WARN)
LOG.getLogger(const.LOGGER_COMPONENT_NAME)
CREDENTIALS_FILE = find_config_file({}, None, "credentials.ini")
TENANT = const.NETWORK_ADMIN TENANT = const.NETWORK_ADMIN
CREDENTIALS_FILE = "../conf/credentials.ini" cp = confp.CiscoConfigParser(CREDENTIALS_FILE)
cp = confp.CiscoConfigParser(os.path.dirname(os.path.realpath(__file__)) \
+ "/" + CREDENTIALS_FILE)
_creds_dictionary = cp.walk(cp.dummy) _creds_dictionary = cp.walk(cp.dummy)

View File

@ -20,14 +20,11 @@
""" """
import os import os
from quantum.common.config import find_config_file
from quantum.plugins.cisco.common import cisco_configparser as confp from quantum.plugins.cisco.common import cisco_configparser as confp
CONF_FILE = "conf/l2network_plugin.ini" CONF_FILE = find_config_file({}, None, "l2network_plugin.ini")
CONF_PARSER_OBJ = confp.CiscoConfigParser(CONF_FILE)
CONF_PARSER_OBJ = confp.\
CiscoConfigParser(os.path.dirname(os.path.realpath(__file__)) + \
"/" + CONF_FILE)
""" """
Reading the conf for the l2network_plugin Reading the conf for the l2network_plugin
@ -49,25 +46,22 @@ MAX_NETWORKS = SECTION_CONF['max_networks']
SECTION_CONF = CONF_PARSER_OBJ['MODEL'] SECTION_CONF = CONF_PARSER_OBJ['MODEL']
MODEL_CLASS = SECTION_CONF['model_class'] MODEL_CLASS = SECTION_CONF['model_class']
CONF_FILE = find_config_file({}, None, "cisco_plugins.ini")
SECTION_CONF = CONF_PARSER_OBJ['SEGMENTATION'] SECTION_CONF = CONF_PARSER_OBJ['SEGMENTATION']
MANAGER_CLASS = SECTION_CONF['manager_class'] MANAGER_CLASS = SECTION_CONF['manager_class']
CONF_FILE = "conf/plugins.ini"
CONF_PARSER_OBJ = confp.\ CONF_PARSER_OBJ = confp.CiscoConfigParser(CONF_FILE)
CiscoConfigParser(os.path.dirname(os.path.realpath(__file__)) + \
"/" + CONF_FILE)
""" """
Reading the config for the device plugins Reading the config for the device plugins
""" """
PLUGINS = CONF_PARSER_OBJ.walk(CONF_PARSER_OBJ.dummy) PLUGINS = CONF_PARSER_OBJ.walk(CONF_PARSER_OBJ.dummy)
CONF_FILE = "conf/db_conn.ini" CONF_FILE = find_config_file({}, None, "db_conn.ini")
CONF_PARSER_OBJ = confp.\ CONF_PARSER_OBJ = confp.CiscoConfigParser(CONF_FILE)
CiscoConfigParser(os.path.dirname(os.path.realpath(__file__)) + \
"/" + CONF_FILE)
""" """
Reading DB config for the Quantum DB Reading DB config for the Quantum DB

View File

@ -20,13 +20,10 @@
""" """
import os import os
from quantum.common.config import find_config_file
from quantum.plugins.cisco.common import cisco_configparser as confp from quantum.plugins.cisco.common import cisco_configparser as confp
CONF_FILE = "../conf/ucs.ini" CP = confp.CiscoConfigParser(find_config_file({}, [], 'ucs.ini'))
CP = confp.CiscoConfigParser(os.path.dirname(os.path.realpath(__file__)) \
+ "/" + CONF_FILE)
SECTION = CP['UCSM'] SECTION = CP['UCSM']
UCSM_IP_ADDRESS = SECTION['ip_address'] UCSM_IP_ADDRESS = SECTION['ip_address']
@ -38,9 +35,7 @@ PROFILE_NAME_PREFIX = SECTION['profile_name_prefix']
SECTION = CP['DRIVER'] SECTION = CP['DRIVER']
UCSM_DRIVER = SECTION['name'] UCSM_DRIVER = SECTION['name']
CONF_FILE = "../conf/ucs_inventory.ini" CP = confp.CiscoConfigParser(find_config_file({}, [],
'ucs_inventory.ini'))
CP = confp.CiscoConfigParser(os.path.dirname(os.path.realpath(__file__)) \
+ "/" + CONF_FILE)
INVENTORY = CP.walk(CP.dummy) INVENTORY = CP.walk(CP.dummy)

View File

@ -20,12 +20,10 @@
""" """
import os import os
from quantum.common.config import find_config_file
from quantum.plugins.cisco.common import cisco_configparser as confp from quantum.plugins.cisco.common import cisco_configparser as confp
CONF_FILE = "../conf/ucs_inventory.ini" CP = confp.CiscoConfigParser(find_config_file({}, [],
'plugins/cisco/ucs_inventory.ini'))
CP = confp.CiscoConfigParser(os.path.dirname(os.path.realpath(__file__)) \
+ "/" + CONF_FILE)
INVENTORY = CP.walk(CP.dummy) INVENTORY = CP.walk(CP.dummy)

View File

@ -16,3 +16,8 @@
# #
# @author: Sumit Naiksatam, Cisco Systems, Inc. # @author: Sumit Naiksatam, Cisco Systems, Inc.
# #
try:
__import__('pkg_resources').declare_namespace(__name__)
except ImportError:
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)

View File

@ -17,6 +17,12 @@
# See http://code.google.com/p/python-nose/issues/detail?id=373 # See http://code.google.com/p/python-nose/issues/detail?id=373
# The code below enables nosetests to work with i18n _() blocks # The code below enables nosetests to work with i18n _() blocks
try:
__import__('pkg_resources').declare_namespace(__name__)
except ImportError:
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)
import __builtin__ import __builtin__
import unittest import unittest
setattr(__builtin__, '_', lambda x: x) setattr(__builtin__, '_', lambda x: x)

View File

@ -0,0 +1,72 @@
try:
from setuptools import setup, find_packages
except ImportError:
from ez_setup import use_setuptools
use_setuptools()
from setuptools import setup, find_packages
import sys
Name = 'quantum-cisco-plugin'
ProjecUrl = ""
Version = '0.1'
License = 'Apache License 2.0'
# Change as required
Author = 'Cisco Systems'
AuthorEmail = ''
Maintainer = ''
Summary = 'Cisco plugin for Quantum'
ShortDescription = Summary
Description = Summary
requires = [
'quantum-common',
'quantum-server',
]
EagerResources = [
'quantum',
]
ProjectScripts = [
]
PackageData = {
}
# If we're installing server-wide, use an aboslute path for config
# if not, use a relative path
config_path = '/etc/quantum/plugins/cisco'
relative_locations = ['--user', '--virtualenv', '--venv']
if [x for x in relative_locations if x in sys.argv]:
config_path = 'etc/quantum/plugins/cisco'
DataFiles = [
(config_path,
['etc/credentials.ini', 'etc/l2network_plugin.ini', 'etc/nexus.ini',
'etc/ucs.ini', 'etc/cisco_plugins.ini', 'etc/db_conn.ini'])
]
setup(
name=Name,
version=Version,
author=Author,
author_email=AuthorEmail,
description=ShortDescription,
long_description=Description,
license=License,
scripts=ProjectScripts,
install_requires=requires,
include_package_data=True,
packages=find_packages('lib'),
package_data=PackageData,
data_files=DataFiles,
package_dir={'': 'lib'},
eager_resources=EagerResources,
namespace_packages=['quantum'],
entry_points={
'console_scripts': [
'quantum-cisco-tests = quantum.plugins.cisco.run_tests:main'
]
},
)

View File

@ -0,0 +1 @@
include etc/*

Some files were not shown because too many files have changed in this diff Show More