Coder Social home page Coder Social logo

vspc.py's People

Contributors

bldewolf avatar cpeel avatar dave4444 avatar isnotajoke avatar pbhenson avatar shaozi avatar vinceval avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

vspc.py's Issues

fix 'NameError: global name 'getopt' is not defined'

error:

Traceback (most recent call last):
File "/localdisk/vspc/ins/vSPCServer", line 126, in
backend.setup(options.backend_args)
File "/localdisk/vspc/isnotajoke-vSPC.py-5dc6d12/vSPC.py", line 960, in setup
except getopt.GetoptError, err:
NameError: global name 'getopt' is not defined

fixed by:

diff -Nuar isnotajoke-vSPC.py-5dc6d12.orig/vSPC.py isnotajoke-vSPC.py-5dc6d12/vSPC.py
--- isnotajoke-vSPC.py-5dc6d12.orig/vSPC.py 2012-05-23 17:23:49.000000000 -0400
+++ isnotajoke-vSPC.py-5dc6d12/vSPC.py 2012-09-04 15:27:01.000000000 -0400
@@ -46,6 +46,7 @@

BASENAME='vSPC.py'

+import getopt
import fcntl
import logging
import os

filebacking not written

Not sure if this is caused by a specific version of python, but I'm running into an issue where the file backing was never written.

Because shelf.close() is never called, adding calls to shelf.sync() after each modification resolves my issue.

patch:

diff -Nuar isnotajoke-vSPC.py-5dc6d12.orig/vSPC.py isnotajoke-vSPC.py-5dc6d12/vSPC.py
--- isnotajoke-vSPC.py-5dc6d12.orig/vSPC.py 2012-05-23 17:23:49.000000000 -0400
+++ isnotajoke-vSPC.py-5dc6d12/vSPC.py 2012-09-04 16:11:13.000000000 -0400
@@ -969,9 +969,11 @@

 def vm_hook(self, uuid, name, port):
     self.shelf[uuid] = { P_UUID : uuid, P_NAME : name, P_PORT : port }
  •    self.shelf.sync()
    

    def vm_del_hook(self, uuid):
    del self.shelf[uuid]

  •    self.shelf.sync()
    

    def load_vms(self):
    vms = {}

Terminal doesn't handle newlines correctly

While working on another issue, I noticed that the vSPCClient's terminal preparation routine doesn't seem to work. In particular, the settings seem to eat newlines off of incoming strings.

Nothing appears with the client

Hi Guys,

Trying to connect with vSPCClient.py to my vm but nothing is displayed, and it seems stuck.

Server side :

DEBUG:root:vm_hook: uuid: 5037c7d794d4d0fe-9271993db2ea9630, name: fredvm, port: 50000
DEBUG:root:client wants to 44, sending DONT
DEBUG:root:Current open connection count: 1 vms (0 orphans), 0 clients
DEBUG:root:Current open connection count: 1 vms (0 orphans), 0 clients
DEBUG:root:version 3 query
DEBUG:root:Trying to lock vm fredvm for client
DEBUG:root:free-for-all selected
DEBUG:root:No one thinks they have exclusive write access, returning True
DEBUG:root:sending WILL 0
DEBUG:root:sending WILL 3
DEBUG:root:sending WILL 1
DEBUG:root:sending DO 0
DEBUG:root:sending DO 3
DEBUG:root:uuid 5037c7d794d4d0fe-9271993db2ea9630 new client, 1 active clients
DEBUG:root:client will 0
DEBUG:root:client will 3
DEBUG:root:client says we should 0
DEBUG:root:client says we should 3
DEBUG:root:client doesn't want us to 1

Client side:

vSPCClient.py --debug --stdout fredvm
DEBUG:root:sending WILL 0
DEBUG:root:sending WILL 3
DEBUG:root:sending DO 0
DEBUG:root:sending DO 3
DEBUG:root:client will 0
DEBUG:root:client will 3
DEBUG:root:client wants to 1, sending DONT
DEBUG:root:client says we should 0
DEBUG:root:client says we should 3

Any clue about the "we should, or doesn't want us ?"

Thanks
Fred

Great! vSPCClient

Hi,

I was thinking how to setup conserver (conserver.com) to talk to vSPC.py and to let it open via vSPC.py connection to VM's console. Now, with vSPCClient it seems much more possible while giving as argument.

Not tested yet, but it is nice anyway!

Cheers.

Fails when using File backend

This must be something trivial, but I dont do python so this should be trivial for you ;)

$ ./vSPCServer -f /tmp/foo
Traceback (most recent call last):
  File "./vSPCServer", line 129, in <module>
    (options, args) = parser.parse_args()
  File "/usr/lib64/python2.6/optparse.py", line 1394, in parse_args
    stop = self._process_args(largs, rargs, values)
  File "/usr/lib64/python2.6/optparse.py", line 1438, in _process_args
    self._process_short_opts(rargs, values)
  File "/usr/lib64/python2.6/optparse.py", line 1545, in _process_short_opts
    option.process(opt, value, values, self)
  File "/usr/lib64/python2.6/optparse.py", line 788, in process
    self.action, self.dest, opt, value, values, parser)
  File "/usr/lib64/python2.6/optparse.py", line 808, in take_action
    self.callback(self, opt, value, parser, *args, **kwargs)
  File "./vSPCServer", line 72, in handle_persist_file
    parser.values.backend_args = "-f %s" % pipes.quote(value)
  File "/usr/lib64/python2.6/pipes.py", line 271, in quote
    for c in file:
TypeError: 'NoneType' object is not iterable

vMotion Issue

Running Esxi 5.5. When vmotioning a vm with a serial interface on it, the VMware Client says "An error occurred while saving the state for migration.
The serial port concentrator did not acknowledge the migration within the allowed time limit. ".

From a logging perspective from vSPC, I see the following:
05-30-2015 09:34:06 User.Error 10.255.83.94 May 30 08:34:06 ps-collab-vsc vSPC.py[1027]: Worker exception caught#012Traceback (most recent call last):#12 File "build/bdist.linux-x86_64/egg/vSPC/server.py", line 112, in _queue_run#012 queue.get()()#12 File "build/bdist.linux-x86_64/egg/vSPC/server.py", line 224, in #12 self.task_queue.put(lambda: self.new_vm_data(vt))#12 File "build/bdist.linux-x86_64/egg/vSPC/server.py", line 179, in new_vm_data#012 neg_done = vt.negotiation_done()#12 File "build/bdist.linux-x86_64/egg/vSPC/telnet.py", line 276, in negotiation_done#012 self.process_available()#12 File "build/bdist.linux-x86_64/egg/vSPC/telnet.py", line 273, in process_available#012 self.process_rawq()#12 File "build/bdist.linux-x86_64/egg/vSPC/telnet.py", line 156, in process_rawq#012 self.option_callback(self.sock, c, NOOPT)#12 File "build/bdist.linux-x86_64/egg/vSPC/telnet.py", line 415, in _option_callback#012 getattr(self, meth)(data)#12 File "build/bdist.linux-x86_64/egg/vSPC/telnet.py", line 34...

MacOS compatibility

Git source compiled and installed just fine on MacOS. However, running fails like this:

Traceback (most recent call last): File "/usr/local/bin/vSPCServer", line 222, in <module> sys.exit(main()) File "/usr/local/bin/vSPCServer", line 180, in main handler = SysLogHandler(address="/dev/log") File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/logging/handlers.py", line 762, in __init__ self._connect_unixsocket(address) File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/logging/handlers.py", line 790, in _connect_unixsocket self.socket.connect(address) File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py", line 228, in meth return getattr(self._sock,name)(*args) socket.error: [Errno 2] No such file or directory

I realize MacOS compatibility wasn't promised but it sure would be nice. I'm not smart enough to know if this is easy or difficult to make work but figured I throw in a request. Happy to help test or otherwise contribute, just have near zero code skills.

Need way to test vSPCServer implementation without vSphere

I haven't worked at a place with a vSphere installation in several months, so I'm limited in my ability to make and test changes. The virtual serial port protocol is open enough for there to be a server that supports it; it seems like it wouldn't be any harder to write a client program that mimics the other half of the connection, and having such a program would allow me to resolve some of these other issues without access to a vSphere installation.

vMotion Issue #21 not yet fixed

We started to use this wonderful piece of software, but the bug described in Issue #21 is not yet fixed in 'master' as I see it here on GitHub.

At least the proposed code in that issue is not part of the version I downloaded. When replaced, however, things work fine.

Thank you for the good work, and Regards,

Matthias Hofer

number of connections in the CLOSE-WAIT state keeps growing

Here is the information of the system running the concentrator:

$ python -V

Python 2.7.2

$ cat /etc/*release*

CentOS release 6.4 (Final)
LSB_VERSION=base-4.0-amd64:base-4.0-noarch:core-4.0-amd64:core-4.0-noarch:graphics-4.0-amd64:graphics-4.0-noarch:printing-4.0-amd64:printing-4.0-noarch
CentOS release 6.4 (Final)
CentOS release 6.4 (Final)
cpe:/o:centos:linux:6:GA

$ uname -a

Linux dev-daniel 2.6.32-358.14.1.el6.x86_64 #1 SMP Tue Jul 16 23:51:20 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux

I have 5 VMs on the concetrator and to monitor the socket state I'm running a simple loop like this one:

while true; do ss | grep 5000[0-4] && echo $(ss | grep -c 5000[0-4]) && sleep 1; done;

To see the number of sockets in the CLOSE-WAIT state grow, I just use the Linux telnet client to establish the connection and then close it (Ctrl+] q ENTER), then repeat.

Let me know if you need more information.

Thanks.

struct error in vmotion cookie handling

Aug 2 11:29:42 hostname vSPC.py[5615]: Worker exception caught
Aug 2 11:29:42 hostname Traceback (most recent call last):
Aug 2 11:29:42 hostname File "/usr/lib64/python2.7/site-packages/vSPC.py", line 1034, in _queue_run
Aug 2 11:29:42 hostname queue.get()()
Aug 2 11:29:42 hostname File "/usr/lib64/python2.7/site-packages/vSPC.py", line 1145, in
Aug 2 11:29:42 hostname self.task_queue.put(lambda: self.new_vm_data(vt))
Aug 2 11:29:42 hostname File "/usr/lib64/python2.7/site-packages/vSPC.py", line 1100, in new_vm_data
Aug 2 11:29:42 hostname neg_done = vt.negotiation_done()
Aug 2 11:29:42 hostname File "/usr/lib64/python2.7/site-packages/vSPC.py", line 330, in negotiation_done
Aug 2 11:29:42 hostname self.process_available()
Aug 2 11:29:42 hostname File "/usr/lib64/python2.7/site-packages/vSPC.py", line 327, in process_available
Aug 2 11:29:42 hostname self.process_rawq()
Aug 2 11:29:42 hostname File "/usr/lib64/python2.7/site-packages/vSPC.py", line 210, in process_rawq
Aug 2 11:29:42 hostname self.option_callback(self.sock, c, NOOPT)
Aug 2 11:29:42 hostname File "/usr/lib64/python2.7/site-packages/vSPC.py", line 469, in _option_callback
Aug 2 11:29:42 hostname getattr(self, meth)(data)
Aug 2 11:29:42 hostname File "/usr/lib64/python2.7/site-packages/vSPC.py", line 400, in _handle_vmotion_begin
Aug 2 11:29:42 hostname cookie = data + struct.pack("i", hash(self) & 0xFFFFFFFF)
Aug 2 11:29:42 hostname error: 'i' format requires -2147483648 <= number <= 2147483647

Inproper handling of NULL character and IAC character

I wanted to connect two Windows machines via serial ports (in Windows terms - I wanted to dial in from one machine to another using null modem thus establish a ppp session between the two). On the infrastructure the serial ports of both machines were exposed via vSPC.py (I connect the two TCP/IP ports together using socat utility). This didn't work. After a bit of investigation I was able to see that the vSCP.py is screwing up the transmitted data by two things.
First thing is - not escaping the 0xFF character on outbound data (thus making the remote end interpret the FF as IAC sequence).
Second thing - removing NULL characters from inbound data.

After I fixed both problems, everything started to work properly.
Fix for the first thing was fixing the send_buffered method:

def send_buffered(self, s = ''):
self.send_buffer += s
if IAC in self.send_buffer:
self.send_buffer = self.send_buffer.replace(IAC, IAC+IAC)

I am not sure why the author is defining its own method for sending data instead of using one from telnetlib while for receiving is using the original one (from the python's telnetlib library).

Fix for the second was to remove special handling of NULL in the process_rawq method

if self.sb == 0 and c == theNULL:
continue

I am not sure why there is a special handling of NULL character. Having looked at the Telnet RFC, the NULL is not a special character.

Let me know what you think and if I shall put it into the source code.

Cheers,
Prema

Refactor common code from command-line clients

The admin protocol client & dummy client both do fairly similar things; it's likely that a lot of their behavior could be refactored into common code, reducing duplication and making maintenance easier later.

Problem to run on OpenBSD 5.1-current

Problem to run on OpenBSD 5.1-current...

$ python2.7 ./vSPCServer -d 
ERROR:root:Top level exception caught
Traceback (most recent call last):
  File "./vSPCServer", line 138, in <module>
    vSPC(options.proxy_port, options.admin_port, options.vm_port_start, options.vm_expire_time, backend, options.ssl, options.cert, options.key).run()
  File "/tmp/isnotajoke-vSPC.py-9154df7/vSPC.py", line 1009, in __init__
    Poller.__init__(self)
  File "/tmp/isnotajoke-vSPC.py-9154df7/vSPC.py", line 501, in __init__
    self.epoll = select.epoll()
AttributeError: 'module' object has no attribute 'epoll'
$ sysctl kern.version                                                                                                                                                                                                                         
kern.version=OpenBSD 5.1-current (GENERIC.MP) #202: Thu Mar 15 18:33:09 MDT 2012
    [email protected]:/usr/src/sys/arch/i386/compile/GENERIC.MP

$ python2.7 -V
Python 2.7.2

Strange, it works on Ubuntu Lucid (Python 2.6.5)? What my OpenBSD misses in python?

Thank you for help.

Memory Usage

We had an issue with the original vSPC.py process dying every few minutes, with lots of connections. With this newer vSPC.py it doesn't die every few minutes, it lasts for a number of hours but eventually dies.

I can see in dmesg in my centos machine out of memory error relating to vSPC, and can see in top output, the %MEM is constantly growing over a number of hours, even though the number of vSPC.py connections (68) remains constant. Is there a memory leak that could cause this, where can I check to see whats causing memory to increase indefinitely until the process is killed?

VMware ESXi 7.0 U3 vMotion Error

Hello all.

The following error occurs when vMotion is performed with Virtual Serial Concentorator settings in vSPC.
This error does not occur on the server side (port 13370), but only on the client side (port 50000 ).
The server side can vMotion normally.

vCenter Error Messages

Unable to prepare migration. The Virtual Serial Port Concentrator appliance connected to this virtual machine does not support migration.\

There is no problem with the Remote Virtual Serial Port Concentrator license.

ESXi Licensing

Licensed features
Unlimited virtual SMP
H.264 for Remote Console Connections
vCenter agent for VMware host
vSphere API
Content Library
Storage APIs
vSphere vMotion
X-Switch vMotion
vSphere HA
vSphere Data Protection
vShield Endpoint
vSphere Replication
vShield Zones
Hot-Pluggable virtual HW
vSphere Storage vMotion
Shared Smart Card Reader
vSphere FT (up to 2 virtual CPUs)
Virtual Volumes
APIs for Storage Awareness
Storage-Policy Based Management
vSphere Storage APIs for Array Integration
Remote virtual Serial Port Concentrator

Do I need to modify the code in vSPC?
Or is it a VMware configuration issue?

Syntax error on Python 2.4.3

The script does not work on Python 2.4.3 as found in RHEL5. It returns a syntax error.

To solve this:

  1. Add Python version requirements (or tested versions) to the README
  2. Test for required functionality and return proper, helpful error
  3. As last resort: test for minimal version and return error/warning

AttributeError from vSPCServer when handling telnet connection to VM port

From syslog:

Traceback (most recent call last):#12 File "/home/kevan/code/vSPC.py/vSPC.py", line 1039, in _queue_run#012 queue.get()()#12 File "/home/kevan/code/vSPC.py/vSPC.py", line 1150, in #12 self.task_queue.put(lambda: self.new_vm_data(vt))#12 File "/home/kevan/code/vSPC.py/vSPC.py", line 1123, in new_vm_data#012 self.abort_vm_connection(vt)#12 File "/home/kevan/code/vSPC.py/vSPC.py", line 1100, in abort_vm_connection#012 self.del_all(vt)#12 File "/home/kevan/code/vSPC.py/vSPC.py", line 552, in del_all#012 self.del_reader(stream)#12 File "/home/kevan/code/vSPC.py/vSPC.py", line 527, in del_reader#012 self.epoll.modify(stream, self.fd_mask[stream])#12 File "/usr/lib/python2.7/telnetlib.py", line 268, in fileno#012 return self.sock.fileno()#012AttributeError: 'int' object has no attribute 'fileno'

To reproduce:
python vSPCServer
telnet localhost 13370

The telnet client sees:

Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.

You are trying to connect to the vSPC.py proxy port with a normal
telnet client. This port is intended for VMware connections only.

Connection closed by foreign host.

vMotion secret with IAC in it

If the cookie sent to VMware by _handle_vmotion_begin contains an IAC (0xff) character then it will violate the telnet protocol.

Observed behavior is that VMware considered the secret sent with VMOTION_GOAHEAD to end at the IAC. It returns the truncated SEQUENCE+SECRET (cookie) in VMOTION_PEER, which vSPC.py rejects as not mapping to a known vMotion.

Minimal fix is to AND the cookie with something other than 0xFFFFFFFF so it can't contain an IAC. A better fix is probably to modify _send_vmware to escape IACs:

    def _send_vmware(self, s):
        s = s.replace(IAC, IAC+IAC)
        self.sock.sendall(IAC + SB + VMWARE_EXT + s + IAC + SE)

but I haven't tested if that (a) causes any other problems or (b) even makes it through correctly.

In any case hash(self) is a terrible way to make a random secret.

KeyError when timout reached after disconnecting the VSP

Hi,

I am only connecting the VSP ondemand to a vSPC based on this project (commit 7fd4ca9), I follow this procedure:

  • Connect VSP within vCenter to vSPC -> Connection is established and showing up in the logs (over telnets) => OK
  • Connecting to local vSPC daemon via the local vSPC client via the Admin-Port => OK
  • Disconnecting the local vSPC Client => OK
  • Disconnecting the VSP via vCenter from the vSPC -> Connection is terminated => OK
  • After the timout is reached and a new vSPC client connection is tried (again locally over the admin port), the server throws this backtrace and the local client hangs until ctrl+C-d:
Feb 17 15:40:50 vSPCsrv vSPCServer[9174]: ** Starting Server with opts: --stdout --no-fork --backend Logging --logdir /srv/vspc/logs --ssl --cert /srv/vspc/tls/crt --key /srv/vspc/tls/key --no-vm-ports --vm-expire-time 60 --admin-port 6780 --proxy-port 6778 --mode 640
Feb 17 15:40:50 vSPCsrv vSPCServer[9174]: INFO:root:Starting vSPC on proxy iface 0.0.0.0 port 6778, admin iface 127.0.0.1 port 6780
Feb 17 15:41:37 vSPCsrv vSPCServer[9174]: INFO:root:VM linux012.my.corp (uuid 50302d754410d8f7-17ddffbaf375b458) connected, listening on port None
Feb 17 15:42:26 vSPCsrv vSPCServer[9174]: INFO:root:Client disconnected from linux012.my.corp (uuid 50302d754410d8f7-17ddffbaf375b458), 0 active clients
Feb 17 15:42:36 vSPCsrv vSPCServer[9174]: INFO:root:VM linux012.my.corp (uuid 50302d754410d8f7-17ddffbaf375b458) disconnected
Feb 17 15:43:00 vSPCsrv vSPCServer[9174]: INFO:root:Client disconnected from linux012.my.corp (uuid 50302d754410d8f7-17ddffbaf375b458), 0 active clients
Feb 17 15:43:08 vSPCsrv vSPCServer[9174]: INFO:root:Client disconnected from linux012.my.corp (uuid 50302d754410d8f7-17ddffbaf375b458), 0 active clients
Feb 17 15:45:00 vSPCsrv vSPCServer[9174]: ERROR:root:Worker exception caught
Feb 17 15:45:00 vSPCsrv vSPCServer[9174]: Traceback (most recent call last):
Feb 17 15:45:00 vSPCsrv vSPCServer[9174]:   File "/srv/vspc/usr/lib/python2.7/site-packages/vSPC/server.py", line 130, in _queue_run
Feb 17 15:45:00 vSPCsrv vSPCServer[9174]:     queue.get()()
Feb 17 15:45:00 vSPCsrv vSPCServer[9174]:   File "/srv/vspc/usr/lib/python2.7/site-packages/vSPC/server.py", line 478, in <lambda>
Feb 17 15:45:00 vSPCsrv vSPCServer[9174]:     self.task_queue.put(lambda: self.new_admin_connection(sock))
Feb 17 15:45:00 vSPCsrv vSPCServer[9174]:   File "/srv/vspc/usr/lib/python2.7/site-packages/vSPC/server.py", line 466, in new_admin_connection
Feb 17 15:45:00 vSPCsrv vSPCServer[9174]:     self.collect_orphans()
Feb 17 15:45:00 vSPCsrv vSPCServer[9174]:   File "/srv/vspc/usr/lib/python2.7/site-packages/vSPC/server.py", line 512, in collect_orphans
Feb 17 15:45:00 vSPCsrv vSPCServer[9174]:     self.expire_orphan(vm)
Feb 17 15:45:00 vSPCsrv vSPCServer[9174]:   File "/srv/vspc/usr/lib/python2.7/site-packages/vSPC/server.py", line 541, in expire_orphan
Feb 17 15:45:00 vSPCsrv vSPCServer[9174]:     self.delete_stream(vm)
Feb 17 15:45:00 vSPCsrv vSPCServer[9174]:   File "/srv/vspc/usr/lib/python2.7/site-packages/vSPC/poll.py", line 181, in delete_stream
Feb 17 15:45:00 vSPCsrv vSPCServer[9174]:     self.unsafe_remove_fd(stream)
Feb 17 15:45:00 vSPCsrv vSPCServer[9174]:   File "/srv/vspc/usr/lib/python2.7/site-packages/vSPC/poll.py", line 190, in unsafe_remove_fd
Feb 17 15:45:00 vSPCsrv vSPCServer[9174]:     pes = self.event_sources_by_stream[fd]
Feb 17 15:45:00 vSPCsrv vSPCServer[9174]: KeyError: <vSPC.server.Vm instance at 0x7f1190b26878>

Any ideas on how to debug it further or resolve it?
thanks,
-- J

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.