Syncrhonized plugins codebase

This commit is contained in:
Omar Rivera 2017-05-05 12:07:42 -05:00
parent 76ab80040a
commit d2e79829a3
49 changed files with 655 additions and 562 deletions

106
plugins/.gitignore vendored Normal file
View 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
View File

@ -0,0 +1,4 @@
[gerrit]
host=gerrit.mtn5.cci.att.com
port=29418
project=aic-valet-openstack-plugins.git

View File

@ -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
View 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>

View File

@ -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.

View File

@ -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

View File

@ -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
View 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)

View File

@ -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

View File

@ -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

View File

@ -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, }

View File

@ -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.

View File

@ -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

View File

@ -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

View 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

View File

@ -0,0 +1,7 @@
'''
Created on Sep 14, 2016
@author: stack
'''
_ = None

View File

@ -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 '''

View 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

View File

@ -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 '''

View File

@ -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])

View 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

View 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)

View File

@ -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

View File

@ -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)

View File

@ -1,4 +0,0 @@
Metadata-Version: 1.2
Name: valet_plugins
Version: 0.1.0
Author-email: jdandrea@research.att.com

View File

@ -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

View File

@ -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

View File

@ -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."""

View File

@ -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

View File

@ -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."""

View File

@ -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

View File

@ -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)