b12197bb0d
Currently we log any exceptions as INFO and without much details. Start logging them as ERROR with tracebacks. Add missing logging. However, don't treat KeyboardInterrupt as an exception, just exit. Also log the successful start-up, its absence is confusing. Change-Id: I7464ce425ed05248152f264d64950e0ab1c0aec0
225 lines
6.6 KiB
Python
225 lines
6.6 KiB
Python
# Copyright 2017 Red Hat, Inc.
|
|
# All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import json
|
|
import signal
|
|
import sys
|
|
|
|
import zmq
|
|
|
|
from virtualbmc import config as vbmc_config
|
|
from virtualbmc import exception
|
|
from virtualbmc import log
|
|
from virtualbmc.manager import VirtualBMCManager
|
|
|
|
CONF = vbmc_config.get_config()
|
|
|
|
LOG = log.get_logger()
|
|
|
|
TIMER_PERIOD = 3000 # milliseconds
|
|
|
|
|
|
def main_loop(vbmc_manager, handle_command):
|
|
"""Server part of the CLI control interface
|
|
|
|
Receives JSON messages from ZMQ socket, calls the command handler and
|
|
sends JSON response back to the client.
|
|
|
|
Client builds requests out of its command-line options which
|
|
include the command (e.g. `start`, `list` etc) and command-specific
|
|
options.
|
|
|
|
Server handles the commands and responds with a JSON document which
|
|
contains at least the `rc` and `msg` attributes, used to indicate the
|
|
outcome of the command, and optionally 2-D table conveyed through the
|
|
`header` and `rows` attributes pointing to lists of cell values.
|
|
"""
|
|
server_port = CONF['default']['server_port']
|
|
|
|
context = socket = None
|
|
|
|
try:
|
|
context = zmq.Context()
|
|
socket = context.socket(zmq.REP)
|
|
socket.setsockopt(zmq.LINGER, 5)
|
|
socket.bind("tcp://127.0.0.1:%s" % server_port)
|
|
|
|
poller = zmq.Poller()
|
|
poller.register(socket, zmq.POLLIN)
|
|
|
|
LOG.info('Started vBMC server on port %s', server_port)
|
|
|
|
while True:
|
|
socks = dict(poller.poll(timeout=TIMER_PERIOD))
|
|
if socket in socks and socks[socket] == zmq.POLLIN:
|
|
message = socket.recv()
|
|
else:
|
|
vbmc_manager.periodic()
|
|
continue
|
|
|
|
try:
|
|
data_in = json.loads(message.decode('utf-8'))
|
|
|
|
except ValueError as ex:
|
|
LOG.warning(
|
|
'Control server request deserialization error: '
|
|
'%(error)s', {'error': ex}
|
|
)
|
|
continue
|
|
|
|
LOG.debug('Command request data: %(request)s',
|
|
{'request': data_in})
|
|
|
|
try:
|
|
data_out = handle_command(vbmc_manager, data_in)
|
|
|
|
except exception.VirtualBMCError as ex:
|
|
msg = 'Command failed: %(error)s' % {'error': ex}
|
|
LOG.error(msg)
|
|
data_out = {
|
|
'rc': 1,
|
|
'msg': [msg]
|
|
}
|
|
|
|
LOG.debug('Command response data: %(response)s',
|
|
{'response': data_out})
|
|
|
|
try:
|
|
message = json.dumps(data_out)
|
|
|
|
except ValueError as ex:
|
|
LOG.warning(
|
|
'Control server response serialization error: '
|
|
'%(error)s', {'error': ex}
|
|
)
|
|
continue
|
|
|
|
socket.send(message.encode('utf-8'))
|
|
|
|
finally:
|
|
if socket:
|
|
socket.close()
|
|
if context:
|
|
context.destroy()
|
|
|
|
|
|
def command_dispatcher(vbmc_manager, data_in):
|
|
"""Control CLI command dispatcher
|
|
|
|
Calls vBMC manager to execute commands, implements uniform
|
|
dictionary-based interface to the caller.
|
|
"""
|
|
command = data_in.pop('command')
|
|
|
|
LOG.debug('Running "%(cmd)s" command handler', {'cmd': command})
|
|
|
|
if command == 'add':
|
|
|
|
# Check if the username and password were given for SASL
|
|
sasl_user = data_in['libvirt_sasl_username']
|
|
sasl_pass = data_in['libvirt_sasl_password']
|
|
if any((sasl_user, sasl_pass)):
|
|
if not all((sasl_user, sasl_pass)):
|
|
error = ("A password and username are required to use "
|
|
"Libvirt's SASL authentication")
|
|
return {'msg': [error], 'rc': 1}
|
|
|
|
rc, msg = vbmc_manager.add(**data_in)
|
|
|
|
return {
|
|
'rc': rc,
|
|
'msg': [msg] if msg else []
|
|
}
|
|
|
|
elif command == 'delete':
|
|
data_out = [vbmc_manager.delete(domain_name)
|
|
for domain_name in set(data_in['domain_names'])]
|
|
return {
|
|
'rc': max(rc for rc, msg in data_out),
|
|
'msg': [msg for rc, msg in data_out if msg],
|
|
}
|
|
|
|
elif command == 'start':
|
|
data_out = [vbmc_manager.start(domain_name)
|
|
for domain_name in set(data_in['domain_names'])]
|
|
return {
|
|
'rc': max(rc for rc, msg in data_out),
|
|
'msg': [msg for rc, msg in data_out if msg],
|
|
}
|
|
|
|
elif command == 'stop':
|
|
data_out = [vbmc_manager.stop(domain_name)
|
|
for domain_name in set(data_in['domain_names'])]
|
|
return {
|
|
'rc': max(rc for rc, msg in data_out),
|
|
'msg': [msg for rc, msg in data_out if msg],
|
|
}
|
|
|
|
elif command == 'list':
|
|
rc, tables = vbmc_manager.list()
|
|
|
|
header = ('Domain name', 'Status', 'Address', 'Port')
|
|
keys = ('domain_name', 'status', 'address', 'port')
|
|
return {
|
|
'rc': rc,
|
|
'header': header,
|
|
'rows': [
|
|
[table.get(key, '?') for key in keys] for table in tables
|
|
]
|
|
}
|
|
|
|
elif command == 'show':
|
|
rc, table = vbmc_manager.show(data_in['domain_name'])
|
|
|
|
return {
|
|
'rc': rc,
|
|
'header': ('Property', 'Value'),
|
|
'rows': table,
|
|
}
|
|
|
|
else:
|
|
return {
|
|
'rc': 1,
|
|
'msg': ['Unknown command'],
|
|
}
|
|
|
|
|
|
def application():
|
|
"""vbmcd application entry point
|
|
|
|
Initializes, serves and cleans up everything.
|
|
"""
|
|
vbmc_manager = VirtualBMCManager()
|
|
|
|
vbmc_manager.periodic()
|
|
|
|
def kill_children(*args):
|
|
vbmc_manager.periodic(shutdown=True)
|
|
sys.exit(0)
|
|
|
|
# SIGTERM does not seem to propagate to multiprocessing
|
|
signal.signal(signal.SIGTERM, kill_children)
|
|
|
|
try:
|
|
main_loop(vbmc_manager, command_dispatcher)
|
|
except KeyboardInterrupt:
|
|
LOG.info('Got keyboard interrupt, exiting')
|
|
vbmc_manager.periodic(shutdown=True)
|
|
except Exception as ex:
|
|
LOG.error(
|
|
'Control server error: %(error)s', {'error': ex}
|
|
)
|
|
vbmc_manager.periodic(shutdown=True)
|