You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
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)
|