virtualbmc/virtualbmc/control.py

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)