6de3cfdc08
VMOTION-ABORT is sent by the source host. It does not require any specific response or action, but we currently fail to recognize it, which causes the _source_ vSPC connection to be terminated, and that seems undesirable as the source should be fine -- it is the _destination_ which has failed and is expected to disconnect. This change handles it with a debug log message, but the side effect of handling it is that the connection is no longer terminated. Also fix the direction indicator for the debug message for VMOTION-PEER-OK. Change-Id: Id09ed363d6713c023323235a60d4c678c65079f1
270 lines
9.6 KiB
Python
Executable File
270 lines
9.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# Copyright (c) 2017 VMware 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 asyncio
|
|
import functools
|
|
import os
|
|
import ssl
|
|
import sys
|
|
|
|
from oslo_config import cfg
|
|
from oslo_log import log as logging
|
|
|
|
from vspc import async_telnet
|
|
from vspc.async_telnet import IAC, SB, SE, DO, DONT, WILL, WONT
|
|
|
|
opts = [
|
|
cfg.StrOpt('host',
|
|
default='0.0.0.0',
|
|
help='Host on which to listen for incoming requests'),
|
|
cfg.IntOpt('port',
|
|
default=13370,
|
|
help='Port on which to listen for incoming requests'),
|
|
cfg.StrOpt('cert', help='SSL certificate file'),
|
|
cfg.StrOpt('key', help='SSL key file (if separate from cert)'),
|
|
cfg.StrOpt('uri', help='VSPC URI'),
|
|
cfg.StrOpt('serial_log_dir', help='The directory where serial logs are '
|
|
'saved'),
|
|
]
|
|
|
|
CONF = cfg.CONF
|
|
CONF.register_opts(opts)
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
BINARY = bytes([0]) # 8-bit data path
|
|
SGA = bytes([3]) # suppress go ahead
|
|
VMWARE_EXT = bytes([232])
|
|
|
|
KNOWN_SUBOPTIONS_1 = bytes([0])
|
|
KNOWN_SUBOPTIONS_2 = bytes([1])
|
|
VMOTION_BEGIN = bytes([40])
|
|
VMOTION_GOAHEAD = bytes([41])
|
|
VMOTION_NOTNOW = bytes([43])
|
|
VMOTION_PEER = bytes([44])
|
|
VMOTION_PEER_OK = bytes([45])
|
|
VMOTION_COMPLETE = bytes([46])
|
|
VMOTION_ABORT = bytes([48])
|
|
VM_VC_UUID = bytes([80])
|
|
GET_VM_VC_UUID = bytes([81])
|
|
VM_NAME = bytes([82])
|
|
GET_VM_NAME = bytes([83])
|
|
DO_PROXY = bytes([70])
|
|
WILL_PROXY = bytes([71])
|
|
WONT_PROXY = bytes([73])
|
|
|
|
SUPPORTED_OPTS = (KNOWN_SUBOPTIONS_1 + KNOWN_SUBOPTIONS_2 + VMOTION_BEGIN +
|
|
VMOTION_GOAHEAD + VMOTION_NOTNOW + VMOTION_PEER +
|
|
VMOTION_PEER_OK + VMOTION_COMPLETE + VMOTION_ABORT +
|
|
VM_VC_UUID + GET_VM_VC_UUID + VM_NAME + GET_VM_NAME +
|
|
DO_PROXY + WILL_PROXY + WONT_PROXY)
|
|
|
|
|
|
class VspcServer(object):
|
|
def __init__(self):
|
|
self.sock_to_uuid = dict()
|
|
|
|
@asyncio.coroutine
|
|
def handle_known_suboptions(self, writer, data):
|
|
socket = writer.get_extra_info('socket')
|
|
peer = socket.getpeername()
|
|
LOG.debug("<< %s KNOWN-SUBOPTIONS-1 %s", peer, data)
|
|
LOG.debug(">> %s KNOWN-SUBOPTIONS-2 %s", peer, SUPPORTED_OPTS)
|
|
writer.write(IAC + SB + VMWARE_EXT + KNOWN_SUBOPTIONS_2 +
|
|
SUPPORTED_OPTS + IAC + SE)
|
|
LOG.debug(">> %s GET-VM-VC-UUID", peer)
|
|
writer.write(IAC + SB + VMWARE_EXT + GET_VM_VC_UUID + IAC + SE)
|
|
yield from writer.drain()
|
|
|
|
@asyncio.coroutine
|
|
def handle_do_proxy(self, writer, data):
|
|
socket = writer.get_extra_info('socket')
|
|
peer = socket.getpeername()
|
|
dir, uri = data[0], data[1:].decode('ascii')
|
|
LOG.debug("<< %s DO-PROXY %c %s", peer, dir, uri)
|
|
if chr(dir) != 'S' or uri != CONF.uri:
|
|
LOG.debug(">> %s WONT-PROXY", peer)
|
|
writer.write(IAC + SB + VMWARE_EXT + WONT_PROXY + IAC + SE)
|
|
yield from writer.drain()
|
|
writer.close()
|
|
else:
|
|
LOG.debug(">> %s WILL-PROXY", peer)
|
|
writer.write(IAC + SB + VMWARE_EXT + WILL_PROXY + IAC + SE)
|
|
yield from writer.drain()
|
|
|
|
def handle_vm_vc_uuid(self, socket, data):
|
|
peer = socket.getpeername()
|
|
uuid = data.decode('ascii')
|
|
LOG.debug("<< %s VM-VC-UUID %s", peer, uuid)
|
|
uuid = uuid.replace(' ', '')
|
|
uuid = uuid.replace('-', '')
|
|
self.sock_to_uuid[socket] = uuid
|
|
|
|
@asyncio.coroutine
|
|
def handle_vmotion_begin(self, writer, data):
|
|
socket = writer.get_extra_info('socket')
|
|
peer = socket.getpeername()
|
|
LOG.debug("<< %s VMOTION-BEGIN %s", peer, data)
|
|
secret = os.urandom(4)
|
|
LOG.debug(">> %s VMOTION-GOAHEAD %s %s", peer, data, secret)
|
|
writer.write(IAC + SB + VMWARE_EXT + VMOTION_GOAHEAD +
|
|
async_telnet.AsyncTelnet.escape(data + secret) + IAC + SE)
|
|
yield from writer.drain()
|
|
|
|
@asyncio.coroutine
|
|
def handle_vmotion_peer(self, writer, data):
|
|
socket = writer.get_extra_info('socket')
|
|
peer = socket.getpeername()
|
|
LOG.debug("<< %s VMOTION-PEER %s", peer, data)
|
|
LOG.debug(">> %s VMOTION-PEER-OK %s", peer, data)
|
|
writer.write(IAC + SB + VMWARE_EXT + VMOTION_PEER_OK +
|
|
async_telnet.AsyncTelnet.escape(data) + IAC + SE)
|
|
yield from writer.drain()
|
|
|
|
def handle_vmotion_complete(self, socket, data):
|
|
peer = socket.getpeername()
|
|
LOG.debug("<< %s VMOTION-COMPLETE %s", peer, data)
|
|
|
|
def handle_vmotion_abort(self, socket, data):
|
|
peer = socket.getpeername()
|
|
LOG.debug("<< %s VMOTION-ABORT %s", peer, data)
|
|
|
|
@asyncio.coroutine
|
|
def handle_do(self, writer, opt):
|
|
socket = writer.get_extra_info('socket')
|
|
peer = socket.getpeername()
|
|
LOG.debug("<< %s DO %s", peer, opt)
|
|
if opt in (BINARY, SGA):
|
|
LOG.debug(">> %s WILL", peer)
|
|
writer.write(IAC + WILL + opt)
|
|
yield from writer.drain()
|
|
else:
|
|
LOG.debug(">> %s WONT", peer)
|
|
writer.write(IAC + WONT + opt)
|
|
yield from writer.drain()
|
|
|
|
@asyncio.coroutine
|
|
def handle_will(self, writer, opt):
|
|
socket = writer.get_extra_info('socket')
|
|
peer = socket.getpeername()
|
|
LOG.debug("<< %s WILL %s", peer, opt)
|
|
if opt in (BINARY, SGA, VMWARE_EXT):
|
|
LOG.debug(">> %s DO", peer)
|
|
writer.write(IAC + DO + opt)
|
|
yield from writer.drain()
|
|
else:
|
|
LOG.debug(">> %s DONT", peer)
|
|
writer.write(IAC + DONT + opt)
|
|
yield from writer.drain()
|
|
|
|
@asyncio.coroutine
|
|
def option_handler(self, cmd, opt, writer, data=None):
|
|
socket = writer.get_extra_info('socket')
|
|
if cmd == SE and data[0:1] == VMWARE_EXT:
|
|
vmw_cmd = data[1:2]
|
|
if vmw_cmd == KNOWN_SUBOPTIONS_1:
|
|
yield from self.handle_known_suboptions(writer, data[2:])
|
|
elif vmw_cmd == DO_PROXY:
|
|
yield from self.handle_do_proxy(writer, data[2:])
|
|
elif vmw_cmd == VM_VC_UUID:
|
|
self.handle_vm_vc_uuid(socket, data[2:])
|
|
elif vmw_cmd == VMOTION_BEGIN:
|
|
yield from self.handle_vmotion_begin(writer, data[2:])
|
|
elif vmw_cmd == VMOTION_PEER:
|
|
yield from self.handle_vmotion_peer(writer, data[2:])
|
|
elif vmw_cmd == VMOTION_COMPLETE:
|
|
self.handle_vmotion_complete(socket, data[2:])
|
|
elif vmw_cmd == VMOTION_ABORT:
|
|
self.handle_vmotion_abort(socket, data[2:])
|
|
else:
|
|
LOG.error("Unknown VMware cmd: %s %s", vmw_cmd, data[2:])
|
|
writer.close()
|
|
elif cmd == DO:
|
|
yield from self.handle_do(writer, opt)
|
|
elif cmd == WILL:
|
|
yield from self.handle_will(writer, opt)
|
|
|
|
def save_to_log(self, uuid, data):
|
|
fpath = os.path.join(CONF.serial_log_dir, uuid)
|
|
with open(fpath, 'ab') as f:
|
|
f.write(data)
|
|
|
|
@asyncio.coroutine
|
|
def handle_telnet(self, reader, writer):
|
|
opt_handler = functools.partial(self.option_handler, writer=writer)
|
|
telnet = async_telnet.AsyncTelnet(reader, opt_handler)
|
|
socket = writer.get_extra_info('socket')
|
|
peer = socket.getpeername()
|
|
LOG.info("%s connected", peer)
|
|
data = yield from telnet.read_some()
|
|
uuid = self.sock_to_uuid.get(socket)
|
|
if uuid is None:
|
|
LOG.error("%s didn't present UUID", peer)
|
|
writer.close()
|
|
return
|
|
try:
|
|
while data:
|
|
self.save_to_log(uuid, data)
|
|
data = yield from telnet.read_some()
|
|
finally:
|
|
self.sock_to_uuid.pop(socket, None)
|
|
LOG.info("%s disconnected", peer)
|
|
writer.close()
|
|
|
|
def start(self):
|
|
loop = asyncio.get_event_loop()
|
|
ssl_context = None
|
|
if CONF.cert:
|
|
ssl_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
|
|
ssl_context.load_cert_chain(certfile=CONF.cert, keyfile=CONF.key)
|
|
coro = asyncio.start_server(self.handle_telnet,
|
|
CONF.host,
|
|
CONF.port,
|
|
ssl=ssl_context,
|
|
loop=loop)
|
|
server = loop.run_until_complete(coro)
|
|
|
|
# Serve requests until Ctrl+C is pressed
|
|
LOG.info("Serving on %s", server.sockets[0].getsockname())
|
|
LOG.info("Log directory: %s", CONF.serial_log_dir)
|
|
try:
|
|
loop.run_forever()
|
|
except KeyboardInterrupt:
|
|
pass
|
|
|
|
# Close the server
|
|
server.close()
|
|
loop.run_until_complete(server.wait_closed())
|
|
loop.close()
|
|
|
|
|
|
def main():
|
|
logging.register_options(CONF)
|
|
CONF(sys.argv[1:], prog='vspc')
|
|
logging.setup(CONF, "vspc")
|
|
if not CONF.serial_log_dir:
|
|
LOG.error("serial_log_dir is not specified")
|
|
sys.exit(1)
|
|
if not os.path.exists(CONF.serial_log_dir):
|
|
LOG.info("Creating log directory: %s", CONF.serial_log_dir)
|
|
os.makedirs(CONF.serial_log_dir)
|
|
srv = VspcServer()
|
|
srv.start()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|