Syncrhonized plugins codebase
This commit is contained in:
parent
76ab80040a
commit
d2e79829a3
106
plugins/.gitignore
vendored
Normal file
106
plugins/.gitignore
vendored
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
|
||||||
|
#ignore thumbnails created by windows
|
||||||
|
Thumbs.db
|
||||||
|
#Ignore files build by Visual Studio
|
||||||
|
*.obj
|
||||||
|
*.exe
|
||||||
|
*.pdb
|
||||||
|
*.user
|
||||||
|
*.aps
|
||||||
|
*.pch
|
||||||
|
*.vspscc
|
||||||
|
*_i.c
|
||||||
|
*_p.c
|
||||||
|
*.ncb
|
||||||
|
*.suo
|
||||||
|
*.tlb
|
||||||
|
*.tlh
|
||||||
|
*.bak
|
||||||
|
*.cache
|
||||||
|
*.ilk
|
||||||
|
*.log
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
env/
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
*.egg-info/
|
||||||
|
.eggs/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.coverage
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
.project
|
||||||
|
.pydevproject
|
||||||
|
.settings/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
target/
|
||||||
|
|
||||||
|
#ignore thumbnails created by windows
|
||||||
|
Thumbs.db
|
||||||
|
#Ignore files build by Visual Studio
|
||||||
|
*.obj
|
||||||
|
*.exe
|
||||||
|
*.pdb
|
||||||
|
*.user
|
||||||
|
*.aps
|
||||||
|
*.pch
|
||||||
|
*.vspscc
|
||||||
|
*_i.c
|
||||||
|
*_p.c
|
||||||
|
*.ncb
|
||||||
|
*.suo
|
||||||
|
*.tlb
|
||||||
|
*.tlh
|
||||||
|
*.bak
|
||||||
|
*.cache
|
||||||
|
*.ilk
|
||||||
|
[Bb]in
|
||||||
|
[Dd]ebug*/
|
||||||
|
*.lib
|
||||||
|
*.sbr
|
||||||
|
obj/
|
||||||
|
[Rr]elease*/
|
||||||
|
_ReSharper*/
|
||||||
|
[Tt]est[Rr]esult*
|
||||||
|
.idea/*
|
4
plugins/.gitreview
Normal file
4
plugins/.gitreview
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[gerrit]
|
||||||
|
host=gerrit.mtn5.cci.att.com
|
||||||
|
port=29418
|
||||||
|
project=aic-valet-openstack-plugins.git
|
@ -1,8 +1,12 @@
|
|||||||
Ostro version 2.0.2 Installation and Usage Guide
|
Ostro version 2.0.2 Installation and Usage Guide
|
||||||
|
|
||||||
|
Author: Gueyoung Jung
|
||||||
|
Contact: gjung@research.att.com
|
||||||
|
|
||||||
|
|
||||||
INSTALLATION
|
INSTALLATION
|
||||||
|
|
||||||
You can download the latest Ostro python code from repository (GitHub).
|
You can download the latest Ostro python code from repository (CodeCloud).
|
||||||
|
|
||||||
USAGE
|
USAGE
|
||||||
|
|
||||||
@ -49,3 +53,5 @@ Ostro will run as a daemon process. Go to “ostro_server” directory, then sta
|
|||||||
To stop this daemon process:
|
To stop this daemon process:
|
||||||
|
|
||||||
python ostro_daemon.py stop
|
python ostro_daemon.py stop
|
||||||
|
|
||||||
|
|
34
plugins/README.md
Normal file
34
plugins/README.md
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# Valet
|
||||||
|
|
||||||
|
Valet gives OpenStack the ability to optimize cloud resources while simultaneously meeting a cloud application's QoS requirements. Valet provides an api service, a placement optimizer (Ostro), a high availability data storage and persistence layer (Music), and a set of OpenStack plugins.
|
||||||
|
|
||||||
|
**IMPORTANT**: [Overall AT&T AIC Installation of Valet is covered in a separate document](https://codecloud.web.att.com/plugins/servlet/readmeparser/display/ST_CLOUDQOS/allegro/atRef/refs/heads/master/renderFile/doc/aic/README.md).
|
||||||
|
|
||||||
|
Learn more about Valet:
|
||||||
|
|
||||||
|
* [OpenStack Newton Summit Presentation](https://www.openstack.org/videos/video/valet-holistic-data-center-optimization-for-openstack) (Austin, TX, 27 April 2016)
|
||||||
|
* [Presentation Slides](http://www.research.att.com/export/sites/att_labs/techdocs/TD_101806.pdf) (PDF)
|
||||||
|
|
||||||
|
Valet consists of the following components:
|
||||||
|
|
||||||
|
* [valet-openstack](https://codecloud.web.att.com/plugins/servlet/readmeparser/display/ST_CLOUDQOS/allegro/atRef/refs/heads/master/renderFile/valet_os/README.md): a set of OpenStack plugins used to interact with Valet
|
||||||
|
* [valet-api](https://codecloud.web.att.com/plugins/servlet/readmeparser/display/ST_CLOUDQOS/allegro/atRef/refs/heads/master/renderFile/valet_api/README.md): an API engine used to interact with Valet
|
||||||
|
* [Ostro](https://codecloud.web.att.com/plugins/servlet/readmeparser/display/ST_CLOUDQOS/ostro/atRef/refs/heads/master/renderFile/README): a placement optimization engine
|
||||||
|
* [Music](https://codecloud.web.att.com/plugins/servlet/readmeparser/display/ST_CLOUDQOS/music/atRef/refs/heads/master/renderFile/README.md): a data storage and persistence service
|
||||||
|
* [ostro-listener](https://codecloud.web.att.com/plugins/servlet/readmeparser/display/ST_CLOUDQOS/allegro/atRef/refs/heads/master/renderFile/ostro_listener/README.md): a message bus listener used in conjunction with Ostro and Music
|
||||||
|
* [havalet](https://codecloud.web.att.com/plugins/servlet/readmeparser/display/ST_CLOUDQOS/allegro/atRef/refs/heads/master/renderFile/havalet/README.md): a service that assists in providing high availability for Valet
|
||||||
|
|
||||||
|
Additional documents:
|
||||||
|
|
||||||
|
* [OpenStack Heat Resource Plugins](https://codecloud.web.att.com/plugins/servlet/readmeparser/display/ST_CLOUDQOS/allegro/atRef/refs/heads/master/renderFile/valet_os/etc/valet_os/heat/README.md): Heat resources
|
||||||
|
* [Placement API](https://codecloud.web.att.com/plugins/servlet/readmeparser/display/ST_CLOUDQOS/allegro/atRef/refs/heads/master/renderFile/valet_api/doc/README.md): API requests/responses
|
||||||
|
* [Using Postman with valet-api](https://codecloud.web.att.com/plugins/servlet/readmeparser/display/ST_CLOUDQOS/allegro/atRef/refs/heads/master/renderFile/valet_api/valet_api/tests/README.md): Postman support
|
||||||
|
|
||||||
|
## Thank You
|
||||||
|
|
||||||
|
Alicia Abella, Saar Alaluf, Bharath Balasubramanian, Roy Ben Hai, Shimon Benattar, Yael Ben Shalom, Benny Bustan, Rachel Cohen, Joe D'Andrea, Harel Dorfman, Boaz Elitzur, P.K. Esrawan, Inbal Harduf, Matti Hiltunen, Doron Honigsberg, Kaustubh Joshi, Gueyoung Jung, Gerald Karam, David Khanin, Israel Kliger, Erez Korn, Max Osipov, Chris Rice, Amnon Sagiv, Gideon Shafran, Galit Shemesh, Anna Yefimov; AT&T Advanced Technology and Architecture, AT&T Technology Development - AIC, Additional partners in AT&T Domain 2.0. Apologies if we missed anyone (please advise via email!).
|
||||||
|
|
||||||
|
## Contact
|
||||||
|
|
||||||
|
Joe D'Andrea <jdandrea@research.att.com>
|
||||||
|
|
@ -12,8 +12,15 @@ Valet1.0/Ostro features
|
|||||||
- Resource standby
|
- Resource standby
|
||||||
When allocating resources (CPU, memory, disk, and later network bandwidth), Ostro intentionally leaves a certain percentage of resources as unused. This is because of the concern of load spikes of tenant applications. Later, we will deploy more dynamic mechanism in the future version of Ostro.
|
When allocating resources (CPU, memory, disk, and later network bandwidth), Ostro intentionally leaves a certain percentage of resources as unused. This is because of the concern of load spikes of tenant applications. Later, we will deploy more dynamic mechanism in the future version of Ostro.
|
||||||
|
|
||||||
|
- High availability
|
||||||
|
Ostro replicas run as active-passive way. When active Ostro fails, automatically the passive one is activated via HAValet. All data is updated in MUSIC database at runtime whenever it is changed. When the passive Ostro is activated, it gets data from MUSIC to initialize its status rather than from OpenStack. Ostro also takes ping messages to show if it is alive or not.
|
||||||
|
|
||||||
- Runtime update via the Oslo message bus or RO
|
- Runtime update via the Oslo message bus or RO
|
||||||
Working on this.
|
Working on this.
|
||||||
|
|
||||||
- Migration tip
|
- Migration tip
|
||||||
Working on this.
|
Working on this.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -3,4 +3,4 @@
|
|||||||
# process, which may cause wedges in the gate later.
|
# process, which may cause wedges in the gate later.
|
||||||
|
|
||||||
pip
|
pip
|
||||||
simplejson
|
simplejson
|
@ -5,7 +5,7 @@ summary = Valet Orchestration Plugins for OpenStack
|
|||||||
description-file = README.md
|
description-file = README.md
|
||||||
author = AT&T
|
author = AT&T
|
||||||
author-email = jdandrea@research.att.com
|
author-email = jdandrea@research.att.com
|
||||||
homepage = https://github.com/att-comdev/valet
|
homepage = https://codecloud.web.att.com/projects/ST_CLOUDQOS/
|
||||||
classifier =
|
classifier =
|
||||||
Environment :: OpenStack
|
Environment :: OpenStack
|
||||||
Intended Audience :: Information Technology
|
Intended Audience :: Information Technology
|
||||||
@ -31,3 +31,4 @@ data_files =
|
|||||||
# ValetFilter = valet_os.cinder.valet_filter:ValetFilter
|
# ValetFilter = valet_os.cinder.valet_filter:ValetFilter
|
||||||
heat.stack_lifecycle_plugins =
|
heat.stack_lifecycle_plugins =
|
||||||
valet.lifecycle_plugin = valet_plugins.plugins.heat.plugins:ValetLifecyclePlugin
|
valet.lifecycle_plugin = valet_plugins.plugins.heat.plugins:ValetLifecyclePlugin
|
||||||
|
|
32
plugins/setup.py
Normal file
32
plugins/setup.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (c) 2014-2016 AT&T
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
|
||||||
|
'''Setup'''
|
||||||
|
|
||||||
|
import setuptools
|
||||||
|
|
||||||
|
# In python < 2.7.4, a lazy loading of package `pbr` will break
|
||||||
|
# setuptools if some other modules registered functions in `atexit`.
|
||||||
|
# solution from: http://bugs.python.org/issue15881#msg170215
|
||||||
|
try:
|
||||||
|
import multiprocessing # noqa # pylint: disable=W0611,C0411
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
setuptools.setup(
|
||||||
|
setup_requires=['pbr'],
|
||||||
|
pbr=True)
|
@ -1,7 +1,6 @@
|
|||||||
[tox]
|
[tox]
|
||||||
#minversion = 2.0
|
#minversion = 2.0
|
||||||
envlist = pep8
|
envlist = py27
|
||||||
#py27
|
|
||||||
#py27-constraints, pep8-constraints
|
#py27-constraints, pep8-constraints
|
||||||
#py34-constraints,py27-constraints,pypy-constraints,pep8-constraints
|
#py34-constraints,py27-constraints,pypy-constraints,pep8-constraints
|
||||||
#skipsdist = True
|
#skipsdist = True
|
||||||
@ -53,3 +52,4 @@ show-source = True
|
|||||||
ignore = E123,E125,E501,H401,H501,H301
|
ignore = E123,E125,E501,H401,H501,H301
|
||||||
builtins = _
|
builtins = _
|
||||||
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,*egg-info
|
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,*egg-info
|
||||||
|
|
@ -1,19 +1,20 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright 2014-2017 AT&T Intellectual Property
|
# Copyright (c) 2014-2016 AT&T
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
# You may obtain a copy of the License at
|
# You may obtain a copy of the License at
|
||||||
#
|
#
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
#
|
#
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
# See the License for the specific language governing permissions and
|
# implied. See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
"""Valet API Wrapper."""
|
'''Valet API Wrapper'''
|
||||||
|
|
||||||
from heat.common.i18n import _
|
from heat.common.i18n import _
|
||||||
import json
|
import json
|
||||||
@ -29,45 +30,44 @@ LOG = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
def _exception(exc, exc_info, req):
|
def _exception(exc, exc_info, req):
|
||||||
"""Handle an exception."""
|
'''Handle an exception'''
|
||||||
response = None
|
response = None
|
||||||
try:
|
try:
|
||||||
response = json.loads(req.text)
|
if req is not None:
|
||||||
|
response = json.loads(req.text)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
# FIXME(GJ): if Valet returns error
|
||||||
LOG.error("Exception is: %s, body is: %s" % (e, req.text))
|
LOG.error("Exception is: %s, body is: %s" % (e, req.text))
|
||||||
return
|
return None
|
||||||
|
|
||||||
if 'error' in response:
|
if response and 'error' in response:
|
||||||
|
# FIXME(GJ): if Valet returns error
|
||||||
error = response.get('error')
|
error = response.get('error')
|
||||||
msg = "%(explanation)s (valet-api: %(message)s)" % {
|
msg = "%(explanation)s (valet-api: %(message)s)" % {
|
||||||
'explanation': response.get('explanation',
|
'explanation': response.get('explanation', _('No remediation available')),
|
||||||
_('No remediation available')),
|
|
||||||
'message': error.get('message', _('Unknown error'))
|
'message': error.get('message', _('Unknown error'))
|
||||||
}
|
}
|
||||||
raise ValetAPIError(msg)
|
LOG.error("Response with error: " + msg)
|
||||||
|
return "error"
|
||||||
else:
|
else:
|
||||||
# TODO(JD): Re-evaluate if this clause is necessary.
|
# TODO(JD): Re-evaluate if this clause is necessary.
|
||||||
exc_class, exc, traceback = exc_info # pylint: disable=W0612
|
exc_class, exc, traceback = exc_info # pylint: disable=W0612
|
||||||
msg = _("%(exc)s for %(method)s %(url)s with body %(body)s") %\
|
msg = _("%(exc)s for %(method)s %(url)s with body %(body)s") % {'exc': exc, 'method': exc.request.method, 'url': exc.request.url, 'body': exc.request.body}
|
||||||
{'exc': exc, 'method': exc.request.method, 'url': exc.request.url,
|
LOG.error("Response is none: " + msg)
|
||||||
'body': exc.request.body}
|
return "error"
|
||||||
my_exc = ValetAPIError(msg)
|
|
||||||
# traceback can be added to the end of the raise
|
|
||||||
raise my_exc.__class__, my_exc
|
|
||||||
|
|
||||||
|
|
||||||
# TODO(UNKNOWN): Improve exception reporting back up to heat
|
# TODO(JD): Improve exception reporting back up to heat
|
||||||
class ValetAPIError(Exception):
|
class ValetAPIError(Exception):
|
||||||
"""Valet API Error."""
|
'''Valet API Error'''
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ValetAPIWrapper(object):
|
class ValetAPIWrapper(object):
|
||||||
"""Valet API Wrapper."""
|
'''Valet API Wrapper'''
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Initializer."""
|
'''Initializer'''
|
||||||
self.headers = {'Content-Type': 'application/json'}
|
self.headers = {'Content-Type': 'application/json'}
|
||||||
self.opt_group_str = 'valet'
|
self.opt_group_str = 'valet'
|
||||||
self.opt_name_str = 'url'
|
self.opt_name_str = 'url'
|
||||||
@ -76,7 +76,7 @@ class ValetAPIWrapper(object):
|
|||||||
self._register_opts()
|
self._register_opts()
|
||||||
|
|
||||||
def _api_endpoint(self):
|
def _api_endpoint(self):
|
||||||
"""Return API endpoint."""
|
'''Returns API endpoint'''
|
||||||
try:
|
try:
|
||||||
opt = getattr(cfg.CONF, self.opt_group_str)
|
opt = getattr(cfg.CONF, self.opt_group_str)
|
||||||
endpoint = opt[self.opt_name_str]
|
endpoint = opt[self.opt_name_str]
|
||||||
@ -89,80 +89,75 @@ class ValetAPIWrapper(object):
|
|||||||
raise # exception.Error(_('API Endpoint not defined.'))
|
raise # exception.Error(_('API Endpoint not defined.'))
|
||||||
|
|
||||||
def _get_timeout(self):
|
def _get_timeout(self):
|
||||||
"""Get timeout.
|
'''Returns Valet plugin API request timeout tuple (conn_timeout, read_timeout)'''
|
||||||
|
read_timeout = 600
|
||||||
Return Valet plugin API request timeout
|
|
||||||
tuple (conn_timeout, read_timeout).
|
|
||||||
"""
|
|
||||||
conn_timeout = 3
|
|
||||||
read_timeout = 5
|
|
||||||
try:
|
try:
|
||||||
opt = getattr(cfg.CONF, self.opt_group_str)
|
opt = getattr(cfg.CONF, self.opt_group_str)
|
||||||
conn_timeout = opt[self.opt_conn_timeout]
|
# conn_timeout = opt[self.opt_conn_timeout]
|
||||||
read_timeout = opt[self.opt_read_timeout]
|
read_timeout = opt[self.opt_read_timeout]
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return conn_timeout, read_timeout
|
# Timeout accepts tupple on 'requests' version 2.4.0 and above - adding *connect* timeouts
|
||||||
|
# return conn_timeout, read_timeout
|
||||||
|
return read_timeout
|
||||||
|
|
||||||
def _register_opts(self):
|
def _register_opts(self):
|
||||||
"""Register options."""
|
'''Register options'''
|
||||||
opts = []
|
opts = []
|
||||||
option = cfg.StrOpt(self.opt_name_str, default=None,
|
option = cfg.StrOpt(self.opt_name_str, default=None, help=_('Valet API endpoint'))
|
||||||
help=_('Valet API endpoint'))
|
|
||||||
opts.append(option)
|
opts.append(option)
|
||||||
option = cfg.IntOpt(self.opt_conn_timeout, default=3,
|
option = cfg.IntOpt(self.opt_conn_timeout, default=3, help=_('Valet Plugin Connect Timeout'))
|
||||||
help=_('Valet Plugin Connect Timeout'))
|
|
||||||
opts.append(option)
|
opts.append(option)
|
||||||
option = cfg.IntOpt(self.opt_read_timeout, default=5,
|
option = cfg.IntOpt(self.opt_read_timeout, default=5, help=_('Valet Plugin Read Timeout'))
|
||||||
help=_('Valet Plugin Read Timeout'))
|
|
||||||
opts.append(option)
|
opts.append(option)
|
||||||
|
|
||||||
opt_group = cfg.OptGroup(self.opt_group_str)
|
opt_group = cfg.OptGroup(self.opt_group_str)
|
||||||
cfg.CONF.register_group(opt_group)
|
cfg.CONF.register_group(opt_group)
|
||||||
cfg.CONF.register_opts(opts, group=opt_group)
|
cfg.CONF.register_opts(opts, group=opt_group)
|
||||||
|
|
||||||
# TODO(UNKOWN): Keep stack param for now. We may need it again.
|
# TODO(JD): Keep stack param for now. We may need it again.
|
||||||
def plans_create(self, stack, plan, auth_token=None): # pylint: disable=W0613
|
def plans_create(self, stack, plan, auth_token=None): # pylint: disable=W0613
|
||||||
"""Create a plan."""
|
'''Create a plan'''
|
||||||
response = None
|
response = None
|
||||||
try:
|
try:
|
||||||
|
req = None
|
||||||
timeout = self._get_timeout()
|
timeout = self._get_timeout()
|
||||||
url = self._api_endpoint() + '/plans/'
|
url = self._api_endpoint() + '/plans/'
|
||||||
payload = json.dumps(plan)
|
payload = json.dumps(plan)
|
||||||
self.headers['X-Auth-Token'] = auth_token
|
self.headers['X-Auth-Token'] = auth_token
|
||||||
req = requests.post(url, data=payload, headers=self.headers,
|
req = requests.post(url, data=payload, headers=self.headers, timeout=timeout)
|
||||||
timeout=timeout)
|
|
||||||
req.raise_for_status()
|
req.raise_for_status()
|
||||||
response = json.loads(req.text)
|
response = json.loads(req.text)
|
||||||
except (requests.exceptions.HTTPError,
|
except (requests.exceptions.HTTPError, requests.exceptions.Timeout, requests.exceptions.ConnectionError)\
|
||||||
requests.exceptions.ConnectTimeout,
|
|
||||||
requests.exceptions.ConnectionError) \
|
|
||||||
as exc:
|
as exc:
|
||||||
_exception(exc, sys.exc_info(), req)
|
return _exception(exc, sys.exc_info(), req)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error("Exception (at plans_create) is: %s" % e)
|
LOG.error("Exception (at plans_create) is: %s" % e)
|
||||||
|
return None
|
||||||
return response
|
return response
|
||||||
|
|
||||||
# TODO(UNKNOWN): Keep stack param for now. We may need it again.
|
# TODO(JD): Keep stack param for now. We may need it again.
|
||||||
def plans_delete(self, stack, auth_token=None): # pylint: disable=W0613
|
def plans_delete(self, stack, auth_token=None): # pylint: disable=W0613
|
||||||
"""Delete a plan."""
|
'''Delete a plan'''
|
||||||
try:
|
try:
|
||||||
|
req = None
|
||||||
timeout = self._get_timeout()
|
timeout = self._get_timeout()
|
||||||
url = self._api_endpoint() + '/plans/' + stack.id
|
url = self._api_endpoint() + '/plans/' + stack.id
|
||||||
self.headers['X-Auth-Token'] = auth_token
|
self.headers['X-Auth-Token'] = auth_token
|
||||||
req = requests.delete(url, headers=self.headers, timeout=timeout)
|
req = requests.delete(url, headers=self.headers, timeout=timeout)
|
||||||
except (requests.exceptions.HTTPError,
|
except (requests.exceptions.HTTPError, requests.exceptions.Timeout, requests.exceptions.ConnectionError)\
|
||||||
requests.exceptions.ConnectTimeout,
|
|
||||||
requests.exceptions.ConnectionError)\
|
|
||||||
as exc:
|
as exc:
|
||||||
_exception(exc, sys.exc_info(), req)
|
return _exception(exc, sys.exc_info(), req)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error("Exception (plans_delete) is: %s" % e)
|
LOG.error("Exception (plans_delete) is: %s" % e)
|
||||||
|
return None
|
||||||
# Delete does not return a response body.
|
# Delete does not return a response body.
|
||||||
|
|
||||||
def placement(self, orch_id, res_id, hosts=None, auth_token=None):
|
def placement(self, orch_id, res_id, hosts=None, auth_token=None):
|
||||||
"""Reserve previously made placement."""
|
'''Reserve previously made placement.'''
|
||||||
try:
|
try:
|
||||||
|
req = None
|
||||||
|
payload = None
|
||||||
timeout = self._get_timeout()
|
timeout = self._get_timeout()
|
||||||
url = self._api_endpoint() + '/placements/' + orch_id
|
url = self._api_endpoint() + '/placements/' + orch_id
|
||||||
self.headers['X-Auth-Token'] = auth_token
|
self.headers['X-Auth-Token'] = auth_token
|
||||||
@ -172,15 +167,19 @@ class ValetAPIWrapper(object):
|
|||||||
"resource_id": res_id
|
"resource_id": res_id
|
||||||
}
|
}
|
||||||
payload = json.dumps(kwargs)
|
payload = json.dumps(kwargs)
|
||||||
req = requests.post(url, data=payload, headers=self.headers,
|
req = requests.post(url, data=payload, headers=self.headers, timeout=timeout)
|
||||||
timeout=timeout)
|
|
||||||
else:
|
else:
|
||||||
req = requests.get(url, headers=self.headers, timeout=timeout)
|
req = requests.get(url, headers=self.headers, timeout=timeout)
|
||||||
|
|
||||||
# TODO(UNKNOWN): Raise an exception IFF the scheduler can handle it
|
# TODO(JD): Raise an exception IFF the scheduler can handle it
|
||||||
|
# req.raise_for_status()
|
||||||
|
|
||||||
response = json.loads(req.text)
|
response = json.loads(req.text)
|
||||||
except Exception: # pylint: disable=W0702
|
except (requests.exceptions.HTTPError, requests.exceptions.Timeout, requests.exceptions.ConnectionError)\
|
||||||
|
as exc:
|
||||||
|
return _exception(exc, sys.exc_info(), req)
|
||||||
|
except Exception as e: # pylint: disable=W0702
|
||||||
|
LOG.error("Exception (placement) is: %s" % e)
|
||||||
# FIXME: Find which exceptions we should really handle here.
|
# FIXME: Find which exceptions we should really handle here.
|
||||||
response = None
|
response = None
|
||||||
|
|
@ -1,19 +1,20 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright 2014-2017 AT&T Intellectual Property
|
# Copyright (c) 2014-2016 AT&T
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
# You may obtain a copy of the License at
|
# You may obtain a copy of the License at
|
||||||
#
|
#
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
#
|
#
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
# See the License for the specific language governing permissions and
|
# implied. See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
"""GroupAssignment Heat Resource Plugin."""
|
'''GroupAssignment Heat Resource Plugin'''
|
||||||
|
|
||||||
from heat.common.i18n import _
|
from heat.common.i18n import _
|
||||||
from heat.engine import constraints
|
from heat.engine import constraints
|
||||||
@ -26,20 +27,15 @@ LOG = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class GroupAssignment(resource.Resource):
|
class GroupAssignment(resource.Resource):
|
||||||
"""Group Assignment.
|
''' A Group Assignment describes one or more resources assigned to a particular type of group.
|
||||||
|
|
||||||
Group Assignment describes one or more resources assigned to a
|
Assignments can reference other assignments, so long as there are no circular references.
|
||||||
particular type of group.
|
There are three types of groups: affinity, diversity, and exclusivity.
|
||||||
|
Exclusivity groups have a unique name, assigned through Valet.
|
||||||
|
|
||||||
Assignments can reference other assignments, so long as there are no
|
This resource is purely informational in nature and makes no changes to heat, nova, or cinder.
|
||||||
circular references. There are three types of groups: affinity, diversity,
|
The Valet Heat Lifecycle Plugin passes this information to the optimizer.
|
||||||
and exclusivity. Exclusivity groups have a unique name, assigned through
|
'''
|
||||||
Valet.
|
|
||||||
|
|
||||||
This resource is purely informational in nature and makes no changes to
|
|
||||||
heat, nova, or cinder. The Valet Heat Lifecycle Plugin passes this
|
|
||||||
information to the optimizer.
|
|
||||||
"""
|
|
||||||
|
|
||||||
_RELATIONSHIP_TYPES = (
|
_RELATIONSHIP_TYPES = (
|
||||||
AFFINITY, DIVERSITY, EXCLUSIVITY,
|
AFFINITY, DIVERSITY, EXCLUSIVITY,
|
||||||
@ -57,7 +53,7 @@ class GroupAssignment(resource.Resource):
|
|||||||
GROUP_NAME: properties.Schema(
|
GROUP_NAME: properties.Schema(
|
||||||
properties.Schema.STRING,
|
properties.Schema.STRING,
|
||||||
_('Group name. Required for exclusivity groups.'),
|
_('Group name. Required for exclusivity groups.'),
|
||||||
# TODO(UNKNOWN): Add a custom constraint
|
# TODO(JD): Add a custom constraint
|
||||||
# Constraint must ensure a valid and allowed name
|
# Constraint must ensure a valid and allowed name
|
||||||
# when an exclusivity group is in use.
|
# when an exclusivity group is in use.
|
||||||
# This is presently enforced by valet-api and can also
|
# This is presently enforced by valet-api and can also
|
||||||
@ -92,19 +88,18 @@ class GroupAssignment(resource.Resource):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def handle_create(self):
|
def handle_create(self):
|
||||||
"""Create resource."""
|
'''Create resource'''
|
||||||
self.resource_id_set(self.physical_resource_name())
|
self.resource_id_set(self.physical_resource_name())
|
||||||
|
|
||||||
def handle_update(self, json_snippet, templ_diff, # pylint: disable=W0613
|
def handle_update(self, json_snippet, templ_diff, prop_diff): # pylint: disable=W0613
|
||||||
prop_diff):
|
'''Update resource'''
|
||||||
"""Update resource."""
|
|
||||||
self.resource_id_set(self.physical_resource_name())
|
self.resource_id_set(self.physical_resource_name())
|
||||||
|
|
||||||
def handle_delete(self):
|
def handle_delete(self):
|
||||||
"""Delete resource."""
|
'''Delete resource'''
|
||||||
self.resource_id_set(None)
|
self.resource_id_set(None)
|
||||||
|
|
||||||
|
|
||||||
def resource_mapping():
|
def resource_mapping():
|
||||||
"""Map names to resources."""
|
'''Map names to resources.'''
|
||||||
return {'ATT::Valet::GroupAssignment': GroupAssignment, }
|
return {'ATT::Valet::GroupAssignment': GroupAssignment, }
|
@ -1,6 +1,6 @@
|
|||||||
# OpenStack Heat Resource Plugins
|
# OpenStack Heat Resource Plugins
|
||||||
|
|
||||||
[Valet](https://github.com/att-comdev/valet/blob/master/README.md) works with OpenStack Heat through the use of Resource Plugins. This document explains what they are and how they work. As new plugins become formally introduced, they will be added here.
|
[Valet](https://codecloud.web.att.com/plugins/servlet/readmeparser/display/ST_CLOUDQOS/allegro/atRef/refs/heads/master/renderFile/README.md) works with OpenStack Heat through the use of Resource Plugins. This document explains what they are and how they work. As new plugins become formally introduced, they will be added here.
|
||||||
|
|
||||||
The following is current as of Valet Release 1.0.
|
The following is current as of Valet Release 1.0.
|
||||||
|
|
@ -1,19 +1,20 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright 2014-2017 AT&T Intellectual Property
|
# Copyright (c) 2014-2016 AT&T
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
# You may obtain a copy of the License at
|
# You may obtain a copy of the License at
|
||||||
#
|
#
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
#
|
#
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
# See the License for the specific language governing permissions and
|
# implied. See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
"""Valet Plugins for Heat."""
|
'''Valet Plugins for Heat'''
|
||||||
|
|
||||||
from heat.engine import lifecycle_plugin
|
from heat.engine import lifecycle_plugin
|
||||||
|
|
||||||
@ -30,13 +31,13 @@ LOG = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
def validate_uuid4(uuid_string):
|
def validate_uuid4(uuid_string):
|
||||||
"""Validate that a UUID string is in fact a valid uuid4.
|
''' Validate that a UUID string is in fact a valid uuid4.
|
||||||
|
|
||||||
Happily, the uuid module does the actual checking for us.
|
Happily, the uuid module does the actual checking for us.
|
||||||
It is vital that the 'version' kwarg be passed to the
|
It is vital that the 'version' kwarg be passed to the
|
||||||
UUID() call, otherwise any 32-character hex string
|
UUID() call, otherwise any 32-character hex string
|
||||||
is considered valid.
|
is considered valid.
|
||||||
"""
|
'''
|
||||||
try:
|
try:
|
||||||
val = uuid.UUID(uuid_string, version=4)
|
val = uuid.UUID(uuid_string, version=4)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
@ -54,13 +55,11 @@ def validate_uuid4(uuid_string):
|
|||||||
|
|
||||||
|
|
||||||
class ValetLifecyclePlugin(lifecycle_plugin.LifecyclePlugin):
|
class ValetLifecyclePlugin(lifecycle_plugin.LifecyclePlugin):
|
||||||
"""Base class for pre-op and post-op work on a stack.
|
''' Base class for pre-op and post-op work on a stack.
|
||||||
|
|
||||||
Implementations should extend this class and override the methods.
|
Implementations should extend this class and override the methods.
|
||||||
"""
|
'''
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Initialize."""
|
|
||||||
self.api = valet_api.ValetAPIWrapper()
|
self.api = valet_api.ValetAPIWrapper()
|
||||||
self.hints_enabled = False
|
self.hints_enabled = False
|
||||||
|
|
||||||
@ -69,11 +68,10 @@ class ValetLifecyclePlugin(lifecycle_plugin.LifecyclePlugin):
|
|||||||
self.hints_enabled = cfg.CONF.stack_scheduler_hints
|
self.hints_enabled = cfg.CONF.stack_scheduler_hints
|
||||||
|
|
||||||
def _parse_stack_preview(self, dest, preview):
|
def _parse_stack_preview(self, dest, preview):
|
||||||
"""Walk the preview list (possibly nested).
|
''' Walk the preview list (possibly nested)
|
||||||
|
|
||||||
extracting parsed template dicts and storing modified versions in a flat
|
extracting parsed template dicts and storing modified versions in a flat dict.
|
||||||
dict.
|
'''
|
||||||
"""
|
|
||||||
# The preview is either a list or not.
|
# The preview is either a list or not.
|
||||||
if not isinstance(preview, list):
|
if not isinstance(preview, list):
|
||||||
# Heat does not assign orchestration UUIDs to
|
# Heat does not assign orchestration UUIDs to
|
||||||
@ -88,21 +86,21 @@ class ValetLifecyclePlugin(lifecycle_plugin.LifecyclePlugin):
|
|||||||
preview.uuid and validate_uuid4(preview.uuid):
|
preview.uuid and validate_uuid4(preview.uuid):
|
||||||
key = preview.uuid
|
key = preview.uuid
|
||||||
else:
|
else:
|
||||||
# TODO(UNK): Heat should be authoritative for UUID assignments.
|
# TODO(JD): Heat should be authoritative for UUID assignments.
|
||||||
# This will require a change to heat-engine.
|
# This will require a change to heat-engine.
|
||||||
# Looks like it may be: heat/db/sqlalchemy/models.py#L279
|
# Looks like it may be: heat/db/sqlalchemy/models.py#L279
|
||||||
# It could be that nested stacks aren't added to the DB yet.
|
# It could be that nested stacks aren't added to the DB yet.
|
||||||
key = str(uuid.uuid4())
|
key = str(uuid.uuid4())
|
||||||
parsed = preview.parsed_template()
|
parsed = preview.parsed_template()
|
||||||
parsed['name'] = preview.name
|
parsed['name'] = preview.name
|
||||||
# TODO(UNKWN): Replace resource referenced names with their UUIDs.
|
# TODO(JD): Replace resource referenced names with their UUIDs.
|
||||||
dest[key] = parsed
|
dest[key] = parsed
|
||||||
else:
|
else:
|
||||||
for item in preview:
|
for item in preview:
|
||||||
self._parse_stack_preview(dest, item)
|
self._parse_stack_preview(dest, item)
|
||||||
|
|
||||||
def do_pre_op(self, cnxt, stack, current_stack=None, action=None):
|
def do_pre_op(self, cnxt, stack, current_stack=None, action=None):
|
||||||
"""Method to be run by heat before stack operations."""
|
''' Method to be run by heat before stack operations. '''
|
||||||
if not self.hints_enabled or stack.status != 'IN_PROGRESS':
|
if not self.hints_enabled or stack.status != 'IN_PROGRESS':
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -133,19 +131,19 @@ class ValetLifecyclePlugin(lifecycle_plugin.LifecyclePlugin):
|
|||||||
|
|
||||||
self.api.plans_create(stack, plan, auth_token=cnxt.auth_token)
|
self.api.plans_create(stack, plan, auth_token=cnxt.auth_token)
|
||||||
|
|
||||||
def do_post_op(self, cnxt, stack, # pylint: disable=R0913
|
def do_post_op(self, cnxt, stack, current_stack=None, action=None, # pylint: disable=R0913
|
||||||
current_stack=None, action=None, is_stack_failure=False):
|
is_stack_failure=False):
|
||||||
"""Method to be run by heat after stack operations, including failures.
|
''' Method to be run by heat after stack operations, including failures.
|
||||||
|
|
||||||
On failure to execute all the registered pre_ops, this method will be
|
On failure to execute all the registered pre_ops, this method will be
|
||||||
called if and only if the corresponding pre_op was successfully called.
|
called if and only if the corresponding pre_op was successfully called.
|
||||||
On failures of the actual stack operation, this method will
|
On failures of the actual stack operation, this method will
|
||||||
be called if all the pre operations were successfully called.
|
be called if all the pre operations were successfully called.
|
||||||
"""
|
'''
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def get_ordinal(self):
|
def get_ordinal(self):
|
||||||
"""Ordinal to order class instances for pre /post operation execution.
|
''' An ordinal used to order class instances for pre and post operation execution.
|
||||||
|
|
||||||
The values returned by get_ordinal are used to create a partial order
|
The values returned by get_ordinal are used to create a partial order
|
||||||
for pre and post operation method invocations. The default ordinal
|
for pre and post operation method invocations. The default ordinal
|
||||||
@ -156,5 +154,5 @@ class ValetLifecyclePlugin(lifecycle_plugin.LifecyclePlugin):
|
|||||||
class1inst will be executed after the method on class2inst.
|
class1inst will be executed after the method on class2inst.
|
||||||
If class1inst.ordinal() == class2inst.ordinal(), then the order of
|
If class1inst.ordinal() == class2inst.ordinal(), then the order of
|
||||||
method invocation is indeterminate.
|
method invocation is indeterminate.
|
||||||
"""
|
'''
|
||||||
return 100
|
return 100
|
@ -1,19 +1,20 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright 2014-2017 AT&T Intellectual Property
|
# Copyright (c) 2014-2016 AT&T
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
# You may obtain a copy of the License at
|
# You may obtain a copy of the License at
|
||||||
#
|
#
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
#
|
#
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
# See the License for the specific language governing permissions and
|
# implied. See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
"""Valet Nova Scheduler Filter."""
|
'''Valet Nova Scheduler Filter'''
|
||||||
|
|
||||||
from keystoneclient.v2_0 import client
|
from keystoneclient.v2_0 import client
|
||||||
|
|
||||||
@ -26,12 +27,14 @@ from valet_plugins.common import valet_api
|
|||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ValetFilter(filters.BaseHostFilter):
|
class ValetFilter(filters.BaseHostFilter):
|
||||||
"""Filter on Valet assignment."""
|
'''Filter on Valet assignment.'''
|
||||||
|
|
||||||
# Host state does not change within a request
|
# Host state does not change within a request
|
||||||
run_filter_once_per_request = True
|
run_filter_once_per_request = True
|
||||||
@ -40,7 +43,7 @@ class ValetFilter(filters.BaseHostFilter):
|
|||||||
_auth_token = None
|
_auth_token = None
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Initializer."""
|
'''Initializer'''
|
||||||
self.api = valet_api.ValetAPIWrapper()
|
self.api = valet_api.ValetAPIWrapper()
|
||||||
self.opt_group_str = 'valet'
|
self.opt_group_str = 'valet'
|
||||||
self.opt_failure_mode_str = 'failure_mode'
|
self.opt_failure_mode_str = 'failure_mode'
|
||||||
@ -50,8 +53,11 @@ class ValetFilter(filters.BaseHostFilter):
|
|||||||
self.opt_auth_uri_str = 'admin_auth_url'
|
self.opt_auth_uri_str = 'admin_auth_url'
|
||||||
self._register_opts()
|
self._register_opts()
|
||||||
|
|
||||||
|
self.retries = 60
|
||||||
|
self.interval = 1
|
||||||
|
|
||||||
def _authorize(self):
|
def _authorize(self):
|
||||||
"""Keystone AuthN."""
|
'''Keystone AuthN'''
|
||||||
opt = getattr(cfg.CONF, self.opt_group_str)
|
opt = getattr(cfg.CONF, self.opt_group_str)
|
||||||
project_name = opt[self.opt_project_name_str]
|
project_name = opt[self.opt_project_name_str]
|
||||||
username = opt[self.opt_username_str]
|
username = opt[self.opt_username_str]
|
||||||
@ -68,61 +74,68 @@ class ValetFilter(filters.BaseHostFilter):
|
|||||||
self._auth_token = keystone_client.auth_token
|
self._auth_token = keystone_client.auth_token
|
||||||
|
|
||||||
def _is_same_host(self, host, location): # pylint: disable=R0201
|
def _is_same_host(self, host, location): # pylint: disable=R0201
|
||||||
"""Return true if host matches location."""
|
'''Returns true if host matches location'''
|
||||||
return host == location
|
return host == location
|
||||||
|
|
||||||
def _register_opts(self):
|
def _register_opts(self):
|
||||||
"""Register Options."""
|
'''Register Options'''
|
||||||
opts = []
|
opts = []
|
||||||
option = cfg.StrOpt(
|
option = cfg.StrOpt(self.opt_failure_mode_str, choices=['reject', 'yield'], default='reject',
|
||||||
self.opt_failure_mode_str,
|
help=_('Mode to operate in if Valet planning fails for any reason.'))
|
||||||
choices=['reject', 'yield'],
|
|
||||||
default='reject',
|
|
||||||
help=_('Mode to operate in if Valet planning fails for any reason.'))
|
|
||||||
opts.append(option)
|
opts.append(option)
|
||||||
option = cfg.StrOpt(self.opt_project_name_str, default=None,
|
option = cfg.StrOpt(self.opt_project_name_str, default=None, help=_('Valet Project Name'))
|
||||||
help=_('Valet Project Name'))
|
|
||||||
opts.append(option)
|
opts.append(option)
|
||||||
option = cfg.StrOpt(self.opt_username_str, default=None,
|
option = cfg.StrOpt(self.opt_username_str, default=None, help=_('Valet Username'))
|
||||||
help=_('Valet Username'))
|
|
||||||
opts.append(option)
|
opts.append(option)
|
||||||
option = cfg.StrOpt(self.opt_password_str, default=None,
|
option = cfg.StrOpt(self.opt_password_str, default=None, help=_('Valet Password'))
|
||||||
help=_('Valet Password'))
|
|
||||||
opts.append(option)
|
opts.append(option)
|
||||||
option = cfg.StrOpt(self.opt_auth_uri_str, default=None,
|
option = cfg.StrOpt(self.opt_auth_uri_str, default=None, help=_('Keystone Authorization API Endpoint'))
|
||||||
help=_('Keystone Authorization API Endpoint'))
|
|
||||||
opts.append(option)
|
opts.append(option)
|
||||||
|
|
||||||
opt_group = cfg.OptGroup(self.opt_group_str)
|
opt_group = cfg.OptGroup(self.opt_group_str)
|
||||||
cfg.CONF.register_group(opt_group)
|
cfg.CONF.register_group(opt_group)
|
||||||
cfg.CONF.register_opts(opts, group=opt_group)
|
cfg.CONF.register_opts(opts, group=opt_group)
|
||||||
|
|
||||||
# TODO(UNKNOWN): Factor out common code between this and the cinder filter
|
# TODO(JD): Factor out common code between this and the cinder filter
|
||||||
def filter_all(self, filter_obj_list, filter_properties):
|
def filter_all(self, filter_obj_list, filter_properties):
|
||||||
"""Filter all hosts in one swell foop."""
|
'''Filter all hosts in one swell foop'''
|
||||||
|
|
||||||
hints_key = 'scheduler_hints'
|
hints_key = 'scheduler_hints'
|
||||||
orch_id_key = 'heat_resource_uuid'
|
orch_id_key = 'heat_resource_uuid'
|
||||||
|
|
||||||
ad_hoc = False
|
ad_hoc = False
|
||||||
yield_all = False
|
yield_all = False
|
||||||
location = None
|
location = None
|
||||||
res_id = None
|
|
||||||
|
|
||||||
opt = getattr(cfg.CONF, self.opt_group_str)
|
opt = getattr(cfg.CONF, self.opt_group_str)
|
||||||
failure_mode = opt[self.opt_failure_mode_str]
|
failure_mode = opt[self.opt_failure_mode_str]
|
||||||
|
|
||||||
# Get the resource_id (physical id)
|
# Get the resource_id (physical id) and host candidates
|
||||||
request_spec = filter_properties.get('request_spec')
|
request_spec = filter_properties.get('request_spec')
|
||||||
instance_properties = request_spec.get('instance_properties')
|
instance_properties = request_spec.get('instance_properties')
|
||||||
res_id = instance_properties.get('uuid')
|
res_id = instance_properties.get('uuid')
|
||||||
|
hosts = [obj.host for obj in filter_obj_list]
|
||||||
|
|
||||||
# TODO(JD): If we can't reach Valet at all, we may opt to fail
|
# TODO(JD): If we can't reach Valet at all, we may opt to fail
|
||||||
# TODO(JD): all hosts depending on a TBD config flag.
|
# TODO(JD): all hosts depending on a TBD config flag.
|
||||||
|
|
||||||
if orch_id_key not in filter_properties.get(hints_key, {}):
|
# Max
|
||||||
|
# fix for invaid authorization in yield mode
|
||||||
|
failed = None
|
||||||
|
try:
|
||||||
self._authorize()
|
self._authorize()
|
||||||
LOG.warn(_LW("Valet: Heat Stack Lifecycle Scheduler Hints not "
|
except Exception as ex:
|
||||||
"found. Performing ad-hoc placement."))
|
failed = ex
|
||||||
|
if failed:
|
||||||
|
LOG.warn("Failed to filter the hosts, failure mode is %s" % failure_mode)
|
||||||
|
if failure_mode == 'yield':
|
||||||
|
LOG.warn(failed)
|
||||||
|
yield_all = True
|
||||||
|
else:
|
||||||
|
LOG.error(failed)
|
||||||
|
# if not filter_properties.get(hints_key, {}).has_key(orch_id_key):
|
||||||
|
elif orch_id_key not in filter_properties.get(hints_key, {}):
|
||||||
|
LOG.info(_LW("Valet: Heat Stack Lifecycle Scheduler Hints not found. Performing ad-hoc placement."))
|
||||||
ad_hoc = True
|
ad_hoc = True
|
||||||
|
|
||||||
# We'll need the flavor.
|
# We'll need the flavor.
|
||||||
@ -153,17 +166,26 @@ class ValetFilter(filters.BaseHostFilter):
|
|||||||
plan = {
|
plan = {
|
||||||
'plan_name': res_id,
|
'plan_name': res_id,
|
||||||
'stack_id': res_id,
|
'stack_id': res_id,
|
||||||
|
'locations': hosts,
|
||||||
'timeout': '%d sec' % timeout,
|
'timeout': '%d sec' % timeout,
|
||||||
'resources': resources
|
'resources': resources
|
||||||
}
|
}
|
||||||
try:
|
|
||||||
response = self.api.plans_create(None, plan,
|
count = 0
|
||||||
auth_token=self._auth_token)
|
response = None
|
||||||
except Exception:
|
while count < self.retries:
|
||||||
# TODO(UNKNOWN): Get context from exception
|
try:
|
||||||
LOG.error(_LE("Valet did not respond to ad hoc placement "
|
response = self.api.plans_create(None, plan, auth_token=self._auth_token)
|
||||||
"request."))
|
except Exception:
|
||||||
response = None
|
# TODO(JD): Get context from exception
|
||||||
|
LOG.error(_LE("Raise exception for ad hoc placement request."))
|
||||||
|
response = None
|
||||||
|
if response is None:
|
||||||
|
count += 1
|
||||||
|
LOG.warn("Valet conn error for plan_create, Retry " + str(count) + " for stack = " + res_id)
|
||||||
|
time.sleep(self.interval)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
if response and response.get('plan'):
|
if response and response.get('plan'):
|
||||||
plan = response['plan']
|
plan = response['plan']
|
||||||
@ -174,40 +196,41 @@ class ValetFilter(filters.BaseHostFilter):
|
|||||||
location = placement['location']
|
location = placement['location']
|
||||||
|
|
||||||
if not location:
|
if not location:
|
||||||
LOG.error(_LE("Valet ad-hoc placement unknown for resource id "
|
LOG.error(_LE("Valet ad-hoc placement unknown for resource id %s.") % res_id)
|
||||||
"%s.") % res_id)
|
|
||||||
if failure_mode == 'yield':
|
if failure_mode == 'yield':
|
||||||
LOG.warn(_LW("Valet will yield to Nova for placement "
|
LOG.warn(_LW("Valet will yield to Nova for placement decisions."))
|
||||||
"decisions."))
|
|
||||||
yield_all = True
|
yield_all = True
|
||||||
else:
|
else:
|
||||||
yield_all = False
|
yield_all = False
|
||||||
else:
|
else:
|
||||||
orch_id = filter_properties[hints_key][orch_id_key]
|
orch_id = filter_properties[hints_key][orch_id_key]
|
||||||
self._authorize()
|
|
||||||
hosts = [obj.host for obj in filter_obj_list]
|
|
||||||
|
|
||||||
try:
|
count = 0
|
||||||
response = self.api.placement(orch_id, res_id, hosts=hosts,
|
response = None
|
||||||
auth_token=self._auth_token)
|
while count < self.retries:
|
||||||
except Exception:
|
try:
|
||||||
print("Exception in creating placement")
|
response = self.api.placement(orch_id, res_id, hosts=hosts, auth_token=self._auth_token)
|
||||||
LOG.error(_LW("Valet did not respond to placement request."))
|
except Exception:
|
||||||
response = None
|
LOG.error(_LW("Raise exception for placement request."))
|
||||||
|
response = None
|
||||||
|
|
||||||
|
if response is None:
|
||||||
|
count += 1
|
||||||
|
LOG.warn("Valet conn error for placement Retry " + str(count) + " for stack = " + orch_id)
|
||||||
|
time.sleep(self.interval)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
if response and response.get('placement'):
|
if response and response.get('placement'):
|
||||||
placement = response['placement']
|
placement = response['placement']
|
||||||
if placement.get('location'):
|
if placement.get('location'):
|
||||||
|
|
||||||
location = placement['location']
|
location = placement['location']
|
||||||
|
|
||||||
if not location:
|
if not location:
|
||||||
# TODO(UNKNOWN): Get context from exception
|
# TODO(JD): Get context from exception
|
||||||
LOG.error(_LE("Valet placement unknown for resource id {0},"
|
LOG.error(_LE("Valet placement unknown for resource id {0}, orchestration id {1}.").format(res_id, orch_id))
|
||||||
"orchestration id {1}.").format(res_id, orch_id))
|
|
||||||
if failure_mode == 'yield':
|
if failure_mode == 'yield':
|
||||||
LOG.warn(_LW("Valet will yield to Nova for placement"
|
LOG.warn(_LW("Valet will yield to Nova for placement decisions."))
|
||||||
"decisions."))
|
|
||||||
yield_all = True
|
yield_all = True
|
||||||
else:
|
else:
|
||||||
yield_all = False
|
yield_all = False
|
||||||
@ -220,19 +243,15 @@ class ValetFilter(filters.BaseHostFilter):
|
|||||||
match = self._is_same_host(obj.host, location)
|
match = self._is_same_host(obj.host, location)
|
||||||
if match:
|
if match:
|
||||||
if ad_hoc:
|
if ad_hoc:
|
||||||
LOG.info(_LI("Valet ad-hoc placement for resource "
|
LOG.info(_LI("Valet ad-hoc placement for resource id {0}: {1}.").format(res_id, obj.host))
|
||||||
"id {0}: {1}.").format(res_id, obj.host))
|
|
||||||
else:
|
else:
|
||||||
LOG.info(_LI("Valet placement for resource id %s, "
|
LOG.info(_LI("Valet placement for resource id %s, orchestration id {0}: {1}.").format(res_id, orch_id, obj.host))
|
||||||
"orchestration id {0}: {1}.").format(
|
|
||||||
res_id, orch_id, obj.host))
|
|
||||||
else:
|
else:
|
||||||
match = None
|
match = None
|
||||||
if yield_all or match:
|
if yield_all or match:
|
||||||
yield obj
|
yield obj
|
||||||
|
|
||||||
def host_passes(self, host_state, # pylint: disable=W0613,R0201
|
def host_passes(self, host_state, filter_properties): # pylint: disable=W0613,R0201
|
||||||
filter_properties):
|
'''Individual host pass check'''
|
||||||
"""Individual host pass check."""
|
|
||||||
# Intentionally let filter_all() handle in one swell foop.
|
# Intentionally let filter_all() handle in one swell foop.
|
||||||
return False
|
return False
|
49
plugins/valet_plugins/tests/base.py
Normal file
49
plugins/valet_plugins/tests/base.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (c) 2014-2016 AT&T
|
||||||
|
#
|
||||||
|
# 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 oslo_config import fixture as fixture_config
|
||||||
|
from oslo_log import log as logging
|
||||||
|
from oslotest.base import BaseTestCase
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Base(BaseTestCase):
|
||||||
|
"""Test case base class for all unit tests."""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwds):
|
||||||
|
''' '''
|
||||||
|
super(Base, self).__init__(*args, **kwds)
|
||||||
|
|
||||||
|
self.CONF = self.useFixture(fixture_config.Config()).conf
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(Base, self).setUp()
|
||||||
|
|
||||||
|
def run_test(self, stack_name, template_path):
|
||||||
|
''' main function '''
|
||||||
|
pass
|
||||||
|
|
||||||
|
def validate(self, result):
|
||||||
|
self.assertEqual(True, result.ok, result.message)
|
||||||
|
|
||||||
|
def validate_test(self, result):
|
||||||
|
self.assertTrue(result)
|
||||||
|
|
||||||
|
def get_name(self):
|
||||||
|
pass
|
@ -0,0 +1,7 @@
|
|||||||
|
'''
|
||||||
|
Created on Sep 14, 2016
|
||||||
|
|
||||||
|
@author: stack
|
||||||
|
'''
|
||||||
|
|
||||||
|
_ = None
|
@ -0,0 +1,25 @@
|
|||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
'''
|
||||||
|
Created on Sep 14, 2016
|
||||||
|
|
||||||
|
@author: stack
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
class LifecyclePlugin(object):
|
||||||
|
''' classdocs '''
|
||||||
|
|
||||||
|
def __init__(self, params):
|
||||||
|
''' Constructor '''
|
33
plugins/valet_plugins/tests/unit/mocks/nova/i18n.py
Normal file
33
plugins/valet_plugins/tests/unit/mocks/nova/i18n.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#
|
||||||
|
# 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.
|
||||||
|
'''
|
||||||
|
Created on Sep 14, 2016
|
||||||
|
|
||||||
|
@author: stack
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
def _(string):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _LI(string):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _LW(string):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _LE(string):
|
||||||
|
return string
|
@ -0,0 +1,24 @@
|
|||||||
|
#
|
||||||
|
# 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.
|
||||||
|
'''
|
||||||
|
Created on Sep 15, 2016
|
||||||
|
|
||||||
|
@author: stack
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
class BaseHostFilter(object):
|
||||||
|
''' classdocs '''
|
||||||
|
|
||||||
|
def __init__(self, params):
|
||||||
|
''' Constructor '''
|
@ -1,19 +1,15 @@
|
|||||||
#
|
#
|
||||||
# Copyright 2014-2017 AT&T Intellectual Property
|
# 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
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
# 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
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
# License for the specific language governing permissions and limitations
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# under the License.
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
"""Test Plugins."""
|
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
from valet_plugins.plugins.heat.plugins import ValetLifecyclePlugin
|
from valet_plugins.plugins.heat.plugins import ValetLifecyclePlugin
|
||||||
@ -21,22 +17,18 @@ from valet_plugins.tests.base import Base
|
|||||||
|
|
||||||
|
|
||||||
class TestPlugins(Base):
|
class TestPlugins(Base):
|
||||||
"""Test valet_plugins.plugins.heat.plugins ValetLifecyclePlugin."""
|
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
"""Setup Test Plugins and call ValetLifecyclePlugin Init."""
|
|
||||||
super(TestPlugins, self).setUp()
|
super(TestPlugins, self).setUp()
|
||||||
|
|
||||||
self.valet_life_cycle_plugin = self.init_ValetLifecyclePlugin()
|
self.valet_life_cycle_plugin = self.init_ValetLifecyclePlugin()
|
||||||
|
|
||||||
@mock.patch('valet_plugins.common.valet_api.ValetAPIWrapper')
|
@mock.patch('valet_plugins.common.valet_api.ValetAPIWrapper')
|
||||||
def init_ValetLifecyclePlugin(self, mock_class):
|
def init_ValetLifecyclePlugin(self, mock_class):
|
||||||
"""Called by setup to init, return ValetLifecyclePlugin()."""
|
|
||||||
with mock.patch('oslo_config.cfg.CONF'):
|
with mock.patch('oslo_config.cfg.CONF'):
|
||||||
return ValetLifecyclePlugin()
|
return ValetLifecyclePlugin()
|
||||||
|
|
||||||
def test_do_pre_op(self):
|
def test_do_pre_op(self):
|
||||||
"""Validate life cycle pre_ops by checking api method calls."""
|
|
||||||
stack = mock.MagicMock()
|
stack = mock.MagicMock()
|
||||||
stack.status = "IN_PROGRESS"
|
stack.status = "IN_PROGRESS"
|
||||||
|
|
||||||
@ -58,10 +50,8 @@ class TestPlugins(Base):
|
|||||||
self.valet_life_cycle_plugin.hints_enabled = True
|
self.valet_life_cycle_plugin.hints_enabled = True
|
||||||
stack.status = "IN_PROGRESS"
|
stack.status = "IN_PROGRESS"
|
||||||
self.valet_life_cycle_plugin.do_pre_op(cnxt, stack, action="DELETE")
|
self.valet_life_cycle_plugin.do_pre_op(cnxt, stack, action="DELETE")
|
||||||
self.validate_test("plans_delete" in
|
self.validate_test("plans_delete" in self.valet_life_cycle_plugin.api.method_calls[0])
|
||||||
self.valet_life_cycle_plugin.api.method_calls[0])
|
|
||||||
|
|
||||||
# action create
|
# action create
|
||||||
self.valet_life_cycle_plugin.do_pre_op(cnxt, stack, action="CREATE")
|
self.valet_life_cycle_plugin.do_pre_op(cnxt, stack, action="CREATE")
|
||||||
self.validate_test("plans_create" in
|
self.validate_test("plans_create" in self.valet_life_cycle_plugin.api.method_calls[1])
|
||||||
self.valet_life_cycle_plugin.api.method_calls[1])
|
|
32
plugins/valet_plugins/tests/unit/test_valet_api.py
Normal file
32
plugins/valet_plugins/tests/unit/test_valet_api.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
#
|
||||||
|
# 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 mock
|
||||||
|
from valet_plugins.tests.base import Base
|
||||||
|
from valet_plugins.common.valet_api import ValetAPIWrapper, requests
|
||||||
|
|
||||||
|
|
||||||
|
class TestValetApi(Base):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestValetApi, self).setUp()
|
||||||
|
self.valet_api_wrapper = self.init_ValetAPIWrapper()
|
||||||
|
|
||||||
|
@mock.patch.object(ValetAPIWrapper, "_register_opts")
|
||||||
|
def init_ValetAPIWrapper(self, mock_api):
|
||||||
|
mock_api.return_value = None
|
||||||
|
return ValetAPIWrapper()
|
||||||
|
|
||||||
|
@mock.patch.object(requests, 'request')
|
||||||
|
def test_plans_create(self, mock_request):
|
||||||
|
mock_request.post.return_value = None
|
71
plugins/valet_plugins/tests/unit/test_valet_filter.py
Normal file
71
plugins/valet_plugins/tests/unit/test_valet_filter.py
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
#
|
||||||
|
# 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 keystoneclient.v2_0 import client
|
||||||
|
import mock
|
||||||
|
from valet_plugins.common import valet_api
|
||||||
|
from valet_plugins.plugins.nova.valet_filter import ValetFilter
|
||||||
|
from valet_plugins.tests.base import Base
|
||||||
|
|
||||||
|
|
||||||
|
class TestResources(object):
|
||||||
|
def __init__(self, host_name):
|
||||||
|
self.host = host_name
|
||||||
|
|
||||||
|
|
||||||
|
class TestValetFilter(Base):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestValetFilter, self).setUp()
|
||||||
|
|
||||||
|
client.Client = mock.MagicMock()
|
||||||
|
self.valet_filter = self.init_ValetFilter()
|
||||||
|
|
||||||
|
@mock.patch.object(valet_api.ValetAPIWrapper, '_register_opts')
|
||||||
|
@mock.patch.object(ValetFilter, '_register_opts')
|
||||||
|
def init_ValetFilter(self, mock_opt, mock_init):
|
||||||
|
mock_init.return_value = None
|
||||||
|
mock_opt.return_value = None
|
||||||
|
return ValetFilter()
|
||||||
|
|
||||||
|
@mock.patch.object(valet_api.ValetAPIWrapper, 'plans_create')
|
||||||
|
@mock.patch.object(valet_api.ValetAPIWrapper, 'placement')
|
||||||
|
def test_filter_all(self, mock_placement, mock_create):
|
||||||
|
mock_placement.return_value = None
|
||||||
|
mock_create.return_value = None
|
||||||
|
|
||||||
|
with mock.patch('oslo_config.cfg.CONF') as config:
|
||||||
|
setattr(config, "valet", {self.valet_filter.opt_failure_mode_str: "yield",
|
||||||
|
self.valet_filter.opt_project_name_str: "test_admin_tenant_name",
|
||||||
|
self.valet_filter.opt_username_str: "test_admin_username",
|
||||||
|
self.valet_filter.opt_password_str: "test_admin_password",
|
||||||
|
self.valet_filter.opt_auth_uri_str: "test_admin_auth_url"})
|
||||||
|
|
||||||
|
filter_properties = {'request_spec': {'instance_properties': {'uuid': ""}},
|
||||||
|
'scheduler_hints': {'heat_resource_uuid': "123456"},
|
||||||
|
'instance_type': {'name': "instance_name"}}
|
||||||
|
|
||||||
|
resources = self.valet_filter.filter_all([TestResources("first_host"), TestResources("second_host")], filter_properties)
|
||||||
|
|
||||||
|
for resource in resources:
|
||||||
|
self.validate_test(resource.host in "first_host, second_host")
|
||||||
|
self.validate_test(mock_placement.called)
|
||||||
|
|
||||||
|
filter_properties = {'request_spec': {'instance_properties': {'uuid': ""}},
|
||||||
|
'scheduler_hints': "scheduler_hints",
|
||||||
|
'instance_type': {'name': "instance_name"}}
|
||||||
|
|
||||||
|
resources = self.valet_filter.filter_all([TestResources("first_host"), TestResources("second_host")], filter_properties)
|
||||||
|
|
||||||
|
for _ in resources:
|
||||||
|
self.validate_test(mock_create.called)
|
@ -1,24 +0,0 @@
|
|||||||
# Valet
|
|
||||||
|
|
||||||
Valet gives OpenStack the ability to optimize cloud resources while simultaneously meeting a cloud application's QoS requirements. Valet provides an api service, a placement optimizer (Ostro), a high availability data storage and persistence layer (Music), and a set of OpenStack plugins.
|
|
||||||
|
|
||||||
**IMPORTANT**: [Overall Installation of Valet is covered in a separate document](https://github.com/att-comdev/valet/blob/master/doc/valet_os.md).
|
|
||||||
|
|
||||||
Learn more about Valet:
|
|
||||||
|
|
||||||
* [OpenStack Newton Summit Presentation](https://www.openstack.org/videos/video/valet-holistic-data-center-optimization-for-openstack) (Austin, TX, 27 April 2016)
|
|
||||||
* [Presentation Slides](http://www.research.att.com/export/sites/att_labs/techdocs/TD_101806.pdf) (PDF)
|
|
||||||
|
|
||||||
Valet consists of the following components:
|
|
||||||
|
|
||||||
* [valet-openstack](https://github.com/att-comdev/valet/blob/master/doc/valet_os.md): a set of OpenStack plugins used to interact with Valet
|
|
||||||
* [valet-api](https://github.com/att-comdev/valet/blob/master/doc/valet_api.md): an API engine used to interact with Valet
|
|
||||||
* [Ostro](https://github.com/att-comdev/valet/blob/master/doc/ostro.md): a placement optimization engine
|
|
||||||
* [Music](https://github.com/att/music): a data storage and persistence service
|
|
||||||
* [ostro-listener](https://github.com/att-comdev/valet/blob/master/doc/ostro_listener.md): a message bus listener used in conjunction with Ostro and Music
|
|
||||||
|
|
||||||
Additional documents:
|
|
||||||
|
|
||||||
* [OpenStack Heat Resource Plugins](https://github.com/att-comdev/valet/blob/master/valet_plugins/valet_plugins/heat/README.md): Heat resources
|
|
||||||
* [Placement API](https://github.com/att-comdev/valet/blob/master/doc/valet_api.md): API requests/responses
|
|
||||||
* [Using Postman with valet-api](https://github.com/att-comdev/valet/blob/master/valet/tests/api/README.md): Postman support
|
|
@ -1,30 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright 2014-2017 AT&T Intellectual Property
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
"""Setup."""
|
|
||||||
|
|
||||||
import setuptools
|
|
||||||
|
|
||||||
# In python < 2.7.4, a lazy loading of package `pbr` will break
|
|
||||||
# setuptools if some other modules registered functions in `atexit`.
|
|
||||||
# solution from: http://bugs.python.org/issue15881#msg170215
|
|
||||||
try:
|
|
||||||
import multiprocessing # noqa # pylint: disable=W0611,C0411
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
setuptools.setup(
|
|
||||||
setup_requires=['pbr'],
|
|
||||||
pbr=True)
|
|
@ -1,4 +0,0 @@
|
|||||||
Metadata-Version: 1.2
|
|
||||||
Name: valet_plugins
|
|
||||||
Version: 0.1.0
|
|
||||||
Author-email: jdandrea@research.att.com
|
|
@ -1,53 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright 2014-2017 AT&T Intellectual Property
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
"""Tests Base."""
|
|
||||||
|
|
||||||
from oslo_config import fixture as fixture_config
|
|
||||||
from oslo_log import log as logging
|
|
||||||
from oslotest.base import BaseTestCase
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class Base(BaseTestCase):
|
|
||||||
"""Test case base class for all unit tests."""
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwds):
|
|
||||||
"""Initialize."""
|
|
||||||
super(Base, self).__init__(*args, **kwds)
|
|
||||||
|
|
||||||
self.CONF = self.useFixture(fixture_config.Config()).conf
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
"""Setup."""
|
|
||||||
super(Base, self).setUp()
|
|
||||||
|
|
||||||
def run_test(self, stack_name, template_path):
|
|
||||||
"""Main function."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def validate(self, result):
|
|
||||||
"""Validate."""
|
|
||||||
self.assertEqual(True, result.ok, result.message)
|
|
||||||
|
|
||||||
def validate_test(self, result):
|
|
||||||
"""Validate Test."""
|
|
||||||
self.assertTrue(result)
|
|
||||||
|
|
||||||
def get_name(self):
|
|
||||||
"""Get Name."""
|
|
||||||
pass
|
|
@ -1,19 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright 2014-2017 AT&T Intellectual Property
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
"""i18n."""
|
|
||||||
|
|
||||||
|
|
||||||
_ = None
|
|
@ -1,23 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright 2014-2017 AT&T Intellectual Property
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
"""Lifecycle Plugin."""
|
|
||||||
|
|
||||||
|
|
||||||
class LifecyclePlugin(object):
|
|
||||||
"""Classdoc."""
|
|
||||||
|
|
||||||
def __init__(self, params):
|
|
||||||
"""Constructor."""
|
|
@ -1,32 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright 2014-2017 AT&T Intellectual Property
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
"""i18n."""
|
|
||||||
|
|
||||||
|
|
||||||
def _(string):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def _LI(string):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def _LW(string):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def _LE(string):
|
|
||||||
return string
|
|
@ -1,23 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright 2014-2017 AT&T Intellectual Property
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
"""Filters."""
|
|
||||||
|
|
||||||
|
|
||||||
class BaseHostFilter(object):
|
|
||||||
"""Classdocs."""
|
|
||||||
|
|
||||||
def __init__(self, params):
|
|
||||||
"""Constructor."""
|
|
@ -1,40 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright 2014-2017 AT&T Intellectual Property
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
"""Test Valet API."""
|
|
||||||
|
|
||||||
import mock
|
|
||||||
from valet_plugins.tests.base import Base
|
|
||||||
from valet_plugins.common.valet_api import ValetAPIWrapper, requests
|
|
||||||
|
|
||||||
|
|
||||||
class TestValetApi(Base):
|
|
||||||
"""Test Valet Plugins API."""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
"""Setup Test Valet Api and call ValetAPIWrapper init."""
|
|
||||||
super(TestValetApi, self).setUp()
|
|
||||||
self.valet_api_wrapper = self.init_ValetAPIWrapper()
|
|
||||||
|
|
||||||
@mock.patch.object(ValetAPIWrapper, "_register_opts")
|
|
||||||
def init_ValetAPIWrapper(self, mock_api):
|
|
||||||
"""Called by setup, mock api return value to none."""
|
|
||||||
mock_api.return_value = None
|
|
||||||
return ValetAPIWrapper()
|
|
||||||
|
|
||||||
@mock.patch.object(requests, 'request')
|
|
||||||
def test_plans_create(self, mock_request):
|
|
||||||
"""Test Plans create, mock request return value to none."""
|
|
||||||
mock_request.post.return_value = None
|
|
@ -1,91 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright 2014-2017 AT&T Intellectual Property
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
"""Test Valet Filter."""
|
|
||||||
|
|
||||||
from keystoneclient.v2_0 import client
|
|
||||||
import mock
|
|
||||||
from valet_plugins.common import valet_api
|
|
||||||
from valet_plugins.plugins.nova.valet_filter import ValetFilter
|
|
||||||
from valet_plugins.tests.base import Base
|
|
||||||
|
|
||||||
|
|
||||||
class TestResources(object):
|
|
||||||
"""Test Resources."""
|
|
||||||
|
|
||||||
def __init__(self, host_name):
|
|
||||||
"""Initialize."""
|
|
||||||
self.host = host_name
|
|
||||||
|
|
||||||
|
|
||||||
class TestValetFilter(Base):
|
|
||||||
"""Test Valet Filter Base."""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
"""Setup by mocking client and init valet filter."""
|
|
||||||
super(TestValetFilter, self).setUp()
|
|
||||||
|
|
||||||
client.Client = mock.MagicMock()
|
|
||||||
self.valet_filter = self.init_ValetFilter()
|
|
||||||
|
|
||||||
@mock.patch.object(valet_api.ValetAPIWrapper, '_register_opts')
|
|
||||||
@mock.patch.object(ValetFilter, '_register_opts')
|
|
||||||
def init_ValetFilter(self, mock_opt, mock_init):
|
|
||||||
"""Called by setup, mock init and opt and return ValetFilter()."""
|
|
||||||
mock_init.return_value = None
|
|
||||||
mock_opt.return_value = None
|
|
||||||
return ValetFilter()
|
|
||||||
|
|
||||||
@mock.patch.object(valet_api.ValetAPIWrapper, 'plans_create')
|
|
||||||
@mock.patch.object(valet_api.ValetAPIWrapper, 'placement')
|
|
||||||
def test_filter_all(self, mock_placement, mock_create):
|
|
||||||
"""Test Filter All by validating resource host values."""
|
|
||||||
mock_placement.return_value = None
|
|
||||||
mock_create.return_value = None
|
|
||||||
|
|
||||||
with mock.patch('oslo_config.cfg.CONF') as config:
|
|
||||||
setattr(config, "valet",
|
|
||||||
{self.valet_filter.opt_failure_mode_str: "yield",
|
|
||||||
self.valet_filter.opt_project_name_str:
|
|
||||||
"test_admin_tenant_name",
|
|
||||||
self.valet_filter.opt_username_str: "test_admin_username",
|
|
||||||
self.valet_filter.opt_password_str: "test_admin_password",
|
|
||||||
self.valet_filter.opt_auth_uri_str: "test_admin_auth_url"})
|
|
||||||
|
|
||||||
filter_properties = {'request_spec': {'instance_properties':
|
|
||||||
{'uuid': ""}},
|
|
||||||
'scheduler_hints':
|
|
||||||
{'heat_resource_uuid': "123456"},
|
|
||||||
'instance_type': {'name': "instance_name"}}
|
|
||||||
|
|
||||||
resources = self.valet_filter.filter_all(
|
|
||||||
[TestResources("first_host"), TestResources("second_host")],
|
|
||||||
filter_properties)
|
|
||||||
|
|
||||||
for resource in resources:
|
|
||||||
self.validate_test(resource.host in "first_host, second_host")
|
|
||||||
self.validate_test(mock_placement.called)
|
|
||||||
|
|
||||||
filter_properties = {'request_spec': {'instance_properties':
|
|
||||||
{'uuid': ""}},
|
|
||||||
'scheduler_hints': "scheduler_hints",
|
|
||||||
'instance_type': {'name': "instance_name"}}
|
|
||||||
|
|
||||||
resources = self.valet_filter.filter_all(
|
|
||||||
[TestResources("first_host"), TestResources("second_host")],
|
|
||||||
filter_properties)
|
|
||||||
|
|
||||||
for _ in resources:
|
|
||||||
self.validate_test(mock_create.called)
|
|
Loading…
Reference in New Issue
Block a user