Compare commits

..

1 Commits

Author SHA1 Message Date
Jeremy Stanley
d2c241194f Update .gitreview for new namespace
Change-Id: Ia52da537a3315a2f15dda019b35b2aaaf5f8374a
2015-10-17 22:38:08 +00:00
58 changed files with 2047 additions and 1236 deletions

View File

@@ -4,4 +4,4 @@ source = tackerclient
omit = tackerclient/openstack/*,tackerclient/tests/*
[report]
ignore_errors = True
ignore-errors = True

4
.gitignore vendored
View File

@@ -17,9 +17,5 @@ run_tests.log
.autogenerated
.coverage
.testrepository/
.idea/
.tox/
.venv/
# Files created by releasenotes build
releasenotes/build

View File

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

View File

@@ -1,4 +1,4 @@
Tacker Style Commandments
Neutron Style Commandments
================================
- Step 1: Read the OpenStack Style Commandments

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
#
project = 'python-tackerclient'
project = 'python-neutronclient'
# -- General configuration ---------------------------------------------

View File

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

7
openstack-common.conf Normal file
View File

@@ -0,0 +1,7 @@
[DEFAULT]
# The list of modules to copy from openstack-common
modules=gettextutils,jsonutils,strutils,timeutils
# The base module to hold the copy of openstack.common
base=tackerclient

View File

@@ -1,261 +0,0 @@
# 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.
#
# tacker client documentation build configuration file, created by
# sphinx-quickstart on Tue May 31 19:07:30 2016.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
#
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
# sys.path.insert(0, os.path.abspath('.'))
# -- General configuration -------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [
'oslosphinx',
'reno.sphinxext'
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
# source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'Tacker Client Release Notes'
copyright = u'2016, Tacker Developers'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
import pbr.version
tacker_client_version = pbr.version.VersionInfo('python-tackerclient')
release = tacker_client_version.version_string_with_vcs()
version = tacker_client_version.canonical_version_string()
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
# language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
# today = ''
# Else, today_fmt is used as the format for a strftime call.
# today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = []
# The reST default role (used for this markup: `text`) to
# use for all documents.
# default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
# add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
# add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
# modindex_common_prefix = []
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'default'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
# html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
# html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
# html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
# html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
# html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
# html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
# html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
# html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
# html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
# html_additional_pages = {}
# If false, no module index is generated.
# html_domain_indices = True
# If false, no index is generated.
# html_use_index = True
# If true, the index is split into individual pages for each letter.
# html_split_index = False
# If true, links to the reST sources are added to the pages.
# html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
# html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
# html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
# html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
# html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'tackerclientdoc'
# -- Options for LaTeX output ------------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
# 'preamble': '',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass
# [howto/manual]).
latex_documents = [
('index', 'TackerClientReleaseNotes.tex',
u'Tacker Client Release Notes Documentation',
u'Tacker Developers', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
# latex_use_parts = False
# If true, show page references after internal links.
# latex_show_pagerefs = False
# If true, show URL addresses after external links.
# latex_show_urls = False
# Documents to append as an appendix to all manuals.
# latex_appendices = []
# If false, no module index is generated.
# latex_domain_indices = True
# -- Options for manual page output ------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'tackerreleasenotes',
u'Tacker Client Release Notes Documentation',
[u'Tacker Developers'], 1)
]
# If true, show URL addresses after external links.
# man_show_urls = False
# -- Options for Texinfo output ----------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
('index', 'TackerClientReleaseNotes',
u'Tacker Client Release Notes Documentation',
u'Tacker Developers', 'TackerClientReleaseNotes',
'Tacker Client Project.',
'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
# texinfo_appendices = []
# If false, no module index is generated.
# texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
# texinfo_show_urls = 'footnote'

View File

@@ -1,9 +0,0 @@
Python-TackerClient Release Notes
=================================
Contents:
.. toctree::
:maxdepth: 2
unreleased

View File

@@ -1,5 +0,0 @@
============================
Current Series Release Notes
============================
.. release-notes::

View File

@@ -1,16 +1,10 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
pbr>=1.6 # Apache-2.0
cliff!=1.16.0,!=1.17.0,>=1.15.0 # Apache-2.0
iso8601>=0.1.11 # MIT
netaddr!=0.7.16,>=0.7.12 # BSD
requests>=2.10.0 # Apache-2.0
python-keystoneclient!=1.8.0,!=2.1.0,>=1.7.0 # Apache-2.0
simplejson>=2.2.0 # MIT
six>=1.9.0 # MIT
Babel>=2.3.4 # BSD
oslo.i18n>=2.1.0 # Apache-2.0
oslo.utils>=3.15.0 # Apache-2.0
oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0
pbr>=0.6,!=0.7,<1.0
argparse
cliff>=1.4.3
iso8601>=0.1.9
netaddr>=0.7.6
requests>=1.1
python-keystoneclient>=0.9.0
simplejson>=2.0.9
six>=1.6.0
Babel>=1.3

View File

@@ -1,9 +1,9 @@
[metadata]
name = python-tackerclient
summary = CLI and Client Library for OpenStack Tacker
summary = CLI and Client Library for OpenStack Networking
description-file =
README.rst
author = OpenStack
author = OpenStack Networking Project
author-email = openstack-dev@lists.openstack.org
home-page = http://www.openstack.org/
classifier =
@@ -16,6 +16,7 @@ classifier =
Programming Language :: Python
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
Programming Language :: Python :: 2.6
[files]
packages =
@@ -34,10 +35,5 @@ all_files = 1
build-dir = doc/build
source-dir = doc/source
[build_releasenotes]
all_files = 1
build-dir = releasenotes/build
source-dir = releasenotes/source
[wheel]
universal = 1

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env python
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -25,5 +26,5 @@ except ImportError:
pass
setuptools.setup(
setup_requires=['pbr>=1.8'],
setup_requires=['pbr'],
pbr=True)

View File

@@ -56,7 +56,7 @@ class HTTPClient(object):
endpoint_url=None, insecure=False,
endpoint_type='publicURL',
auth_strategy='keystone', ca_cert=None, log_credentials=False,
service_type='nfv-orchestration',
service_type='servicevm',
**kwargs):
self.username = username
@@ -171,6 +171,8 @@ class HTTPClient(object):
return resp, body
except exceptions.Unauthorized:
self.authenticate()
kwargs.setdefault('headers', {})
kwargs['headers']['X-Auth-Token'] = self.auth_token
resp, body = self._cs_request(
self.endpoint_url + url, method, **kwargs)
return resp, body
@@ -185,7 +187,7 @@ class HTTPClient(object):
if not self.endpoint_url:
self.endpoint_url = self.service_catalog.url_for(
region_name=self.region_name,
attr='region', filter_value=self.region_name,
service_type=self.service_type,
endpoint_type=self.endpoint_type)
@@ -254,7 +256,7 @@ class HTTPClient(object):
body = json.loads(body)
for endpoint in body.get('endpoints', []):
if (endpoint['type'] == 'nfv-orchestration' and
if (endpoint['type'] == 'servicevm' and
endpoint.get('region') == self.region_name):
if self.endpoint_type not in endpoint:
raise exceptions.EndpointTypeNotFound(
@@ -357,7 +359,7 @@ def construct_http_client(username=None,
log_credentials=None,
auth_strategy='keystone',
ca_cert=None,
service_type='nfv-orchestration',
service_type='servicevm',
session=None,
**kwargs):

View File

@@ -1,50 +0,0 @@
# Copyright 2016 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""oslo.i18n integration module.
See http://docs.openstack.org/developer/oslo.i18n/usage.html .
"""
import oslo_i18n
DOMAIN = "tackerclient"
_translators = oslo_i18n.TranslatorFactory(domain=DOMAIN)
# The primary translation function using the well-known name "_"
_ = _translators.primary
# The contextual translation function using the name "_C"
# requires oslo.i18n >=2.1.0
_C = _translators.contextual_form
# The plural translation function using the name "_P"
# requires oslo.i18n >=2.1.0
_P = _translators.plural_form
# Translators for log levels.
#
# The abbreviated names are meant to reflect the usual use of a short
# name like '_'. The "L" is for "log" and the other letter comes from
# the level.
_LI = _translators.log_info
_LW = _translators.log_warning
_LE = _translators.log_error
_LC = _translators.log_critical
def get_available_languages():
return oslo_i18n.get_available_languages(DOMAIN)

View File

@@ -17,10 +17,15 @@
"""Manage access to the clients, including authenticating when needed.
"""
import logging
from tackerclient import client
from tackerclient.tacker import client as tacker_client
LOG = logging.getLogger(__name__)
class ClientCache(object):
"""Descriptor class for caching created client handles."""

View File

@@ -35,4 +35,5 @@ TYPE_DICT = "dict"
PLURALS = {'templates': 'template',
'devices': 'device'}
'devices': 'device',
'services': 'service'}

View File

@@ -148,6 +148,12 @@ class OverQuotaClient(Conflict):
pass
# TODO(amotoki): It is unused in Tacker, but it is referred to
# in Horizon code. After Horizon code is updated, remove it.
class AlreadyAttachedClient(Conflict):
pass
class IpAddressGenerationFailureClient(Conflict):
pass
@@ -215,9 +221,7 @@ class CommandError(TackerCLIError):
class UnsupportedVersion(TackerCLIError):
"""Unsupported Version.
Indicates that the user is trying to use an unsupported version of
"""Indicates that the user is trying to use an unsupported version of
the API.
"""
pass

View File

@@ -17,7 +17,7 @@ import logging
from xml.etree import ElementTree as etree
from xml.parsers import expat
from oslo_serialization import jsonutils
from oslo.serialization import jsonutils
import six
from tackerclient.common import constants

View File

@@ -21,10 +21,9 @@ import argparse
import logging
import os
from oslo_utils import encodeutils
from oslo_utils import importutils
from oslo.utils import encodeutils
from oslo.utils import importutils
import six
import six.moves.urllib.parse as urlparse
from tackerclient.common import exceptions
from tackerclient.i18n import _
@@ -172,10 +171,3 @@ def add_boolean_argument(parser, name, **kwargs):
choices=['True', 'true', 'False', 'false'],
default=default,
**kwargs)
def validate_url(url):
url_parts = urlparse.urlparse(url)
if not url_parts.scheme or not url_parts.netloc or not url_parts.port:
raise exceptions.TackerClientException(message='Invalid URL')
return url_parts

View File

@@ -10,7 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import oslo_i18n as i18n
from oslo import i18n
_translators = i18n.TranslatorFactory(domain='tackerclient')

View File

@@ -0,0 +1,17 @@
#
# 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 six
six.add_move(six.MovedModule('mox', 'mox', 'mox3.mox'))

View File

@@ -0,0 +1,320 @@
# Copyright 2012 Red Hat, Inc.
# Copyright 2013 IBM Corp.
# 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.
"""
gettext for openstack-common modules.
Usual usage in an openstack.common module:
from tackerclient.openstack.common.gettextutils import _
"""
import copy
import gettext
import logging
import os
import re
import UserString
from babel import localedata
import six
_localedir = os.environ.get('tackerclient'.upper() + '_LOCALEDIR')
_t = gettext.translation('tackerclient', localedir=_localedir, fallback=True)
_AVAILABLE_LANGUAGES = {}
USE_LAZY = False
def enable_lazy():
"""Convenience function for configuring _() to use lazy gettext
Call this at the start of execution to enable the gettextutils._
function to use lazy gettext functionality. This is useful if
your project is importing _ directly instead of using the
gettextutils.install() way of importing the _ function.
"""
global USE_LAZY
USE_LAZY = True
def _(msg):
if USE_LAZY:
return Message(msg, 'tackerclient')
else:
return _t.ugettext(msg)
def install(domain, lazy=False):
"""Install a _() function using the given translation domain.
Given a translation domain, install a _() function using gettext's
install() function.
The main difference from gettext.install() is that we allow
overriding the default localedir (e.g. /usr/share/locale) using
a translation-domain-specific environment variable (e.g.
NOVA_LOCALEDIR).
:param domain: the translation domain
:param lazy: indicates whether or not to install the lazy _() function.
The lazy _() introduces a way to do deferred translation
of messages by installing a _ that builds Message objects,
instead of strings, which can then be lazily translated into
any available locale.
"""
if lazy:
# NOTE(mrodden): Lazy gettext functionality.
#
# The following introduces a deferred way to do translations on
# messages in OpenStack. We override the standard _() function
# and % (format string) operation to build Message objects that can
# later be translated when we have more information.
#
# Also included below is an example LocaleHandler that translates
# Messages to an associated locale, effectively allowing many logs,
# each with their own locale.
def _lazy_gettext(msg):
"""Create and return a Message object.
Lazy gettext function for a given domain, it is a factory method
for a project/module to get a lazy gettext function for its own
translation domain (i.e. nova, glance, cinder, etc.)
Message encapsulates a string so that we can translate
it later when needed.
"""
return Message(msg, domain)
import __builtin__
__builtin__.__dict__['_'] = _lazy_gettext
else:
localedir = '%s_LOCALEDIR' % domain.upper()
gettext.install(domain,
localedir=os.environ.get(localedir),
unicode=True)
class Message(UserString.UserString, object):
"""Class used to encapsulate translatable messages."""
def __init__(self, msg, domain):
# _msg is the gettext msgid and should never change
self._msg = msg
self._left_extra_msg = ''
self._right_extra_msg = ''
self.params = None
self.locale = None
self.domain = domain
@property
def data(self):
# NOTE(mrodden): this should always resolve to a unicode string
# that best represents the state of the message currently
localedir = os.environ.get(self.domain.upper() + '_LOCALEDIR')
if self.locale:
lang = gettext.translation(self.domain,
localedir=localedir,
languages=[self.locale],
fallback=True)
else:
# use system locale for translations
lang = gettext.translation(self.domain,
localedir=localedir,
fallback=True)
full_msg = (self._left_extra_msg +
lang.ugettext(self._msg) +
self._right_extra_msg)
if self.params is not None:
full_msg = full_msg % self.params
return six.text_type(full_msg)
def _save_dictionary_parameter(self, dict_param):
full_msg = self.data
# look for %(blah) fields in string;
# ignore %% and deal with the
# case where % is first character on the line
keys = re.findall('(?:[^%]|^)?%\((\w*)\)[a-z]', full_msg)
# if we don't find any %(blah) blocks but have a %s
if not keys and re.findall('(?:[^%]|^)%[a-z]', full_msg):
# apparently the full dictionary is the parameter
params = copy.deepcopy(dict_param)
else:
params = {}
for key in keys:
try:
params[key] = copy.deepcopy(dict_param[key])
except TypeError:
# cast uncopyable thing to unicode string
params[key] = unicode(dict_param[key])
return params
def _save_parameters(self, other):
# we check for None later to see if
# we actually have parameters to inject,
# so encapsulate if our parameter is actually None
if other is None:
self.params = (other, )
elif isinstance(other, dict):
self.params = self._save_dictionary_parameter(other)
else:
# fallback to casting to unicode,
# this will handle the problematic python code-like
# objects that cannot be deep-copied
try:
self.params = copy.deepcopy(other)
except TypeError:
self.params = unicode(other)
return self
# overrides to be more string-like
def __unicode__(self):
return self.data
def __str__(self):
return self.data.encode('utf-8')
def __getstate__(self):
to_copy = ['_msg', '_right_extra_msg', '_left_extra_msg',
'domain', 'params', 'locale']
new_dict = self.__dict__.fromkeys(to_copy)
for attr in to_copy:
new_dict[attr] = copy.deepcopy(self.__dict__[attr])
return new_dict
def __setstate__(self, state):
for (k, v) in state.items():
setattr(self, k, v)
# operator overloads
def __add__(self, other):
copied = copy.deepcopy(self)
copied._right_extra_msg += other.__str__()
return copied
def __radd__(self, other):
copied = copy.deepcopy(self)
copied._left_extra_msg += other.__str__()
return copied
def __mod__(self, other):
# do a format string to catch and raise
# any possible KeyErrors from missing parameters
self.data % other
copied = copy.deepcopy(self)
return copied._save_parameters(other)
def __mul__(self, other):
return self.data * other
def __rmul__(self, other):
return other * self.data
def __getitem__(self, key):
return self.data[key]
def __getslice__(self, start, end):
return self.data.__getslice__(start, end)
def __getattribute__(self, name):
# NOTE(mrodden): handle lossy operations that we can't deal with yet
# These override the UserString implementation, since UserString
# uses our __class__ attribute to try and build a new message
# after running the inner data string through the operation.
# At that point, we have lost the gettext message id and can just
# safely resolve to a string instead.
ops = ['capitalize', 'center', 'decode', 'encode',
'expandtabs', 'ljust', 'lstrip', 'replace', 'rjust', 'rstrip',
'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
if name in ops:
return getattr(self.data, name)
else:
return UserString.UserString.__getattribute__(self, name)
def get_available_languages(domain):
"""Lists the available languages for the given translation domain.
:param domain: the domain to get languages for
"""
if domain in _AVAILABLE_LANGUAGES:
return copy.copy(_AVAILABLE_LANGUAGES[domain])
localedir = '%s_LOCALEDIR' % domain.upper()
find = lambda x: gettext.find(domain,
localedir=os.environ.get(localedir),
languages=[x])
# NOTE(mrodden): en_US should always be available (and first in case
# order matters) since our in-line message strings are en_US
language_list = ['en_US']
# NOTE(luisg): Babel <1.0 used a function called list(), which was
# renamed to locale_identifiers() in >=1.0, the requirements master list
# requires >=0.9.6, uncapped, so defensively work with both. We can remove
# this check when the master list updates to >=1.0, and all projects udpate
list_identifiers = (getattr(localedata, 'list', None) or
getattr(localedata, 'locale_identifiers'))
locale_identifiers = list_identifiers()
for i in locale_identifiers:
if find(i) is not None:
language_list.append(i)
_AVAILABLE_LANGUAGES[domain] = language_list
return copy.copy(language_list)
def get_localized_message(message, user_locale):
"""Gets a localized version of the given message in the given locale."""
if isinstance(message, Message):
if user_locale:
message.locale = user_locale
return unicode(message)
else:
return message
class LocaleHandler(logging.Handler):
"""Handler that can have a locale associated to translate Messages.
A quick example of how to utilize the Message class above.
LocaleHandler takes a locale and a target logging.Handler object
to forward LogRecord objects to after translating the internal Message.
"""
def __init__(self, locale, target):
"""Initialize a LocaleHandler
:param locale: locale to use for translating messages
:param target: logging.Handler object to forward
LogRecord objects to after translation
"""
logging.Handler.__init__(self)
self.locale = locale
self.target = target
def emit(self, record):
if isinstance(record.msg, Message):
# set the locale and resolve to a string
record.msg.locale = self.locale
self.target.emit(record)

View File

@@ -0,0 +1,67 @@
# Copyright 2011 OpenStack Foundation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Import related utilities and helper functions.
"""
import sys
import traceback
from tackerclient.openstack.common.gettextutils import _
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 (ValueError, AttributeError):
raise ImportError(_('Class %s cannot be found (%s)') %
(class_str,
traceback.format_exception(*sys.exc_info())))
def import_object(import_str, *args, **kwargs):
"""Import a class and return an instance of it."""
return import_class(import_str)(*args, **kwargs)
def import_object_ns(name_space, import_str, *args, **kwargs):
"""Tries to import object from default namespace.
Imports a class and return an instance of it, first by trying
to find the class in a default namespace, then failing back to
a full path if not found in the default namespace.
"""
import_value = "%s.%s" % (name_space, import_str)
try:
return import_class(import_value)(*args, **kwargs)
except ImportError:
return import_class(import_str)(*args, **kwargs)
def import_module(import_str):
"""Import a module."""
__import__(import_str)
return sys.modules[import_str]
def try_import(import_str, default=None):
"""Try to import a module and if it fails return default."""
try:
return import_module(import_str)
except ImportError:
return default

View File

@@ -0,0 +1,186 @@
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# 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.
'''
JSON related utilities.
This module provides a few things:
1) A handy function for getting an object down to something that can be
JSON serialized. See to_primitive().
2) Wrappers around loads() and dumps(). The dumps() wrapper will
automatically use to_primitive() for you if needed.
3) This sets up anyjson to use the loads() and dumps() wrappers if anyjson
is available.
'''
import codecs
import datetime
import functools
import inspect
import itertools
import sys
if sys.version_info < (2, 7):
# On Python <= 2.6, json module is not C boosted, so try to use
# simplejson module if available
try:
import simplejson as json
except ImportError:
import json
else:
import json
import six
import six.moves.xmlrpc_client as xmlrpclib
from tackerclient.openstack.common import gettextutils
from tackerclient.openstack.common import importutils
from tackerclient.openstack.common import strutils
from tackerclient.openstack.common import timeutils
netaddr = importutils.try_import("netaddr")
_nasty_type_tests = [inspect.ismodule, inspect.isclass, inspect.ismethod,
inspect.isfunction, inspect.isgeneratorfunction,
inspect.isgenerator, inspect.istraceback, inspect.isframe,
inspect.iscode, inspect.isbuiltin, inspect.isroutine,
inspect.isabstract]
_simple_types = (six.string_types + six.integer_types
+ (type(None), bool, float))
def to_primitive(value, convert_instances=False, convert_datetime=True,
level=0, max_depth=3):
"""Convert a complex object into primitives.
Handy for JSON serialization. We can optionally handle instances,
but since this is a recursive function, we could have cyclical
data structures.
To handle cyclical data structures we could track the actual objects
visited in a set, but not all objects are hashable. Instead we just
track the depth of the object inspections and don't go too deep.
Therefore, convert_instances=True is lossy ... be aware.
"""
# handle obvious types first - order of basic types determined by running
# full tests on nova project, resulting in the following counts:
# 572754 <type 'NoneType'>
# 460353 <type 'int'>
# 379632 <type 'unicode'>
# 274610 <type 'str'>
# 199918 <type 'dict'>
# 114200 <type 'datetime.datetime'>
# 51817 <type 'bool'>
# 26164 <type 'list'>
# 6491 <type 'float'>
# 283 <type 'tuple'>
# 19 <type 'long'>
if isinstance(value, _simple_types):
return value
if isinstance(value, datetime.datetime):
if convert_datetime:
return timeutils.strtime(value)
else:
return value
# value of itertools.count doesn't get caught by nasty_type_tests
# and results in infinite loop when list(value) is called.
if type(value) == itertools.count:
return six.text_type(value)
# FIXME(vish): Workaround for LP bug 852095. Without this workaround,
# tests that raise an exception in a mocked method that
# has a @wrap_exception with a notifier will fail. If
# we up the dependency to 0.5.4 (when it is released) we
# can remove this workaround.
if getattr(value, '__module__', None) == 'mox':
return 'mock'
if level > max_depth:
return '?'
# The try block may not be necessary after the class check above,
# but just in case ...
try:
recursive = functools.partial(to_primitive,
convert_instances=convert_instances,
convert_datetime=convert_datetime,
level=level,
max_depth=max_depth)
if isinstance(value, dict):
return dict((k, recursive(v)) for k, v in six.iteritems(value))
elif isinstance(value, (list, tuple)):
return [recursive(lv) for lv in value]
# It's not clear why xmlrpclib created their own DateTime type, but
# for our purposes, make it a datetime type which is explicitly
# handled
if isinstance(value, xmlrpclib.DateTime):
value = datetime.datetime(*tuple(value.timetuple())[:6])
if convert_datetime and isinstance(value, datetime.datetime):
return timeutils.strtime(value)
elif isinstance(value, gettextutils.Message):
return value.data
elif hasattr(value, 'iteritems'):
return recursive(dict(value.iteritems()), level=level + 1)
elif hasattr(value, '__iter__'):
return recursive(list(value))
elif convert_instances and hasattr(value, '__dict__'):
# Likely an instance of something. Watch for cycles.
# Ignore class member vars.
return recursive(value.__dict__, level=level + 1)
elif netaddr and isinstance(value, netaddr.IPAddress):
return six.text_type(value)
else:
if any(test(value) for test in _nasty_type_tests):
return six.text_type(value)
return value
except TypeError:
# Class objects are tricky since they may define something like
# __iter__ defined but it isn't callable as list().
return six.text_type(value)
def dumps(value, default=to_primitive, **kwargs):
return json.dumps(value, default=default, **kwargs)
def loads(s, encoding='utf-8'):
return json.loads(strutils.safe_decode(s, encoding))
def load(fp, encoding='utf-8'):
return json.load(codecs.getreader(encoding)(fp))
try:
import anyjson
except ImportError:
pass
else:
anyjson._modules.append((__name__, 'dumps', TypeError,
'loads', ValueError, 'load'))
anyjson.force_implementation(__name__)

View File

@@ -0,0 +1,216 @@
# Copyright 2011 OpenStack Foundation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
System-level utilities and helper functions.
"""
import re
import sys
import unicodedata
import six
from tackerclient.openstack.common.gettextutils import _ # noqa
# Used for looking up extensions of text
# to their 'multiplied' byte amount
BYTE_MULTIPLIERS = {
'': 1,
't': 1024 ** 4,
'g': 1024 ** 3,
'm': 1024 ** 2,
'k': 1024,
}
BYTE_REGEX = re.compile(r'(^-?\d+)(\D*)')
TRUE_STRINGS = ('1', 't', 'true', 'on', 'y', 'yes')
FALSE_STRINGS = ('0', 'f', 'false', 'off', 'n', 'no')
SLUGIFY_STRIP_RE = re.compile(r"[^\w\s-]")
SLUGIFY_HYPHENATE_RE = re.compile(r"[-\s]+")
def int_from_bool_as_string(subject):
"""Interpret a string as a boolean and return either 1 or 0.
Any string value in:
('True', 'true', 'On', 'on', '1')
is interpreted as a boolean True.
Useful for JSON-decoded stuff and config file parsing
"""
return bool_from_string(subject) and 1 or 0
def bool_from_string(subject, strict=False):
"""Interpret a string as a boolean.
A case-insensitive match is performed such that strings matching 't',
'true', 'on', 'y', 'yes', or '1' are considered True and, when
`strict=False`, anything else is considered False.
Useful for JSON-decoded stuff and config file parsing.
If `strict=True`, unrecognized values, including None, will raise a
ValueError which is useful when parsing values passed in from an API call.
Strings yielding False are 'f', 'false', 'off', 'n', 'no', or '0'.
"""
if not isinstance(subject, six.string_types):
subject = str(subject)
lowered = subject.strip().lower()
if lowered in TRUE_STRINGS:
return True
elif lowered in FALSE_STRINGS:
return False
elif strict:
acceptable = ', '.join(
"'%s'" % s for s in sorted(TRUE_STRINGS + FALSE_STRINGS))
msg = _("Unrecognized value '%(val)s', acceptable values are:"
" %(acceptable)s") % {'val': subject,
'acceptable': acceptable}
raise ValueError(msg)
else:
return False
def safe_decode(text, incoming=None, errors='strict'):
"""Decodes incoming str using `incoming` if they're not already unicode.
:param incoming: Text's current encoding
:param errors: Errors handling policy. See here for valid
values http://docs.python.org/2/library/codecs.html
:returns: text or a unicode `incoming` encoded
representation of it.
:raises TypeError: If text is not an isntance of str
"""
if not isinstance(text, six.string_types):
raise TypeError("%s can't be decoded" % type(text))
if isinstance(text, six.text_type):
return text
if not incoming:
incoming = (sys.stdin.encoding or
sys.getdefaultencoding())
try:
return text.decode(incoming, errors)
except UnicodeDecodeError:
# Note(flaper87) If we get here, it means that
# sys.stdin.encoding / sys.getdefaultencoding
# didn't return a suitable encoding to decode
# text. This happens mostly when global LANG
# var is not set correctly and there's no
# default encoding. In this case, most likely
# python will use ASCII or ANSI encoders as
# default encodings but they won't be capable
# of decoding non-ASCII characters.
#
# Also, UTF-8 is being used since it's an ASCII
# extension.
return text.decode('utf-8', errors)
def safe_encode(text, incoming=None,
encoding='utf-8', errors='strict'):
"""Encodes incoming str/unicode using `encoding`.
If incoming is not specified, text is expected to be encoded with
current python's default encoding. (`sys.getdefaultencoding`)
:param incoming: Text's current encoding
:param encoding: Expected encoding for text (Default UTF-8)
:param errors: Errors handling policy. See here for valid
values http://docs.python.org/2/library/codecs.html
:returns: text or a bytestring `encoding` encoded
representation of it.
:raises TypeError: If text is not an isntance of str
"""
if not isinstance(text, six.string_types):
raise TypeError(_("%s can't be encoded") % type(text).capitalize())
if not incoming:
incoming = (sys.stdin.encoding or
sys.getdefaultencoding())
if isinstance(text, six.text_type):
return text.encode(encoding, errors)
elif text and encoding != incoming:
# Decode text before encoding it with `encoding`
text = safe_decode(text, incoming, errors)
return text.encode(encoding, errors)
return text
def to_bytes(text, default=0):
"""Converts a string into an integer of bytes.
Looks at the last characters of the text to determine
what conversion is needed to turn the input text into a byte number.
Supports "B, K(B), M(B), G(B), and T(B)". (case insensitive)
:param text: String input for bytes size conversion.
:param default: Default return value when text is blank.
"""
match = BYTE_REGEX.search(text)
if match:
magnitude = int(match.group(1))
mult_key_org = match.group(2)
if not mult_key_org:
return magnitude
elif text:
msg = _('Invalid string format: %s') % text
raise TypeError(msg)
else:
return default
mult_key = mult_key_org.lower().replace('b', '', 1)
multiplier = BYTE_MULTIPLIERS.get(mult_key)
if multiplier is None:
msg = _('Unknown byte multiplier: %s') % mult_key_org
raise TypeError(msg)
return magnitude * multiplier
def to_slug(value, incoming=None, errors="strict"):
"""Normalize string.
Convert to lowercase, remove non-word characters, and convert spaces
to hyphens.
Inspired by Django's `slugify` filter.
:param value: Text to slugify
:param incoming: Text's current encoding
:param errors: Errors handling policy. See here for valid
values http://docs.python.org/2/library/codecs.html
:returns: slugified unicode representation of `value`
:raises TypeError: If text is not an instance of str
"""
value = safe_decode(value, incoming, errors)
# NOTE(aababilov): no need to use safe_(encode|decode) here:
# encodings are always "ascii", error handling is always "ignore"
# and types are always known (first: unicode; second: str)
value = unicodedata.normalize("NFKD", value).encode(
"ascii", "ignore").decode("ascii")
value = SLUGIFY_STRIP_RE.sub("", value).strip().lower()
return SLUGIFY_HYPHENATE_RE.sub("-", value)

View File

@@ -0,0 +1,186 @@
# Copyright 2011 OpenStack Foundation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Time related utilities and helper functions.
"""
import calendar
import datetime
import iso8601
import six
# ISO 8601 extended time format with microseconds
_ISO8601_TIME_FORMAT_SUBSECOND = '%Y-%m-%dT%H:%M:%S.%f'
_ISO8601_TIME_FORMAT = '%Y-%m-%dT%H:%M:%S'
PERFECT_TIME_FORMAT = _ISO8601_TIME_FORMAT_SUBSECOND
def isotime(at=None, subsecond=False):
"""Stringify time in ISO 8601 format."""
if not at:
at = utcnow()
st = at.strftime(_ISO8601_TIME_FORMAT
if not subsecond
else _ISO8601_TIME_FORMAT_SUBSECOND)
tz = at.tzinfo.tzname(None) if at.tzinfo else 'UTC'
st += ('Z' if tz == 'UTC' else tz)
return st
def parse_isotime(timestr):
"""Parse time from ISO 8601 format."""
try:
return iso8601.parse_date(timestr)
except iso8601.ParseError as e:
raise ValueError(unicode(e))
except TypeError as e:
raise ValueError(unicode(e))
def strtime(at=None, fmt=PERFECT_TIME_FORMAT):
"""Returns formatted utcnow."""
if not at:
at = utcnow()
return at.strftime(fmt)
def parse_strtime(timestr, fmt=PERFECT_TIME_FORMAT):
"""Turn a formatted time back into a datetime."""
return datetime.datetime.strptime(timestr, fmt)
def normalize_time(timestamp):
"""Normalize time in arbitrary timezone to UTC naive object."""
offset = timestamp.utcoffset()
if offset is None:
return timestamp
return timestamp.replace(tzinfo=None) - offset
def is_older_than(before, seconds):
"""Return True if before is older than seconds."""
if isinstance(before, six.string_types):
before = parse_strtime(before).replace(tzinfo=None)
return utcnow() - before > datetime.timedelta(seconds=seconds)
def is_newer_than(after, seconds):
"""Return True if after is newer than seconds."""
if isinstance(after, six.string_types):
after = parse_strtime(after).replace(tzinfo=None)
return after - utcnow() > datetime.timedelta(seconds=seconds)
def utcnow_ts():
"""Timestamp version of our utcnow function."""
return calendar.timegm(utcnow().timetuple())
def utcnow():
"""Overridable version of utils.utcnow."""
if utcnow.override_time:
try:
return utcnow.override_time.pop(0)
except AttributeError:
return utcnow.override_time
return datetime.datetime.utcnow()
def iso8601_from_timestamp(timestamp):
"""Returns a iso8601 formated date from timestamp."""
return isotime(datetime.datetime.utcfromtimestamp(timestamp))
utcnow.override_time = None
def set_time_override(override_time=datetime.datetime.utcnow()):
"""Overrides utils.utcnow.
Make it return a constant time or a list thereof, one at a time.
"""
utcnow.override_time = override_time
def advance_time_delta(timedelta):
"""Advance overridden time using a datetime.timedelta."""
assert(not utcnow.override_time is None)
try:
for dt in utcnow.override_time:
dt += timedelta
except TypeError:
utcnow.override_time += timedelta
def advance_time_seconds(seconds):
"""Advance overridden time by seconds."""
advance_time_delta(datetime.timedelta(0, seconds))
def clear_time_override():
"""Remove the overridden time."""
utcnow.override_time = None
def marshall_now(now=None):
"""Make an rpc-safe datetime with microseconds.
Note: tzinfo is stripped, but not required for relative times.
"""
if not now:
now = utcnow()
return dict(day=now.day, month=now.month, year=now.year, hour=now.hour,
minute=now.minute, second=now.second,
microsecond=now.microsecond)
def unmarshall_time(tyme):
"""Unmarshall a datetime dict."""
return datetime.datetime(day=tyme['day'],
month=tyme['month'],
year=tyme['year'],
hour=tyme['hour'],
minute=tyme['minute'],
second=tyme['second'],
microsecond=tyme['microsecond'])
def delta_seconds(before, after):
"""Return the difference between two timing objects.
Compute the difference in seconds between two date, time, or
datetime objects (as a float, to microsecond resolution).
"""
delta = after - before
try:
return delta.total_seconds()
except AttributeError:
return ((delta.days * 24 * 3600) + delta.seconds +
float(delta.microseconds) / (10 ** 6))
def is_soon(dt, window):
"""Determines if time is going to happen in the next window seconds.
:params dt: the time
:params window: minimum seconds to remain to consider the time not soon
:return: True if expiration is within the given duration
"""
soon = (utcnow() + datetime.timedelta(seconds=window))
return normalize_time(dt) <= soon

View File

@@ -31,9 +31,9 @@ import sys
from keystoneclient.auth.identity import v2 as v2_auth
from keystoneclient.auth.identity import v3 as v3_auth
from keystoneclient import discover
from keystoneclient import exceptions as ks_exc
from keystoneclient.openstack.common.apiclient import exceptions as ks_exc
from keystoneclient import session
from oslo_utils import encodeutils
from oslo.utils import encodeutils
import six.moves.urllib.parse as urlparse
from cliff import app
@@ -46,7 +46,8 @@ from tackerclient.common import extension as client_extension
from tackerclient.common import utils
from tackerclient.i18n import _
from tackerclient.tacker.v1_0 import extension
from tackerclient.tacker.v1_0.nfvo import vim
from tackerclient.tacker.v1_0.vm import device
from tackerclient.tacker.v1_0.vm import device_template
from tackerclient.tacker.v1_0.vm import vnf
from tackerclient.tacker.v1_0.vm import vnfd
from tackerclient.version import __version__
@@ -102,13 +103,24 @@ COMMAND_V1 = {
'bash-completion': BashCompletionCommand,
'ext-list': extension.ListExt,
'ext-show': extension.ShowExt,
'device-template-create': device_template.CreateDeviceTemplate,
'device-template-list': device_template.ListDeviceTemplate,
'device-template-show': device_template.ShowDeviceTemplate,
'device-template-update': device_template.UpdateDeviceTemplate,
'device-template-delete': device_template.DeleteDeviceTemplate,
'device-create': device.CreateDevice,
'device-list': device.ListDevice,
'device-show': device.ShowDevice,
'device-update': device.UpdateDevice,
'device-delete': device.DeleteDevice,
'interface-attach': device.AttachInterface,
'interface-detach': device.DetachInterface,
# MANO lingo
'vnfd-create': vnfd.CreateVNFD,
'vnfd-delete': vnfd.DeleteVNFD,
'vnfd-list': vnfd.ListVNFD,
'vnfd-show': vnfd.ShowVNFD,
'vnfd-template-show': vnfd.ShowTemplateVNFD,
'vnf-create': vnf.CreateVNF,
'vnf-update': vnf.UpdateVNF,
@@ -117,24 +129,17 @@ COMMAND_V1 = {
'vnf-show': vnf.ShowVNF,
# 'vnf-config-create'
# 'vnf-config-push'
'vim-register': vim.CreateVIM,
'vim-update': vim.UpdateVIM,
'vim-delete': vim.DeleteVIM,
'vim-list': vim.ListVIM,
'vim-show': vim.ShowVIM,
}
COMMANDS = {'1.0': COMMAND_V1}
class HelpAction(argparse.Action):
"""Provides a custom action for the -h and --help options.
"""Provide a custom action so the -h and --help options
to the main app will print a list of the commands.
The commands are determined by checking the CommandManager
instance, passed in as the "default" value for the action.
:returns: a list of the commands
"""
def __call__(self, parser, namespace, values, option_string=None):
outputs = []
@@ -244,10 +249,8 @@ class TackerShell(app.App):
parser.add_argument(
'--os-service-type', metavar='<os-service-type>',
default=env('OS_TACKER_SERVICE_TYPE',
default='nfv-orchestration'),
help=_('Defaults to env[OS_TACKER_SERVICE_TYPE] or \
nfv-orchestration.'))
default=env('OS_SERVICEVM_SERVICE_TYPE', default='servicevm'),
help=_('Defaults to env[OS_SERVICEVM_SERVICE_TYPE] or servicevm.'))
parser.add_argument(
'--os-endpoint-type', metavar='<os-endpoint-type>',
@@ -258,8 +261,7 @@ class TackerShell(app.App):
# backward compatibility.
parser.add_argument(
'--service-type', metavar='<service-type>',
default=env('OS_TACKER_SERVICE_TYPE',
default='nfv-orchestration'),
default=env('OS_SERVICEVM_SERVICE_TYPE', default='servicevm'),
help=_('DEPRECATED! Use --os-service-type.'))
# FIXME(bklei): --endpoint-type is deprecated but kept in for
@@ -554,9 +556,7 @@ class TackerShell(app.App):
return 1
def authenticate_user(self):
"""Authentication validation.
Make sure the user has provided all of the authentication
"""Make sure the user has provided all of the authentication
info we need.
"""
if self.options.os_auth_strategy == 'keystone':
@@ -670,8 +670,7 @@ class TackerShell(app.App):
super(TackerShell, self).initialize_app(argv)
self.api_version = {'nfv-orchestration':
self.api_version}
self.api_version = {'servicevm': self.api_version}
# If the user is not asking for help, make sure they
# have given us auth.

View File

@@ -14,20 +14,20 @@
# under the License.
#
from tackerclient.common._i18n import _
from tackerclient.common import exceptions
from tackerclient.common import utils
from tackerclient.openstack.common.gettextutils import _
API_NAME = 'nfv-orchestration'
API_NAME = 'servicevm'
API_VERSIONS = {
'1.0': 'tackerclient.v1_0.client.Client',
}
def make_client(instance):
"""Returns an tacker client."""
"""Returns an tacker client.
"""
tacker_client = utils.get_client_class(
API_NAME,
instance._api_version[API_NAME],
@@ -61,8 +61,7 @@ def make_client(instance):
def Client(api_version, *args, **kwargs):
"""Return an tacker client.
:param api_version: only 1.0 is supported now
@param api_version: only 1.0 is supported now
"""
tacker_client = utils.get_client_class(
API_NAME,

View File

@@ -24,13 +24,13 @@ import re
from cliff.formatters import table
from cliff import lister
from cliff import show
from oslo_serialization import jsonutils
from oslo.serialization import jsonutils
import six
from tackerclient.common._i18n import _
from tackerclient.common import command
from tackerclient.common import exceptions
from tackerclient.common import utils
from tackerclient.openstack.common.gettextutils import _
HEX_ELEM = '[0-9A-Fa-f]'
UUID_PATTERN = '-'.join([HEX_ELEM + '{8}', HEX_ELEM + '{4}',
@@ -263,7 +263,7 @@ def parse_args_to_dict(values_specs):
# All others are value items
# Make sure '--' occurs first and allow minus value
if (not current_item or '=' in current_item or
_item.startswith('-') and not is_number(_item)):
_item.startswith('-') and not is_number(_item)):
raise exceptions.CommandError(
_("Invalid values_specs %s") % ' '.join(values_specs))
_value_number += 1
@@ -277,7 +277,7 @@ def parse_args_to_dict(values_specs):
# populate the parser with arguments
_parser = argparse.ArgumentParser(add_help=False)
for opt, optspec in six.iteritems(_options):
for opt, optspec in _options.iteritems():
_parser.add_argument(opt, **optspec)
_args = _parser.parse_args(_values_specs)
@@ -301,14 +301,14 @@ def _merge_args(qCmd, parsed_args, _extra_values, value_specs):
@param values_specs: the unparsed unknown parts
"""
temp_values = _extra_values.copy()
for key, value in six.iteritems(temp_values):
for key, value in temp_values.iteritems():
if hasattr(parsed_args, key):
arg_value = getattr(parsed_args, key)
if arg_value is not None and value is not None:
if isinstance(arg_value, list):
if value and isinstance(value, list):
if (not arg_value or
isinstance(arg_value[0], type(value[0]))):
type(arg_value[0]) == type(value[0])):
arg_value.extend(value)
_extra_values.pop(key)
@@ -353,7 +353,7 @@ class TackerCommandMeta(abc.ABCMeta):
@six.add_metaclass(TackerCommandMeta)
class TackerCommand(command.OpenStackCommand):
api = 'nfv-orchestration'
api = 'servicevm'
values_specs = []
json_indent = None
@@ -362,8 +362,8 @@ class TackerCommand(command.OpenStackCommand):
# NOTE(markmcclain): This is no longer supported in cliff version 1.5.2
# see https://bugs.launchpad.net/python-tackerclient/+bug/1265926
# if hasattr(self, 'formatters'):
# self.formatters['table'] = TableFormater()
#if hasattr(self, 'formatters'):
#self.formatters['table'] = TableFormater()
def get_client(self):
return self.app.client_manager.tacker
@@ -385,7 +385,7 @@ class TackerCommand(command.OpenStackCommand):
def format_output_data(self, data):
# Modify data to make it more readable
if self.resource in data:
for k, v in six.iteritems(data[self.resource]):
for k, v in data[self.resource].iteritems():
if isinstance(v, list):
value = '\n'.join(jsonutils.dumps(
i, indent=self.json_indent) if isinstance(i, dict)
@@ -409,7 +409,7 @@ class CreateCommand(TackerCommand, show.ShowOne):
"""
api = 'nfv-orchestration'
api = 'servicevm'
resource = None
log = None
remove_output_fields = []
@@ -448,13 +448,14 @@ class CreateCommand(TackerCommand, show.ShowOne):
info.pop(f)
else:
info = {'': ''}
return zip(*sorted(six.iteritems(info)))
return zip(*sorted(info.iteritems()))
class UpdateCommand(TackerCommand):
"""Update resource's information."""
"""Update resource's information
"""
api = 'nfv-orchestration'
api = 'servicevm'
resource = None
log = None
allow_names = True
@@ -502,7 +503,7 @@ class DeleteCommand(TackerCommand):
"""
api = 'nfv-orchestration'
api = 'servicevm'
resource = None
log = None
allow_names = True
@@ -542,7 +543,7 @@ class ListCommand(TackerCommand, lister.Lister):
"""
api = 'nfv-orchestration'
api = 'servicevm'
resource = None
log = None
_formatters = {}
@@ -641,15 +642,11 @@ class ShowCommand(TackerCommand, show.ShowOne):
"""
api = 'nfv-orchestration'
api = 'servicevm'
resource = None
log = None
allow_names = True
def get_id(self):
if self.resource:
return self.resource.upper()
def get_parser(self, prog_name):
parser = super(ShowCommand, self).get_parser(prog_name)
add_show_list_common_argument(parser)
@@ -658,7 +655,7 @@ class ShowCommand(TackerCommand, show.ShowOne):
else:
help_str = _('ID of %s to look up')
parser.add_argument(
'id', metavar=self.get_id(),
'id', metavar=self.resource.upper(),
help=help_str % self.resource)
return parser
@@ -683,6 +680,6 @@ class ShowCommand(TackerCommand, show.ShowOne):
self.format_output_data(data)
resource = data[self.resource]
if self.resource in data:
return zip(*sorted(six.iteritems(resource)))
return zip(*sorted(resource.iteritems()))
else:
return None

View File

@@ -14,6 +14,7 @@
# under the License.
#
from tackerclient.openstack.common.gettextutils import _
from tackerclient.tacker import v1_0 as cmd_base
@@ -30,5 +31,10 @@ class ShowExt(cmd_base.ShowCommand):
resource = "extension"
allow_names = False
def get_id(self):
return 'EXT-ALIAS'
def get_parser(self, prog_name):
parser = super(cmd_base.ShowCommand, self).get_parser(prog_name)
cmd_base.add_show_list_common_argument(parser)
parser.add_argument(
'id', metavar='EXT-ALIAS',
help=_('The extension alias'))
return parser

View File

@@ -1,131 +0,0 @@
# Copyright 2016 Brocade Communications 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.
import yaml
from tackerclient.common import exceptions
from tackerclient.common import utils
from tackerclient.tacker import v1_0 as tackerV10
from tackerclient.tacker.v1_0.nfvo import vim_utils
_VIM = "vim"
class ListVIM(tackerV10.ListCommand):
"""List VIMs that belong to a given tenant."""
resource = _VIM
list_columns = ['id', 'tenant_id', 'name', 'type', 'description',
'auth_url', 'placement_attr', 'auth_cred', 'status']
class ShowVIM(tackerV10.ShowCommand):
"""Show information of a given VIM."""
resource = _VIM
class CreateVIM(tackerV10.CreateCommand):
"""Create a VIM."""
resource = _VIM
def add_known_arguments(self, parser):
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('--config-file', help='Specify VIM specific '
'config parameters in a file')
group.add_argument('--config', help='Specify VIM config parameters '
'as a direct input')
parser.add_argument(
'--name',
help='Set a name for the VIM')
parser.add_argument(
'--description',
help='Set a description for the VIM')
parser.add_argument(
'--is-default',
action='store_true',
default=False,
help='Set as default VIM')
def args2body(self, parsed_args):
body = {self.resource: {}}
if parsed_args.config_file:
with open(parsed_args.config_file) as f:
vim_config = f.read()
config_param = yaml.load(vim_config)
if parsed_args.config:
parsed_args.config = parsed_args.config.decode('unicode_escape')
config_param = yaml.load(parsed_args.config)
vim_obj = body[self.resource]
try:
auth_url = config_param.pop('auth_url')
except KeyError:
raise exceptions.TackerClientException(message='Auth URL must be '
'specified',
status_code=404)
vim_obj['auth_url'] = utils.validate_url(auth_url).geturl()
vim_obj['type'] = config_param.pop('type', 'openstack')
vim_utils.args2body_vim(config_param, vim_obj)
tackerV10.update_dict(parsed_args, body[self.resource],
['tenant_id', 'name', 'description',
'is_default'])
return body
class UpdateVIM(tackerV10.UpdateCommand):
"""Update a given VIM."""
resource = _VIM
def add_known_arguments(self, parser):
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument(
'--config-file',
help='Specify VIM specific config parameters in a file')
group.add_argument(
'--config',
help='Specify VIM config parameters as a direct input')
parser.add_argument(
'--is-default',
action='store_true',
default=False,
help='Set as default VIM')
def args2body(self, parsed_args):
body = {self.resource: {}}
# config arg passed as data overrides config yaml when both args passed
if parsed_args.config_file:
with open(parsed_args.config_file) as f:
config_yaml = f.read()
config_param = yaml.load(config_yaml)
if parsed_args.config:
parsed_args.config = parsed_args.config.decode('unicode_escape')
config_param = yaml.load(parsed_args.config)
if 'auth_url' in config_param:
raise exceptions.TackerClientException(message='Auth URL cannot '
'be updated',
status_code=404)
vim_obj = body[self.resource]
vim_utils.args2body_vim(config_param, vim_obj)
tackerV10.update_dict(parsed_args, body[self.resource],
['tenant_id', 'is_default'])
return body
class DeleteVIM(tackerV10.DeleteCommand):
"""Delete a given VIM."""
resource = _VIM

View File

@@ -1,39 +0,0 @@
# Copyright 2016 Brocade Communications 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.
from tackerclient.common import exceptions
def args2body_vim(config_param, vim):
"""Create additional args to vim body
:param vim: vim request object
:return: vim body with args populated
"""
vim['vim_project'] = {'id': config_param.pop('project_id', ''),
'name': config_param.pop('project_name', ''),
'project_domain_name':
config_param.pop('project_domain_name', '')}
if not vim['vim_project']['id'] and not vim['vim_project']['name']:
raise exceptions.TackerClientException(message='Project Id or name '
'must be specified',
status_code=404)
vim['auth_cred'] = {'username': config_param.pop('username', ''),
'password': config_param.pop('password', ''),
'user_id': config_param.pop('user_id', ''),
'user_domain_name':
config_param.pop('user_domain_name', '')}

View File

@@ -0,0 +1,190 @@
#
# Copyright 2013 Intel
# Copyright 2013 Isaku Yamahata <isaku.yamahata at intel com>
# <isaku.yamahata at gmail com>
# 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: Isaku Yamahata, Intel
import abc
import six
from tackerclient.common import exceptions
from tackerclient.openstack.common.gettextutils import _
from tackerclient.tacker import v1_0 as tackerV10
_DEVICE = 'device'
class ListDevice(tackerV10.ListCommand):
"""List device that belong to a given tenant."""
resource = _DEVICE
list_columns = ['id', 'name', 'description', 'mgmt_url', 'status']
class ShowDevice(tackerV10.ShowCommand):
"""show information of a given Device."""
resource = _DEVICE
class CreateDevice(tackerV10.CreateCommand):
"""create a Device."""
resource = _DEVICE
def add_known_arguments(self, parser):
parser.add_argument(
'--name',
help='Set a name for the devicef')
parser.add_argument(
'--device-template-id',
required=True,
help='device template id to create device based on')
parser.add_argument(
'--attributes',
metavar='<key>=<value>',
action='append',
dest='attributes',
default=[],
help='instance specific argument')
parser.add_argument(
'--service-context',
metavar='<network-id=network-uuid,subnet-id=subnet-uuid,'
'port-id=port-uuid,router-id=router-uuid,'
'role=role-string,index=int>',
action='append',
dest='service_context',
default=[],
help='service context to insert service')
def args2body(self, parsed_args):
body = {
self.resource: {
'template_id': parsed_args.device_template_id,
}
}
if parsed_args.attributes:
try:
attributes = dict(key_value.split('=', 1)
for key_value in parsed_args.attributes)
except ValueError:
msg = (_('invalid argument for --attributes %s') %
parsed_args.attributes)
raise exceptions.TackerCLIError(msg)
if attributes:
body[self.resource]['attributes'] = attributes
if parsed_args.service_context:
try:
service_contexts = [dict(
(k.replace('-', '_'), v)
for k, v in (key_value.split('=', 1)
for key_value in entry_string.split(',')))
for entry_string in parsed_args.service_context]
except ValueError:
msg = (_('invalid argument for --service-context %s') %
parsed_args.service_context)
raise exceptions.TackerCLIError(msg)
if service_contexts:
body[self.resource]['service_contexts'] = service_contexts
tackerV10.update_dict(parsed_args, body[self.resource], ['tenant_id'])
return body
class UpdateDevice(tackerV10.UpdateCommand):
"""Update a given Device."""
resource = _DEVICE
def add_known_arguments(self, parser):
parser.add_argument(
'--attributes',
metavar='<key>=<value>',
action='append',
dest='attributes',
default=[],
help='instance specific argument')
def args2body(self, parsed_args):
body = {self.resource: {}}
if parsed_args.attributes:
try:
attributes = dict(key_value.split('=', 1)
for key_value in parsed_args.attributes)
except ValueError:
msg = (_('invalid argument for --attributes %s') %
parsed_args.attributes)
raise exceptions.TackerCLIError(msg)
if attributes:
body[self.resource]['attributes'] = attributes
tackerV10.update_dict(parsed_args, body[self.resource], ['tenant_id'])
return body
class DeleteDevice(tackerV10.DeleteCommand):
"""Delete a given Device."""
resource = _DEVICE
@six.add_metaclass(abc.ABCMeta)
class _XtachInterface(tackerV10.UpdateCommand):
resource = _DEVICE
@abc.abstractmethod
def call_api(self, tacker_client, device_id, body):
pass
def args2body(self, parsed_args):
body = {
'port_id': parsed_args.port_id,
}
tackerV10.update_dict(parsed_args, body, [])
return body
def get_parser(self, prog_name):
parser = super(_XtachInterface, self).get_parser(prog_name)
parser.add_argument('port_id', metavar='PORT',
help=_('port to attach/detach'))
self.add_known_arguments(parser)
return parser
def run(self, parsed_args):
tacker_client = self.get_client()
tacker_client.format = parsed_args.request_format
body = self.args2body(parsed_args)
_id = tackerV10.find_resourceid_by_name_or_id(tacker_client,
self.resource,
parsed_args.id)
self.call_api(tacker_client, _id, body)
class AttachInterface(_XtachInterface):
"""Attach a network interface to a server."""
def call_api(self, tacker_client, device_id, body):
return tacker_client.attach_interface(device_id, body)
class DetachInterface(_XtachInterface):
"""Detach a network interface from a server."""
def call_api(self, tacker_client, device_id, body):
return tacker_client.detach_interface(device_id, body)

View File

@@ -0,0 +1,94 @@
#
# Copyright 2013 Intel
# Copyright 2013 Isaku Yamahata <isaku.yamahata at intel com>
# <isaku.yamahata at gmail com>
# 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: Isaku Yamahata, Intel
from tackerclient.tacker import v1_0 as tackerV10
_DEVICE_TEMPLATE = "device_template"
class ListDeviceTemplate(tackerV10.ListCommand):
"""List device template that belong to a given tenant."""
resource = _DEVICE_TEMPLATE
class ShowDeviceTemplate(tackerV10.ShowCommand):
"""show information of a given DeviceTemplate."""
resource = _DEVICE_TEMPLATE
class CreateDeviceTemplate(tackerV10.CreateCommand):
"""create a DeviceTemplate."""
resource = _DEVICE_TEMPLATE
def add_known_arguments(self, parser):
parser.add_argument(
'--name',
help='Set a name for the devicetemplate')
parser.add_argument(
'--description',
help='Set a description for the devicetemplate')
parser.add_argument(
'--template-service-type',
action='append',
help='Add a servicetype for the devicetemplate')
parser.add_argument(
'--infra-driver',
help='Set a infra driver name for the devicetemplate')
parser.add_argument(
'--mgmt-driver',
help='Set a manegement driver name for the devicetemplate')
parser.add_argument(
'--attribute',
nargs=2,
action='append',
help='Set a servicetypes for the devicetemplate')
def args2body(self, parsed_args):
body = {
self.resource: {
'service_types': [
{'service_type': service_type}
for service_type in parsed_args.template_service_type],
'infra_driver': parsed_args.infra_driver,
'mgmt_driver': parsed_args.mgmt_driver,
}
}
if parsed_args.attribute:
body[self.resource]['attributes'] = dict(parsed_args.attribute)
tackerV10.update_dict(parsed_args, body[self.resource],
['tenant_id', 'name', 'description'])
return body
class UpdateDeviceTemplate(tackerV10.UpdateCommand):
"""Update a given DeviceTemplate."""
resource = _DEVICE_TEMPLATE
allow_names = False
class DeleteDeviceTemplate(tackerV10.DeleteCommand):
"""Delete a given DeviceTemplate."""
resource = _DEVICE_TEMPLATE

View File

@@ -1,5 +1,7 @@
#
# Copyright 2013 Intel Corporation
# Copyright 2013 Intel
# Copyright 2013 Isaku Yamahata <isaku.yamahata at intel com>
# <isaku.yamahata at gmail com>
# All Rights Reserved.
#
#
@@ -14,6 +16,8 @@
# 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: Isaku Yamahata, Intel
from tackerclient.tacker import v1_0 as tackerV10
@@ -22,21 +26,20 @@ _VNF = 'vnf'
class ListVNF(tackerV10.ListCommand):
"""List VNF that belong to a given tenant."""
"""List device that belong to a given tenant."""
resource = _VNF
list_columns = ['id', 'name', 'description', 'mgmt_url', 'status',
'vim_id', 'placement_attr', 'error_reason']
list_columns = ['id', 'name', 'description', 'mgmt_url', 'status']
class ShowVNF(tackerV10.ShowCommand):
"""Show information of a given VNF."""
"""show information of a given VNF."""
resource = _VNF
class CreateVNF(tackerV10.CreateCommand):
"""Create a VNF."""
"""create a VNF."""
resource = _VNF
remove_output_fields = ["attributes"]
@@ -44,71 +47,41 @@ class CreateVNF(tackerV10.CreateCommand):
def add_known_arguments(self, parser):
parser.add_argument(
'--name',
help='Set a name for the VNF')
vnfd_group = parser.add_mutually_exclusive_group(required=True)
vnfd_group.add_argument(
help='Set a name for the vnf')
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument(
'--vnfd-id',
help='VNFD ID to use as template to create VNF')
vnfd_group.add_argument(
group.add_argument(
'--vnfd-name',
help='VNFD Name to use as template to create VNF')
vim_group = parser.add_mutually_exclusive_group()
vim_group.add_argument(
'--vim-id',
help='VIM ID to use to create VNF on the specified VIM')
vim_group.add_argument(
'--vim-name',
help='VIM name to use to create VNF on the specified VIM')
parser.add_argument(
'--vim-region-name',
help='VIM Region to use to create VNF on the specified VIM')
parser.add_argument(
'--config-file',
help='Specify config yaml file')
help='specify config yaml file')
parser.add_argument(
'--config',
help='Specify config yaml data')
parser.add_argument(
'--param-file',
help='Specify parameter yaml file'
)
help='specify config yaml file')
def args2body(self, parsed_args):
args = {'attributes': {}}
body = {self.resource: args}
# config arg passed as data overrides config yaml when both args passed
body = {self.resource: {}}
if parsed_args.config_file:
with open(parsed_args.config_file) as f:
config_yaml = f.read()
args['attributes']['config'] = config_yaml
body[self.resource]['config'] = config_yaml
if parsed_args.config:
parsed_args.config = parsed_args.config.decode('unicode_escape')
args['attributes']['config'] = parsed_args.config
if parsed_args.vim_region_name:
args.setdefault('placement_attr', {})['region_name'] = \
parsed_args.vim_region_name
tacker_client = self.get_client()
tacker_client.format = parsed_args.request_format
if parsed_args.vim_name:
_id = tackerV10.find_resourceid_by_name_or_id(tacker_client,
'vim',
parsed_args.
vim_name)
parsed_args.vim_id = _id
body[self.resource]['config'] = parsed_args.config
if parsed_args.vnfd_name:
_id = tackerV10.find_resourceid_by_name_or_id(tacker_client,
'vnfd',
parsed_args.
vnfd_name)
parsed_args.vnfd_id = _id
if parsed_args.param_file:
with open(parsed_args.param_file) as f:
param_yaml = f.read()
args['attributes']['param_values'] = param_yaml
tacker_client = self.get_client()
tacker_client.format = parsed_args.request_format
_id = tackerV10.find_resourceid_by_name_or_id(
tacker_client, 'vnfd',
parsed_args.vnfd_name)
parsed_args.vnfd_id = _id
tackerV10.update_dict(parsed_args, body[self.resource],
['tenant_id', 'name', 'vnfd_id', 'vim_id'])
['tenant_id', 'name', 'vnfd_id'])
return body
@@ -120,21 +93,19 @@ class UpdateVNF(tackerV10.UpdateCommand):
def add_known_arguments(self, parser):
parser.add_argument(
'--config-file',
help='Specify config yaml file')
help='specify config yaml file')
parser.add_argument(
'--config',
help='Specify config yaml data')
help='specify config yaml file')
def args2body(self, parsed_args):
body = {self.resource: {}}
# config arg passed as data overrides config yaml when both args passed
if parsed_args.config_file:
with open(parsed_args.config_file) as f:
config_yaml = f.read()
body[self.resource]['attributes'] = {'config': config_yaml}
body[self.resource]['config'] = config_yaml
if parsed_args.config:
parsed_args.config = parsed_args.config.decode('unicode_escape')
body[self.resource]['attributes'] = {'config': parsed_args.config}
body[self.resource]['config'] = parsed_args.config
tackerV10.update_dict(parsed_args, body[self.resource], ['tenant_id'])
return body

View File

@@ -1,5 +1,7 @@
#
# Copyright 2013 Intel Corporation
# Copyright 2013 Intel
# Copyright 2013 Isaku Yamahata <isaku.yamahata at intel com>
# <isaku.yamahata at gmail com>
# All Rights Reserved.
#
#
@@ -14,10 +16,9 @@
# 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: Isaku Yamahata, Intel
from oslo_serialization import jsonutils
from tackerclient.i18n import _
from tackerclient.tacker import v1_0 as tackerV10
@@ -32,37 +33,36 @@ class ListVNFD(tackerV10.ListCommand):
class ShowVNFD(tackerV10.ShowCommand):
"""Show information of a given VNFD."""
"""show information of a given VNFD."""
resource = _VNFD
class CreateVNFD(tackerV10.CreateCommand):
"""Create a VNFD."""
"""create a VNFD."""
resource = _VNFD
remove_output_fields = ["attributes"]
def add_known_arguments(self, parser):
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('--vnfd-file', help='Specify VNFD file')
group.add_argument('--vnfd', help='Specify VNFD')
group.add_argument('--vnfd-file', help='specify vnfd file')
group.add_argument('--vnfd', help='specify vnfd')
parser.add_argument(
'--name',
help='Set a name for the VNFD')
help='Set a name for the vnfd')
parser.add_argument(
'--description',
help='Set a description for the VNFD')
help='Set a description for the vnfd')
def args2body(self, parsed_args):
body = {self.resource: {}}
if parsed_args.vnfd_file:
with open(parsed_args.vnfd_file) as f:
vnfd = f.read()
body[self.resource]['attributes'] = {'vnfd': vnfd}
if parsed_args.vnfd:
body[self.resource]['attributes'] = {'vnfd': parsed_args.vnfd}
vnfd = parsed_args.vnfd
body[self.resource]['vnfd'] = vnfd
tackerV10.update_dict(parsed_args, body[self.resource],
['tenant_id', 'name', 'description'])
return body
@@ -71,21 +71,3 @@ class CreateVNFD(tackerV10.CreateCommand):
class DeleteVNFD(tackerV10.DeleteCommand):
"""Delete a given VNFD."""
resource = _VNFD
class ShowTemplateVNFD(tackerV10.ShowCommand):
"""Show template of a given VNFD."""
resource = _VNFD
def run(self, parsed_args):
self.log.debug('run(%s)', parsed_args)
template = None
data = self.get_data(parsed_args)
try:
attributes_index = data[0].index('attributes')
attributes_json = data[1][attributes_index]
template = jsonutils.loads(attributes_json).get('vnfd', None)
except (IndexError, TypeError, ValueError) as e:
self.log.debug('Data handling error: %s', str(e))
print(template or _('Unable to display VNFD template!'))

View File

@@ -1,52 +0,0 @@
# Copyright 2016 OpenStack Foundation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from mock import sentinel
import testtools
from tackerclient.common import exceptions
from tackerclient.tacker.v1_0.nfvo import vim_utils
class CLITestAuthNoAuth(testtools.TestCase):
def test_args2body_vim(self):
config_param = {'project_id': sentinel.prj_id1,
'username': sentinel.usrname1,
'password': sentinel.password1,
'project_domain_name': sentinel.prj_domain_name1,
'user_domain_name': sentinel.user_domain.name, }
vim = {}
auth_cred = config_param.copy()
auth_cred.pop('project_id')
auth_cred.pop('project_domain_name')
auth_cred.update({'user_id': ''})
expected_vim = {'auth_cred': auth_cred,
'vim_project':
{'id': sentinel.prj_id1,
'name': '',
'project_domain_name': sentinel.prj_domain_name1}}
vim_utils.args2body_vim(config_param.copy(), vim)
self.assertEqual(expected_vim, vim)
def test_args2body_vim_no_project(self):
config_param = {'username': sentinel.usrname1,
'password': sentinel.password1,
'user_domain_name': sentinel.user_domain.name, }
vim = {}
self.assertRaises(exceptions.TackerClientException,
vim_utils.args2body_vim,
config_param, vim)

View File

@@ -52,7 +52,7 @@ KS_TOKEN_RESULT = {
'internalURL': ENDPOINT_URL,
'publicURL': ENDPOINT_URL,
'region': REGION}],
'type': 'nfv-orchestration',
'type': 'servicevm',
'name': 'Tacker Service'}
]
}
@@ -60,7 +60,7 @@ KS_TOKEN_RESULT = {
ENDPOINTS_RESULT = {
'endpoints': [{
'type': 'nfv-orchestration',
'type': 'servicevm',
'name': 'Tacker Service',
'region': REGION,
'adminURL': ENDPOINT_URL,
@@ -127,10 +127,8 @@ class CLITestAuthKeystone(testtools.TestCase):
self.addCleanup(self.mox.UnsetStubs)
def test_reused_token_get_auth_info(self):
"""Test Client.get_auth_info().
Test that Client.get_auth_info() works even if client was
instantiated with predefined token.
"""Test that Client.get_auth_info() works even if client was
instantiated with predefined token.
"""
client_ = client.HTTPClient(username=USERNAME,
tenant_name=TENANT_NAME,

View File

@@ -20,7 +20,6 @@ import contextlib
import cStringIO
import fixtures
import mox
import six
import sys
import testtools
@@ -47,7 +46,7 @@ def capture_std_streams():
sys.stdout, sys.stderr = stdout, stderr
class FakeStdout(object):
class FakeStdout:
def __init__(self):
self.content = []
@@ -112,7 +111,7 @@ class MyComparator(mox.Comparator):
def _com_dict(self, lhs, rhs):
if len(lhs) != len(rhs):
return False
for key, value in lhs.items():
for key, value in lhs.iteritems():
if key not in rhs:
return False
rhs_value = rhs[key]
@@ -205,7 +204,15 @@ class CLITestV10Base(testtools.TestCase):
self.mox.StubOutWithMock(cmd, "get_client")
self.mox.StubOutWithMock(self.client.httpclient, "request")
cmd.get_client().MultipleTimes().AndReturn(self.client)
non_admin_status_resources = ['vnfd', 'vnf', 'vim']
non_admin_status_resources = ['subnet', 'floatingip', 'security_group',
'security_group_rule', 'qos_queue',
'network_gateway', 'gateway_device',
'credential', 'network_profile',
'policy_profile', 'ikepolicy',
'ipsecpolicy', 'metering_label',
'metering_label_rule', 'net_partition',
'device_template', 'device',
'service_instance']
if (resource in non_admin_status_resources):
body = {resource: {}, }
else:
@@ -304,7 +311,7 @@ class CLITestV10Base(testtools.TestCase):
args.append("--tag")
for tag in tags:
args.append(tag)
if isinstance(tag, six.string_types):
if isinstance(tag, unicode):
tag = urllib.quote(tag.encode('utf-8'))
if query:
query += "&tag=" + tag

View File

@@ -1,4 +1,6 @@
# Copyright 2013 Intel Corporation
# Copyright 2013 Intel
# Copyright 2013 Isaku Yamahata <isaku.yamahata at intel com>
# <isaku.yamahata at gmail com>
# All Rights Reserved.
#
#
@@ -13,6 +15,8 @@
# 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: Isaku Yamahata, Intel
import logging

View File

@@ -35,7 +35,7 @@ DEFAULT_TENANT_ID = 'tenant_id'
DEFAULT_TENANT_NAME = 'tenant_name'
DEFAULT_AUTH_URL = 'http://127.0.0.1:5000/v1.0/'
DEFAULT_TOKEN = '3bcc3d3a03f44e3d8377f9247b0ad155'
DEFAULT_URL = 'http://tacker.example.org:9890/'
DEFAULT_URL = 'http://tacker.example.org:8888/'
class ShellTest(testtools.TestCase):
@@ -93,8 +93,8 @@ class ShellTest(testtools.TestCase):
def test_help_on_subcommand(self):
required = [
'.*?^usage: .* vnfd-list']
stdout, stderr = self.shell('help vnfd-list')
'.*?^usage: .* device-template-list']
stdout, stderr = self.shell('help device-template-list')
for r in required:
self.assertThat(
stdout,
@@ -112,13 +112,13 @@ class ShellTest(testtools.TestCase):
def test_unknown_auth_strategy(self):
self.useFixture(fixtures.FakeLogger(level=logging.DEBUG))
stdout, stderr = self.shell('--os-auth-strategy fake '
'vnfd-list')
'device-template-list')
self.assertFalse(stdout)
self.assertEqual('You must provide a service URL via '
'either --os-url or env[OS_URL]', stderr.strip())
def test_auth(self):
# import pdb; pdb.set_trace()
#import pdb; pdb.set_trace()
tacker_shell = openstack_shell.TackerShell('1.0')
self.addCleanup(self.mox.UnsetStubs)
self.mox.StubOutWithMock(clientmanager.ClientManager, '__init__')
@@ -127,19 +127,17 @@ class ShellTest(testtools.TestCase):
token='', url='', auth_url='http://127.0.0.1:5000/',
tenant_name='test', tenant_id='tenant_id',
username='test', user_id='',
password='test', region_name='',
api_version={'nfv-orchestration': '1.0'},
auth_strategy='keystone',
service_type='nfv-orchestration',
password='test', region_name='', api_version={'servicevm': '1.0'},
auth_strategy='keystone', service_type='servicevm',
endpoint_type='publicURL', insecure=False, ca_cert=None,
log_credentials=True)
tacker_shell.run_subcommand(['vnfd-list'])
tacker_shell.run_subcommand(['device-template-list'])
self.mox.ReplayAll()
cmdline = ('--os-username test '
'--os-password test '
'--os-tenant-name test '
'--os-auth-url http://127.0.0.1:5000/ '
'--os-auth-strategy keystone vnfd-list')
'--os-auth-strategy keystone device-template-list')
tacker_shell.run(cmdline.split())
self.mox.VerifyAll()

View File

@@ -118,7 +118,7 @@ class TestSSL(testtools.TestCase):
)
self.mox.ReplayAll()
version = {'nfv-orchestration': '1.0'}
version = {'servicevm': '1.0'}
ClientManager(ca_cert=CA_CERT,
api_version=version,
url=END_URL,

View File

@@ -19,7 +19,7 @@ from tackerclient.common import exceptions
from tackerclient.common import validators
class FakeParsedArgs(object):
class FakeParsedArgs():
pass

View File

@@ -0,0 +1,126 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2014 Intel
# Copyright 2014 Isaku Yamahata <isaku.yamahata at intel com>
# <isaku.yamahata at gmail com>
# 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: Isaku Yamahata, Intel
import sys
from tackerclient.tacker.v1_0.vm import device
from tackerclient.tests.unit import test_cli10
class CLITestV10VmDeviceJSON(test_cli10.CLITestV10Base):
_RESOURCE = 'device'
_RESOURCES = 'devices'
def setUp(self):
plurals = {'devices': 'device'}
super(CLITestV10VmDeviceJSON, self).setUp(plurals=plurals)
def test_create_device_all_params(self):
cmd = device.CreateDevice(test_cli10.MyApp(sys.stdout), None)
my_id = 'my-id'
template_id = 'template_id'
key = 'key'
value = 'value'
network_id = 'network_id'
subnet_id = 'subnet_id'
port_id = 'port_id'
router_id = 'router_id'
role = 'role'
index = 1
args = [
'--device-template-id', template_id,
'--kwargs', '%s=%s' % (key, value),
'--service-context',
('network-id=%s,subnet-id=%s,port-id=%s,router-id=%s,'
'role=%s,index=%s' % (network_id, subnet_id, port_id, router_id,
role, index))
]
position_names = ['template_id']
position_values = [template_id]
extra_body = {
'kwargs': {
key: value
},
'service_context': [{
'network_id': network_id,
'subnet_id': subnet_id,
'port_id': port_id,
'router_id': router_id,
'role': role,
'index': str(index),
}],
}
self._test_create_resource(self._RESOURCE, cmd, None, my_id,
args, position_names, position_values,
extra_body=extra_body)
def test_create_device_with_mandatory_params(self):
cmd = device.CreateDevice(test_cli10.MyApp(sys.stdout), None)
my_id = 'my-id'
template_id = 'template_id'
args = [
'--device-template-id', template_id,
]
position_names = ['template_id']
position_values = [template_id]
self._test_create_resource(self._RESOURCE, cmd, None, my_id,
args, position_names, position_values)
def test_list_devices(self):
cmd = device.ListDevice(test_cli10.MyApp(sys.stdout), None)
self._test_list_resources(self._RESOURCES, cmd, True)
def test_list_devices_pagenation(self):
cmd = device.ListDevice(test_cli10.MyApp(sys.stdout), None)
self._test_list_resources(self._RESOURCES, cmd, True)
def test_show_device_id(self):
cmd = device.ShowDevice(test_cli10.MyApp(sys.stdout), None)
args = ['--fields', 'id', self.test_id]
self._test_show_resource(self._RESOURCE, cmd, self.test_id, args,
['id'])
def test_show_device_id_name(self):
cmd = device.ShowDevice(test_cli10.MyApp(sys.stdout), None)
args = ['--fields', 'id', '--fields', 'name', self.test_id]
self._test_show_resource(self._RESOURCE, cmd, self.test_id,
args, ['id', 'name'])
def test_update_device(self):
cmd = device.UpdateDevice(test_cli10.MyApp(sys.stdout), None)
my_id = 'my-id'
key = 'new-key'
value = 'new-value'
self._test_update_resource(self._RESOURCE, cmd, my_id,
[my_id, '--kwargs', '%s=%s' % (key, value)],
{'kwargs': {key: value}})
def test_delete_device(self):
cmd = device.DeleteDevice(test_cli10.MyApp(sys.stdout), None)
my_id = 'my-id'
args = [my_id]
self._test_delete_resource(self._RESOURCE, cmd, my_id, args)
class CLITestV10VmDeviceXML(CLITestV10VmDeviceJSON):
format = 'xml'

View File

@@ -0,0 +1,132 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2014 Intel
# Copyright 2014 Isaku Yamahata <isaku.yamahata at intel com>
# <isaku.yamahata at gmail com>
# 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: Isaku Yamahata, Intel
import sys
from tackerclient.tacker.v1_0.vm import device_template
from tackerclient.tests.unit import test_cli10
class CLITestV10VmDeviceTemplateJSON(test_cli10.CLITestV10Base):
_RESOURCE = 'device_template'
_RESOURCES = 'device_templates'
def setUp(self):
plurals = {'device_templates': 'device_template'}
super(CLITestV10VmDeviceTemplateJSON, self).setUp(plurals=plurals)
def test_create_device_template_all_params(self):
cmd = device_template.CreateDeviceTemplate(
test_cli10.MyApp(sys.stdout), None)
my_id = 'my-id'
name = 'my-name'
description = 'my-description'
service_type = 'MY-SERVICE'
device_driver = 'device-driver'
mgmt_driver = 'mgmt-driver'
attr_key = 'attr-key'
attr_val = 'attr-val'
args = [
'--name', name,
'--description', description,
'--template-service-type', service_type,
'--device-driver', device_driver,
'--mgmt-driver', mgmt_driver,
'--attribute', attr_key, attr_val,
]
position_names = ['name', 'description',
'device_driver', 'mgmt_driver']
position_values = [name, description, device_driver, mgmt_driver]
extra_body = {
'service_types': [{'service_type': service_type}],
'attributes': {attr_key: attr_val},
}
self._test_create_resource(self._RESOURCE, cmd, None, my_id,
args, position_names, position_values,
extra_body=extra_body)
def test_create_device_template_with_mandatory_params(self):
cmd = device_template.CreateDeviceTemplate(
test_cli10.MyApp(sys.stdout), None)
my_id = 'my-id'
service_type = 'MY-SERVICE'
device_driver = 'device-driver'
mgmt_driver = 'mgmt-driver'
args = [
'--template-service-type', service_type,
'--device-driver', device_driver,
'--mgmt-driver', mgmt_driver,
]
position_names = ['device_driver', 'mgmt_driver']
position_values = [device_driver, mgmt_driver]
extra_body = {
'service_types': [{'service_type': service_type}],
}
self._test_create_resource(self._RESOURCE, cmd, None, my_id,
args, position_names, position_values,
extra_body=extra_body)
def test_list_device_templates(self):
cmd = device_template.ListDeviceTemplate(test_cli10.MyApp(sys.stdout),
None)
self._test_list_resources(self._RESOURCES, cmd, True)
def test_list_device_templates_pagenation(self):
cmd = device_template.ListDeviceTemplate(test_cli10.MyApp(sys.stdout),
None)
self._test_list_resources(self._RESOURCES, cmd, True)
def test_show_device_template_id(self):
cmd = device_template.ShowDeviceTemplate(test_cli10.MyApp(sys.stdout),
None)
args = ['--fields', 'id', self.test_id]
self._test_show_resource(self._RESOURCE, cmd, self.test_id, args,
['id'])
def test_show_device_template_id_name(self):
cmd = device_template.ShowDeviceTemplate(test_cli10.MyApp(sys.stdout),
None)
args = ['--fields', 'id', '--fields', 'name', self.test_id]
self._test_show_resource(self._RESOURCE, cmd, self.test_id,
args, ['id', 'name'])
def test_update_device_template(self):
cmd = device_template.UpdateDeviceTemplate(
test_cli10.MyApp(sys.stdout), None)
my_id = 'my-id'
name = 'new-name'
description = 'new-description'
self._test_update_resource(self._RESOURCE, cmd, my_id,
[my_id, '--name', name,
'--description', description],
{'name': name, 'description': description})
def test_delete_device_tempalte(self):
cmd = device_template.DeleteDeviceTemplate(
test_cli10.MyApp(sys.stdout), None)
my_id = 'my-id'
args = [my_id]
self._test_delete_resource(self._RESOURCE, cmd, my_id, args)
class CLITestV10VmDeviceTemplateXML(CLITestV10VmDeviceTemplateJSON):
format = 'xml'

View File

@@ -0,0 +1,155 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2014 Intel
# Copyright 2014 Isaku Yamahata <isaku.yamahata at intel com>
# <isaku.yamahata at gmail com>
# 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: Isaku Yamahata, Intel
import sys
from tackerclient.tacker.v1_0.vm import service_instance
from tackerclient.tests.unit import test_cli10
class CLITestV10VmServiceInstanceJSON(test_cli10.CLITestV10Base):
_RESOURCE = 'service_instance'
_RESOURCES = 'service_instances'
def setUp(self):
plurals = {'service_instances': 'service_instance'}
super(CLITestV10VmServiceInstanceJSON, self).setUp(plurals=plurals)
def test_create_service_instance_all_params(self):
cmd = service_instance.CreateServiceInstance(
test_cli10.MyApp(sys.stdout), None)
my_id = 'my-id'
name = 'my-name'
service_type_id = 'service-type-id'
service_table_id = 'service-table-id'
mgmt_driver = 'mgmt-driver'
network_id = 'network_id'
subnet_id = 'subnet_id'
port_id = 'port_id'
router_id = 'router_id'
role = 'role'
index = 1
device = 'my-device'
key = 'key'
value = 'value'
args = [
'--name', name,
'--service-type-id', service_type_id,
'--service-table-id', service_table_id,
'--mgmt-driver', mgmt_driver,
'--service-context',
('network-id=%s,subnet-id=%s,port-id=%s,router-id=%s,'
'role=%s,index=%s' % (network_id, subnet_id, port_id, router_id,
role, index)),
'--device', device,
'--kwargs', '%s=%s' % (key, value),
]
position_names = ['name', 'service_type_id', 'service_table_id',
'mgmt_driver']
position_values = [name, service_type_id, service_table_id,
mgmt_driver]
extra_body = {
'devices': [device],
'service_context': [{
'network_id': network_id,
'subnet_id': subnet_id,
'port_id': port_id,
'router_id': router_id,
'role': role,
'index': str(index),
}],
'kwargs': {
key: value
},
}
self._test_create_resource(self._RESOURCE, cmd, None, my_id,
args, position_names, position_values,
extra_body=extra_body)
def test_create_service_instance_with_mandatory_params(self):
cmd = service_instance.CreateServiceInstance(
test_cli10.MyApp(sys.stdout), None)
my_id = 'my-id'
service_type_id = 'service-type-id'
service_table_id = 'service-table-id'
device = 'my-device'
args = [
'--service-type-id', service_type_id,
'--service-table-id', service_table_id,
'--device', device,
]
position_names = ['service_type_id', 'service_table_id']
position_values = [service_type_id, service_table_id]
extra_body = {
'devices': [device],
}
self._test_create_resource(self._RESOURCE, cmd, None, my_id,
args, position_names, position_values,
extra_body=extra_body)
def test_list_service_instances(self):
cmd = service_instance.ListServiceInstance(
test_cli10.MyApp(sys.stdout), None)
self._test_list_resources(self._RESOURCES, cmd, True)
def test_list_service_instances_pagenation(self):
cmd = service_instance.ListServiceInstance(
test_cli10.MyApp(sys.stdout), None)
self._test_list_resources(self._RESOURCES, cmd, True)
def test_show_service_instance_id(self):
cmd = service_instance.ShowServiceInstance(
test_cli10.MyApp(sys.stdout), None)
args = ['--fields', 'id', self.test_id]
self._test_show_resource(self._RESOURCE, cmd, self.test_id, args,
['id'])
def test_show_service_instance_id_name(self):
cmd = service_instance.ShowServiceInstance(
test_cli10.MyApp(sys.stdout), None)
args = ['--fields', 'id', '--fields', 'name', self.test_id]
self._test_show_resource(self._RESOURCE, cmd, self.test_id,
args, ['id', 'name'])
def test_update_service_instance(self):
cmd = service_instance.UpdateServiceInstance(
test_cli10.MyApp(sys.stdout), None)
my_id = 'my-id'
key = 'new-key'
value = 'new-value'
self._test_update_resource(self._RESOURCE, cmd, my_id,
[my_id, '--kwargs', '%s=%s' % (key, value)],
{'kwargs': {key: value}})
def test_delete_service_instance(self):
cmd = service_instance.DeleteServiceInstance(
test_cli10.MyApp(sys.stdout), None)
my_id = 'my-id'
args = [my_id]
self._test_delete_resource(self._RESOURCE, cmd, my_id, args)
class CLITestV10VmServiceInstanceXML(CLITestV10VmServiceInstanceJSON):
format = 'xml'

View File

@@ -1,117 +0,0 @@
# Copyright 2015-2016 Brocade Communications 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.
import sys
from tackerclient.tacker.v1_0.nfvo import vim
from tackerclient.tests.unit import test_cli10
API_VERSION = "1.0"
FORMAT = 'json'
TOKEN = 'testtoken'
ENDURL = 'localurl'
class CLITestV10VIMJSON(test_cli10.CLITestV10Base):
_RESOURCE = 'vim'
_RESOURCES = 'vims'
def setUp(self):
plurals = {'vims': 'vim'}
super(CLITestV10VIMJSON, self).setUp(plurals=plurals)
self.vim_project = {
'name': 'abc', 'id': '',
'project_domain_name': 'prj_domain_name'}
self.auth_cred = {'username': 'xyz', 'password': '12345', 'user_id':
'', 'user_domain_name': 'user_domain_name'}
self.auth_url = 'http://1.2.3.4:5000'
def test_register_vim_all_params(self):
cmd = vim.CreateVIM(test_cli10.MyApp(sys.stdout), None)
my_id = 'my-id'
name = 'test_vim'
description = 'Vim Description'
vim_config = {'auth_url': 'http://1.2.3.4:5000', 'username': 'xyz',
'password': '12345', 'project_name': 'abc',
'project_domain_name': 'prj_domain_name',
'user_domain_name': 'user_domain_name'}
args = [
'--config', str(vim_config),
'--name', name,
'--description', description]
position_names = ['auth_cred', 'vim_project', 'auth_url']
position_values = [self.auth_cred, self.vim_project, self.auth_url]
extra_body = {'type': 'openstack', 'name': name, 'description':
description, 'is_default': False}
self._test_create_resource(self._RESOURCE, cmd, None, my_id,
args, position_names, position_values,
extra_body=extra_body)
def test_register_vim_with_mandatory_params(self):
cmd = vim.CreateVIM(test_cli10.MyApp(sys.stdout), None)
my_id = 'my-id'
vim_config = {'auth_url': 'http://1.2.3.4:5000', 'username': 'xyz',
'password': '12345', 'project_name': 'abc',
'project_domain_name': 'prj_domain_name',
'user_domain_name': 'user_domain_name'}
args = [
'--config', str(vim_config),
]
position_names = ['auth_cred', 'vim_project', 'auth_url']
position_values = [self.auth_cred, self.vim_project, self.auth_url]
extra_body = {'type': 'openstack', 'is_default': False}
self._test_create_resource(self._RESOURCE, cmd, None, my_id, args,
position_names, position_values,
extra_body=extra_body)
def test_list_vims(self):
cmd = vim.ListVIM(test_cli10.MyApp(sys.stdout), None)
self._test_list_resources(self._RESOURCES, cmd, True)
def test_show_vim_id(self):
cmd = vim.ShowVIM(test_cli10.MyApp(sys.stdout), None)
args = ['--fields', 'id', self.test_id]
self._test_show_resource(self._RESOURCE, cmd, self.test_id, args,
['id'])
def test_show_vim_id_name(self):
cmd = vim.ShowVIM(test_cli10.MyApp(sys.stdout), None)
args = ['--fields', 'id', '--fields', 'name', self.test_id]
self._test_show_resource(self._RESOURCE, cmd, self.test_id,
args, ['id', 'name'])
def test_update_vim(self):
cmd = vim.UpdateVIM(test_cli10.MyApp(sys.stdout), None)
update_config = {'username': 'xyz', 'password': '12345',
'project_name': 'abc',
'project_domain_name': 'prj_domain_name',
'user_domain_name': 'user_domain_name'}
my_id = 'my-id'
key = 'config'
value = str(update_config)
extra_fields = {'vim_project': self.vim_project, 'auth_cred':
self.auth_cred, 'is_default': False}
self._test_update_resource(self._RESOURCE, cmd, my_id, [my_id,
'--%s' %
key, value],
extra_fields)
def test_delete_vim(self):
cmd = vim.DeleteVIM(test_cli10.MyApp(sys.stdout), None)
my_id = 'my-id'
args = [my_id]
self._test_delete_resource(self._RESOURCE, cmd, my_id, args)

View File

@@ -1,159 +0,0 @@
# Copyright 2014 Intel Corporation
# 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 mox
import sys
from tackerclient import shell
from tackerclient.tacker import v1_0 as tackerV1_0
from tackerclient.tacker.v1_0.vm import vnf
from tackerclient.tests.unit import test_cli10
API_VERSION = "1.0"
FORMAT = 'json'
TOKEN = 'testtoken'
ENDURL = 'localurl'
class CLITestV10VmVNFJSON(test_cli10.CLITestV10Base):
_RESOURCE = 'vnf'
_RESOURCES = 'vnfs'
def setUp(self):
plurals = {'vnfs': 'vnf'}
super(CLITestV10VmVNFJSON, self).setUp(plurals=plurals)
def _test_create_resource(self, resource, cmd,
name, myid, args,
position_names, position_values, tenant_id=None,
tags=None, admin_state_up=True, extra_body=None,
**kwargs):
self.mox.StubOutWithMock(cmd, "get_client")
self.mox.StubOutWithMock(self.client.httpclient, "request")
cmd.get_client().MultipleTimes().AndReturn(self.client)
non_admin_status_resources = ['vnfd', 'vnf']
if (resource in non_admin_status_resources):
body = {resource: {}, }
else:
body = {resource: {'admin_state_up': admin_state_up, }, }
if tenant_id:
body[resource].update({'tenant_id': tenant_id})
if tags:
body[resource].update({'tags': tags})
if extra_body:
body[resource].update(extra_body)
body[resource].update(kwargs)
for i in range(len(position_names)):
body[resource].update({position_names[i]: position_values[i]})
ress = {resource:
{self.id_field: myid}, }
if name:
ress[resource].update({'name': name})
self.client.format = self.format
resstr = self.client.serialize(ress)
# url method body
resource_plural = tackerV1_0._get_resource_plural(resource,
self.client)
path = getattr(self.client, resource_plural + "_path")
# Work around for LP #1217791. XML deserializer called from
# MyComparator does not decodes XML string correctly.
if self.format == 'json':
mox_body = test_cli10.MyComparator(body, self.client)
else:
mox_body = self.client.serialize(body)
self.client.httpclient.request(
test_cli10.end_url(path, format=self.format), 'POST',
body=mox_body,
headers=mox.ContainsKeyValue(
'X-Auth-Token', TOKEN)).AndReturn((
test_cli10.MyResp(200), resstr))
args.extend(['--request-format', self.format])
args.extend(['--vnfd-id', 'vnfd'])
self.mox.ReplayAll()
cmd_parser = cmd.get_parser('create_' + resource)
shell.run_command(cmd, cmd_parser, args)
self.mox.VerifyAll()
def test_create_vnf_all_params(self):
cmd = vnf.CreateVNF(test_cli10.MyApp(sys.stdout), None)
my_id = 'my-id'
vnfd_id = 'vnfd'
vim_id = 'vim_id'
region_name = 'region'
key = 'key'
value = 'value'
args = [
'--vnfd-id', vnfd_id,
'--vim-id', vim_id,
'--vim-region-name', region_name,
'--%s' % key, value]
position_names = ['vnfd_id', 'vim_id', 'attributes']
position_values = [vnfd_id, vim_id, {}]
extra_body = {key: value, 'placement_attr': {'region_name':
region_name}}
self._test_create_resource(self._RESOURCE, cmd, None, my_id,
args, position_names, position_values,
extra_body=extra_body)
def test_create_vnf_with_mandatory_params(self):
cmd = vnf.CreateVNF(test_cli10.MyApp(sys.stdout), None)
my_id = 'my-id'
vnfd_id = 'vnfd'
args = [
'--vnfd-id', vnfd_id,
]
position_names = ['vnfd_id', 'attributes']
position_values = [vnfd_id, {}]
self._test_create_resource(self._RESOURCE, cmd, None, my_id,
args, position_names, position_values)
def test_list_vnfs(self):
cmd = vnf.ListVNF(test_cli10.MyApp(sys.stdout), None)
self._test_list_resources(self._RESOURCES, cmd, True)
def test_list_vnfs_pagenation(self):
cmd = vnf.ListVNF(test_cli10.MyApp(sys.stdout), None)
self._test_list_resources(self._RESOURCES, cmd, True)
def test_show_vnf_id(self):
cmd = vnf.ShowVNF(test_cli10.MyApp(sys.stdout), None)
args = ['--fields', 'id', self.test_id]
self._test_show_resource(self._RESOURCE, cmd, self.test_id, args,
['id'])
def test_show_vnf_id_name(self):
cmd = vnf.ShowVNF(test_cli10.MyApp(sys.stdout), None)
args = ['--fields', 'id', '--fields', 'name', self.test_id]
self._test_show_resource(self._RESOURCE, cmd, self.test_id,
args, ['id', 'name'])
def test_update_vnf(self):
cmd = vnf.UpdateVNF(test_cli10.MyApp(sys.stdout), None)
my_id = 'my-id'
key = 'new_key'
value = 'new-value'
self._test_update_resource(self._RESOURCE, cmd, my_id,
[my_id, '--%s' % key, value],
{key: value})
def test_delete_vnf(self):
cmd = vnf.DeleteVNF(test_cli10.MyApp(sys.stdout), None)
my_id = 'my-id'
args = [my_id]
self._test_delete_resource(self._RESOURCE, cmd, my_id, args)

View File

@@ -1,96 +0,0 @@
# Copyright 2014 Intel Corporation
# 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 sys
from tackerclient.tacker.v1_0.vm import vnfd
from tackerclient.tests.unit import test_cli10
class CLITestV10VmVNFDJSON(test_cli10.CLITestV10Base):
_RESOURCE = 'vnfd'
_RESOURCES = 'vnfds'
def setUp(self):
plurals = {'vnfds': 'vnfd'}
super(CLITestV10VmVNFDJSON, self).setUp(plurals=plurals)
def test_create_vnfd_all_params(self):
cmd = vnfd.CreateVNFD(
test_cli10.MyApp(sys.stdout), None)
my_id = 'my-id'
name = 'my-name'
mgmt_driver = 'noop'
infra_driver = 'heat'
attr_key = 'vnfd'
attr_val = 'vnfd'
args = [
'--name', name,
'--vnfd', 'vnfd'
]
position_names = ['name', 'mgmt_driver', 'infra_driver']
position_values = [name, mgmt_driver, infra_driver]
extra_body = {
'service_types': [{'service_type': 'vnfd'}],
'attributes': {attr_key: attr_val},
}
self._test_create_resource(self._RESOURCE, cmd, None, my_id,
args, position_names, position_values,
extra_body=extra_body)
def test_create_vnfd_with_mandatory_params(self):
cmd = vnfd.CreateVNFD(
test_cli10.MyApp(sys.stdout), None)
my_id = 'my-id'
mgmt_driver = 'noop'
infra_driver = 'heat'
args = ['--vnfd', 'vnfd', ]
position_names = ['mgmt_driver', 'infra_driver']
position_values = [mgmt_driver, infra_driver]
extra_body = {
'service_types': [{'service_type': 'vnfd'}],
'attributes': {'vnfd': 'vnfd'}
}
self._test_create_resource(self._RESOURCE, cmd, None, my_id,
args, position_names, position_values,
extra_body=extra_body)
def test_list_vnfds(self):
cmd = vnfd.ListVNFD(test_cli10.MyApp(sys.stdout), None)
self._test_list_resources(self._RESOURCES, cmd, True)
def test_list_vnfds_pagenation(self):
cmd = vnfd.ListVNFD(test_cli10.MyApp(sys.stdout), None)
self._test_list_resources(self._RESOURCES, cmd, True)
def test_show_vnfd_id(self):
cmd = vnfd.ShowVNFD(test_cli10.MyApp(sys.stdout), None)
args = ['--fields', 'id', self.test_id]
self._test_show_resource(self._RESOURCE, cmd, self.test_id, args,
['id'])
def test_show_vnfd_id_name(self):
cmd = vnfd.ShowVNFD(test_cli10.MyApp(sys.stdout), None)
args = ['--fields', 'id', '--fields', 'name', self.test_id]
self._test_show_resource(self._RESOURCE, cmd, self.test_id,
args, ['id', 'name'])
def test_delete_vnfd(self):
cmd = vnfd.DeleteVNFD(
test_cli10.MyApp(sys.stdout), None)
my_id = 'my-id'
args = [my_id]
self._test_delete_resource(self._RESOURCE, cmd, my_id, args)

View File

@@ -28,9 +28,8 @@ from tackerclient.common import serializer
from tackerclient.common import utils
from tackerclient.i18n import _
_logger = logging.getLogger(__name__)
DEFAULT_DESC_LENGTH = 25
DEFAULT_ERROR_REASON_LENGTH = 100
def exception_handler_v10(status_code, error_content):
@@ -87,8 +86,8 @@ def exception_handler_v10(status_code, error_content):
class APIParamsCall(object):
"""A Decorator to support formating and tenant overriding and filters."""
"""A Decorator to add support for format and tenant overriding and filters.
"""
def __init__(self, function):
self.function = function
@@ -334,13 +333,12 @@ class Client(ClientBase):
extensions_path = "/extensions"
extension_path = "/extensions/%s"
vnfds_path = '/vnfds'
vnfd_path = '/vnfds/%s'
vnfs_path = '/vnfs'
vnf_path = '/vnfs/%s'
vims_path = '/vims'
vim_path = '/vims/%s'
device_templates_path = '/device-templates'
device_template_path = '/device-templates/%s'
devices_path = '/devices'
device_path = '/devices/%s'
interface_attach_path = '/devices/%s/attach_interface'
interface_detach_path = '/devices/%s/detach_interface'
# API has no way to report plurals, so we have to hard code them
# EXTED_PLURALS = {}
@@ -355,86 +353,136 @@ class Client(ClientBase):
"""Fetch a list of all exts on server side."""
return self.get(self.extension_path % ext_alias, params=_params)
def list_device_templates(self, retrieve_all=True, **_params):
return self.list('device_templates', self.device_templates_path,
retrieve_all, **_params)
@APIParamsCall
def show_device_template(self, device_template, **_params):
return self.get(self.device_template_path % device_template,
params=_params)
@APIParamsCall
def update_device_template(self, device_template, body=None):
return self.put(self.device_template_path % device_template, body=body)
@APIParamsCall
def create_device_template(self, body=None):
return self.post(self.device_templates_path, body=body)
@APIParamsCall
def delete_device_template(self, device_template):
return self.delete(self.device_template_path % device_template)
@APIParamsCall
def list_devices(self, retrieve_all=True, **_params):
return self.list('devices', self.devices_path, retrieve_all, **_params)
@APIParamsCall
def show_device(self, device, **_params):
return self.get(self.device_path % device, params=_params)
@APIParamsCall
def update_device(self, device, body=None):
return self.put(self.device_path % device, body=body)
@APIParamsCall
def create_device(self, body=None):
return self.post(self.devices_path, body=body)
@APIParamsCall
def delete_device(self, device):
return self.delete(self.device_path % device)
@APIParamsCall
def attach_interface(self, device, body=None):
return self.put(self.attach_interface_path % device, body)
@APIParamsCall
def detach_interface(self, device, body=None):
return self.put(self.detach_interface_path % device, body)
# VNFD
_DEVICE_TEMPLATE = "device_template"
_VNFD = "vnfd"
@APIParamsCall
def list_vnfds(self, retrieve_all=True, **_params):
vnfds_dict = self.list(self._VNFD + 's',
self.vnfds_path,
retrieve_all,
**_params)
for vnfd in vnfds_dict['vnfds']:
if 'description' in vnfd.keys() and \
len(vnfd['description']) > DEFAULT_DESC_LENGTH:
vnfd['description'] = vnfd['description'][:DEFAULT_DESC_LENGTH]
vnfd['description'] += '...'
return vnfds_dict
ret = self.list_device_templates(retrieve_all, **_params)
return {self._VNFD + 's': ret[self._DEVICE_TEMPLATE + 's']}
@APIParamsCall
def show_vnfd(self, vnfd, **_params):
return self.get(self.vnfd_path % vnfd,
params=_params)
ret = self.show_device_template(vnfd, **_params)
return {self._VNFD: ret[self._DEVICE_TEMPLATE]}
@APIParamsCall
def create_vnfd(self, body=None):
# e.g.
# body = {'vnfd': {'vnfd': 'yaml vnfd definition strings...'}}
if body is not None:
body[self._VNFD]['service_types'] = [{'service_type': 'vnfd'}]
body[self._VNFD]['infra_driver'] = 'heat'
body[self._VNFD]['mgmt_driver'] = 'noop'
args = body[self._VNFD]
args_ = {
'service_types': [{'service_type': 'vnfd'}],
'infra_driver': 'heat',
'mgmt_driver': 'noop',
}
KEY_LIST = ('name', 'description')
args_.update(dict((key, args[key])
for key in KEY_LIST if key in args))
body_ = {self._DEVICE_TEMPLATE: args_}
if 'vnfd' in args:
args_['attributes'] = {'vnfd': args['vnfd']}
else:
body = None
return self.post(self.vnfds_path, body)
body_ = None
ret = self.create_device_template(body_)
return {self._VNFD: ret[self._DEVICE_TEMPLATE]}
@APIParamsCall
def delete_vnfd(self, vnfd):
return self.delete(self.vnfd_path % vnfd)
return self.delete_device_template(vnfd)
# vnf
_DEVICE = "device"
_VNF = "vnf"
@APIParamsCall
def list_vnfs(self, retrieve_all=True, **_params):
vnfs = self.list('vnfs', self.vnfs_path, retrieve_all, **_params)
for vnf in vnfs['vnfs']:
error_reason = vnf.get('error_reason', None)
if error_reason and \
len(error_reason) > DEFAULT_ERROR_REASON_LENGTH:
vnf['error_reason'] = error_reason[
:DEFAULT_ERROR_REASON_LENGTH]
vnf['error_reason'] += '...'
return vnfs
ret = self.list_devices(retrieve_all, **_params)
return {self._VNF + 's': ret[self._DEVICE + 's']}
@APIParamsCall
def show_vnf(self, vnf, **_params):
return self.get(self.vnf_path % vnf, params=_params)
ret = self.show_device(vnf, **_params)
return {self._VNF: ret[self._DEVICE]}
@APIParamsCall
def create_vnf(self, body=None):
return self.post(self.vnfs_path, body=body)
arg = body[self._VNF]
arg_ = {
'template_id': arg['vnfd_id'],
}
for key in ('tenant_id', 'name'):
if key in arg:
arg_[key] = arg[key]
if 'config' in arg:
arg_['attributes'] = {'config': arg['config']}
body_ = {self._DEVICE: arg_}
ret = self.create_device(body_)
return {self._VNF: ret[self._DEVICE]}
@APIParamsCall
def delete_vnf(self, vnf):
return self.delete(self.vnf_path % vnf)
return self.delete_device(vnf)
@APIParamsCall
def update_vnf(self, vnf, body=None):
return self.put(self.vnf_path % vnf, body=body)
@APIParamsCall
def show_vim(self, vim, **_params):
return self.get(self.vim_path % vim, params=_params)
_VIM = "vim"
@APIParamsCall
def create_vim(self, body=None):
return self.post(self.vims_path, body=body)
@APIParamsCall
def delete_vim(self, vim):
return self.delete(self.vim_path % vim)
@APIParamsCall
def update_vim(self, vim, body=None):
return self.put(self.vim_path % vim, body=body)
@APIParamsCall
def list_vims(self, retrieve_all=True, **_params):
return self.list('vims', self.vims_path, retrieve_all, **_params)
args = body[self._VNF]
args_ = {}
if 'config' in args:
args_['attributes'] = {'config': args['config']}
body_ = {self._DEVICE: args_}
ret = self.update_device(vnf, body_)
return {self._VNF: ret[self._DEVICE]}

View File

@@ -12,6 +12,9 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# @author: Carl Baldwin, Hewlett-Packard
import pbr.version

View File

@@ -1,19 +1,11 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
hacking<0.11,>=0.10.2
cliff-tablib>=1.0 # Apache-2.0
coverage>=3.6 # Apache-2.0
discover # BSD
fixtures>=3.0.0 # Apache-2.0/BSD
mox>=0.5.3 # Apache-2.0
flake8<2.6.0,>=2.5.4 # MIT
pep8==1.5.7 # MIT
pyflakes==0.8.1 # MIT
python-subunit>=0.0.18 # Apache-2.0/BSD
sphinx!=1.3b1,<1.3,>=1.2.1 # BSD
testrepository>=0.0.18 # Apache-2.0/BSD
testtools>=1.4.0 # MIT
hacking>=0.8.0,<0.9
# releasenotes
reno>=1.8.0 # Apache2
cliff-tablib>=1.0
coverage>=3.6
discover
fixtures>=0.3.14
mox>=0.5.3
python-subunit>=0.0.18
sphinx>=1.1.2,<1.2
testrepository>=0.0.18
testtools>=0.9.34

13
tox.ini
View File

@@ -1,5 +1,5 @@
[tox]
envlist = py34,py27,pypy,pep8
envlist = py26,py27,py33,py34,pypy,pep8
minversion = 1.6
skipsdist = True
@@ -21,14 +21,15 @@ distribute = false
[testenv:venv]
commands = {posargs}
[testenv:releasenotes]
commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html
[testenv:cover]
commands = python setup.py testr --coverage --testr-args='{posargs}'
[tox:jenkins]
downloadcache = ~/cache/pip
[flake8]
# E125 continuation line does not distinguish itself from next logical line
ignore = E125
# H302 import only modules
ignore = E125,H302
show-source = true
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,tools
exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,tools