Merge "Refactored Watcher API service"
This commit is contained in:
@@ -6,6 +6,7 @@ enum34;python_version=='2.7' or python_version=='2.6' or python_version=='3.3' #
|
|||||||
jsonpatch>=1.1 # BSD
|
jsonpatch>=1.1 # BSD
|
||||||
keystoneauth1>=2.1.0 # Apache-2.0
|
keystoneauth1>=2.1.0 # Apache-2.0
|
||||||
keystonemiddleware!=4.1.0,>=4.0.0 # Apache-2.0
|
keystonemiddleware!=4.1.0,>=4.0.0 # Apache-2.0
|
||||||
|
oslo.concurrency>=3.5.0 # Apache-2.0
|
||||||
oslo.config>=3.9.0 # Apache-2.0
|
oslo.config>=3.9.0 # Apache-2.0
|
||||||
oslo.context>=2.2.0 # Apache-2.0
|
oslo.context>=2.2.0 # Apache-2.0
|
||||||
oslo.db>=4.1.0 # Apache-2.0
|
oslo.db>=4.1.0 # Apache-2.0
|
||||||
@@ -13,6 +14,7 @@ oslo.i18n>=2.1.0 # Apache-2.0
|
|||||||
oslo.log>=1.14.0 # Apache-2.0
|
oslo.log>=1.14.0 # Apache-2.0
|
||||||
oslo.messaging>=4.5.0 # Apache-2.0
|
oslo.messaging>=4.5.0 # Apache-2.0
|
||||||
oslo.policy>=0.5.0 # Apache-2.0
|
oslo.policy>=0.5.0 # Apache-2.0
|
||||||
|
oslo.reports>=0.6.0 # Apache-2.0
|
||||||
oslo.service>=1.0.0 # Apache-2.0
|
oslo.service>=1.0.0 # Apache-2.0
|
||||||
oslo.utils>=3.5.0 # Apache-2.0
|
oslo.utils>=3.5.0 # Apache-2.0
|
||||||
PasteDeploy>=1.5.0 # MIT
|
PasteDeploy>=1.5.0 # MIT
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
import pecan
|
import pecan
|
||||||
|
|
||||||
|
from watcher._i18n import _
|
||||||
from watcher.api import acl
|
from watcher.api import acl
|
||||||
from watcher.api import config as api_config
|
from watcher.api import config as api_config
|
||||||
from watcher.api import middleware
|
from watcher.api import middleware
|
||||||
@@ -27,16 +28,31 @@ from watcher.decision_engine.strategy.selection import default \
|
|||||||
|
|
||||||
# Register options for the service
|
# Register options for the service
|
||||||
API_SERVICE_OPTS = [
|
API_SERVICE_OPTS = [
|
||||||
cfg.IntOpt('port',
|
cfg.PortOpt('port',
|
||||||
default=9322,
|
default=9322,
|
||||||
help='The port for the watcher API server'),
|
help=_('The port for the watcher API server')),
|
||||||
cfg.StrOpt('host',
|
cfg.StrOpt('host',
|
||||||
default='0.0.0.0',
|
default='0.0.0.0',
|
||||||
help='The listen IP for the watcher API server'),
|
help=_('The listen IP for the watcher API server')),
|
||||||
cfg.IntOpt('max_limit',
|
cfg.IntOpt('max_limit',
|
||||||
default=1000,
|
default=1000,
|
||||||
help='The maximum number of items returned in a single '
|
help=_('The maximum number of items returned in a single '
|
||||||
'response from a collection resource.')
|
'response from a collection resource')),
|
||||||
|
cfg.IntOpt('workers',
|
||||||
|
min=1,
|
||||||
|
help=_('Number of workers for Watcher API service. '
|
||||||
|
'The default is equal to the number of CPUs available '
|
||||||
|
'if that can be determined, else a default worker '
|
||||||
|
'count of 1 is returned.')),
|
||||||
|
|
||||||
|
cfg.BoolOpt('enable_ssl_api',
|
||||||
|
default=False,
|
||||||
|
help=_("Enable the integrated stand-alone API to service "
|
||||||
|
"requests via HTTPS instead of HTTP. If there is a "
|
||||||
|
"front-end service performing HTTPS offloading from "
|
||||||
|
"the service, this option should be False; note, you "
|
||||||
|
"will want to change public API endpoint to represent "
|
||||||
|
"SSL termination URL with 'public_endpoint' option.")),
|
||||||
]
|
]
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
@@ -68,3 +84,12 @@ def setup_app(config=None):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return acl.install(app, CONF, config.app.acl_public_routes)
|
return acl.install(app, CONF, config.app.acl_public_routes)
|
||||||
|
|
||||||
|
|
||||||
|
class VersionSelectorApplication(object):
|
||||||
|
def __init__(self):
|
||||||
|
pc = get_pecan_config()
|
||||||
|
self.v1 = setup_app(config=pc)
|
||||||
|
|
||||||
|
def __call__(self, environ, start_response):
|
||||||
|
return self.v1(environ, start_response)
|
||||||
|
|||||||
@@ -17,17 +17,15 @@
|
|||||||
|
|
||||||
"""Starter script for the Watcher API service."""
|
"""Starter script for the Watcher API service."""
|
||||||
|
|
||||||
import logging as std_logging
|
|
||||||
import os
|
|
||||||
import sys
|
import sys
|
||||||
from wsgiref import simple_server
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
from oslo_reports import guru_meditation_report as gmr
|
||||||
|
|
||||||
from watcher._i18n import _
|
from watcher._i18n import _
|
||||||
from watcher.api import app as api_app
|
|
||||||
from watcher.common import service
|
from watcher.common import service
|
||||||
|
from watcher import version
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@@ -37,15 +35,12 @@ CONF = cfg.CONF
|
|||||||
def main():
|
def main():
|
||||||
service.prepare_service(sys.argv)
|
service.prepare_service(sys.argv)
|
||||||
|
|
||||||
app = api_app.setup_app()
|
gmr.TextGuruMeditation.setup_autorun(version)
|
||||||
|
|
||||||
# Create the WSGI server and start it
|
|
||||||
host, port = cfg.CONF.api.host, cfg.CONF.api.port
|
host, port = cfg.CONF.api.host, cfg.CONF.api.port
|
||||||
srv = simple_server.make_server(host, port, app)
|
# Build and start the WSGI app
|
||||||
|
server = service.WSGIService(
|
||||||
LOG.info(_('Starting server in PID %s') % os.getpid())
|
'watcher-api', CONF.api.enable_ssl_api)
|
||||||
LOG.debug("Watcher configuration:")
|
|
||||||
cfg.CONF.log_opt_values(LOG, std_logging.DEBUG)
|
|
||||||
|
|
||||||
if host == '0.0.0.0':
|
if host == '0.0.0.0':
|
||||||
LOG.info(_('serving on 0.0.0.0:%(port)s, '
|
LOG.info(_('serving on 0.0.0.0:%(port)s, '
|
||||||
@@ -55,4 +50,6 @@ def main():
|
|||||||
LOG.info(_('serving on http://%(host)s:%(port)s') %
|
LOG.info(_('serving on http://%(host)s:%(port)s') %
|
||||||
dict(host=host, port=port))
|
dict(host=host, port=port))
|
||||||
|
|
||||||
srv.serve_forever()
|
launcher = service.process_launcher()
|
||||||
|
launcher.launch_service(server, workers=server.workers)
|
||||||
|
launcher.wait()
|
||||||
|
|||||||
@@ -15,108 +15,39 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import signal
|
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
|
from oslo_concurrency import processutils
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import _options
|
from oslo_log import _options
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
import oslo_messaging as messaging
|
from oslo_reports import opts as gmr_opts
|
||||||
from oslo_service import service
|
from oslo_service import service
|
||||||
from oslo_utils import importutils
|
from oslo_service import wsgi
|
||||||
|
|
||||||
from watcher._i18n import _LE
|
from watcher._i18n import _
|
||||||
from watcher._i18n import _LI
|
from watcher.api import app
|
||||||
from watcher.common import config
|
from watcher.common import config
|
||||||
from watcher.common import context
|
|
||||||
from watcher.common import rpc
|
|
||||||
from watcher.objects import base as objects_base
|
|
||||||
|
|
||||||
|
|
||||||
service_opts = [
|
service_opts = [
|
||||||
cfg.IntOpt('periodic_interval',
|
cfg.IntOpt('periodic_interval',
|
||||||
default=60,
|
default=60,
|
||||||
help='Seconds between running periodic tasks.'),
|
help=_('Seconds between running periodic tasks.')),
|
||||||
cfg.StrOpt('host',
|
cfg.StrOpt('host',
|
||||||
default=socket.getfqdn(),
|
default=socket.getfqdn(),
|
||||||
help='Name of this node. This can be an opaque identifier. '
|
help=_('Name of this node. This can be an opaque identifier. '
|
||||||
'It is not necessarily a hostname, FQDN, or IP address. '
|
'It is not necessarily a hostname, FQDN, or IP address. '
|
||||||
'However, the node name must be valid within '
|
'However, the node name must be valid within '
|
||||||
'an AMQP key, and if using ZeroMQ, a valid '
|
'an AMQP key, and if using ZeroMQ, a valid '
|
||||||
'hostname, FQDN, or IP address.'),
|
'hostname, FQDN, or IP address.')),
|
||||||
]
|
]
|
||||||
|
|
||||||
cfg.CONF.register_opts(service_opts)
|
cfg.CONF.register_opts(service_opts)
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class RPCService(service.Service):
|
|
||||||
|
|
||||||
def __init__(self, host, manager_module, manager_class):
|
|
||||||
super(RPCService, self).__init__()
|
|
||||||
self.host = host
|
|
||||||
manager_module = importutils.try_import(manager_module)
|
|
||||||
manager_class = getattr(manager_module, manager_class)
|
|
||||||
self.manager = manager_class(host, manager_module.MANAGER_TOPIC)
|
|
||||||
self.topic = self.manager.topic
|
|
||||||
self.rpcserver = None
|
|
||||||
self.deregister = True
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
super(RPCService, self).start()
|
|
||||||
admin_context = context.RequestContext('admin', 'admin', is_admin=True)
|
|
||||||
|
|
||||||
target = messaging.Target(topic=self.topic, server=self.host)
|
|
||||||
endpoints = [self.manager]
|
|
||||||
serializer = objects_base.IronicObjectSerializer()
|
|
||||||
self.rpcserver = rpc.get_server(target, endpoints, serializer)
|
|
||||||
self.rpcserver.start()
|
|
||||||
|
|
||||||
self.handle_signal()
|
|
||||||
self.manager.init_host()
|
|
||||||
self.tg.add_dynamic_timer(
|
|
||||||
self.manager.periodic_tasks,
|
|
||||||
periodic_interval_max=cfg.CONF.periodic_interval,
|
|
||||||
context=admin_context)
|
|
||||||
|
|
||||||
LOG.info(_LI('Created RPC server for service %(service)s on host '
|
|
||||||
'%(host)s.'),
|
|
||||||
{'service': self.topic, 'host': self.host})
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
try:
|
|
||||||
self.rpcserver.stop()
|
|
||||||
self.rpcserver.wait()
|
|
||||||
except Exception as e:
|
|
||||||
LOG.exception(_LE('Service error occurred when stopping the '
|
|
||||||
'RPC server. Error: %s'), e)
|
|
||||||
try:
|
|
||||||
self.manager.del_host(deregister=self.deregister)
|
|
||||||
except Exception as e:
|
|
||||||
LOG.exception(_LE('Service error occurred when cleaning up '
|
|
||||||
'the RPC manager. Error: %s'), e)
|
|
||||||
|
|
||||||
super(RPCService, self).stop(graceful=True)
|
|
||||||
LOG.info(_LI('Stopped RPC server for service %(service)s on host '
|
|
||||||
'%(host)s.'),
|
|
||||||
{'service': self.topic, 'host': self.host})
|
|
||||||
|
|
||||||
def _handle_signal(self):
|
|
||||||
LOG.info(_LI('Got signal SIGUSR1. Not deregistering on next shutdown '
|
|
||||||
'of service %(service)s on host %(host)s.'),
|
|
||||||
{'service': self.topic, 'host': self.host})
|
|
||||||
self.deregister = False
|
|
||||||
|
|
||||||
def handle_signal(self):
|
|
||||||
"""Add a signal handler for SIGUSR1.
|
|
||||||
|
|
||||||
The handler ensures that the manager is not deregistered when it is
|
|
||||||
shutdown.
|
|
||||||
"""
|
|
||||||
signal.signal(signal.SIGUSR1, self._handle_signal)
|
|
||||||
|
|
||||||
|
|
||||||
_DEFAULT_LOG_LEVELS = ['amqp=WARN', 'amqplib=WARN', 'qpid.messaging=INFO',
|
_DEFAULT_LOG_LEVELS = ['amqp=WARN', 'amqplib=WARN', 'qpid.messaging=INFO',
|
||||||
'oslo.messaging=INFO', 'sqlalchemy=WARN',
|
'oslo.messaging=INFO', 'sqlalchemy=WARN',
|
||||||
'keystoneclient=INFO', 'stevedore=INFO',
|
'keystoneclient=INFO', 'stevedore=INFO',
|
||||||
@@ -125,8 +56,50 @@ _DEFAULT_LOG_LEVELS = ['amqp=WARN', 'amqplib=WARN', 'qpid.messaging=INFO',
|
|||||||
'glanceclient=WARN', 'watcher.openstack.common=WARN']
|
'glanceclient=WARN', 'watcher.openstack.common=WARN']
|
||||||
|
|
||||||
|
|
||||||
def prepare_service(argv=[], conf=cfg.CONF):
|
class WSGIService(service.ServiceBase):
|
||||||
|
"""Provides ability to launch Watcher API from wsgi app."""
|
||||||
|
|
||||||
|
def __init__(self, name, use_ssl=False):
|
||||||
|
"""Initialize, but do not start the WSGI server.
|
||||||
|
|
||||||
|
:param name: The name of the WSGI server given to the loader.
|
||||||
|
:param use_ssl: Wraps the socket in an SSL context if True.
|
||||||
|
"""
|
||||||
|
self.name = name
|
||||||
|
self.app = app.VersionSelectorApplication()
|
||||||
|
self.workers = (CONF.api.workers or
|
||||||
|
processutils.get_worker_count())
|
||||||
|
self.server = wsgi.Server(CONF, name, self.app,
|
||||||
|
host=CONF.api.host,
|
||||||
|
port=CONF.api.port,
|
||||||
|
use_ssl=use_ssl,
|
||||||
|
logger_name=name)
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
"""Start serving this service using loaded configuration"""
|
||||||
|
self.server.start()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
"""Stop serving this API"""
|
||||||
|
self.server.stop()
|
||||||
|
|
||||||
|
def wait(self):
|
||||||
|
"""Wait for the service to stop serving this API"""
|
||||||
|
self.server.wait()
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
"""Reset server greenpool size to default"""
|
||||||
|
self.server.reset()
|
||||||
|
|
||||||
|
|
||||||
|
def process_launcher(conf=cfg.CONF):
|
||||||
|
return service.ProcessLauncher(conf)
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_service(argv=(), conf=cfg.CONF):
|
||||||
log.register_options(conf)
|
log.register_options(conf)
|
||||||
|
gmr_opts.set_defaults(conf)
|
||||||
|
|
||||||
config.parse_args(argv)
|
config.parse_args(argv)
|
||||||
cfg.set_defaults(_options.log_opts,
|
cfg.set_defaults(_options.log_opts,
|
||||||
default_log_levels=_DEFAULT_LOG_LEVELS)
|
default_log_levels=_DEFAULT_LOG_LEVELS)
|
||||||
|
|||||||
@@ -7,16 +7,46 @@
|
|||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: python-watcher 0.25.1.dev3\n"
|
"Project-Id-Version: python-watcher 0.26.1.dev13\n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2016-03-30 10:10+0200\n"
|
"POT-Creation-Date: 2016-04-22 10:31+0200\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=utf-8\n"
|
"Content-Type: text/plain; charset=utf-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Generated-By: Babel 2.2.0\n"
|
"Generated-By: Babel 2.3.2\n"
|
||||||
|
|
||||||
|
#: watcher/api/app.py:33
|
||||||
|
msgid "The port for the watcher API server"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/api/app.py:36
|
||||||
|
msgid "The listen IP for the watcher API server"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/api/app.py:39
|
||||||
|
msgid ""
|
||||||
|
"The maximum number of items returned in a single response from a "
|
||||||
|
"collection resource"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/api/app.py:43
|
||||||
|
msgid ""
|
||||||
|
"Number of workers for Watcher API service. The default is equal to the "
|
||||||
|
"number of CPUs available if that can be determined, else a default worker"
|
||||||
|
" count of 1 is returned."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/api/app.py:50
|
||||||
|
msgid ""
|
||||||
|
"Enable the integrated stand-alone API to service requests via HTTPS "
|
||||||
|
"instead of HTTP. If there is a front-end service performing HTTPS "
|
||||||
|
"offloading from the service, this option should be False; note, you will "
|
||||||
|
"want to change public API endpoint to represent SSL termination URL with "
|
||||||
|
"'public_endpoint' option."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/api/controllers/v1/action.py:364
|
#: watcher/api/controllers/v1/action.py:364
|
||||||
msgid "Cannot create an action directly"
|
msgid "Cannot create an action directly"
|
||||||
@@ -30,41 +60,41 @@ msgstr ""
|
|||||||
msgid "Cannot delete an action directly"
|
msgid "Cannot delete an action directly"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/api/controllers/v1/action_plan.py:102
|
#: watcher/api/controllers/v1/action_plan.py:87
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Invalid state: %(state)s"
|
msgid "Invalid state: %(state)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/api/controllers/v1/action_plan.py:422
|
#: watcher/api/controllers/v1/action_plan.py:407
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "State transition not allowed: (%(initial_state)s -> %(new_state)s)"
|
msgid "State transition not allowed: (%(initial_state)s -> %(new_state)s)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/api/controllers/v1/audit.py:359
|
#: watcher/api/controllers/v1/audit.py:365
|
||||||
msgid "The audit template UUID or name specified is invalid"
|
msgid "The audit template UUID or name specified is invalid"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/api/controllers/v1/types.py:148
|
#: watcher/api/controllers/v1/types.py:123
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%s is not JSON serializable"
|
msgid "%s is not JSON serializable"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/api/controllers/v1/types.py:184
|
#: watcher/api/controllers/v1/types.py:159
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Wrong type. Expected '%(type)s', got '%(value)s'"
|
msgid "Wrong type. Expected '%(type)s', got '%(value)s'"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/api/controllers/v1/types.py:223
|
#: watcher/api/controllers/v1/types.py:198
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "'%s' is an internal attribute and can not be updated"
|
msgid "'%s' is an internal attribute and can not be updated"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/api/controllers/v1/types.py:227
|
#: watcher/api/controllers/v1/types.py:202
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "'%s' is a mandatory attribute and can not be removed"
|
msgid "'%s' is a mandatory attribute and can not be removed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/api/controllers/v1/types.py:232
|
#: watcher/api/controllers/v1/types.py:207
|
||||||
msgid "'add' and 'replace' operations needs value"
|
msgid "'add' and 'replace' operations needs value"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -135,22 +165,21 @@ msgstr ""
|
|||||||
msgid "Oops! We need disaster recover plan"
|
msgid "Oops! We need disaster recover plan"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/cmd/api.py:46 watcher/cmd/applier.py:39
|
#: watcher/cmd/api.py:46
|
||||||
#: watcher/cmd/decisionengine.py:40
|
|
||||||
#, python-format
|
|
||||||
msgid "Starting server in PID %s"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: watcher/cmd/api.py:51
|
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "serving on 0.0.0.0:%(port)s, view at http://127.0.0.1:%(port)s"
|
msgid "serving on 0.0.0.0:%(port)s, view at http://127.0.0.1:%(port)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/cmd/api.py:55
|
#: watcher/cmd/api.py:50
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "serving on http://%(host)s:%(port)s"
|
msgid "serving on http://%(host)s:%(port)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/cmd/applier.py:39 watcher/cmd/decisionengine.py:40
|
||||||
|
#, python-format
|
||||||
|
msgid "Starting server in PID %s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/clients.py:29
|
#: watcher/common/clients.py:29
|
||||||
msgid "Version of Nova API to use in novaclient."
|
msgid "Version of Nova API to use in novaclient."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -252,149 +281,139 @@ msgstr ""
|
|||||||
|
|
||||||
#: watcher/common/exception.py:184
|
#: watcher/common/exception.py:184
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Audit %(audit)s could not be found"
|
msgid "Audit type %(audit_type)s could not be found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:188
|
#: watcher/common/exception.py:188
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "An audit with UUID %(uuid)s already exists"
|
msgid "Audit %(audit)s could not be found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:192
|
#: watcher/common/exception.py:192
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Audit %(audit)s is referenced by one or multiple action plans"
|
msgid "An audit with UUID %(uuid)s already exists"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:197
|
#: watcher/common/exception.py:196
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "ActionPlan %(action_plan)s could not be found"
|
msgid "Audit %(audit)s is referenced by one or multiple action plans"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:201
|
#: watcher/common/exception.py:201
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "An action plan with UUID %(uuid)s already exists"
|
msgid "ActionPlan %(action_plan)s could not be found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:205
|
#: watcher/common/exception.py:205
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Action Plan %(action_plan)s is referenced by one or multiple actions"
|
msgid "An action plan with UUID %(uuid)s already exists"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:210
|
#: watcher/common/exception.py:209
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Action %(action)s could not be found"
|
msgid "Action Plan %(action_plan)s is referenced by one or multiple actions"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:214
|
#: watcher/common/exception.py:214
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "An action with UUID %(uuid)s already exists"
|
msgid "Action %(action)s could not be found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:218
|
#: watcher/common/exception.py:218
|
||||||
#, python-format
|
#, python-format
|
||||||
|
msgid "An action with UUID %(uuid)s already exists"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/exception.py:222
|
||||||
|
#, python-format
|
||||||
msgid "Action plan %(action_plan)s is referenced by one or multiple goals"
|
msgid "Action plan %(action_plan)s is referenced by one or multiple goals"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:223
|
#: watcher/common/exception.py:227
|
||||||
msgid "Filtering actions on both audit and action-plan is prohibited"
|
msgid "Filtering actions on both audit and action-plan is prohibited"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:232
|
#: watcher/common/exception.py:236
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Couldn't apply patch '%(patch)s'. Reason: %(reason)s"
|
msgid "Couldn't apply patch '%(patch)s'. Reason: %(reason)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:238
|
#: watcher/common/exception.py:242
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Workflow execution error: %(error)s"
|
msgid "Workflow execution error: %(error)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:242
|
#: watcher/common/exception.py:246
|
||||||
msgid "Illegal argument"
|
msgid "Illegal argument"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:246
|
#: watcher/common/exception.py:250
|
||||||
msgid "No such metric"
|
msgid "No such metric"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:250
|
#: watcher/common/exception.py:254
|
||||||
msgid "No rows were returned"
|
msgid "No rows were returned"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:254
|
#: watcher/common/exception.py:258
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(client)s connection failed. Reason: %(reason)s"
|
msgid "%(client)s connection failed. Reason: %(reason)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:258
|
#: watcher/common/exception.py:262
|
||||||
msgid "'Keystone API endpoint is missing''"
|
msgid "'Keystone API endpoint is missing''"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:262
|
#: watcher/common/exception.py:266
|
||||||
msgid "The list of hypervisor(s) in the cluster is empty"
|
msgid "The list of hypervisor(s) in the cluster is empty"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:266
|
#: watcher/common/exception.py:270
|
||||||
msgid "The metrics resource collector is not defined"
|
msgid "The metrics resource collector is not defined"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:270
|
#: watcher/common/exception.py:274
|
||||||
msgid "the cluster state is not defined"
|
msgid "the cluster state is not defined"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:276
|
#: watcher/common/exception.py:280
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "The instance '%(name)s' is not found"
|
msgid "The instance '%(name)s' is not found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:280
|
|
||||||
msgid "The hypervisor is not found"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: watcher/common/exception.py:284
|
#: watcher/common/exception.py:284
|
||||||
#, python-format
|
msgid "The hypervisor is not found"
|
||||||
msgid "Error loading plugin '%(name)s'"
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:288
|
#: watcher/common/exception.py:288
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "The identifier '%(name)s' is a reserved word"
|
msgid "Error loading plugin '%(name)s'"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:292
|
#: watcher/common/exception.py:292
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "The %(name)s resource %(id)s is not soft deleted"
|
msgid "The identifier '%(name)s' is a reserved word"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/exception.py:296
|
#: watcher/common/exception.py:296
|
||||||
|
#, python-format
|
||||||
|
msgid "The %(name)s resource %(id)s is not soft deleted"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/exception.py:300
|
||||||
msgid "Limit should be positive"
|
msgid "Limit should be positive"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/service.py:83
|
#: watcher/common/service.py:36
|
||||||
#, python-format
|
msgid "Seconds between running periodic tasks."
|
||||||
msgid "Created RPC server for service %(service)s on host %(host)s."
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/service.py:92
|
#: watcher/common/service.py:39
|
||||||
#, python-format
|
|
||||||
msgid "Service error occurred when stopping the RPC server. Error: %s"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: watcher/common/service.py:97
|
|
||||||
#, python-format
|
|
||||||
msgid "Service error occurred when cleaning up the RPC manager. Error: %s"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: watcher/common/service.py:101
|
|
||||||
#, python-format
|
|
||||||
msgid "Stopped RPC server for service %(service)s on host %(host)s."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: watcher/common/service.py:106
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Got signal SIGUSR1. Not deregistering on next shutdown of service "
|
"Name of this node. This can be an opaque identifier. It is not "
|
||||||
"%(service)s on host %(host)s."
|
"necessarily a hostname, FQDN, or IP address. However, the node name must "
|
||||||
|
"be valid within an AMQP key, and if using ZeroMQ, a valid hostname, FQDN,"
|
||||||
|
" or IP address."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/common/utils.py:53
|
#: watcher/common/utils.py:53
|
||||||
@@ -489,25 +508,25 @@ msgstr ""
|
|||||||
msgid "Purge process completed"
|
msgid "Purge process completed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/db/sqlalchemy/api.py:362
|
#: watcher/db/sqlalchemy/api.py:361
|
||||||
msgid ""
|
msgid ""
|
||||||
"Multiple audit templates exist with the same name. Please use the audit "
|
"Multiple audit templates exist with the same name. Please use the audit "
|
||||||
"template uuid instead"
|
"template uuid instead"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/db/sqlalchemy/api.py:384
|
#: watcher/db/sqlalchemy/api.py:383
|
||||||
msgid "Cannot overwrite UUID for an existing Audit Template."
|
msgid "Cannot overwrite UUID for an existing Audit Template."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/db/sqlalchemy/api.py:495
|
#: watcher/db/sqlalchemy/api.py:494
|
||||||
msgid "Cannot overwrite UUID for an existing Audit."
|
msgid "Cannot overwrite UUID for an existing Audit."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/db/sqlalchemy/api.py:588
|
#: watcher/db/sqlalchemy/api.py:587
|
||||||
msgid "Cannot overwrite UUID for an existing Action."
|
msgid "Cannot overwrite UUID for an existing Action."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/db/sqlalchemy/api.py:699
|
#: watcher/db/sqlalchemy/api.py:698
|
||||||
msgid "Cannot overwrite UUID for an existing Action Plan."
|
msgid "Cannot overwrite UUID for an existing Action Plan."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -517,12 +536,12 @@ msgid ""
|
|||||||
"instead"
|
"instead"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/decision_engine/model/model_root.py:37
|
#: watcher/decision_engine/model/model_root.py:33
|
||||||
#: watcher/decision_engine/model/model_root.py:42
|
#: watcher/decision_engine/model/model_root.py:38
|
||||||
msgid "'obj' argument type is not valid"
|
msgid "'obj' argument type is not valid"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/decision_engine/planner/default.py:79
|
#: watcher/decision_engine/planner/default.py:78
|
||||||
msgid "The action plan is empty"
|
msgid "The action plan is empty"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -563,22 +582,22 @@ msgstr ""
|
|||||||
msgid "No proper target host could be found"
|
msgid "No proper target host could be found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/decision_engine/strategy/strategies/vm_workload_consolidation.py:104
|
#: watcher/decision_engine/strategy/strategies/vm_workload_consolidation.py:105
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Unexpexted resource state type, state=%(state)s, state_type=%(st)s."
|
msgid "Unexpexted resource state type, state=%(state)s, state_type=%(st)s."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/decision_engine/strategy/strategies/vm_workload_consolidation.py:156
|
#: watcher/decision_engine/strategy/strategies/vm_workload_consolidation.py:157
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Cannot live migrate: vm_uuid=%(vm_uuid)s, state=%(vm_state)s."
|
msgid "Cannot live migrate: vm_uuid=%(vm_uuid)s, state=%(vm_state)s."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/decision_engine/strategy/strategies/vm_workload_consolidation.py:240
|
#: watcher/decision_engine/strategy/strategies/vm_workload_consolidation.py:241
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "No values returned by %(resource_id)s for memory.usage or disk.root.size"
|
msgid "No values returned by %(resource_id)s for memory.usage or disk.root.size"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: watcher/decision_engine/strategy/strategies/vm_workload_consolidation.py:489
|
#: watcher/decision_engine/strategy/strategies/vm_workload_consolidation.py:490
|
||||||
msgid "Executing Smart Strategy"
|
msgid "Executing Smart Strategy"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -16,22 +16,22 @@
|
|||||||
|
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
from six.moves.socketserver import BaseServer
|
|
||||||
|
|
||||||
|
|
||||||
import types
|
import types
|
||||||
from wsgiref import simple_server
|
|
||||||
|
|
||||||
from mock import Mock
|
import mock
|
||||||
from mock import patch
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
from oslo_service import wsgi
|
||||||
from pecan.testing import load_test_app
|
from pecan.testing import load_test_app
|
||||||
|
|
||||||
from watcher.api import config as api_config
|
from watcher.api import config as api_config
|
||||||
from watcher.cmd import api
|
from watcher.cmd import api
|
||||||
from watcher.tests.base import BaseTestCase
|
from watcher.common import service
|
||||||
|
from watcher.tests import base
|
||||||
|
|
||||||
|
|
||||||
class TestApi(BaseTestCase):
|
class TestApi(base.BaseTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestApi, self).setUp()
|
super(TestApi, self).setUp()
|
||||||
|
|
||||||
@@ -48,19 +48,18 @@ class TestApi(BaseTestCase):
|
|||||||
super(TestApi, self).tearDown()
|
super(TestApi, self).tearDown()
|
||||||
self.conf._parse_cli_opts = self._parse_cli_opts
|
self.conf._parse_cli_opts = self._parse_cli_opts
|
||||||
|
|
||||||
@patch("watcher.api.app.pecan.make_app")
|
@mock.patch.object(wsgi, "Server", mock.Mock())
|
||||||
@patch.object(BaseServer, "serve_forever", Mock())
|
@mock.patch("watcher.api.app.pecan.make_app")
|
||||||
@patch.object(simple_server, "make_server")
|
@mock.patch.object(service, "process_launcher")
|
||||||
def test_run_api_app(self, m_make, m_make_app):
|
def test_run_api_app(self, m_launcher, m_make_app):
|
||||||
m_make_app.return_value = load_test_app(config=api_config.PECAN_CONFIG)
|
m_make_app.return_value = load_test_app(config=api_config.PECAN_CONFIG)
|
||||||
api.main()
|
api.main()
|
||||||
self.assertEqual(1, m_make.call_count)
|
self.assertEqual(1, m_launcher.call_count)
|
||||||
|
|
||||||
@patch("watcher.api.app.pecan.make_app")
|
@mock.patch("watcher.api.app.pecan.make_app")
|
||||||
@patch.object(BaseServer, "serve_forever", Mock())
|
@mock.patch.object(service, "process_launcher")
|
||||||
@patch.object(simple_server, "make_server")
|
def test_run_api_app_serve_specific_address(self, m_launcher, m_make_app):
|
||||||
def test_run_api_app_serve_specific_address(self, m_make, m_make_app):
|
|
||||||
cfg.CONF.set_default("host", "localhost", group="api")
|
cfg.CONF.set_default("host", "localhost", group="api")
|
||||||
m_make_app.return_value = load_test_app(config=api_config.PECAN_CONFIG)
|
m_make_app.return_value = load_test_app(config=api_config.PECAN_CONFIG)
|
||||||
api.main()
|
api.main()
|
||||||
self.assertEqual(1, m_make.call_count)
|
self.assertEqual(1, m_launcher.call_count)
|
||||||
|
|||||||
@@ -17,3 +17,4 @@
|
|||||||
import pbr.version
|
import pbr.version
|
||||||
|
|
||||||
version_info = pbr.version.VersionInfo('python-watcher')
|
version_info = pbr.version.VersionInfo('python-watcher')
|
||||||
|
version_string = version_info.version_string
|
||||||
|
|||||||
Reference in New Issue
Block a user