Initial public version
This commit is contained in:
commit
a4c2934d50
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
.idea
|
||||
.tox
|
||||
venv/
|
||||
|
229
LICENSE
Normal file
229
LICENSE
Normal file
@ -0,0 +1,229 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
========================================================================
|
||||
|
||||
This program includes a modified version of the telnetlib module from Python3
|
||||
which has the following license:
|
||||
|
||||
PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
|
||||
--------------------------------------------
|
||||
|
||||
1. This LICENSE AGREEMENT is between the Python Software Foundation
|
||||
("PSF"), and the Individual or Organization ("Licensee") accessing and
|
||||
otherwise using this software ("Python") in source or binary form and
|
||||
its associated documentation.
|
||||
|
||||
2. Subject to the terms and conditions of this License Agreement, PSF hereby
|
||||
grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
|
||||
analyze, test, perform and/or display publicly, prepare derivative works,
|
||||
distribute, and otherwise use Python alone or in any derivative version,
|
||||
provided, however, that PSF's License Agreement and PSF's notice of copyright,
|
||||
i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
|
||||
2011, 2012, 2013, 2014, 2015, 2016, 2017 Python Software Foundation; All Rights
|
||||
Reserved" are retained in Python alone or in any derivative version prepared by
|
||||
Licensee.
|
||||
|
||||
3. In the event Licensee prepares a derivative work that is based on
|
||||
or incorporates Python or any part thereof, and wants to make
|
||||
the derivative work available to others as provided herein, then
|
||||
Licensee hereby agrees to include in any such work a brief summary of
|
||||
the changes made to Python.
|
||||
|
||||
4. PSF is making Python available to Licensee on an "AS IS"
|
||||
basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
||||
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
|
||||
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
||||
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
|
||||
INFRINGE ANY THIRD PARTY RIGHTS.
|
||||
|
||||
5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
|
||||
FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
|
||||
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
|
||||
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
||||
|
||||
6. This License Agreement will automatically terminate upon a material
|
||||
breach of its terms and conditions.
|
||||
|
||||
7. Nothing in this License Agreement shall be deemed to create any
|
||||
relationship of agency, partnership, or joint venture between PSF and
|
||||
Licensee. This License Agreement does not grant permission to use PSF
|
||||
trademarks or trade name in a trademark sense to endorse or promote
|
||||
products or services of Licensee, or any third party.
|
||||
|
||||
8. By copying, installing or otherwise using Python, Licensee
|
||||
agrees to be bound by the terms and conditions of this License
|
||||
Agreement.
|
24
README.rst
Normal file
24
README.rst
Normal file
@ -0,0 +1,24 @@
|
||||
vmware-vspc
|
||||
===========
|
||||
|
||||
Virtual Serial Port Concentrator for use in the vSphere environment. It collects
|
||||
serial console logs from VMs which have configured virtual serial port pointing
|
||||
to it.
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
Copy ``vspc.conf.sample`` as ``vspc.conf`` and edit as appropriate::
|
||||
|
||||
[DEFAULT]
|
||||
debug = True
|
||||
host = 0.0.0.0
|
||||
port = 13370
|
||||
cert = cert.pem
|
||||
key = key.pem
|
||||
uri = vmware-vspc
|
||||
serial_log_dir = /opt/vmware/vspc
|
||||
|
||||
Then start with::
|
||||
|
||||
$ vmware-vspc --config-file vspc.conf
|
9
etc/vspc.conf.sample
Normal file
9
etc/vspc.conf.sample
Normal file
@ -0,0 +1,9 @@
|
||||
[DEFAULT]
|
||||
debug = True
|
||||
host = 0.0.0.0
|
||||
port = 13370
|
||||
cert = cert.pem
|
||||
key = key.pem
|
||||
uri = vmware-vspc
|
||||
serial_log_dir = /opt/vmware/vspc
|
||||
|
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@ -0,0 +1,2 @@
|
||||
oslo.config>=3.7.0 # Apache-2.0
|
||||
oslo.log>=1.14.0 # Apache-2.0
|
25
setup.py
Normal file
25
setup.py
Normal file
@ -0,0 +1,25 @@
|
||||
from setuptools import setup
|
||||
|
||||
with open('README.rst') as f:
|
||||
readme = f.read()
|
||||
|
||||
|
||||
with open('LICENSE') as f:
|
||||
license = f.read()
|
||||
|
||||
|
||||
setup(
|
||||
name='vmware-vspc',
|
||||
version='0.0.1',
|
||||
description='Virtual Serial Port Concentrator',
|
||||
long_description=readme,
|
||||
author='VMware Inc.',
|
||||
author_email='rgerganov@vmware.com',
|
||||
url='https://github.com/rgerganov/vmware-vspc',
|
||||
license=license,
|
||||
entry_points = {
|
||||
'console_scripts': ['vmware-vspc=vspc.server:main'],
|
||||
},
|
||||
packages=['vspc'],
|
||||
install_requires=['oslo.config', 'oslo.log']
|
||||
)
|
2
test-requirements.txt
Normal file
2
test-requirements.txt
Normal file
@ -0,0 +1,2 @@
|
||||
flake8
|
||||
|
16
tox.ini
Normal file
16
tox.ini
Normal file
@ -0,0 +1,16 @@
|
||||
[tox]
|
||||
envlist = py34,pep8
|
||||
skipsdist = True
|
||||
|
||||
[testenv]
|
||||
deps = -rrequirements.txt
|
||||
-rtest-requirements.txt
|
||||
#commands = python setup.py testr --slowest --testr-args='{posargs}'
|
||||
|
||||
[testenv:pep8]
|
||||
basepython = /usr/bin/python3
|
||||
commands = flake8
|
||||
|
||||
[flake8]
|
||||
ignore = E121,E122,E123,E124,E125,E126,E127,E128,E129,E131,E251,H405
|
||||
exclude = venv,.venv,.git,.tox,dist,doc,*lib/python*,*egg,build
|
0
vspc/__init__.py
Normal file
0
vspc/__init__.py
Normal file
145
vspc/async_telnet.py
Normal file
145
vspc/async_telnet.py
Normal file
@ -0,0 +1,145 @@
|
||||
# Copyright (c) 2001-2016 Python Software Foundation
|
||||
# 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.
|
||||
"""
|
||||
Telnet client class using asyncio.
|
||||
Based on the standard telnetlib module from Python3.
|
||||
"""
|
||||
import asyncio
|
||||
|
||||
# Telnet protocol characters (don't change)
|
||||
IAC = bytes([255]) # "Interpret As Command"
|
||||
DONT = bytes([254])
|
||||
DO = bytes([253])
|
||||
WONT = bytes([252])
|
||||
WILL = bytes([251])
|
||||
theNULL = bytes([0])
|
||||
SE = bytes([240]) # Subnegotiation End
|
||||
NOP = bytes([241]) # No Operation
|
||||
SB = bytes([250]) # Subnegotiation Begin
|
||||
NOOPT = bytes([0])
|
||||
|
||||
|
||||
class AsyncTelnet:
|
||||
def __init__(self, reader, opt_handler):
|
||||
self._reader = reader
|
||||
self._opt_handler = opt_handler
|
||||
self.rawq = b''
|
||||
self.irawq = 0
|
||||
self.cookedq = b''
|
||||
self.eof = 0
|
||||
self.iacseq = b'' # Buffer for IAC sequence.
|
||||
self.sb = 0 # flag for SB and SE sequence.
|
||||
self.sbdataq = b''
|
||||
|
||||
@asyncio.coroutine
|
||||
def process_rawq(self):
|
||||
"""Transfer from raw queue to cooked queue.
|
||||
|
||||
Set self.eof when connection is closed.
|
||||
"""
|
||||
buf = [b'', b'']
|
||||
try:
|
||||
while self.rawq:
|
||||
c = yield from self.rawq_getchar()
|
||||
if not self.iacseq:
|
||||
if self.sb == 0 and c == theNULL:
|
||||
continue
|
||||
if self.sb == 0 and c == b"\021":
|
||||
continue
|
||||
if c != IAC:
|
||||
buf[self.sb] = buf[self.sb] + c
|
||||
continue
|
||||
else:
|
||||
self.iacseq += c
|
||||
elif len(self.iacseq) == 1:
|
||||
# 'IAC: IAC CMD [OPTION only for WILL/WONT/DO/DONT]'
|
||||
if c in (DO, DONT, WILL, WONT):
|
||||
self.iacseq += c
|
||||
continue
|
||||
|
||||
self.iacseq = b''
|
||||
if c == IAC:
|
||||
buf[self.sb] = buf[self.sb] + c
|
||||
else:
|
||||
if c == SB: # SB ... SE start.
|
||||
self.sb = 1
|
||||
self.sbdataq = b''
|
||||
elif c == SE:
|
||||
self.sb = 0
|
||||
self.sbdataq = self.sbdataq + buf[1]
|
||||
buf[1] = b''
|
||||
yield from self._opt_handler(c, NOOPT,
|
||||
data=self.sbdataq)
|
||||
elif len(self.iacseq) == 2:
|
||||
cmd = self.iacseq[1:2]
|
||||
self.iacseq = b''
|
||||
opt = c
|
||||
if cmd in (DO, DONT):
|
||||
yield from self._opt_handler(cmd, opt)
|
||||
elif cmd in (WILL, WONT):
|
||||
yield from self._opt_handler(cmd, opt)
|
||||
except EOFError: # raised by self.rawq_getchar()
|
||||
self.iacseq = b'' # Reset on EOF
|
||||
self.sb = 0
|
||||
pass
|
||||
self.cookedq = self.cookedq + buf[0]
|
||||
self.sbdataq = self.sbdataq + buf[1]
|
||||
|
||||
@asyncio.coroutine
|
||||
def rawq_getchar(self):
|
||||
"""Get next char from raw queue.
|
||||
|
||||
Raise EOFError when connection is closed.
|
||||
"""
|
||||
if not self.rawq:
|
||||
yield from self.fill_rawq()
|
||||
if self.eof:
|
||||
raise EOFError
|
||||
c = self.rawq[self.irawq:self.irawq + 1]
|
||||
self.irawq = self.irawq + 1
|
||||
if self.irawq >= len(self.rawq):
|
||||
self.rawq = b''
|
||||
self.irawq = 0
|
||||
return c
|
||||
|
||||
@asyncio.coroutine
|
||||
def fill_rawq(self):
|
||||
"""Fill raw queue from exactly one recv() system call.
|
||||
|
||||
Set self.eof when connection is closed.
|
||||
"""
|
||||
if self.irawq >= len(self.rawq):
|
||||
self.rawq = b''
|
||||
self.irawq = 0
|
||||
# The buffer size should be fairly small so as to avoid quadratic
|
||||
# behavior in process_rawq() above
|
||||
buf = yield from self._reader.read(50)
|
||||
self.eof = (not buf)
|
||||
self.rawq = self.rawq + buf
|
||||
|
||||
@asyncio.coroutine
|
||||
def read_some(self):
|
||||
"""Read at least one byte of cooked data unless EOF is hit.
|
||||
|
||||
Return b'' if EOF is hit.
|
||||
"""
|
||||
yield from self.process_rawq()
|
||||
while not self.cookedq and not self.eof:
|
||||
yield from self.fill_rawq()
|
||||
yield from self.process_rawq()
|
||||
buf = self.cookedq
|
||||
self.cookedq = b''
|
||||
return buf
|
261
vspc/server.py
Executable file
261
vspc/server.py
Executable file
@ -0,0 +1,261 @@
|
||||
#!/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[2:].decode('ascii')
|
||||
LOG.debug("<< %s VM-VC-UUID %s", peer, uuid)
|
||||
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 +
|
||||
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 + 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)
|
||||
|
||||
@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:])
|
||||
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()
|
Loading…
Reference in New Issue
Block a user