blob: fc1c142ec6246c5f966ff75a6c34abb3d61f618d [file] [log] [blame]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you 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 json
from tornado.options import options
from tornado.log import app_log as log
from tornado.httpclient import AsyncHTTPClient
from tornado.gen import coroutine
def _extract_options(body, local_mapping):
try:
dynamic_options = json.loads(body)
log.debug(
"Recieved dynamic options: {0}"
.format(dynamic_options))
return dynamic_options
except Exception as e:
dynamic_options = {}
log.error(
"Can not parse dynamic options, exception: {0}"
.format(e))
return dynamic_options
@coroutine
def process_dynamic_options(name_mapping, handlers, application=None, initial=False):
"""
Process dynamic updates.
`name_mappings` is a dict in the form `remote_option_name`: `local_option_name`
`handlers` is a list of callbacks to run after options update
`application` is the applicatiopn object, that may be needed by some handlers
`initial` is a boolean flag that tells if the caller is a request handler or main loop
Handlers signature:
`updated` is a list of actually updated options (ones that changed value)
`application` is the application object
`initial` is the initial flag
"""
if not options.dynamicOptionsURL:
log.debug("Dynamic options disabled")
updated = {}
else:
try:
client = AsyncHTTPClient()
response = yield client.fetch(options.dynamicOptionsURL)
dynamic_options = _extract_options(response.body, name_mapping)
updated = update_dynamic_options(dynamic_options, name_mapping)
except Exception as e:
log.error("Error while getting dynamic options")
log.error(e)
updated = {}
for handler in handlers:
try:
handler(updated, application, initial)
except Exception as e:
log.error("Error while running update handlers")
log.error(e)
def update_dynamic_options(dynamic_options, local_mapping):
updated = []
for remote_name, remote_value in dynamic_options.items():
if remote_name not in local_mapping:
log.debug(
"Recieved unsupported option for sync: {0}"
.format(remote_name))
continue
local_name = local_mapping[remote_name]
local_value = getattr(options, local_name)
if local_value != remote_value:
setattr(options, local_name, remote_value)
updated.append(local_name)
log.debug("Dynamic options for update {0}".format(updated))
return updated
def generate_dynamic_options(local_mapping):
log.debug("Generating synamic options")
dynamic_options = {}
for remote_name, local_name in local_mapping.items():
dynamic_options[remote_name] = getattr(options, local_name)
return dynamic_options
def _test_dynamic_options_update(
initial_options,
response_body,
expected_options,
local_mapping):
"""
initial_options - dict of initial options to be set.
response_body - body of the recieved response (expected to be json).
expected_options - values to be checked in options object after processing.
local_mapping - local mapping for option names.
"""
for option, value in initial_options.items():
setattr(options, option, value)
dynamic_options = json.loads(response_body)
update_dynamic_options(dynamic_options, local_mapping)
for option, value in expected_options.items():
assert getattr(options, option) == value