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