From: Stefan Hajnoczi <stefanha@redhat.com>
To: qemu-devel@nongnu.org
Cc: Kevin Wolf <kwolf@redhat.com>,
dietmar@proxmox.com, Stefan Hajnoczi <stefanha@redhat.com>,
Markus Armbruster <armbru@redhat.com>
Subject: [Qemu-devel] [RFC 5/8] Add nbd server Python module
Date: Sat, 9 Mar 2013 23:22:25 +0100 [thread overview]
Message-ID: <1362867748-30528-6-git-send-email-stefanha@redhat.com> (raw)
In-Reply-To: <1362867748-30528-1-git-send-email-stefanha@redhat.com>
The nbd module works like this:
server = nbd.Server(sock)
server.add_export('drive0', handler0)
server.add_export('drive1', handler1)
server.run()
The user must provide a handler object which defines the behavior of an
export:
class MyNBDHandler(nbd.ExportHandler):
def write(self, offset, data):
pass # do something
def size(self):
return 10 * 1024 * 1024 * 1024
Note that the handler is invoked from a thread.
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
---
nbd.py | 124 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 124 insertions(+)
create mode 100644 nbd.py
diff --git a/nbd.py b/nbd.py
new file mode 100644
index 0000000..2528531
--- /dev/null
+++ b/nbd.py
@@ -0,0 +1,124 @@
+# NBD server module
+#
+# Copyright 2013 Red Hat, Inc. and/or its affiliates
+#
+# Authors:
+# Stefan Hajnoczi <stefanha@redhat.com>
+#
+# This work is licensed under the terms of the GNU GPL, version 2 or later.
+# See the COPYING file in the top-level directory.
+
+import struct
+import collections
+import threading
+
+__all__ = ['ExportHandler', 'Server']
+
+NBD_CMD_WRITE = 1
+NBD_CMD_DISC = 2
+NBD_REQUEST_MAGIC = 0x25609513
+NBD_REPLY_MAGIC = 0x67446698
+NBD_PASSWD = 0x4e42444d41474943
+NBD_OPTS_MAGIC = 0x49484156454F5054
+NBD_OPT_EXPORT_NAME = 1 << 0
+
+neg1_struct = struct.Struct('>QQH')
+export_tuple = collections.namedtuple('Export', 'reserved magic opt len')
+export_struct = struct.Struct('>IQII')
+neg2_struct = struct.Struct('>QH124x')
+request_tuple = collections.namedtuple('Request', 'magic type handle from_ len')
+request_struct = struct.Struct('>IIQQI')
+reply_struct = struct.Struct('>IIQ')
+
+def recvall(sock, bufsize):
+ received = 0
+ chunks = []
+ while received < bufsize:
+ chunk = sock.recv(bufsize - received)
+ if len(chunk) == 0:
+ raise Exception('unexpected disconnect')
+ chunks.append(chunk)
+ received += len(chunk)
+ return ''.join(chunks)
+
+class ExportHandler(object):
+ def write(self, offset, data):
+ pass
+
+ def size(self):
+ return 0
+
+def negotiate(conn, exports):
+ '''Negotiate export with client'''
+ # Send negotiation part 1
+ buf = neg1_struct.pack(NBD_PASSWD, NBD_OPTS_MAGIC, 0)
+ conn.sendall(buf)
+
+ # Receive export option
+ buf = recvall(conn, export_struct.size)
+ export = export_tuple._make(export_struct.unpack(buf))
+ assert export.magic == NBD_OPTS_MAGIC
+ assert export.opt == NBD_OPT_EXPORT_NAME
+ name = recvall(conn, export.len)
+
+ if name not in exports:
+ print 'name "%s" not in exports' % name
+ return None
+ handler = exports[name]
+
+ # Send negotiation part 2
+ buf = neg2_struct.pack(handler.size(), 0)
+ conn.sendall(buf)
+ return handler
+
+def read_request(conn):
+ '''Parse NBD request from client'''
+ buf = recvall(conn, request_struct.size)
+ req = request_tuple._make(request_struct.unpack(buf))
+ assert req.magic == NBD_REQUEST_MAGIC
+ return req
+
+def write_reply(conn, error, handle):
+ buf = reply_struct.pack(NBD_REPLY_MAGIC, error, handle)
+ conn.sendall(buf)
+
+def server_connection_thread(conn, exports):
+ handler = negotiate(conn, exports)
+ if handler is None:
+ conn.close()
+ return
+
+ while True:
+ req = read_request(conn)
+ if req.type == NBD_CMD_WRITE:
+ # Reply immediately, don't propagate internal errors to client
+ write_reply(conn, 0, req.handle)
+
+ data = recvall(conn, req.len)
+ handler.write(req.from_, data)
+ elif req.type == NBD_CMD_DISC:
+ break
+ else:
+ print 'unrecognized command type %#02x' % req.type
+ break
+ conn.close()
+
+class Server(object):
+ def __init__(self, sock):
+ self.sock = sock
+ self.exports = {}
+
+ def add_export(self, name, handler):
+ self.exports[name] = handler
+
+ def run(self):
+ threads = []
+ for i in range(len(self.exports)):
+ conn, _ = self.sock.accept()
+ t = threading.Thread(target=server_connection_thread,
+ args=(conn, self.exports))
+ t.daemon = True
+ t.start()
+ threads.append(t)
+ for t in threads:
+ t.join()
--
1.8.1.4
next prev parent reply other threads:[~2013-03-09 22:23 UTC|newest]
Thread overview: 37+ messages / expand[flat|nested] mbox.gz Atom feed top
2013-03-09 22:22 [Qemu-devel] [RFC 0/8] block: Live backup prototype Stefan Hajnoczi
2013-03-09 22:22 ` [Qemu-devel] [RFC 1/8] block: add virtual_size to query-block QMP output Stefan Hajnoczi
2013-03-11 17:35 ` Eric Blake
2013-03-09 22:22 ` [Qemu-devel] [RFC 2/8] add basic backup support to block driver Stefan Hajnoczi
2013-03-09 22:22 ` [Qemu-devel] [RFC 3/8] backup: write to BlockDriverState instead of BackupDumpFunc Stefan Hajnoczi
2013-03-10 10:05 ` Dietmar Maurer
2013-03-10 11:13 ` Stefan Hajnoczi
2013-03-09 22:22 ` [Qemu-devel] [RFC 4/8] block: add block_backup QMP command Stefan Hajnoczi
2013-03-14 21:46 ` Eric Blake
2013-03-14 21:52 ` Eric Blake
2013-03-15 8:38 ` Stefan Hajnoczi
2013-04-11 12:32 ` Paolo Bonzini
2013-03-09 22:22 ` Stefan Hajnoczi [this message]
2013-03-09 22:22 ` [Qemu-devel] [RFC 6/8] Add VMA backup archive writer Python module Stefan Hajnoczi
2013-03-09 22:22 ` [Qemu-devel] [RFC 7/8] Add vma-writer.py tool Stefan Hajnoczi
2013-03-09 22:22 ` [Qemu-devel] [RFC 8/8] Add backup.py tool Stefan Hajnoczi
2013-03-10 9:14 ` [Qemu-devel] [RFC 0/8] block: Live backup prototype Dietmar Maurer
2013-03-10 10:19 ` Stefan Hajnoczi
2013-03-10 10:38 ` Dietmar Maurer
2013-03-10 11:09 ` Stefan Hajnoczi
2013-03-10 10:50 ` Dietmar Maurer
2013-03-10 11:10 ` Stefan Hajnoczi
2013-03-11 8:58 ` Dietmar Maurer
2013-03-11 9:26 ` Dietmar Maurer
2013-03-11 14:27 ` Stefan Hajnoczi
2013-03-11 15:00 ` Dietmar Maurer
2013-03-11 17:11 ` Stefan Hajnoczi
2013-03-10 9:57 ` Dietmar Maurer
2013-03-10 10:41 ` Stefan Hajnoczi
2013-03-12 9:18 ` Kevin Wolf
2013-03-12 10:50 ` Stefan Hajnoczi
2013-03-12 11:15 ` Dietmar Maurer
2013-03-12 12:18 ` Stefan Hajnoczi
2013-03-12 11:22 ` Kevin Wolf
2013-03-12 11:31 ` Dietmar Maurer
2013-03-12 11:37 ` Dietmar Maurer
2013-03-12 12:17 ` Stefan Hajnoczi
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1362867748-30528-6-git-send-email-stefanha@redhat.com \
--to=stefanha@redhat.com \
--cc=armbru@redhat.com \
--cc=dietmar@proxmox.com \
--cc=kwolf@redhat.com \
--cc=qemu-devel@nongnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).