qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
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

  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).