diff options
Diffstat (limited to 'tools/python')
29 files changed, 2994 insertions, 792 deletions
diff --git a/tools/python/setup.py b/tools/python/setup.py index e6b04f8708..fabe80bd8b 100644 --- a/tools/python/setup.py +++ b/tools/python/setup.py @@ -9,13 +9,15 @@ extra_compile_args = [ "-fno-strict-aliasing", "-Wall", "-Werror" ] include_dirs = [ XEN_ROOT + "/tools/python/xen/lowlevel/xu", XEN_ROOT + "/tools/libxc", + XEN_ROOT + "/tools/xenstore", XEN_ROOT + "/tools/xcs", ] library_dirs = [ XEN_ROOT + "/tools/libxc", + XEN_ROOT + "/tools/xenstore", ] -libraries = [ "xc" ] +libraries = [ "xc", "xenstore" ] xc = Extension("xc", extra_compile_args = extra_compile_args, @@ -30,7 +32,14 @@ xu = Extension("xu", library_dirs = library_dirs, libraries = libraries, sources = [ "xen/lowlevel/xu/xu.c" ]) - + +xs = Extension("xs", + extra_compile_args = extra_compile_args, + include_dirs = include_dirs + [ "xen/lowlevel/xs" ], + library_dirs = library_dirs, + libraries = libraries, + sources = [ "xen/lowlevel/xs/xs.c" ]) + setup(name = 'xen', version = '2.0', description = 'Xen', @@ -39,11 +48,12 @@ setup(name = 'xen', 'xen.util', 'xen.xend', 'xen.xend.server', + 'xen.xend.xenstore', 'xen.xm', 'xen.web', ], ext_package = "xen.lowlevel", - ext_modules = [ xc, xu ] + ext_modules = [ xc, xu, xs ] ) os.chdir('logging') diff --git a/tools/python/xen/lowlevel/xc/xc.c b/tools/python/xen/lowlevel/xc/xc.c index 013fbe1fcc..13d60be08e 100644 --- a/tools/python/xen/lowlevel/xc/xc.c +++ b/tools/python/xen/lowlevel/xc/xc.c @@ -14,6 +14,7 @@ #include <sys/socket.h> #include <netdb.h> #include <arpa/inet.h> + #include "xc_private.h" #include "linux_boot_params.h" @@ -259,25 +260,28 @@ static PyObject *pyxc_linux_build(PyObject *self, { XcObject *xc = (XcObject *)self; - u32 dom; + u32 dom; char *image, *ramdisk = NULL, *cmdline = ""; - int control_evtchn, flags = 0, vcpus = 1; + int flags = 0, vcpus = 1; + int control_evtchn, store_evtchn; + unsigned long store_mfn = 0; - static char *kwd_list[] = { "dom", "control_evtchn", - "image", "ramdisk", "cmdline", "flags", "vcpus", - NULL }; + static char *kwd_list[] = { "dom", "control_evtchn", "store_evtchn", + "image", "ramdisk", "cmdline", "flags", + "vcpus", NULL }; - if ( !PyArg_ParseTupleAndKeywords(args, kwds, "iis|ssii", kwd_list, - &dom, &control_evtchn, - &image, &ramdisk, &cmdline, &flags, &vcpus) ) + if ( !PyArg_ParseTupleAndKeywords(args, kwds, "iiis|ssii", kwd_list, + &dom, &control_evtchn, &store_evtchn, + &image, &ramdisk, &cmdline, &flags, + &vcpus) ) return NULL; if ( xc_linux_build(xc->xc_handle, dom, image, - ramdisk, cmdline, control_evtchn, flags, vcpus) != 0 ) + ramdisk, cmdline, control_evtchn, flags, vcpus, + store_evtchn, &store_mfn) != 0 ) return PyErr_SetFromErrno(xc_error); - Py_INCREF(zero); - return zero; + return Py_BuildValue("{s:i}", "store_mfn", store_mfn); } static PyObject *pyxc_plan9_build(PyObject *self, @@ -834,6 +838,7 @@ static PyMethodDef pyxc_methods[] = { 0, "\n" "Query the xc control interface file descriptor.\n\n" "Returns: [int] file descriptor\n" }, + { "domain_create", (PyCFunction)pyxc_domain_create, METH_VARARGS | METH_KEYWORDS, "\n" @@ -844,8 +849,8 @@ static PyMethodDef pyxc_methods[] = { { "domain_dumpcore", (PyCFunction)pyxc_domain_dumpcore, METH_VARARGS | METH_KEYWORDS, "\n" - "dump core of a domain.\n" - " dom [int]: Identifier of domain to be paused.\n\n" + "Dump core of a domain.\n" + " dom [int]: Identifier of domain to dump core of.\n" " corefile [string]: Name of corefile to be created.\n\n" "Returns: [int] 0 on success; -1 on error.\n" }, diff --git a/tools/python/xen/lowlevel/xs/xs.c b/tools/python/xen/lowlevel/xs/xs.c new file mode 100644 index 0000000000..98d7826809 --- /dev/null +++ b/tools/python/xen/lowlevel/xs/xs.c @@ -0,0 +1,617 @@ +#include <Python.h> + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include "xs.h" + +/** @file + * Python interface to the Xen Store Daemon (xs). + */ + +/* Needed for Python versions earlier than 2.3. */ +//#ifndef PyMODINIT_FUNC +//#define PyMODINIT_FUNC DL_EXPORT(void) +//#endif + +#define PYPKG "xen.lowlevel.xs" + +/** Python wrapper round an xs handle. + */ +typedef struct XsHandle { + PyObject_HEAD; + struct xs_handle *xh; +} XsHandle; + +static inline struct xs_handle *xshandle(PyObject *self) +{ + struct xs_handle *xh = ((XsHandle*)self)->xh; + if (!xh) + PyErr_SetString(PyExc_RuntimeError, "invalid xenstore daemon handle"); + return xh; +} + +static inline PyObject *pyvalue_int(int val) { + return (val + ? PyInt_FromLong(val) + : PyErr_SetFromErrno(PyExc_RuntimeError)); +} + +static inline PyObject *pyvalue_str(char *val) { + return (val + ? PyString_FromString(val) + : PyErr_SetFromErrno(PyExc_RuntimeError)); +} + +static PyObject *xspy_write(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwd_spec[] = { "path", "data", "create", "excl", NULL }; + static char *arg_spec = "ss#|ii"; + char *path = NULL; + char *data = NULL; + int data_n = 0; + int create = 0; + int excl = 0; + + struct xs_handle *xh = xshandle(self); + PyObject *val = NULL; + int flags = 0; + int xsval = 0; + + if (!xh) + goto exit; + if (!PyArg_ParseTupleAndKeywords(args, kwds, arg_spec, kwd_spec, + &path, &data, &data_n, &create, &excl)) + goto exit; + if (create) + flags |= O_CREAT; + if (excl) + flags |= O_EXCL; + xsval = xs_write(xh, path, data, data_n, flags); + val = pyvalue_int(xsval); + exit: + return val; +} + +static PyObject *xspy_read(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwd_spec[] = { "path", NULL }; + static char *arg_spec = "s|"; + char *path = NULL; + + struct xs_handle *xh = xshandle(self); + char *xsval = NULL; + int xsval_n = 0; + PyObject *val = NULL; + + if (!xh) + goto exit; + if (!PyArg_ParseTupleAndKeywords(args, kwds, arg_spec, kwd_spec, + &path)) + goto exit; + xsval = xs_read(xh, path, &xsval_n); + if (!xsval) { + val = pyvalue_int(0); + goto exit; + } + val = PyString_FromStringAndSize(xsval, xsval_n); + exit: + if (xsval) + free(xsval); + return val; +} + +static PyObject *xspy_mkdir(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwd_spec[] = { "path", NULL }; + static char *arg_spec = "s|"; + char *path = NULL; + + struct xs_handle *xh = xshandle(self); + PyObject *val = NULL; + int xsval = 0; + + if (!xh) + goto exit; + if (!PyArg_ParseTupleAndKeywords(args, kwds, arg_spec, kwd_spec, &path)) + goto exit; + xsval = xs_mkdir(xh, path); + val = pyvalue_int(xsval); + exit: + return val; +} + +static PyObject *xspy_ls(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwd_spec[] = { "path", NULL }; + static char *arg_spec = "s|"; + char *path = NULL; + + struct xs_handle *xh = xshandle(self); + PyObject *val = NULL; + char **xsval = NULL; + int xsval_n = 0; + int i; + + if (!xh) + goto exit; + if (!PyArg_ParseTupleAndKeywords(args, kwds, arg_spec, kwd_spec, &path)) + goto exit; + xsval = xs_directory(xh, path, &xsval_n); + if (!xsval) { + val = pyvalue_int(0); + goto exit; + } + val = PyList_New(xsval_n); + for (i = 0; i < xsval_n; i++) + PyList_SetItem(val, i, PyString_FromString(xsval[i])); + exit: + return val; +} + +static PyObject *xspy_rm(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwd_spec[] = { "path", NULL }; + static char *arg_spec = "s|"; + char *path = NULL; + + struct xs_handle *xh = xshandle(self); + PyObject *val = NULL; + int xsval = 0; + + if (!xh) + goto exit; + if (!PyArg_ParseTupleAndKeywords(args, kwds, arg_spec, kwd_spec, &path)) + goto exit; + xsval = xs_rm(xh, path); + val = pyvalue_int(xsval); + exit: + return val; +} + +static PyObject *xspy_get_permissions(PyObject *self, PyObject *args, + PyObject *kwds) +{ + static char *kwd_spec[] = { "path", NULL }; + static char *arg_spec = "s|"; + char *path = NULL; + + struct xs_handle *xh = xshandle(self); + PyObject *val = NULL; + struct xs_permissions *perms; + int perms_n = 0; + int i; + + if (!xh) + goto exit; + if (!PyArg_ParseTupleAndKeywords(args, kwds, arg_spec, kwd_spec, &path)) + goto exit; + perms = xs_get_permissions(xh, path, &perms_n); + if (!perms) { + PyErr_SetFromErrno(PyExc_RuntimeError); + goto exit; + } + val = PyList_New(perms_n); + for (i = 0; i < perms_n; i++, perms++) { + PyObject *p = Py_BuildValue("{s:i,s:i,s:i,s:i,s:i}", + "dom", perms->id, + "read", (perms->perms & XS_PERM_READ), + "write", (perms->perms & XS_PERM_WRITE), + "create", (perms->perms & XS_PERM_CREATE), + "owner", (perms->perms & XS_PERM_OWNER)); + PyList_SetItem(val, i, p); + } + exit: + return val; +} + +static PyObject *xspy_set_permissions(PyObject *self, PyObject *args, + PyObject *kwds) +{ + static char *kwd_spec[] = { "path", "perms", NULL }; + static char *arg_spec = "sO"; + char *path = NULL; + PyObject *perms = NULL; + static char *perm_names[] = { "dom", "read", "write", "create", "owner", + NULL }; + static char *perm_spec = "i|iiii"; + + struct xs_handle *xh = xshandle(self); + int i, xsval; + struct xs_permissions *xsperms = NULL; + int xsperms_n = 0; + PyObject *tuple0 = NULL; + PyObject *val = NULL; + + if (!xh) + goto exit; + if (!PyArg_ParseTupleAndKeywords(args, kwds, arg_spec, kwd_spec, + &path, &perms)) + goto exit; + if (!PyList_Check(perms)) { + PyErr_SetString(PyExc_RuntimeError, "perms must be a list"); + goto exit; + } + xsperms_n = PyList_Size(perms); + xsperms = calloc(xsperms_n, sizeof(struct xs_permissions)); + if (!xsperms) { + PyErr_SetString(PyExc_RuntimeError, "out of memory"); + goto exit; + } + tuple0 = PyTuple_New(0); + if (!tuple0) + goto exit; + for (i = 0; i < xsperms_n; i++) { + /* Domain the permissions apply to. */ + int dom = 0; + /* Read/write perms. Set these. */ + int p_read = 0, p_write = 0; + /* Create/owner perms. Ignore them. + * This is so the output from get_permissions() can be used + * as input to set_permissions(). + */ + int p_create = 0, p_owner = 0; + PyObject *p = PyList_GetItem(perms, i); + if (!PyArg_ParseTupleAndKeywords(tuple0, p, perm_spec, perm_names, + &dom, &p_read, &p_write, &p_create, + &p_owner)) + goto exit; + xsperms[i].id = dom; + if (p_read) + xsperms[i].perms |= XS_PERM_READ; + if (p_write) + xsperms[i].perms |= XS_PERM_WRITE; + } + xsval = xs_set_permissions(xh, path, xsperms, xsperms_n); + val = pyvalue_int(xsval); + exit: + Py_XDECREF(tuple0); + if (xsperms) + free(xsperms); + return val; +} + +static PyObject *xspy_watch(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwd_spec[] = { "path", "priority", NULL }; + static char *arg_spec = "s|i"; + char *path = NULL; + int priority = 0; + + struct xs_handle *xh = xshandle(self); + PyObject *val = NULL; + int xsval = 0; + + if (!xh) + goto exit; + if (!PyArg_ParseTupleAndKeywords(args, kwds, arg_spec, kwd_spec, + &path, &priority)) + goto exit; + xsval = xs_watch(xh, path, priority); + val = pyvalue_int(xsval); + exit: + return val; +} + +static PyObject *xspy_read_watch(PyObject *self, PyObject *args, + PyObject *kwds) +{ + static char *kwd_spec[] = { NULL }; + static char *arg_spec = ""; + + struct xs_handle *xh = xshandle(self); + PyObject *val = NULL; + char *xsval = NULL; + + if (!xh) + goto exit; + if (!PyArg_ParseTupleAndKeywords(args, kwds, arg_spec, kwd_spec)) + goto exit; + xsval = xs_read_watch(xh); + val = pyvalue_str(xsval); + exit: + if (xsval) + free(xsval); + return val; +} + +static PyObject *xspy_acknowledge_watch(PyObject *self, PyObject *args, + PyObject *kwds) +{ + static char *kwd_spec[] = { NULL }; + static char *arg_spec = ""; + + struct xs_handle *xh = xshandle(self); + PyObject *val = NULL; + int xsval = 0; + + if (!xh) + goto exit; + if (!PyArg_ParseTupleAndKeywords(args, kwds, arg_spec, kwd_spec)) + goto exit; + xsval = xs_acknowledge_watch(xh); + val = pyvalue_int(xsval); + exit: + return val; +} + +static PyObject *xspy_unwatch(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwd_spec[] = { "path", NULL }; + static char *arg_spec = "s|"; + char *path = NULL; + + struct xs_handle *xh = xshandle(self); + PyObject *val = NULL; + int xsval = 0; + + if (!xh) + goto exit; + if (!PyArg_ParseTupleAndKeywords(args, kwds, arg_spec, kwd_spec, &path)) + goto exit; + xsval = xs_unwatch(xh, path); + val = pyvalue_int(xsval); + exit: + return val; +} + +static PyObject *xspy_transaction_start(PyObject *self, PyObject *args, + PyObject *kwds) +{ + static char *kwd_spec[] = { "path", NULL }; + static char *arg_spec = "s|"; + char *path = NULL; + + struct xs_handle *xh = xshandle(self); + PyObject *val = NULL; + int xsval = 0; + + if (!xh) + goto exit; + if (!PyArg_ParseTupleAndKeywords(args, kwds, arg_spec, kwd_spec, &path)) + goto exit; + xsval = xs_transaction_start(xh, path); + val = pyvalue_int(xsval); + exit: + return val; +} + +static PyObject *xspy_transaction_end(PyObject *self, PyObject *args, + PyObject *kwds) +{ + static char *kwd_spec[] = { "abort", NULL }; + static char *arg_spec = "|i"; + int abort = 0; + + struct xs_handle *xh = xshandle(self); + PyObject *val = NULL; + int xsval = 0; + + if (!xh) + goto exit; + if (!PyArg_ParseTupleAndKeywords(args, kwds, arg_spec, kwd_spec, &abort)) + goto exit; + xsval = xs_transaction_end(xh, abort); + val = pyvalue_int(xsval); + exit: + return val; +} + +static PyObject *xspy_introduce_domain(PyObject *self, PyObject *args, + PyObject *kwds) +{ + static char *kwd_spec[] = { "dom", "page", "port", "path", NULL }; + static char *arg_spec = "iiis|"; + domid_t dom = 0; + unsigned long page = 0; + unsigned int port = 0; + char *path = NULL; + + struct xs_handle *xh = xshandle(self); + PyObject *val = NULL; + int xsval = 0; + + if (!xh) + goto exit; + if (!PyArg_ParseTupleAndKeywords(args, kwds, arg_spec, kwd_spec, + &dom, &page, &port, &path)) + goto exit; + printf("%s> dom=%u page=0x%08lx port=%u path=%s\n", __FUNCTION__, dom, + page, port, path); + xsval = xs_introduce_domain(xh, dom, page, port, path); + printf("%s> xsval=%d\n", __FUNCTION__, xsval); + val = pyvalue_int(xsval); + exit: + return val; +} + +static PyObject *xspy_release_domain(PyObject *self, PyObject *args, + PyObject *kwds) +{ + static char *kwd_spec[] = { "dom", NULL }; + static char *arg_spec = "i|"; + domid_t dom; + + struct xs_handle *xh = xshandle(self); + PyObject *val = NULL; + int xsval = 0; + + if (!xh) + goto exit; + if (!PyArg_ParseTupleAndKeywords(args, kwds, arg_spec, kwd_spec, + &dom)) + goto exit; + printf("%s> dom=%u\n", __FUNCTION__, dom); + xsval = xs_release_domain(xh, dom); + printf("%s> xsval=%d\n", __FUNCTION__, xsval); + val = pyvalue_int(xsval); + exit: + return val; +} + +static PyObject *xspy_close(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwd_spec[] = { NULL }; + static char *arg_spec = ""; + + struct xs_handle *xh = xshandle(self); + PyObject *val = NULL; + int xsval = 1; + + if (!xh) + goto exit; + if (!PyArg_ParseTupleAndKeywords(args, kwds, arg_spec, kwd_spec)) + goto exit; + xs_daemon_close(xh); + ((XsHandle*)self)->xh = NULL; + val = pyvalue_int(xsval); + exit: + return val; +} + +static PyObject *xspy_shutdown(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwd_spec[] = { NULL }; + static char *arg_spec = ""; + + struct xs_handle *xh = xshandle(self); + PyObject *val = NULL; + int xsval = 0; + + if (!xh) + goto exit; + if (!PyArg_ParseTupleAndKeywords(args, kwds, arg_spec, kwd_spec)) + goto exit; + xsval = xs_shutdown(xh); + val = pyvalue_int(xsval); + exit: + return val; +} + +#define XSPY_METH(_name) \ + #_name, \ + (PyCFunction) xspy_ ## _name, \ + (METH_VARARGS | METH_KEYWORDS) +// mtime +// ctime + +static PyMethodDef xshandle_methods[] = { + { XSPY_METH(read), + "read(path) : read data\n" }, + { XSPY_METH(write), + "write(path, data, [creat], [excl]): write data\n" }, + { XSPY_METH(ls), + "ls(path): list directory.\n" }, + { XSPY_METH(mkdir), + "mkdir(path): make a directory.\n" }, + { XSPY_METH(rm), + "rm(path): remove a path (dir must be empty).\n" }, + { XSPY_METH(get_permissions), + "get_permissions(path)\n" }, + { XSPY_METH(set_permissions), + "set_permissions(path)\n" }, + { XSPY_METH(watch), + "watch(path)\n" }, + { XSPY_METH(read_watch), + "read_watch()\n" }, + { XSPY_METH(acknowledge_watch), + "acknowledge_watch()\n" }, + { XSPY_METH(unwatch), + "unwatch()\n" }, + { XSPY_METH(transaction_start), + "transaction_start()\n" }, + { XSPY_METH(transaction_end), + "transaction_end([abort])\n" }, + { XSPY_METH(introduce_domain), + "introduce_domain(dom, page, port)\n" }, + { XSPY_METH(release_domain), + "release_domain(dom)\n" }, + { XSPY_METH(close), + "close()\n" }, + { XSPY_METH(shutdown), + "shutdown()\n" }, + { NULL, NULL, 0, NULL } +}; + +static PyObject *xshandle_getattr(PyObject *self, char *name) +{ + PyObject *val = NULL; + if (strcmp(name, "fileno") == 0) { + struct xs_handle *xh = xshandle(self); + val = PyInt_FromLong((xh ? xs_fileno(xh) : -1)); + } else + val = Py_FindMethod(xshandle_methods, self, name); + return val; +} + +static void xshandle_dealloc(PyObject *self) +{ + XsHandle *xh = (XsHandle*)self; + if (xh->xh) { + xs_daemon_close(xh->xh); + xh->xh = NULL; + } + PyObject_Del(self); +} + +static PyTypeObject xshandle_type = { + PyObject_HEAD_INIT(&PyType_Type) + 0, + "xshandle", + sizeof(XsHandle), + 0, + xshandle_dealloc, /* tp_dealloc */ + NULL, /* tp_print */ + xshandle_getattr, /* tp_getattr */ + NULL, /* tp_setattr */ + NULL, /* tp_compare */ + NULL, /* tp_repr */ + NULL, /* tp_as_number */ + NULL, /* tp_as_sequence */ + NULL, /* tp_as_mapping */ + NULL /* tp_hash */ +}; + +static PyObject *xshandle_open(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwd_spec[] = { "readonly", NULL }; + static char *arg_spec = "|i"; + int readonly = 0; + + XsHandle *xsh = NULL; + PyObject *val = NULL; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, arg_spec, kwd_spec, + &readonly)) + goto exit; + + xsh = PyObject_New(XsHandle, &xshandle_type); + if (!xsh) + goto exit; + xsh->xh = (readonly ? xs_daemon_open_readonly() : xs_daemon_open()); + if (!xsh->xh) { + PyObject_Del(xsh); + val = pyvalue_int(0); + goto exit; + } + val = (PyObject *)xsh; + exit: + return val; +} + +static PyMethodDef xs_methods[] = { + { "open", (PyCFunction)xshandle_open, (METH_VARARGS | METH_KEYWORDS), + "Open a connection to the xenstore daemon.\n" }, + { NULL, NULL, 0, NULL } +}; + +PyMODINIT_FUNC initxs (void) +{ + PyObject *module; + + module = Py_InitModule(PYPKG, xs_methods); +} diff --git a/tools/python/xen/lowlevel/xu/xu.c b/tools/python/xen/lowlevel/xu/xu.c index c9c5b3873a..359cb71a2d 100644 --- a/tools/python/xen/lowlevel/xu/xu.c +++ b/tools/python/xen/lowlevel/xu/xu.c @@ -1370,7 +1370,8 @@ static PyObject *xu_port_new(PyObject *self, PyObject *args, PyObject *kwds) fail1: PyObject_Del((PyObject *)xup); - return NULL; + PyErr_SetString(PyExc_ValueError, "cannot create port"); + return NULL; } static PyObject *xu_port_getattr(PyObject *obj, char *name) diff --git a/tools/python/xen/util/mac.py b/tools/python/xen/util/mac.py new file mode 100644 index 0000000000..47dffd80d5 --- /dev/null +++ b/tools/python/xen/util/mac.py @@ -0,0 +1,11 @@ + +from string import join, split + +def macToString(mac): + return ':'.join(map(lambda x: "%02x" % x, mac)) + +def macFromString(str): + mac = [ int(x, 16) for x in str.split(':') ] + if len(mac) != 6: + raise ValueError("invalid mac: %s" % str) + return mac diff --git a/tools/python/xen/web/SrvDir.py b/tools/python/xen/web/SrvDir.py index fb9eb14b3c..b168a8ef48 100644 --- a/tools/python/xen/web/SrvDir.py +++ b/tools/python/xen/web/SrvDir.py @@ -77,19 +77,16 @@ class SrvDir(SrvBase): return v def render_GET(self, req): - try: - if self.use_sxp(req): - req.setHeader("Content-type", sxp.mime_type) - self.ls(req, 1) - else: - req.write('<html><head></head><body>') - self.print_path(req) - self.ls(req) - self.form(req) - req.write('</body></html>') - return '' - except Exception, ex: - self._perform_err(ex, "GET", req) + if self.use_sxp(req): + req.setHeader("Content-type", sxp.mime_type) + self.ls(req, 1) + else: + req.write('<html><head></head><body>') + self.print_path(req) + self.ls(req) + self.form(req) + req.write('</body></html>') + return '' def ls(self, req, use_sxp=0): url = req.prePathURL() diff --git a/tools/python/xen/xend/PrettyPrint.py b/tools/python/xen/xend/PrettyPrint.py index 5fcc6e6d08..a57a3c6b52 100644 --- a/tools/python/xen/xend/PrettyPrint.py +++ b/tools/python/xen/xend/PrettyPrint.py @@ -285,15 +285,18 @@ def prettyprint(sxpr, out=sys.stdout, width=80): sxp.show(sxpr, out=out) print >> out -def prettyprintstring(sxp): - class tmpstr: - def __init__(self): - self.str = "" - def write(self, str): - self.str = self.str + str - tmp = tmpstr() - prettyprint(sxp, out=tmp) - return tmp.str +def prettyprintstring(sxpr, width=80): + """Prettyprint an SXP form to a string. + + sxpr s-expression + width maximum output width + """ + io = StringIO.StringIO() + prettyprint(sxpr, out=io, width=width) + io.seek(0) + val = io.getvalue() + io.close() + return val def main(): pin = sxp.Parser() diff --git a/tools/python/xen/xend/XendCheckpoint.py b/tools/python/xen/xend/XendCheckpoint.py index e3908df885..654fb022c5 100644 --- a/tools/python/xen/xend/XendCheckpoint.py +++ b/tools/python/xen/xend/XendCheckpoint.py @@ -43,7 +43,7 @@ def save(xd, fd, dominfo): write_exact(fd, config, "could not write guest state file: config") cmd = [PATH_XC_SAVE, str(xc.handle()), str(fd), - dominfo.id] + str(dominfo.id)] log.info("[xc_save] " + join(cmd)) child = xPopen3(cmd, True, -1, [fd, xc.handle()]) @@ -63,10 +63,10 @@ def save(xd, fd, dominfo): if fd == child.fromchild.fileno(): l = child.fromchild.readline() if l.rstrip() == "suspend": - log.info("suspending %s" % dominfo.id) + log.info("suspending %d" % dominfo.id) xd.domain_shutdown(dominfo.id, reason='suspend') dominfo.state_wait("suspended") - log.info("suspend %s done" % dominfo.id) + log.info("suspend %d done" % dominfo.id) child.tochild.write("done\n") child.tochild.flush() if filter(lambda (fd, event): event & select.POLLHUP, r): @@ -109,7 +109,7 @@ def restore(xd, fd): "not a valid guest state file: pfn count out of range") cmd = [PATH_XC_RESTORE, str(xc.handle()), str(fd), - dominfo.id, str(nr_pfns)] + str(dominfo.id), str(nr_pfns)] log.info("[xc_restore] " + join(cmd)) child = xPopen3(cmd, True, -1, [fd, xc.handle()]) child.tochild.close() diff --git a/tools/python/xen/xend/XendDomain.py b/tools/python/xen/xend/XendDomain.py index 3fb066327f..ff688f6df1 100644 --- a/tools/python/xen/xend/XendDomain.py +++ b/tools/python/xen/xend/XendDomain.py @@ -7,46 +7,42 @@ """ import errno import os -import scheduler -import string import sys -import traceback import time +import traceback import xen.lowlevel.xc; xc = xen.lowlevel.xc.new() -from xen.xend.server import relocate -import sxp -import XendRoot; xroot = XendRoot.instance() -import XendCheckpoint -import XendDB -import XendDomainInfo -import EventServer; eserver = EventServer.instance() -from XendError import XendError -from XendLogging import log - +from xen.xend import sxp +from xen.xend import XendRoot; xroot = XendRoot.instance() +from xen.xend import XendCheckpoint +from xen.xend.XendDomainInfo import XendDomainInfo, shutdown_reason +from xen.xend import EventServer; eserver = EventServer.instance() +from xen.xend.XendError import XendError +from xen.xend.XendLogging import log +from xen.xend import scheduler from xen.xend.server import channel +from xen.xend.server import relocate +from xen.xend.uuid import getUuid +from xen.xend.xenstore import XenNode, DBMap __all__ = [ "XendDomain" ] SHUTDOWN_TIMEOUT = 30 +class XendDomainDict(dict): + def get_by_name(self, name): + try: + return filter(lambda d: d.name == name, self.values())[0] + except IndexError, err: + return None + class XendDomain: """Index of all domains. Singleton. """ - """Path to domain database.""" - dbpath = "domain" - - class XendDomainDict(dict): - def get_by_name(self, name): - try: - return filter(lambda d: d.name == name, self.values())[0] - except IndexError, err: - return None - """Dict of domain info indexed by domain id.""" - domains = XendDomainDict() + domains = None def __init__(self): # Hack alert. Python does not support mutual imports, but XendDomainInfo @@ -54,8 +50,8 @@ class XendDomain: # to import XendDomain from XendDomainInfo causes unbounded recursion. # So we stuff the XendDomain instance (self) into xroot's components. xroot.add_component("xen.xend.XendDomain", self) - # Table of domain info indexed by domain id. - self.db = XendDB.XendDB(self.dbpath) + self.domains = XendDomainDict() + self.dbmap = DBMap(db=XenNode("/domain")) eserver.subscribe('xend.virq', self.onVirq) self.initial_refresh() @@ -77,18 +73,16 @@ class XendDomain: domlist = xc.domain_getinfo() doms = {} for d in domlist: - domid = str(d['dom']) + domid = d['dom'] doms[domid] = d return doms def xen_domain(self, dom): """Get info about a single domain from xc. Returns None if not found. + + @param dom domain id (int) """ - try: - dom = int(dom) - except ValueError: - return None dominfo = xc.domain_getinfo(dom, 1) if dominfo == [] or dominfo[0]['dom'] != dom: dominfo = None @@ -100,37 +94,36 @@ class XendDomain: """Refresh initial domain info from db. """ doms = self.xen_domains() - for config in self.db.fetchall("").values(): - domid = str(sxp.child_value(config, 'id')) - if domid in doms: + self.dbmap.readDB() + for domdb in self.dbmap.values(): + try: + domid = int(domdb.id) + except: + domid = None + # XXX if domid in self.domains, then something went wrong + if (domid is None) or (domid in self.domains): + domdb.delete() + elif domid in doms: try: - self._new_domain(config, doms[domid]) - self.update_domain(domid) + self._new_domain(domdb, doms[domid]) except Exception, ex: - log.exception("Error recreating domain info: id=%s", domid) + log.exception("Error recreating domain info: id=%d", domid) self._delete_domain(domid) else: self._delete_domain(domid) self.refresh(cleanup=True) - def sync_domain(self, info): - """Sync info for a domain to disk. - - info domain info - """ - self.db.save(info.id, info.sxpr()) - def close(self): pass - def _new_domain(self, savedinfo, info): + def _new_domain(self, db, info): """Create a domain entry from saved info. - @param savedinfo: saved info from the db - @param info: domain info from xen + @param db: saved info from the db + @param info: domain info from xen @return: domain """ - dominfo = XendDomainInfo.vm_recreate(savedinfo, info) + dominfo = XendDomainInfo.recreate(db, info) self.domains[dominfo.id] = dominfo return dominfo @@ -144,11 +137,11 @@ class XendDomain: for i, d in self.domains.items(): if i != d.id: del self.domains[i] - self.db.delete(i) + self.dbmap.delete(d.uuid) if info.id in self.domains: notify = False self.domains[info.id] = info - self.sync_domain(info) + info.exportToDB(save=True) if notify: eserver.inject('xend.domain.create', [info.name, info.id]) @@ -158,12 +151,26 @@ class XendDomain: @param id: domain id @param notify: send a domain died event if true """ + try: + if self.xen_domain(id): + return + except: + pass info = self.domains.get(id) if info: del self.domains[id] + info.cleanup() + info.delete() if notify: eserver.inject('xend.domain.died', [info.name, info.id]) - self.db.delete(id) + # XXX this should not be needed + for domdb in self.dbmap.values(): + try: + domid = int(domdb.id) + except: + domid = None + if (domid is None) or (domid == id): + domdb.delete() def reap(self): """Look for domains that have crashed or stopped. @@ -178,22 +185,19 @@ class XendDomain: not(d['running'] or d['paused'] or d['blocked'])) if dead: casualties.append(d) - destroyed = 0 for d in casualties: - id = str(d['dom']) - #print 'reap>', id + id = d['dom'] dominfo = self.domains.get(id) name = (dominfo and dominfo.name) or '??' if dominfo and dominfo.is_terminated(): - #print 'reap> already terminated:', id continue - log.debug('XendDomain>reap> domain died name=%s id=%s', name, id) + log.debug('XendDomain>reap> domain died name=%s id=%d', name, id) if d['shutdown']: - reason = XendDomainInfo.shutdown_reason(d['shutdown_reason']) - log.debug('XendDomain>reap> shutdown name=%s id=%s reason=%s', name, id, reason) + reason = shutdown_reason(d['shutdown_reason']) + log.debug('XendDomain>reap> shutdown name=%s id=%d reason=%s', name, id, reason) if reason in ['suspend']: if dominfo and dominfo.is_terminated(): - log.debug('XendDomain>reap> Suspended domain died id=%s', id) + log.debug('XendDomain>reap> Suspended domain died id=%d', id) else: eserver.inject('xend.domain.suspended', [name, id]) if dominfo: @@ -203,10 +207,9 @@ class XendDomain: eserver.inject('xend.domain.exit', [name, id, reason]) self.domain_restart_schedule(id, reason) else: - if xroot.get_enable_dump() == 'true': - xc.domain_dumpcore(dom = int(id), corefile = "/var/xen/dump/%s.%s.core"%(name,id)) + if xroot.get_enable_dump(): + self.domain_dumpcore(id) eserver.inject('xend.domain.exit', [name, id, 'crash']) - destroyed += 1 self.final_domain_destroy(id) def refresh(self, cleanup=False): @@ -216,7 +219,7 @@ class XendDomain: self.reap() doms = self.xen_domains() # Add entries for any domains we don't know about. - for (id, d) in doms.items(): + for id in doms.keys(): if id not in self.domains: self.domain_lookup(id) # Remove entries for domains that no longer exist. @@ -234,16 +237,7 @@ class XendDomain: scheduler.now(self.domain_restarts) def update_domain(self, id): - """Update the saved info for a domain. - - @param id: domain id - """ - dominfo = self.domains.get(id) - if dominfo: - self.sync_domain(dominfo) - - def refresh_domain(self, id): - """Refresh information for a single domain. + """Update information for a single domain. @param id: domain id """ @@ -279,8 +273,7 @@ class XendDomain: @param config: configuration @return: domain """ - dominfo = XendDomainInfo.vm_create(config) - self._add_domain(dominfo) + dominfo = XendDomainInfo.create(self.dbmap, config) return dominfo def domain_restart(self, dominfo): @@ -293,7 +286,6 @@ class XendDomain: [dominfo.name, dominfo.id, "begin"]) try: dominfo.restart() - self._add_domain(dominfo) log.info('Restarted domain name=%s id=%s', dominfo.name, dominfo.id) eserver.inject("xend.domain.restart", [dominfo.name, dominfo.id, "success"]) @@ -309,14 +301,13 @@ class XendDomain: """Configure an existing domain. This is intended for internal use by domain restore and migrate. - @param id: domain id @param vmconfig: vm configuration """ config = sxp.child_value(vmconfig, 'config') - dominfo = XendDomainInfo.vm_restore(config) - self._add_domain(dominfo) + uuid = sxp.child_value(vmconfig, 'uuid') + dominfo = XendDomainInfo.restore(self.dbmap, config, uuid=uuid) return dominfo - + def domain_restore(self, src, progress=False): """Restore a domain from file. @@ -326,9 +317,7 @@ class XendDomain: try: fd = os.open(src, os.O_RDONLY) - return XendCheckpoint.restore(self, fd) - except OSError, ex: raise XendError("can't read guest state file %s: %s" % (src, ex[1])) @@ -339,24 +328,35 @@ class XendDomain: @param id: domain id @return: domain object (or None) """ - id = str(id) - self.refresh_domain(id) + self.update_domain(id) return self.domains.get(id) - def domain_lookup(self, name): - name = str(name) - dominfo = self.domains.get_by_name(name) or self.domains.get(name) - if dominfo: - return dominfo - try: - d = self.xen_domain(name) - if d: - log.info("Creating entry for unknown domain: id=%s", name) - dominfo = XendDomainInfo.vm_recreate(None, d) - self._add_domain(dominfo) - return dominfo - except Exception, ex: - log.exception("Error creating domain info: id=%s", name) + def domain_lookup(self, id): + dominfo = self.domains.get(id) + if not dominfo: + try: + info = self.xen_domain(id) + if info: + uuid = getUuid() + log.info( + "Creating entry for unknown domain: id=%d uuid=%s", + id, uuid) + db = self.dbmap.addChild(uuid) + dominfo = XendDomainInfo.recreate(db, info) + self._add_domain(dominfo) + except Exception, ex: + log.exception("Error creating domain info: id=%d", id) + return dominfo + + def domain_lookup_by_name(self, name): + dominfo = self.domains.get_by_name(name) + if not dominfo: + try: + id = int(name) + dominfo = self.domain_lookup(id) + except ValueError: + pass + return dominfo def domain_unpause(self, id): """Unpause domain execution. @@ -366,7 +366,7 @@ class XendDomain: dominfo = self.domain_lookup(id) eserver.inject('xend.domain.unpause', [dominfo.name, dominfo.id]) try: - return xc.domain_unpause(dom=dominfo.dom) + return xc.domain_unpause(dom=dominfo.id) except Exception, ex: raise XendError(str(ex)) @@ -378,7 +378,7 @@ class XendDomain: dominfo = self.domain_lookup(id) eserver.inject('xend.domain.pause', [dominfo.name, dominfo.id]) try: - return xc.domain_pause(dom=dominfo.dom) + return xc.domain_pause(dom=dominfo.id) except Exception, ex: raise XendError(str(ex)) @@ -436,7 +436,7 @@ class XendDomain: @param id: domain id @param reason: shutdown reason """ - log.debug('domain_restart_schedule> %s %s %d', id, reason, force) + log.debug('domain_restart_schedule> %d %s %d', id, reason, force) dominfo = self.domain_lookup(id) if not dominfo: return @@ -484,7 +484,7 @@ class XendDomain: except: #todo try: - val = xc.domain_destroy(dom=int(id)) + val = xc.domain_destroy(dom=id) except Exception, ex: raise XendError(str(ex)) return val @@ -553,7 +553,7 @@ class XendDomain: """ dominfo = self.domain_lookup(id) try: - return xc.domain_pincpu(int(dominfo.id), vcpu, cpumap) + return xc.domain_pincpu(dominfo.id, vcpu, cpumap) except Exception, ex: raise XendError(str(ex)) @@ -562,7 +562,7 @@ class XendDomain: """ dominfo = self.domain_lookup(id) try: - return xc.bvtsched_domain_set(dom=dominfo.dom, mcuadv=mcuadv, + return xc.bvtsched_domain_set(dom=dominfo.id, mcuadv=mcuadv, warpback=warpback, warpvalue=warpvalue, warpl=warpl, warpu=warpu) except Exception, ex: @@ -573,7 +573,7 @@ class XendDomain: """ dominfo = self.domain_lookup(id) try: - return xc.bvtsched_domain_get(dominfo.dom) + return xc.bvtsched_domain_get(dominfo.id) except Exception, ex: raise XendError(str(ex)) @@ -581,20 +581,21 @@ class XendDomain: def domain_cpu_sedf_set(self, id, period, slice, latency, extratime, weight): """Set Simple EDF scheduler parameters for a domain. """ - dominfo = self.domain_lookup(id) + dominfo = self.domain_lookup(id) try: - return xc.sedf_domain_set(dominfo.dom, period, slice, latency, extratime, weight) + return xc.sedf_domain_set(dominfo.id, period, slice, latency, extratime, weight) except Exception, ex: raise XendError(str(ex)) def domain_cpu_sedf_get(self, id): - """Get Atropos scheduler parameters for a domain. + """Get Simple EDF scheduler parameters for a domain. """ dominfo = self.domain_lookup(id) try: - return xc.sedf_domain_get(dominfo.dom) + return xc.sedf_domain_get(dominfo.id) except Exception, ex: raise XendError(str(ex)) + def domain_device_create(self, id, devconfig): """Create a new device for a domain. @@ -603,44 +604,44 @@ class XendDomain: """ dominfo = self.domain_lookup(id) val = dominfo.device_create(devconfig) - self.update_domain(dominfo.id) + dominfo.exportToDB() return val - def domain_device_configure(self, id, devconfig, idx): + def domain_device_configure(self, id, devconfig, devid): """Configure an existing device for a domain. @param id: domain id @param devconfig: device configuration - @param idx: device index + @param devid: device id @return: updated device configuration """ dominfo = self.domain_lookup(id) - val = dominfo.device_configure(devconfig, idx) - self.update_domain(dominfo.id) + val = dominfo.device_configure(devconfig, devid) + dominfo.exportToDB() return val - def domain_device_refresh(self, id, type, idx): + def domain_device_refresh(self, id, type, devid): """Refresh a device. @param id: domain id - @param idx: device index + @param devid: device id @param type: device type """ dominfo = self.domain_lookup(id) - val = dominfo.device_refresh(type, idx) - self.update_domain(dominfo.id) + val = dominfo.device_refresh(type, devid) + dominfo.exportToDB() return val - def domain_device_destroy(self, id, type, idx): + def domain_device_destroy(self, id, type, devid): """Destroy a device. @param id: domain id - @param idx: device index + @param devid: device id @param type: device type """ dominfo = self.domain_lookup(id) - val = dominfo.device_destroy(type, idx) - self.update_domain(dominfo.id) + val = dominfo.device_destroy(type, devid) + dominfo.exportToDB() return val def domain_devtype_ls(self, id, type): @@ -653,22 +654,22 @@ class XendDomain: dominfo = self.domain_lookup(id) return dominfo.getDeviceSxprs(type) - def domain_devtype_get(self, id, type, idx): + def domain_devtype_get(self, id, type, devid): """Get a device from a domain. - + @param id: domain @param type: device type - @param idx: device index + @param devid: device id @return: device object (or None) """ dominfo = self.domain_lookup(id) - return dominfo.getDeviceByIndex(type, idx) + return dominfo.getDevice(type, devid) def domain_vif_limit_set(self, id, vif, credit, period): """Limit the vif's transmission rate """ dominfo = self.domain_lookup(id) - dev = dominfo.getDeviceById('vif', vif) + dev = dominfo.getDevice('vif', vif) if not dev: raise XendError("invalid vif") return dev.setCreditLimit(credit, period) @@ -681,30 +682,47 @@ class XendDomain: """ dominfo = self.domain_lookup(id) try: - return xc.shadow_control(dominfo.dom, op) + return xc.shadow_control(dominfo.id, op) except Exception, ex: raise XendError(str(ex)) def domain_maxmem_set(self, id, mem): """Set the memory limit for a domain. - @param dom: domain + @param id: domain @param mem: memory limit (in MB) @return: 0 on success, -1 on error """ dominfo = self.domain_lookup(id) maxmem = int(mem) * 1024 try: - return xc.domain_setmaxmem(dominfo.dom, maxmem_kb = maxmem) + return xc.domain_setmaxmem(dominfo.id, maxmem_kb = maxmem) except Exception, ex: raise XendError(str(ex)) - def domain_mem_target_set(self, id, target): + def domain_mem_target_set(self, id, mem): + """Set the memory target for a domain. + + @param id: domain + @param mem: memory target (in MB) + @return: 0 on success, -1 on error + """ dominfo = self.domain_lookup(id) - return dominfo.mem_target_set(target) - + return dominfo.mem_target_set(mem) + def domain_dumpcore(self, id): + """Save a core dump for a crashed domain. + @param id: domain + """ + dominfo = self.domain_lookup(id) + corefile = "/var/xen/dump/%s.%s.core"% (dominfo.name, dominfo.id) + try: + xc.domain_dumpcore(dom=dominfo.id, corefile=corefile) + except Exception, ex: + log.warning("Dumpcore failed, id=%s name=%s: %s", + dominfo.id, dominfo.name, ex) + def instance(): """Singleton constructor. Use this instead of the class constructor. """ diff --git a/tools/python/xen/xend/XendDomainInfo.py b/tools/python/xen/xend/XendDomainInfo.py index 0dc1aae79c..16415d78a7 100644 --- a/tools/python/xen/xend/XendDomainInfo.py +++ b/tools/python/xen/xend/XendDomainInfo.py @@ -14,21 +14,23 @@ import time import threading import xen.lowlevel.xc; xc = xen.lowlevel.xc.new() -import xen.util.ip -from xen.xend.server import channel, controller +from xen.util.ip import check_subnet, get_current_ipgw from xen.util.blkif import blkdev_uname_to_file -from server.channel import channelFactory -import server.SrvDaemon; xend = server.SrvDaemon.instance() -from server import messages +from xen.xend.server import controller +from xen.xend.server import SrvDaemon; xend = SrvDaemon.instance() +from xen.xend.server import messages +from xen.xend.server.channel import EventChannel, channelFactory +from xen.xend import sxp +from xen.xend.PrettyPrint import prettyprintstring from xen.xend.XendBootloader import bootloader -import sxp -from XendLogging import log +from xen.xend.XendLogging import log from XendError import XendError, VmError -from XendRoot import get_component +from xen.xend.XendRoot import get_component -from PrettyPrint import prettyprintstring +from xen.xend.uuid import getUuid +from xen.xend.xenstore import DBVar """Flag for a block device backend domain.""" SIF_BLK_BE_DOMAIN = (1<<4) @@ -45,11 +47,16 @@ DOMAIN_REBOOT = 1 """Shutdown code for suspend.""" DOMAIN_SUSPEND = 2 +"""Shutdown code for crash.""" +DOMAIN_CRASH = 3 + """Map shutdown codes to strings.""" shutdown_reasons = { DOMAIN_POWEROFF: "poweroff", DOMAIN_REBOOT : "reboot", - DOMAIN_SUSPEND : "suspend" } + DOMAIN_SUSPEND : "suspend", + DOMAIN_CRASH : "crash", + } """Map shutdown reasons to the message type to use. """ @@ -81,7 +88,7 @@ STATE_VM_SUSPENDED = "suspended" def domain_exists(name): # See comment in XendDomain constructor. xd = get_component('xen.xend.XendDomain') - return xd.domain_lookup(name) + return xd.domain_lookup_by_name(name) def shutdown_reason(code): """Get a shutdown reason from a code. @@ -110,25 +117,6 @@ def get_config_handler(name): """ return config_handlers.get(name) -"""Table of handlers for virtual machine images. -Indexed by image type. -""" -image_handlers = {} - -def add_image_handler(name, h): - """Add a handler for an image type - @param name: image type - @param h: handler: fn(config, name, memory, image) - """ - image_handlers[name] = h - -def get_image_handler(name): - """Get the handler for an image type. - @param name: image type - @return: handler or None - """ - return image_handlers.get(name) - """Table of handlers for devices. Indexed by device type. """ @@ -139,61 +127,6 @@ def add_device_handler(name, type): def get_device_handler(name): return device_handlers[name] - - -def vm_create(config): - """Create a VM from a configuration. - If a vm has been partially created and there is an error it - is destroyed. - - @param config configuration - @raise: VmError for invalid configuration - """ - vm = XendDomainInfo() - vm.construct(config) - return vm - -def vm_restore(config): - """Create a domain and a VM object to do a restore. - - @param config: domain configuration - """ - vm = XendDomainInfo() - dom = xc.domain_create() - vm.dom_construct(dom, config) - return vm - -def vm_recreate(savedinfo, info): - """Create the VM object for an existing domain. - - @param savedinfo: saved info from the domain DB - @type savedinfo: sxpr - @param info: domain info from xc - @type info: xc domain dict - """ - log.debug('savedinfo=' + prettyprintstring(savedinfo)) - log.debug('info=' + str(info)) - vm = XendDomainInfo() - vm.recreate = True - vm.savedinfo = savedinfo - vm.setdom(info['dom']) - vm.memory = info['mem_kb']/1024 - start_time = sxp.child_value(savedinfo, 'start_time') - if start_time is not None: - vm.start_time = float(start_time) - vm.restart_state = sxp.child_value(savedinfo, 'restart_state') - vm.restart_count = int(sxp.child_value(savedinfo, 'restart_count', 0)) - restart_time = sxp.child_value(savedinfo, 'restart_time') - if restart_time is not None: - vm.restart_time = float(restart_time) - config = sxp.child_value(savedinfo, 'config') - if config: - vm.construct(config) - else: - vm.name = sxp.child_value(savedinfo, 'name', "Domain-%d" % info['dom']) - vm.recreate = False - vm.savedinfo = None - return vm def dom_get(dom): """Get info from xen for an existing domain. @@ -213,25 +146,104 @@ class XendDomainInfo: """ MINIMUM_RESTART_TIME = 20 - def __init__(self): + def create(cls, parentdb, config): + """Create a VM from a configuration. + + @param parentdb: parent db + @param config configuration + @raise: VmError for invalid configuration + """ + uuid = getUuid() + db = parentdb.addChild(uuid) + vm = cls(db) + vm.construct(config) + vm.saveDB(sync=True) + return vm + + create = classmethod(create) + + def recreate(cls, db, info): + """Create the VM object for an existing domain. + + @param db: domain db + @param info: domain info from xc + """ + dom = info['dom'] + vm = cls(db) + db.readDB() + vm.importFromDB() + config = vm.config + log.debug('info=' + str(info)) + log.debug('config=' + prettyprintstring(config)) + + vm.setdom(dom) + vm.memory = info['mem_kb']/1024 + + if config: + try: + vm.recreate = True + vm.construct(config) + finally: + vm.recreate = False + else: + vm.setName("Domain-%d" % dom) + + vm.exportToDB(save=True) + return vm + + recreate = classmethod(recreate) + + def restore(cls, parentdb, config, uuid=None): + """Create a domain and a VM object to do a restore. + + @param parentdb: parent db + @param config: domain configuration + @param uuid: uuid to use + """ + db = parentdb.addChild(uuid) + vm = cls(db) + dom = xc.domain_create() + vm.setdom(dom) + vm.dom_construct(vm.id, config) + vm.saveDB(sync=True) + return vm + + restore = classmethod(restore) + + __exports__ = [ + DBVar('id', ty='str'), + DBVar('name', ty='str'), + DBVar('uuid', ty='str'), + DBVar('config', ty='sxpr'), + DBVar('start_time', ty='float'), + DBVar('state', ty='str'), + DBVar('store_mfn', ty='long'), + DBVar('restart_mode', ty='str'), + DBVar('restart_state', ty='str'), + DBVar('restart_time', ty='float'), + DBVar('restart_count', ty='int'), + ] + + def __init__(self, db): + self.db = db + self.uuid = db.getName() + self.recreate = 0 self.restore = 0 + self.config = None self.id = None - self.dom = None self.cpu_weight = 1 self.start_time = None self.name = None self.memory = None self.image = None - self.ramdisk = None - self.cmdline = None self.channel = None + self.store_channel = None + self.store_mfn = None self.controllers = {} - self.configs = [] - self.info = None self.blkif_backend = False self.netif_backend = False @@ -249,22 +261,39 @@ class XendDomainInfo: self.restart_count = 0 self.console_port = None - self.savedinfo = None - self.image_handler = None - self.is_vmx = False self.vcpus = 1 self.bootloader = None + def setDB(self, db): + self.db = db + + def saveDB(self, save=False, sync=False): + self.db.saveDB(save=save, sync=sync) + + def exportToDB(self, save=False, sync=False): + if self.channel: + self.channel.saveToDB(self.db.addChild("channel")) + if self.store_channel: + self.store_channel.saveToDB(self.db.addChild("store_channel")) + self.db.exportToDB(self, fields=self.__exports__, save=save, sync=sync) + + def importFromDB(self): + self.db.importFromDB(self, fields=self.__exports__) + def setdom(self, dom): """Set the domain id. @param dom: domain id """ - self.dom = int(dom) - self.id = str(dom) + self.id = int(dom) + #self.db.id = self.id def getDomain(self): - return self.dom + return self.id + + def setName(self, name): + self.name = name + self.db.name = self.name def getName(self): return self.name @@ -272,6 +301,9 @@ class XendDomainInfo: def getChannel(self): return self.channel + def getStoreChannel(self): + return self.store_channel + def update(self, info): """Update with info from xc.domain_getinfo(). """ @@ -284,6 +316,7 @@ class XendDomainInfo: self.state = state self.state_updated.notifyAll() self.state_updated.release() + self.saveDB() def state_wait(self, state): self.state_updated.acquire() @@ -293,14 +326,12 @@ class XendDomainInfo: def __str__(self): s = "domain" - s += " id=" + self.id + s += " id=" + str(self.id) s += " name=" + self.name s += " memory=" + str(self.memory) console = self.getConsole() if console: s += " console=" + str(console.console_port) - if self.image: - s += " image=" + self.image s += "" return s @@ -327,9 +358,10 @@ class XendDomainInfo: self.controllers[type] = ctrl return ctrl - def createDevice(self, type, devconfig, recreate=False): + def createDevice(self, type, devconfig, change=False): ctrl = self.findDeviceController(type) - return ctrl.createDevice(devconfig, recreate=self.recreate) + return ctrl.createDevice(devconfig, recreate=self.recreate, + change=change) def configureDevice(self, type, id, devconfig): ctrl = self.getDeviceController(type) @@ -343,30 +375,14 @@ class XendDomainInfo: ctrl = self.getDeviceController(type) return ctrl.deleteDevice(id) - def getDevice(self, type, id): + def getDevice(self, type, id, error=True): ctrl = self.getDeviceController(type) - return ctrl.getDevice(id) + return ctrl.getDevice(id, error=error) - def getDeviceByIndex(self, type, idx): - ctrl = self.getDeviceController(type) - return ctrl.getDeviceByIndex(idx) - - def getDeviceConfig(self, type, id): - ctrl = self.getDeviceController(type) - return ctrl.getDeviceConfig(id) - def getDeviceIds(self, type): ctrl = self.getDeviceController(type) return ctrl.getDeviceIds() - def getDeviceIndexes(self, type): - ctrl = self.getDeviceController(type) - return ctrl.getDeviceIndexes() - - def getDeviceConfigs(self, type): - ctrl = self.getDeviceController(type) - return ctrl.getDeviceConfigs() - def getDeviceSxprs(self, type): ctrl = self.getDeviceController(type) return ctrl.getDeviceSxprs() @@ -376,7 +392,8 @@ class XendDomainInfo: ['id', self.id], ['name', self.name], ['memory', self.memory] ] - + if self.uuid: + sxpr.append(['uuid', self.uuid]) if self.info: sxpr.append(['maxmem', self.info['maxmem_kb']/1024 ]) run = (self.info['running'] and 'r') or '-' @@ -403,6 +420,8 @@ class XendDomainInfo: if self.channel: sxpr.append(self.channel.sxpr()) + if self.store_channel: + sxpr.append(self.store_channel.sxpr()) console = self.getConsole() if console: sxpr.append(console.sxpr()) @@ -454,7 +473,7 @@ class XendDomainInfo: return if dominfo.is_terminated(): return - if not self.dom or (dominfo.dom != self.dom): + if not self.id or (dominfo.id != self.id): raise VmError('vm name clash: ' + name) def construct(self, config): @@ -467,10 +486,10 @@ class XendDomainInfo: self.config = config try: # Initial domain create. - self.name = sxp.child_value(config, 'name') + self.setName(sxp.child_value(config, 'name')) self.check_name(self.name) + self.init_image() self.configure_cpus(config) - self.find_image_handler() self.init_domain() self.register_domain() self.configure_bootloader() @@ -481,6 +500,7 @@ class XendDomainInfo: self.configure_restart() self.construct_image() self.configure() + self.exportToDB() except Exception, ex: # Catch errors, cleanup and re-raise. print 'Domain construction error:', ex @@ -492,6 +512,7 @@ class XendDomainInfo: def register_domain(self): xd = get_component('xen.xend.XendDomain') xd._add_domain(self) + self.exportToDB() def configure_cpus(self, config): try: @@ -502,8 +523,8 @@ class XendDomainInfo: if self.memory is None: raise VmError('missing memory size') cpu = sxp.child_value(config, 'cpu') - if self.recreate and self.dom and cpu is not None and int(cpu) >= 0: - xc.domain_pincpu(self.dom, 0, 1<<int(cpu)) + if self.recreate and self.id and cpu is not None and int(cpu) >= 0: + xc.domain_pincpu(self.id, 0, 1<<int(cpu)) try: image = sxp.child_value(self.config, 'image') vcpus = sxp.child_value(image, 'vcpus') @@ -512,89 +533,50 @@ class XendDomainInfo: except: raise VmError('invalid vcpus value') - def find_image_handler(self): - """Construct the boot image for the domain. - - @return vm + def init_image(self): + """Create boot image handler for the domain. """ image = sxp.child_value(self.config, 'image') if image is None: raise VmError('missing image') - image_name = sxp.name(image) - if image_name is None: - raise VmError('missing image name') - if image_name == "vmx": - self.is_vmx = True - image_handler = get_image_handler(image_name) - if image_handler is None: - raise VmError('unknown image type: ' + image_name) - self.image_handler = image_handler - return self + self.image = ImageHandler.create(self, image) def construct_image(self): - image = sxp.child_value(self.config, 'image') - self.image_handler(self, image) - return self - - def config_devices(self, name): - """Get a list of the 'device' nodes of a given type from the config. - - @param name: device type - @type name: string - @return: device configs - @rtype: list - """ - devices = [] - for d in sxp.children(self.config, 'device'): - dev = sxp.child0(d) - if dev is None: continue - if name == sxp.name(dev): - devices.append(dev) - return devices - - def get_device_savedinfo(self, type, index): - val = None - if self.savedinfo is None: - return val - devices = sxp.child(self.savedinfo, 'devices') - if devices is None: - return val - index = str(index) - for d in sxp.children(devices, type): - dindex = sxp.child_value(d, 'index') - if dindex is None: continue - if str(dindex) == index: - val = d - break - return val - - def get_device_recreate(self, type, index): - return self.get_device_savedinfo(type, index) or self.recreate - - def add_config(self, val): - """Add configuration data to a virtual machine. - - @param val: data to add + """Construct the boot image for the domain. """ - self.configs.append(val) - - def destroy(self): - """Completely destroy the vm. + self.create_channel() + self.image.createImage() + self.image.exportToDB() + #if self.store_channel: + # self.db.introduceDomain(self.id, + # self.store_mfn, + # self.store_channel) + + def delete(self): + """Delete the vm's db. """ - self.cleanup() - return self.destroy_domain() + if self.dom_get(self.id): + return + self.id = None + self.saveDB(sync=True) + try: + # Todo: eventually will have to wait for devices to signal + # destruction before can delete the db. + if self.db: + self.db.delete() + except Exception, ex: + log.warning("error in domain db delete: %s", ex) + pass def destroy_domain(self): """Destroy the vm's domain. The domain will not finally go away unless all vm devices have been released. """ - if self.channel: - self.channel.close() - self.channel = None - if self.dom is None: return 0 + if self.id is None: + return try: - return xc.domain_destroy(dom=self.dom) + xc.domain_destroy(dom=self.id) except Exception, err: log.exception("Domain destroy failed: %s", self.name) @@ -603,6 +585,37 @@ class XendDomainInfo: """ self.state = STATE_VM_TERMINATED self.release_devices() + if self.channel: + try: + self.channel.close() + self.channel = None + except: + pass + if self.store_channel: + try: + self.store_channel.close() + self.store_channel = None + except: + pass + #try: + # self.db.releaseDomain(self.id) + #except Exception, ex: + # log.warning("error in domain release on xenstore: %s", ex) + # pass + if self.image: + try: + self.image.destroy() + self.image = None + except: + pass + + def destroy(self): + """Clenup vm and destroy domain. + """ + self.cleanup() + self.destroy_domain() + self.saveDB() + return 0 def is_terminated(self): """Check if a domain has been terminated. @@ -616,20 +629,13 @@ class XendDomainInfo: for ctrl in self.getDeviceControllers(): if ctrl.isDestroyed(): continue ctrl.destroyController(reboot=reboot) - if not reboot: - self.configs = [] def show(self): """Print virtual machine info. """ - print "[VM dom=%d name=%s memory=%d" % (self.dom, self.name, self.memory) + print "[VM dom=%d name=%s memory=%d" % (self.id, self.name, self.memory) print "image:" sxp.show(self.image) - print - for val in self.configs: - print "config:" - sxp.show(val) - print print "]" def init_domain(self): @@ -639,107 +645,42 @@ class XendDomainInfo: return if self.start_time is None: self.start_time = time.time() - if self.restore: - return - dom = self.dom or 0 - memory = self.memory try: cpu = int(sxp.child_value(self.config, 'cpu', '-1')) except: raise VmError('invalid cpu') - cpu_weight = self.cpu_weight - memory = memory * 1024 + self.pgtable_size(memory) - dom = xc.domain_create(dom= dom) - if self.bootloader: - try: - if kernel: os.unlink(kernel) - if ramdisk: os.unlink(ramdisk) - except OSError, e: - log.warning('unable to unlink kernel/ramdisk: %s' %(e,)) - - if dom <= 0: - raise VmError('Creating domain failed: name=%s memory=%d' - % (self.name, memory)) - xc.domain_setcpuweight(dom, cpu_weight) - xc.domain_setmaxmem(dom, memory) - xc.domain_memory_increase_reservation(dom, memory) - if cpu != -1: - xc.domain_pincpu(dom, 0, 1<<int(cpu)) - log.debug('init_domain> Created domain=%d name=%s memory=%d', dom, self.name, memory) - self.setdom(dom) - - def build_domain(self, ostype, kernel, ramdisk, cmdline, memmap): - """Build the domain boot image. - """ - if self.recreate or self.restore: return - if not os.path.isfile(kernel): - raise VmError('Kernel image does not exist: %s' % kernel) - if ramdisk and not os.path.isfile(ramdisk): - raise VmError('Kernel ramdisk does not exist: %s' % ramdisk) - if len(cmdline) >= 256: - log.warning('kernel cmdline too long, domain %d', self.dom) - dom = self.dom - buildfn = getattr(xc, '%s_build' % ostype) - flags = 0 - if self.netif_backend: flags |= SIF_NET_BE_DOMAIN - if self.blkif_backend: flags |= SIF_BLK_BE_DOMAIN - #todo generalise this - if ostype == "vmx": - log.debug('building vmx domain') - err = buildfn(dom = dom, - image = kernel, - control_evtchn = 0, - memsize = self.memory, - memmap = memmap, - cmdline = cmdline, - ramdisk = ramdisk, - flags = flags) - else: - log.debug('building dom with %d vcpus', self.vcpus) - err = buildfn(dom = dom, - image = kernel, - control_evtchn = self.channel.getRemotePort(), - cmdline = cmdline, - ramdisk = ramdisk, - flags = flags, - vcpus = self.vcpus) - if err != 0: - raise VmError('Building domain failed: type=%s dom=%d err=%d' - % (ostype, dom, err)) - - def create_domain(self, ostype, kernel, ramdisk, cmdline, memmap=''): - """Create a domain. Builds the image but does not configure it. + dom = self.image.initDomain(self.id, self.memory, cpu, self.cpu_weight) + log.debug('init_domain> Created domain=%d name=%s memory=%d', + dom, self.name, self.memory) + if not self.restore: + self.setdom(dom) - @param ostype: OS type - @param kernel: kernel image - @param ramdisk: kernel ramdisk - @param cmdline: kernel commandline + def openChannel(self, key, local, remote): + """Create a channel to the domain. + If saved info is available recreate the channel. + + @param key db key for the saved data (if any) + @param local default local port + @param remote default remote port """ - - self.create_channel() - self.build_domain(ostype, kernel, ramdisk, cmdline, memmap) - self.image = kernel - self.ramdisk = ramdisk - self.cmdline = cmdline - + db = self.db.addChild(key) + chan = channelFactory().restoreFromDB(db, self.id, local, remote) + #todo: save here? + #chan.saveToDB(db) + return chan + + def eventChannel(self, key): + db = self.db.addChild(key) + return EventChannel.restoreFromDB(db, 0, self.id) + def create_channel(self): - """Create the control channel to the domain. - If saved info is available recreate the channel using the saved ports. + """Create the channels to the domain. """ - local = 0 - remote = 1 - if self.savedinfo: - info = sxp.child(self.savedinfo, "channel") - if info: - local = int(sxp.child_value(info, "local_port", 0)) - remote = int(sxp.child_value(info, "remote_port", 1)) - self.channel = channelFactory().openChannel(self.dom, - local_port=local, - remote_port=remote) + self.channel = self.openChannel("channel", 0, 1) + self.store_channel = self.eventChannel("store_channel") def create_configured_devices(self): devices = sxp.children(self.config, 'device') - indexes = {} for d in devices: dev_config = sxp.child0(d) if dev_config is None: @@ -748,13 +689,7 @@ class XendDomainInfo: ctrl_type = get_device_handler(dev_type) if ctrl_type is None: raise VmError('unknown device type: ' + dev_type) - # Keep track of device indexes by type, so we can fish - # out saved info for recreation. - idx = indexes.get(dev_type, -1) - idx += 1 - indexes[ctrl_type] = idx - recreate = self.get_device_recreate(dev_type, idx) - self.createDevice(ctrl_type, dev_config, recreate=recreate) + self.createDevice(ctrl_type, dev_config) def create_devices(self): """Create the devices for a vm. @@ -766,43 +701,6 @@ class XendDomainInfo: ctrl.initController(reboot=True) else: self.create_configured_devices() - if self.is_vmx: - self.create_vmx_model() - - def create_vmx_model(self): - #todo: remove special case for vmx - device_model = sxp.child_value(self.config, 'device_model') - if not device_model: - raise VmError("vmx: missing device model") - device_config = sxp.child_value(self.config, 'device_config') - if not device_config: - raise VmError("vmx: missing device config") - #todo: self.memory? - memory = sxp.child_value(self.config, "memory") - # Create an event channel - device_channel = channel.eventChannel(0, self.dom) - # see if a vncviewer was specified - # XXX RN: bit of a hack. should unify this, maybe stick in config space - vncconnect="" - image = sxp.child_value(self.config, "image") - args = sxp.child_value(image, "args") - if args: - arg_list = string.split(args) - for arg in arg_list: - al = string.split(arg, '=') - if al[0] == "VNC_VIEWER": - vncconnect=" -v %s" % al[1] - break - - # Execute device model. - #todo: Error handling - # XXX RN: note that the order of args matter! - os.system(device_model - + " -f %s" % device_config - + vncconnect - + " -d %d" % self.dom - + " -p %d" % device_channel['port1'] - + " -m %s" % memory) def device_create(self, dev_config): """Create a new device. @@ -814,16 +712,14 @@ class XendDomainInfo: self.config.append(['device', dev.getConfig()]) return dev.sxpr() - def device_configure(self, dev_config, idx): + def device_configure(self, dev_config, id): """Configure an existing device. @param dev_config: device configuration - @param idx: device index + @param id: device id """ type = sxp.name(dev_config) - dev = self.getDeviceByIndex(type, idx) - if not dev: - raise VmError('invalid device: %s %s' % (type, idx)) + dev = self.getDevice(type, id) old_config = dev.getConfig() new_config = dev.configure(dev_config, change=True) # Patch new config into vm config. @@ -833,26 +729,22 @@ class XendDomainInfo: self.config[old_index] = new_full_config return new_config - def device_refresh(self, type, idx): + def device_refresh(self, type, id): """Refresh a device. @param type: device type - @param idx: device index + @param id: device id """ - dev = self.getDeviceByIndex(type, idx) - if not dev: - raise VmError('invalid device: %s %s' % (type, idx)) + dev = self.getDevice(type, id) dev.refresh() - def device_delete(self, type, idx): + def device_delete(self, type, id): """Destroy and remove a device. @param type: device type - @param idx: device index + @param id: device id """ - dev = self.getDeviceByIndex(type, idx) - if not dev: - raise VmError('invalid device: %s %s' % (type, idx)) + dev = self.getDevice(type, id) dev_config = dev.getConfig() if dev_config: self.config.remove(['device', dev_config]) @@ -861,9 +753,7 @@ class XendDomainInfo: def configure_bootloader(self): """Configure boot loader. """ - bl = sxp.child_value(self.config, "bootloader") - if bl is not None: - self.bootloader = bl + self.bootloader = sxp.child_value(self.config, "bootloader") def configure_console(self): """Configure the vm console port. @@ -946,6 +836,7 @@ class XendDomainInfo: if self.bootloader: self.config = self.bootloader_config() self.construct(self.config) + self.saveDB() finally: self.restart_state = None @@ -1051,19 +942,6 @@ class XendDomainInfo: log.warning("Unknown config field %s", field_name) index[field_name] = field_index + 1 - def pgtable_size(self, memory): - """Return the size of memory needed for 1:1 page tables for physical - mode. - - @param memory: size in MB - @return size in KB - """ - if self.is_vmx: - # Logic x86-32 specific. - # 1 page for the PGD + 1 pte page for 4MB of memory (rounded) - return (1 + ((memory + 3) >> 2)) * 4 - return 0 - def mem_target_set(self, target): """Set domain memory target in pages. """ @@ -1090,83 +968,6 @@ class XendDomainInfo: return 0 return timeout - (time.time() - self.shutdown_pending['start']) -def vm_image_linux(vm, image): - """Create a VM for a linux image. - - @param name: vm name - @param memory: vm memory - @param image: image config - @return: vm - """ - kernel = sxp.child_value(image, "kernel") - cmdline = "" - ip = sxp.child_value(image, "ip", None) - if ip: - cmdline += " ip=" + ip - root = sxp.child_value(image, "root") - if root: - cmdline += " root=" + root - args = sxp.child_value(image, "args") - if args: - cmdline += " " + args - ramdisk = sxp.child_value(image, "ramdisk", '') - log.debug("creating linux domain with cmdline: %s" %(cmdline,)) - vm.create_domain("linux", kernel, ramdisk, cmdline) - return vm - -def vm_image_plan9(vm, image): - """Create a VM for a Plan 9 image. - - name vm name - memory vm memory - image image config - - returns vm - """ - kernel = sxp.child_value(image, "kernel") - cmdline = "" - ip = sxp.child_value(image, "ip", "dhcp") - if ip: - cmdline += "ip=" + ip - root = sxp.child_value(image, "root") - if root: - cmdline += "root=" + root - args = sxp.child_value(image, "args") - if args: - cmdline += " " + args - ramdisk = sxp.child_value(image, "ramdisk", '') - log.debug("creating plan9 domain with cmdline: %s" %(cmdline,)) - vm.create_domain("plan9", kernel, ramdisk, cmdline) - return vm - -def vm_image_vmx(vm, image): - """Create a VM for the VMX environment. - - @param name: vm name - @param memory: vm memory - @param image: image config - @return: vm - """ - kernel = sxp.child_value(image, "kernel") - cmdline = "" - ip = sxp.child_value(image, "ip", "dhcp") - if ip: - cmdline += " ip=" + ip - root = sxp.child_value(image, "root") - if root: - cmdline += " root=" + root - args = sxp.child_value(image, "args") - if args: - cmdline += " " + args - ramdisk = sxp.child_value(image, "ramdisk", '') - memmap = sxp.child_value(vm.config, "memmap", '') - memmap = sxp.parse(open(memmap))[0] - from xen.util.memmap import memmap_parse - memmap = memmap_parse(memmap) - log.debug("creating vmx domain with cmdline: %s" %(cmdline,)) - vm.create_domain("vmx", kernel, ramdisk, cmdline, memmap) - return vm - def vm_field_ignore(vm, config, val, index): """Dummy config field handler used for fields with built-in handling. @@ -1192,13 +993,20 @@ def vm_field_maxmem(vm, config, val, index): maxmem = int(maxmem) except: raise VmError("invalid maxmem: " + str(maxmem)) - xc.domain_setmaxmem(vm.dom, maxmem_kb = maxmem * 1024) + xc.domain_setmaxmem(vm.id, maxmem_kb = maxmem * 1024) #============================================================================ # Register image handlers. -add_image_handler('linux', vm_image_linux) -add_image_handler('plan9', vm_image_plan9) -add_image_handler('vmx', vm_image_vmx) +from image import \ + addImageHandlerClass, \ + ImageHandler, \ + LinuxImageHandler, \ + Plan9ImageHandler, \ + VmxImageHandler + +addImageHandlerClass(LinuxImageHandler) +addImageHandlerClass(Plan9ImageHandler) +addImageHandlerClass(VmxImageHandler) # Ignore the fields we already handle. add_config_handler('name', vm_field_ignore) diff --git a/tools/python/xen/xend/XendRoot.py b/tools/python/xen/xend/XendRoot.py index d1bd503f8a..045a5a5fa4 100644 --- a/tools/python/xen/xend/XendRoot.py +++ b/tools/python/xen/xend/XendRoot.py @@ -25,9 +25,6 @@ import sxp class XendRoot: """Root of the management classes.""" - """Default path to the root of the database.""" - dbroot_default = "/var/lib/xen/xend-db" - """Default path to the config file.""" config_default = "/etc/xen/xend-config.sxp" @@ -82,7 +79,6 @@ class XendRoot: components = {} def __init__(self): - self.dbroot = None self.config_path = None self.config = None self.logging = None @@ -171,13 +167,15 @@ class XendRoot: def configure(self): self.set_config() self.configure_logger() - self.dbroot = self.get_config_value("dbroot", self.dbroot_default) def configure_logger(self): logfile = self.get_config_value("logfile", self.logfile_default) loglevel = self.get_config_value("loglevel", self.loglevel_default) self.logging = XendLogging(logfile, level=loglevel) - #self.logging.addLogStderr() + + from xen.xend.server import params + if params.XEND_DEBUG: + self.logging.addLogStderr() def get_logging(self): """Get the XendLogging instance. @@ -189,11 +187,6 @@ class XendRoot: """ return self.logging and self.logging.getLogger() - def get_dbroot(self): - """Get the path to the database root. - """ - return self.dbroot - def set_config(self): """If the config file exists, read it. If not, ignore it. @@ -241,9 +234,9 @@ class XendRoot: def get_config_bool(self, name, val=None): v = self.get_config_value(name, val) - if v in ['yes', '1', 'on', 1, True]: + if v in ['yes', '1', 'on', 'true', 1, True]: return True - if v in ['no', '0', 'off', 0, False]: + if v in ['no', '0', 'off', 'false', 0, False]: return False raise XendError("invalid xend config %s: expected bool: %s" % (name, v)) @@ -325,7 +318,7 @@ class XendRoot: return self.get_config_value('network-script', 'network') def get_enable_dump(self): - return self.get_config_value('enable-dump', 'false') + return self.get_config_bool('enable-dump', 'no') def get_vif_bridge(self): return self.get_config_value('vif-bridge', 'xen-br0') diff --git a/tools/python/xen/xend/XendVnet.py b/tools/python/xen/xend/XendVnet.py index d95fd204aa..3614127c49 100644 --- a/tools/python/xen/xend/XendVnet.py +++ b/tools/python/xen/xend/XendVnet.py @@ -4,11 +4,10 @@ """ from xen.util import Brctl - -import sxp -import XendDB -from XendError import XendError -from XendLogging import log +from xen.xend import sxp +from xen.xend.XendError import XendError +from xen.xend.XendLogging import log +from xen.xend.xenstore import XenNode, DBMap def vnet_cmd(cmd): out = None @@ -63,14 +62,15 @@ class XendVnet: """Index of all vnets. Singleton. """ - dbpath = "vnet" + dbpath = "/vnet" def __init__(self): # Table of vnet info indexed by vnet id. self.vnet = {} - self.db = XendDB.XendDB(self.dbpath) - vnets = self.db.fetchall("") - for config in vnets.values(): + self.dbmap = DBMap(db=XenNode(self.dbpath)) + self.dbmap.readDB() + for vnetdb in self.dbmap.values(): + config = vnetdb.config info = XendVnetInfo(config) self.vnet[info.id] = info try: @@ -115,7 +115,7 @@ class XendVnet: """ info = XendVnetInfo(config) self.vnet[info.id] = info - self.db.save(info.id, info.sxpr()) + self.dbmap["%s/config" % info.id] = info.sxpr() info.configure() def vnet_delete(self, id): @@ -126,7 +126,7 @@ class XendVnet: info = self.vnet_get(id) if info: del self.vnet[id] - self.db.delete(id) + self.dbmap.delete(id) info.delete() def instance(): diff --git a/tools/python/xen/xend/image.py b/tools/python/xen/xend/image.py new file mode 100644 index 0000000000..e0d70581bf --- /dev/null +++ b/tools/python/xen/xend/image.py @@ -0,0 +1,339 @@ +import os + +import xen.lowlevel.xc; xc = xen.lowlevel.xc.new() +from xen.xend import sxp +from xen.xend.XendError import VmError +from xen.xend.XendLogging import log +from xen.xend.xenstore import DBVar + +class ImageHandler: + """Abstract base class for image handlers. + + initDomain() is called to initialise the domain memory. + + createImage() is called to configure and build the domain from its + kernel image and ramdisk etc. + + The method buildDomain() is used to build the domain, and must be + defined in a subclass. Usually this is the only method that needs + defining in a subclass. + + The method createDeviceModel() is called to create the domain device + model if it needs one. The default is to do nothing. + + The method destroy() is called when the domain is destroyed. + The default is to do nothing. + + """ + + #====================================================================== + # Class vars and methods. + + """Table of image handler classes for virtual machine images. + Indexed by image type. + """ + imageHandlerClasses = {} + + def addImageHandlerClass(cls, h): + """Add a handler class for an image type + @param h: handler: ImageHandler subclass + """ + cls.imageHandlerClasses[h.ostype] = h + + addImageHandlerClass = classmethod(addImageHandlerClass) + + def findImageHandlerClass(cls, image): + """Find the image handler class for an image config. + + @param image config + @return ImageHandler subclass or None + """ + ty = sxp.name(image) + if ty is None: + raise VmError('missing image type') + imageClass = cls.imageHandlerClasses.get(ty) + if imageClass is None: + raise VmError('unknown image type: ' + ty) + return imageClass + + findImageHandlerClass = classmethod(findImageHandlerClass) + + def create(cls, vm, image): + """Create an image handler for a vm. + + @param vm vm + @param image image config + @return ImageHandler instance + """ + imageClass = cls.findImageHandlerClass(image) + return imageClass(vm, image) + + create = classmethod(create) + + #====================================================================== + # Instance vars and methods. + + db = None + ostype = None + + config = None + kernel = None + ramdisk = None + cmdline = None + flags = 0 + + __exports__ = [ + DBVar('ostype', ty='str'), + DBVar('config', ty='sxpr'), + DBVar('kernel', ty='str'), + DBVar('ramdisk', ty='str'), + DBVar('cmdline', ty='str'), + DBVar('flags', ty='int'), + ] + + def __init__(self, vm, config): + self.vm = vm + self.db = vm.db.addChild('/image') + self.config = config + + def exportToDB(self, save=False): + self.db.exportToDB(self, fields=self.__exports__, save=save) + + def importFromDB(self): + self.db.importFromDB(self, fields=self.__exports__) + + def unlink(self, f): + if not f: return + try: + os.unlink(f) + except OSError, ex: + log.warning("error removing bootloader file '%s': %s", f, ex) + + def initDomain(self, dom, memory, cpu, cpu_weight): + """Initial domain create. + + @return domain id + """ + + mem_kb = self.getDomainMemory(memory) + if not self.vm.restore: + dom = xc.domain_create(dom = dom or 0) + # if bootloader, unlink here. But should go after buildDomain() ? + if self.vm.bootloader: + self.unlink(self.kernel) + self.unlink(self.ramdisk) + if dom <= 0: + raise VmError('Creating domain failed: name=%s' % self.vm.name) + log.debug("initDomain: cpu=%d mem_kb=%d dom=%d", cpu, mem_kb, dom) + # xc.domain_setuuid(dom, uuid) + xc.domain_setcpuweight(dom, cpu_weight) + xc.domain_setmaxmem(dom, mem_kb) + xc.domain_memory_increase_reservation(dom, mem_kb) + if cpu != -1: + xc.domain_pincpu(dom, 0, 1<<int(cpu)) + return dom + + def createImage(self): + """Entry point to create domain memory image. + Override in subclass if needed. + """ + self.configure() + self.createDomain() + + def configure(self): + """Config actions common to all unix-like domains.""" + self.kernel = sxp.child_value(self.config, "kernel") + self.cmdline = "" + ip = sxp.child_value(self.config, "ip", None) + if ip: + self.cmdline += " ip=" + ip + root = sxp.child_value(self.config, "root") + if root: + self.cmdline += " root=" + root + args = sxp.child_value(self.config, "args") + if args: + self.cmdline += " " + args + self.ramdisk = sxp.child_value(self.config, "ramdisk", '') + + def createDomain(self): + """Build the domain boot image. + """ + # Set params and call buildDomain(). + self.flags = 0 + if self.vm.netif_backend: self.flags |= SIF_NET_BE_DOMAIN + if self.vm.blkif_backend: self.flags |= SIF_BLK_BE_DOMAIN + + if self.vm.recreate or self.vm.restore: + return + if not os.path.isfile(self.kernel): + raise VmError('Kernel image does not exist: %s' % self.kernel) + if self.ramdisk and not os.path.isfile(self.ramdisk): + raise VmError('Kernel ramdisk does not exist: %s' % self.ramdisk) + if len(self.cmdline) >= 256: + log.warning('kernel cmdline too long, domain %d', self.vm.getDomain()) + + log.info("buildDomain os=%s dom=%d vcpus=%d", self.ostype, + self.vm.getDomain(), self.vm.vcpus) + err = self.buildDomain() + if err != 0: + raise VmError('Building domain failed: ostype=%s dom=%d err=%d' + % (self.ostype, self.vm.getDomain(), err)) + + def getDomainMemory(self, mem_mb): + """Memory (in KB) the domain will need for mem_mb (in MB).""" + return mem_mb * 1024 + + def buildDomain(self): + """Build the domain. Define in subclass.""" + raise NotImplementedError() + + def createDeviceModel(self): + """Create device model for the domain (define in subclass if needed).""" + pass + + def destroy(self): + """Extra cleanup on domain destroy (define in subclass if needed).""" + pass + +addImageHandlerClass = ImageHandler.addImageHandlerClass + +class LinuxImageHandler(ImageHandler): + + ostype = "linux" + + def buildDomain(self): + if self.vm.store_channel: + store_evtchn = self.vm.store_channel.port2 + else: + store_evtchn = 0 + ret = xc.linux_build(dom = self.vm.getDomain(), + image = self.kernel, + control_evtchn = self.vm.channel.getRemotePort(), + store_evtchn = store_evtchn, + cmdline = self.cmdline, + ramdisk = self.ramdisk, + flags = self.flags, + vcpus = self.vm.vcpus) + if isinstance(ret, dict): + self.vm.store_mfn = ret.get('store_mfn') + return 0 + return ret + +class Plan9ImageHandler(ImageHandler): + + ostype = "plan9" + + def buildDomain(self): + return xc.plan9_build(dom = self.vm.getDomain(), + image = self.kernel, + control_evtchn = self.vm.channel.getRemotePort(), + cmdline = self.cmdline, + ramdisk = self.ramdisk, + flags = self.flags, + vcpus = self.vm.vcpus) + +class VmxImageHandler(ImageHandler): + + __exports__ = ImageHandler.__exports__ + [ + DBVar('memmap', ty='str'), + DBVar('memmap_value', ty='sxpr'), + # device channel? + ] + + ostype = "vmx" + memmap = None + memmap_value = None + device_channel = None + + def createImage(self): + """Create a VM for the VMX environment. + """ + self.configure() + self.parseMemmap() + self.createDomain() + + def buildDomain(self): + return xc.vmx_build(dom = self.vm.getDomain(), + image = self.kernel, + control_evtchn = 0, + memsize = self.vm.memory, + memmap = self.memmap_value, + cmdline = self.cmdline, + ramdisk = self.ramdisk, + flags = self.flags) + + def parseMemmap(self): + self.memmap = sxp.child_value(self.vm.config, "memmap") + if self.memmap is None: + raise VmError("missing memmap") + memmap = sxp.parse(open(self.memmap))[0] + from xen.util.memmap import memmap_parse + self.memmap_value = memmap_parse(memmap) + + def createDeviceModel_old(self): + device_model = sxp.child_value(self.vm.config, 'device_model') + if not device_model: + raise VmError("vmx: missing device model") + device_config = sxp.child_value(self.vm.config, 'device_config') + if not device_config: + raise VmError("vmx: missing device config") + # Create an event channel. + self.device_channel = channel.eventChannel(0, self.vm.getDomain()) + # Execute device model. + #todo: Error handling + os.system(device_model + + " -f %s" % device_config + + " -d %d" % self.vm.getDomain() + + " -p %d" % self.device_channel['port1'] + + " -m %s" % self.vm.memory) + + def createDeviceModel(self): + device_model = sxp.child_value(self.vm.config, 'device_model') + if not device_model: + raise VmError("vmx: missing device model") + device_config = sxp.child_value(self.vm.config, 'device_config') + if not device_config: + raise VmError("vmx: missing device config") + # Create an event channel + self.device_channel = channel.eventChannel(0, self.vm.getDomain()) + # Execute device model. + #todo: Error handling + # XXX RN: note that the order of args matter! + os.system(device_model + + " -f %s" % device_config + + self.vncParams() + + " -d %d" % self.vm.getDomain() + + " -p %d" % self.device_channel['port1'] + + " -m %s" % self.vm.memory) + + def vncParams(self): + # see if a vncviewer was specified + # XXX RN: bit of a hack. should unify this, maybe stick in config space + vncconnect="" + image = self.config + args = sxp.child_value(image, "args") + if args: + arg_list = string.split(args) + for arg in arg_list: + al = string.split(arg, '=') + if al[0] == "VNC_VIEWER": + vncconnect=" -v %s" % al[1] + break + return vncconnect + + def destroy(self): + channel.eventChannelClose(self.device_channel) + + def getDomainMemory(self, mem_mb): + return (mem_mb * 1024) + self.getPageTableSize(mem_mb) + + def getPageTableSize(self, mem_mb): + """Return the size of memory needed for 1:1 page tables for physical + mode. + + @param mem_mb: size in MB + @return size in KB + """ + # Logic x86-32 specific. + # 1 page for the PGD + 1 pte page for 4MB of memory (rounded) + return (1 + ((mem_mb + 3) >> 2)) * 4 diff --git a/tools/python/xen/xend/server/SrvConsole.py b/tools/python/xen/xend/server/SrvConsole.py index 233f62b968..f147f2810b 100644 --- a/tools/python/xen/xend/server/SrvConsole.py +++ b/tools/python/xen/xend/server/SrvConsole.py @@ -30,7 +30,7 @@ class SrvConsole(SrvDir): #self.ls() req.write('<p>%s</p>' % self.info) req.write('<p><a href="%s">Connect to domain %d</a></p>' - % (self.info.uri(), self.info.dom)) + % (self.info.uri(), self.info.id)) self.form(req) req.write('</body></html>') diff --git a/tools/python/xen/xend/server/SrvDaemon.py b/tools/python/xen/xend/server/SrvDaemon.py index 061aa3dba7..133df206b9 100644 --- a/tools/python/xen/xend/server/SrvDaemon.py +++ b/tools/python/xen/xend/server/SrvDaemon.py @@ -32,9 +32,6 @@ import event import relocate from params import * -DAEMONIZE = 0 -DEBUG = 1 - class Daemon: """The xend daemon. """ @@ -128,9 +125,13 @@ class Daemon: def cleanup_xend(self, kill=False): return self.cleanup_process(XEND_PID_FILE, "xend", kill) + def cleanup_xenstored(self, kill=False): + return self.cleanup_process(XENSTORED_PID_FILE, "xenstored", kill) + def cleanup(self, kill=False): self.cleanup_xend(kill=kill) - + #self.cleanup_xenstored(kill=kill) + def status(self): """Returns the status of the xend daemon. The return value is defined by the LSB: @@ -166,8 +167,29 @@ class Daemon: pidfile.close() return pid + def start_xenstored(self): + """Fork and exec xenstored, writing its pid to XENSTORED_PID_FILE. + """ + def mkdirs(p): + try: + os.makedirs(p) + except: + pass + mkdirs(XENSTORED_RUN_DIR) + mkdirs(XENSTORED_LIB_DIR) + + pid = self.fork_pid(XENSTORED_PID_FILE) + if pid: + # Parent + log.info("Started xenstored, pid=%d", pid) + else: + # Child + if XEND_DAEMONIZE and (not XENSTORED_DEBUG): + self.daemonize() + os.execl("/usr/sbin/xenstored", "xenstored", "--no-fork") + def daemonize(self): - if not DAEMONIZE: return + if not XEND_DAEMONIZE: return # Detach from TTY. os.setsid() @@ -177,16 +199,16 @@ class Daemon: os.close(0) os.close(1) os.close(2) - if DEBUG: + if XEND_DEBUG: os.open('/dev/null', os.O_RDONLY) # XXX KAF: Why doesn't this capture output from C extensions that # fprintf(stdout) or fprintf(stderr) ?? - os.open('/var/log/xend-debug.log', os.O_WRONLY|os.O_CREAT) + os.open(XEND_DEBUG_LOG, os.O_WRONLY|os.O_CREAT) os.dup(1) else: os.open('/dev/null', os.O_RDWR) os.dup(0) - os.open('/var/log/xend-debug.log', os.O_WRONLY|os.O_CREAT) + os.open(XEND_DEBUG_LOG, os.O_WRONLY|os.O_CREAT) def start(self, trace=0): @@ -196,11 +218,15 @@ class Daemon: 4 Insufficient privileges """ xend_pid = self.cleanup_xend() + xenstored_pid = self.cleanup_xenstored() if self.set_user(): return 4 os.chdir("/") + if xenstored_pid == 0: + self.start_xenstored() + if xend_pid > 0: # Trying to run an already-running service is a success. return 0 @@ -308,7 +334,7 @@ class Daemon: servers.start() except Exception, ex: print >>sys.stderr, 'Exception starting xend:', ex - if DEBUG: + if XEND_DEBUG: traceback.print_exc() log.exception("Exception starting xend") self.exit(1) diff --git a/tools/python/xen/xend/server/SrvDomain.py b/tools/python/xen/xend/server/SrvDomain.py index c9cf4fe603..255e6157bf 100644 --- a/tools/python/xen/xend/server/SrvDomain.py +++ b/tools/python/xen/xend/server/SrvDomain.py @@ -28,19 +28,19 @@ class SrvDomain(SrvDir): fn = FormFn(self.xd.domain_configure, [['dom', 'int'], ['config', 'sxpr']]) - return fn(req.args, {'dom': self.dom.dom}) + return fn(req.args, {'dom': self.dom.id}) def op_unpause(self, op, req): - val = self.xd.domain_unpause(self.dom.name) + val = self.xd.domain_unpause(self.dom.id) return val def op_pause(self, op, req): - val = self.xd.domain_pause(self.dom.name) + val = self.xd.domain_pause(self.dom.id) return val def op_shutdown(self, op, req): fn = FormFn(self.xd.domain_shutdown, - [['dom', 'str'], + [['dom', 'int'], ['reason', 'str'], ['key', 'int']]) val = fn(req.args, {'dom': self.dom.id}) @@ -50,7 +50,7 @@ class SrvDomain(SrvDir): def op_destroy(self, op, req): fn = FormFn(self.xd.domain_destroy, - [['dom', 'str'], + [['dom', 'int'], ['reason', 'str']]) val = fn(req.args, {'dom': self.dom.id}) req.setHeader("Location", "%s/.." % req.prePathURL()) @@ -61,7 +61,7 @@ class SrvDomain(SrvDir): def do_save(self, op, req): fn = FormFn(self.xd.domain_save, - [['dom', 'str'], + [['dom', 'int'], ['file', 'str']]) val = fn(req.args, {'dom': self.dom.id}) return 0 @@ -71,7 +71,7 @@ class SrvDomain(SrvDir): def do_migrate(self, op, req): fn = FormFn(self.xd.domain_migrate, - [['dom', 'str'], + [['dom', 'int'], ['destination', 'str'], ['live', 'int'], ['resource', 'int']]) @@ -79,7 +79,7 @@ class SrvDomain(SrvDir): def op_pincpu(self, op, req): fn = FormFn(self.xd.domain_pincpu, - [['dom', 'str'], + [['dom', 'int'], ['vcpu', 'int'], ['cpumap', 'int']]) val = fn(req.args, {'dom': self.dom.id}) @@ -87,7 +87,7 @@ class SrvDomain(SrvDir): def op_cpu_bvt_set(self, op, req): fn = FormFn(self.xd.domain_cpu_bvt_set, - [['dom', 'str'], + [['dom', 'int'], ['mcuadv', 'int'], ['warpback', 'int'], ['warpvalue', 'int'], @@ -99,7 +99,7 @@ class SrvDomain(SrvDir): def op_cpu_sedf_set(self, op, req): fn = FormFn(self.xd.domain_cpu_sedf_set, - [['dom', 'str'], + [['dom', 'int'], ['period', 'int'], ['slice', 'int'], ['latency', 'int'], @@ -110,28 +110,28 @@ class SrvDomain(SrvDir): def op_maxmem_set(self, op, req): fn = FormFn(self.xd.domain_maxmem_set, - [['dom', 'str'], + [['dom', 'int'], ['memory', 'int']]) val = fn(req.args, {'dom': self.dom.id}) return val def op_mem_target_set(self, op, req): fn = FormFn(self.xd.domain_mem_target_set, - [['dom', 'str'], + [['dom', 'int'], ['target', 'int']]) val = fn(req.args, {'dom': self.dom.id}) return val def op_devices(self, op, req): fn = FormFn(self.xd.domain_devtype_ls, - [['dom', 'str'], + [['dom', 'int'], ['type', 'str']]) val = fn(req.args, {'dom': self.dom.id}) return val def op_device(self, op, req): fn = FormFn(self.xd.domain_devtype_get, - [['dom', 'str'], + [['dom', 'int'], ['type', 'str'], ['idx', 'int']]) val = fn(req.args, {'dom': self.dom.id}) @@ -142,14 +142,14 @@ class SrvDomain(SrvDir): def op_device_create(self, op, req): fn = FormFn(self.xd.domain_device_create, - [['dom', 'str'], + [['dom', 'int'], ['config', 'sxpr']]) val = fn(req.args, {'dom': self.dom.id}) return val def op_device_refresh(self, op, req): fn = FormFn(self.xd.domain_device_refresh, - [['dom', 'str'], + [['dom', 'int'], ['type', 'str'], ['idx', 'str']]) val = fn(req.args, {'dom': self.dom.id}) @@ -157,7 +157,7 @@ class SrvDomain(SrvDir): def op_device_destroy(self, op, req): fn = FormFn(self.xd.domain_device_destroy, - [['dom', 'str'], + [['dom', 'int'], ['type', 'str'], ['idx', 'str']]) val = fn(req.args, {'dom': self.dom.id}) @@ -165,7 +165,7 @@ class SrvDomain(SrvDir): def op_device_configure(self, op, req): fn = FormFn(self.xd.domain_device_configure, - [['dom', 'str'], + [['dom', 'int'], ['config', 'sxpr'], ['idx', 'str']]) val = fn(req.args, {'dom': self.dom.id}) @@ -173,7 +173,7 @@ class SrvDomain(SrvDir): def op_vif_limit_set(self, op, req): fn = FormFn(self.xd.domain_vif_limit_set, - [['dom', 'str'], + [['dom', 'int'], ['vif', 'int'], ['credit', 'int'], ['period', 'int']]) diff --git a/tools/python/xen/xend/server/SrvDomainDir.py b/tools/python/xen/xend/server/SrvDomainDir.py index e10561ee45..d6f6291716 100644 --- a/tools/python/xen/xend/server/SrvDomainDir.py +++ b/tools/python/xen/xend/server/SrvDomainDir.py @@ -24,7 +24,7 @@ class SrvDomainDir(SrvDir): def domain(self, x): val = None - dom = self.xd.domain_lookup(x) + dom = self.xd.domain_lookup_by_name(x) if not dom: raise XendError('No such domain ' + str(x)) val = SrvDomain(dom) diff --git a/tools/python/xen/xend/server/blkif.py b/tools/python/xen/xend/server/blkif.py index 5a179c23a0..75a76e8bda 100755 --- a/tools/python/xen/xend/server/blkif.py +++ b/tools/python/xen/xend/server/blkif.py @@ -5,14 +5,15 @@ import string from xen.util import blkif from xen.xend.XendError import XendError, VmError -from xen.xend import XendRoot +from xen.xend.XendRoot import get_component from xen.xend.XendLogging import log from xen.xend import sxp from xen.xend import Blkctl +from xen.xend.xenstore import DBVar -import channel -from controller import CtrlMsgRcvr, Dev, DevController -from messages import * +from xen.xend.server import channel +from xen.xend.server.controller import CtrlMsgRcvr, Dev, DevController +from xen.xend.server.messages import * class BlkifBackend: """ Handler for the 'back-end' channel to a block device driver domain @@ -56,7 +57,7 @@ class BlkifBackend: def openEvtchn(self): self.evtchn = channel.eventChannel(self.backendDomain, self.frontendDomain) - + def getEventChannelBackend(self): val = 0 if self.evtchn: @@ -158,6 +159,18 @@ class BlkDev(Dev): """Info record for a block device. """ + __exports__ = Dev.__exports__ + [ + DBVar('dev', ty='str'), + DBVar('vdev', ty='int'), + DBVar('mode', ty='str'), + DBVar('viftype', ty='str'), + DBVar('params', ty='str'), + DBVar('node', ty='str'), + DBVar('device', ty='long'), + DBVar('start_sector', ty='long'), + DBVar('nr_sectors', ty='long'), + ] + def __init__(self, controller, id, config, recreate=False): Dev.__init__(self, controller, id, config, recreate=recreate) self.dev = None @@ -206,7 +219,8 @@ class BlkDev(Dev): raise VmError('vbd: Device not found: %s' % self.dev) try: - self.backendDomain = int(sxp.child_value(config, 'backend', '0')) + xd = get_component('xen.xend.XendDomain') + self.backendDomain = xd.domain_lookup_by_name(sxp.child_value(config, 'backend', '0')).id except: raise XendError('invalid backend domain') @@ -214,8 +228,7 @@ class BlkDev(Dev): def attach(self, recreate=False, change=False): if recreate: - node = sxp.child_value(recreate, 'node') - self.setNode(node) + pass else: node = Blkctl.block('bind', self.type, self.params) self.setNode(node) @@ -263,7 +276,7 @@ class BlkDev(Dev): def check_mounted(self, name): mode = blkif.mount_mode(name) - xd = XendRoot.get_component('xen.xend.XendDomain') + xd = get_component('xen.xend.XendDomain') for vm in xd.list(): ctrl = vm.getDeviceController(self.getType(), error=False) if (not ctrl): continue @@ -292,14 +305,14 @@ class BlkDev(Dev): val.append(['uname', self.uname]) if self.node: val.append(['node', self.node]) - val.append(['index', self.getIndex()]) return val def getBackend(self): return self.controller.getBackend(self.backendDomain) def refresh(self): - log.debug("Refreshing vbd domain=%d id=%s", self.frontendDomain, self.id) + log.debug("Refreshing vbd domain=%d id=%s", self.frontendDomain, + self.id) self.interfaceChanged() def destroy(self, change=False, reboot=False): @@ -308,7 +321,8 @@ class BlkDev(Dev): @param change: change flag """ self.destroyed = True - log.debug("Destroying vbd domain=%d id=%s", self.frontendDomain, self.id) + log.debug("Destroying vbd domain=%d id=%s", self.frontendDomain, + self.id) self.send_be_vbd_destroy() if change: self.interfaceChanged() @@ -445,5 +459,4 @@ class BlkifController(DevController): log.error("Exception connecting backend: %s", ex) else: log.error('interface connect on unknown interface: id=%d', id) - diff --git a/tools/python/xen/xend/server/channel.py b/tools/python/xen/xend/server/channel.py index e2b2043e66..00f451a7b8 100755 --- a/tools/python/xen/xend/server/channel.py +++ b/tools/python/xen/xend/server/channel.py @@ -14,52 +14,129 @@ DEBUG = 0 RESPONSE_TIMEOUT = 20.0 -def eventChannel(dom1, dom2): - """Create an event channel between domains. - The returned dict contains dom1, dom2, port1 and port2 on success. +class EventChannel(dict): + """An event channel between domains. + """ + + def interdomain(cls, dom1, dom2, port1=0, port2=0): + """Create an event channel between domains. + + @return EventChannel (None on error) + """ + v = xc.evtchn_bind_interdomain(dom1=dom1, dom2=dom2, + port1=port1, port2=port2) + if v: + v = cls(dom1, dom2, v) + return v + + interdomain = classmethod(interdomain) + + def restoreFromDB(cls, db, dom1, dom2, port1=0, port2=0): + """Create an event channel using db info if available. + Inverse to saveToDB(). + + @param db db + @param dom1 + @param dom2 + @param port1 + @param port2 + """ + try: + dom1 = int(db['dom1']) + except: pass + try: + dom2 = int(db['dom2']) + except: pass + try: + port1 = int(db['port1']) + except: pass + try: + port2 = int(db['port2']) + except: pass + evtchn = cls.interdomain(dom1, dom2, port1=port1, port2=port2) + return evtchn + + restoreFromDB = classmethod(restoreFromDB) + + def __init__(self, dom1, dom2, d): + d['dom1'] = dom1 + d['dom2'] = dom2 + self.update(d) + self.dom1 = dom1 + self.dom2 = dom2 + self.port1 = d.get('port1') + self.port2 = d.get('port2') + + def close(self): + """Close the event channel. + """ + def evtchn_close(dom, port): + try: + xc.evtchn_close(dom=dom, port=port) + except Exception, ex: + pass + + if DEBUG: + print 'EventChannel>close>', self + evtchn_close(self.dom1, self.port1) + evtchn_close(self.dom2, self.port2) + + def saveToDB(self, db): + """Save the event channel to the db so it can be restored later, + using restoreFromDB() on the class. + + @param db db + """ + db['dom1'] = str(self.dom1) + db['dom2'] = str(self.dom2) + db['port1'] = str(self.port1) + db['port2'] = str(self.port2) + db.saveDB() + + def sxpr(self): + return ['event-channel', + ['dom1', self.dom1 ], + ['port1', self.port1 ], + ['dom2', self.dom2 ], + ['port2', self.port2 ] + ] + + def __repr__(self): + return ("<EventChannel dom1:%d:%d dom2:%d:%d>" + % (self.dom1, self.port1, self.dom2, self.port2)) - @return dict (empty on error) +def eventChannel(dom1, dom2, port1=0, port2=0): + """Create an event channel between domains. + + @return EventChannel (None on error) """ - evtchn = xc.evtchn_bind_interdomain(dom1=dom1, dom2=dom2) - if evtchn: - evtchn['dom1'] = dom1 - evtchn['dom2'] = dom2 - return evtchn + return EventChannel.interdomain(dom1, dom2, port1=port1, port2=port2) def eventChannelClose(evtchn): - """Close an event channel that was opened by eventChannel(). + """Close an event channel. """ - def evtchn_close(dom, port): - if (dom is None) or (port is None): return - try: - xc.evtchn_close(dom=dom, port=port) - except Exception, ex: - pass - if not evtchn: return - if DEBUG: - print 'eventChannelClose>', evtchn - evtchn_close(evtchn.get('dom1'), evtchn.get('port1')) - evtchn_close(evtchn.get('dom2'), evtchn.get('port2')) - + evtchn.close() class ChannelFactory: - """Factory for creating channels. + """Factory for creating control channels. Maintains a table of channels. """ """ Channels indexed by index. """ - channels = {} + channels = None thread = None notifier = None """Map of ports to the virq they signal.""" - virqPorts = {} + virqPorts = None def __init__(self): """Constructor - do not use. Use the channelFactory function.""" + self.channels = {} + self.virqPorts = {} self.notifier = xu.notifier() # Register interest in virqs. self.bind_virq(xen.lowlevel.xc.VIRQ_DOM_EXC) @@ -70,10 +147,6 @@ class ChannelFactory: self.virqPorts[port] = virq log.info("Virq %s on port %s", virq, port) - def virq(self): - log.error("virq") - self.notifier.virq_send(self.virqPort) - def start(self): """Fork a thread to read messages. """ @@ -182,9 +255,13 @@ class ChannelFactory: return None def openChannel(self, dom, local_port=0, remote_port=0): - return (self.findChannel(dom, local_port=local_port, remote_port=remote_port) - or - self.newChannel(dom, local_port, remote_port)) + chan = self.findChannel(dom, local_port=local_port, + remote_port=remote_port) + if chan: + return chan + chan = self.newChannel(dom, local_port, remote_port) + return chan + def createPort(self, dom, local_port=0, remote_port=0): """Create a port for a channel to the given domain. @@ -203,8 +280,31 @@ class ChannelFactory: @type remote: int @return: port object """ - return xu.port(dom, local_port=int(local_port), - remote_port=int(remote_port)) + return xu.port(dom, local_port=local_port, remote_port=remote_port) + + def restoreFromDB(self, db, dom, local, remote): + """Create a channel using ports restored from the db (if available). + Otherwise use the given ports. This is the inverse operation to + saveToDB() on a channel. + + @param db db + @param dom domain the channel connects to + @param local default local port + @param remote default remote port + """ + try: + local_port = int(db['local_port']) + except: + local_port = local + try: + remote_port = int(db['remote_port']) + except: + remote_port = remote + try: + chan = self.openChannel(dom, local_port, remote_port) + except: + return None + return chan def channelFactory(): """Singleton constructor for the channel factory. @@ -218,7 +318,7 @@ def channelFactory(): return inst class Channel: - """Chanel to a domain. + """Control channel to a domain. Maintains a list of device handlers to dispatch requests to, based on the request type. """ @@ -239,6 +339,17 @@ class Channel: # Make sure the port will deliver all the messages. self.port.register(TYPE_WILDCARD) + def saveToDB(self, db): + """Save the channel ports to the db so the channel can be restored later, + using restoreFromDB() on the factory. + + @param db db + """ + if self.closed: return + db['local_port'] = str(self.getLocalPort()) + db['remote_port'] = str(self.getRemotePort()) + db.saveDB() + def getKey(self): """Get the channel key. """ diff --git a/tools/python/xen/xend/server/console.py b/tools/python/xen/xend/server/console.py index f3dade883b..743ace4aec 100755 --- a/tools/python/xen/xend/server/console.py +++ b/tools/python/xen/xend/server/console.py @@ -13,10 +13,11 @@ from xen.xend import EventServer; eserver = EventServer.instance() from xen.xend.XendLogging import log from xen.xend import XendRoot; xroot = XendRoot.instance() from xen.xend import sxp +from xen.xend.xenstore import DBVar -from controller import CtrlMsgRcvr, Dev, DevController -from messages import * -from params import * +from xen.xend.server.controller import CtrlMsgRcvr, Dev, DevController +from xen.xend.server.messages import * +from xen.xend.server.params import * class ConsoleProtocol(protocol.Protocol): """Asynchronous handler for a console socket. @@ -76,6 +77,12 @@ class ConsoleDev(Dev, protocol.ServerFactory): STATUS_CONNECTED = 'connected' STATUS_LISTENING = 'listening' + __exports__ = Dev.__exports__ + [ + DBVar('status', ty='str'), + #DBVar('listening', ty='str'), + DBVar('console_port', ty='int'), + ] + def __init__(self, controller, id, config, recreate=False): Dev.__init__(self, controller, id, config) self.lock = threading.RLock() @@ -129,7 +136,6 @@ class ConsoleDev(Dev, protocol.ServerFactory): val.append(['local_port', self.getLocalPort() ]) val.append(['remote_port', self.getRemotePort() ]) val.append(['console_port', self.console_port ]) - val.append(['index', self.getIndex()]) if self.addr: val.append(['connected', self.addr[0], self.addr[1]]) finally: diff --git a/tools/python/xen/xend/server/controller.py b/tools/python/xen/xend/server/controller.py index 9205b2778e..d1e19efee1 100755 --- a/tools/python/xen/xend/server/controller.py +++ b/tools/python/xen/xend/server/controller.py @@ -4,7 +4,8 @@ for a domain. """ from xen.xend.XendError import XendError -from messages import msgTypeName, printMsg, getMessageType +from xen.xend.xenstore import DBVar +from xen.xend.server.messages import msgTypeName, printMsg, getMessageType DEBUG = 0 @@ -115,18 +116,18 @@ class DevControllerTable: def getDevControllerClass(self, type): return self.controllerClasses.get(type) - def addDevControllerClass(self, klass): - self.controllerClasses[klass.getType()] = klass + def addDevControllerClass(self, cls): + self.controllerClasses[cls.getType()] = cls def delDevControllerClass(self, type): if type in self.controllerClasses: del self.controllerClasses[type] def createDevController(self, type, vm, recreate=False): - klass = self.getDevControllerClass(type) - if not klass: + cls = self.getDevControllerClass(type) + if not cls: raise XendError("unknown device type: " + type) - return klass.createDevController(vm, recreate=recreate) + return cls.createDevController(vm, recreate=recreate) def getDevControllerTable(): """Singleton constructor for the controller table. @@ -138,11 +139,11 @@ def getDevControllerTable(): devControllerTable = DevControllerTable() return devControllerTable -def addDevControllerClass(name, klass): +def addDevControllerClass(name, cls): """Add a device controller class to the controller table. """ - klass.name = name - getDevControllerTable().addDevControllerClass(klass) + cls.type = name + getDevControllerTable().addDevControllerClass(cls) def createDevController(name, vm, recreate=False): return getDevControllerTable().createDevController(name, vm, recreate=recreate) @@ -155,29 +156,57 @@ class DevController: """ - name = None + # State: + # controller/<type> : for controller + # device/<type>/<id> : for each device - def createDevController(klass, vm, recreate=False): + def createDevController(cls, vm, recreate=False): """Class method to create a dev controller. """ - ctrl = klass(vm, recreate=recreate) + ctrl = cls(vm, recreate=recreate) ctrl.initController(recreate=recreate) + ctrl.exportToDB() return ctrl createDevController = classmethod(createDevController) - def getType(klass): - return klass.name + def getType(cls): + return cls.type getType = classmethod(getType) + __exports__ = [ + DBVar('type', 'str'), + DBVar('destroyed', 'bool'), + ] + + # Set when registered. + type = None + def __init__(self, vm, recreate=False): self.destroyed = False self.vm = vm + self.db = self.getDB() self.deviceId = 0 self.devices = {} self.device_order = [] + def getDB(self): + """Get the db node to use for a controller. + """ + return self.vm.db.addChild("/controller/%s" % self.getType()) + + def getDevDB(self, id): + """Get the db node to use for a device. + """ + return self.vm.db.addChild("/device/%s/%s" % (self.getType(), id)) + + def exportToDB(self, save=False): + self.db.exportToDB(self, fields=self.__exports__, save=save) + + def importFromDB(self): + self.db.importFromDB(self, fields=self.__exports__) + def getDevControllerType(self): return self.dctype @@ -229,18 +258,19 @@ class DevController: i.e. it is being added at runtime rather than when the domain is created. """ dev = self.newDevice(self.nextDeviceId(), config, recreate=recreate) + if self.vm.recreate: + dev.importFromDB() dev.init(recreate=recreate) self.addDevice(dev) - idx = self.getDeviceIndex(dev) - recreate = self.vm.get_device_recreate(self.getType(), idx) + if not recreate: + dev.exportToDB() dev.attach(recreate=recreate, change=change) + dev.exportToDB() def configureDevice(self, id, config, change=False): """Reconfigure an existing device. May be defined in subclass.""" - dev = self.getDevice(id) - if not dev: - raise XendError("invalid device id: " + id) + dev = self.getDevice(id, error=True) dev.configure(config, change=change) def destroyDevice(self, id, change=False, reboot=False): @@ -251,9 +281,7 @@ class DevController: The device is not deleted, since it may be recreated later. """ - dev = self.getDevice(id) - if not dev: - raise XendError("invalid device id: " + id) + dev = self.getDevice(id, error=True) dev.destroy(change=change, reboot=reboot) return dev @@ -278,24 +306,15 @@ class DevController: def isDestroyed(self): return self.destroyed - def getDevice(self, id): - return self.devices.get(id) - - def getDeviceByIndex(self, idx): - if 0 <= idx < len(self.device_order): - return self.device_order[idx] - else: - return None - - def getDeviceIndex(self, dev): - return self.device_order.index(dev) + def getDevice(self, id, error=False): + dev = self.devices.get(id) + if error and not dev: + raise XendError("invalid device id: " + id) + return dev def getDeviceIds(self): return [ dev.getId() for dev in self.device_order ] - def getDeviceIndexes(self): - return range(0, len(self.device_order)) - def getDevices(self): return self.device_order @@ -353,11 +372,42 @@ class Dev: @type controller: DevController """ + # ./status : need 2: actual and requested? + # down-down: initial. + # up-up: fully up. + # down-up: down requested, still up. Watch front and back, when both + # down go to down-down. But what if one (or both) is not connected? + # Still have front/back trees with status? Watch front/status, back/status? + # up-down: up requested, still down. + # Back-end watches ./status, front/status + # Front-end watches ./status, back/status + # i.e. each watches the other 2. + # Each is status/request status/actual? + # + # backend? + # frontend? + + __exports__ = [ + DBVar('id', ty='int'), + DBVar('type', ty='str'), + DBVar('config', ty='sxpr'), + DBVar('destroyed', ty='bool'), + ] + def __init__(self, controller, id, config, recreate=False): self.controller = controller self.id = id self.config = config self.destroyed = False + self.type = self.getType() + + self.db = controller.getDevDB(id) + + def exportToDB(self, save=False): + self.db.exportToDB(self, fields=self.__exports__, save=save) + + def importFromDB(self): + self.db.importFromDB(self, fields=self.__exports__) def getDomain(self): return self.controller.getDomain() @@ -380,9 +430,6 @@ class Dev: def getId(self): return self.id - def getIndex(self): - return self.controller.getDeviceIndex(self) - def getConfig(self): return self.config diff --git a/tools/python/xen/xend/server/netif.py b/tools/python/xen/xend/server/netif.py index 9d2dbc4f63..0a49842522 100755 --- a/tools/python/xen/xend/server/netif.py +++ b/tools/python/xen/xend/server/netif.py @@ -4,21 +4,75 @@ import random +from xen.util.mac import macFromString, macToString + from xen.xend import sxp from xen.xend import Vifctl from xen.xend.XendError import XendError, VmError from xen.xend.XendLogging import log from xen.xend import XendVnet from xen.xend.XendRoot import get_component +from xen.xend.xenstore import DBVar -import channel -from controller import CtrlMsgRcvr, Dev, DevController -from messages import * +from xen.xend.server import channel +from xen.xend.server.controller import CtrlMsgRcvr, Dev, DevController +from xen.xend.server.messages import * class NetDev(Dev): """A network device. """ + # State: + # inherited + + # ./config + # ./mac + # ./be_mac + # ./bridge + # ./script + # ./ipaddr ? + # + # ./credit + # ./period + # + # ./vifctl: up/down? + # ./vifname + # + # + # Poss should have no backend state here - except for ref to backend's own tree + # for the device? And a status - the one we want. + # ./back/dom + # ./back/devid - id for back-end (netif_handle) - same as front/devid + # ./back/id - backend id (if more than one b/e per domain) + # ./back/status + # ./back/tx_shmem_frame - actually these belong in back-end state + # ./back/rx_shmem_frame + # + # ./front/dom + # ./front/devid + # ./front/status - need 2: one for requested, one for actual? Or drive from dev status + # and this is front status only. + # ./front/tx_shmem_frame + # ./front/rx_shmem_frame + # + # ./evtchn/front - here or in front/back? + # ./evtchn/back + # ./evtchn/status ? + # At present created by dev: but should be created unbound by front/back + # separately and then bound (by back)? + + __exports__ = Dev.__exports__ + [ + DBVar('config', ty='sxpr'), + DBVar('mac', ty='mac'), + DBVar('be_mac', ty='mac'), + DBVar('bridge', ty='str'), + DBVar('script', ty='str'), + #DBVar('ipaddr'), + DBVar('credit', ty='int'), + DBVar('period', ty='int'), + DBVar('vifname', ty='str'), + DBVar('evtchn'), #todo: export fields (renamed) + ] + def __init__(self, controller, id, config, recreate=False): Dev.__init__(self, controller, id, config, recreate=recreate) self.vif = int(self.id) @@ -49,15 +103,19 @@ class NetDev(Dev): def _get_config_mac(self, config): vmac = sxp.child_value(config, 'mac') if not vmac: return None - mac = [ int(x, 16) for x in vmac.split(':') ] - if len(mac) != 6: raise XendError("invalid mac: %s" % vmac) + try: + mac = macFromString(vmac) + except: + raise XendError("invalid mac: %s" % vmac) return mac def _get_config_be_mac(self, config): vmac = sxp.child_value(config, 'be_mac') if not vmac: return None - mac = [ int(x, 16) for x in vmac.split(':') ] - if len(mac) != 6: raise XendError("invalid backend mac: %s" % vmac) + try: + mac = macFromString(vmac) + except: + raise XendError("invalid backend mac: %s" % vmac) return mac def _get_config_ipaddr(self, config): @@ -102,7 +160,7 @@ class NetDev(Dev): else: #todo: Code below will fail on xend restart when backend is not domain 0. xd = get_component('xen.xend.XendDomain') - self.backendDomain = int(xd.domain_lookup(sxp.child_value(config, 'backend', '0')).id) + self.backendDomain = xd.domain_lookup_by_name(sxp.child_value(config, 'backend', '0')).id except: raise XendError('invalid backend domain') return self.config @@ -127,13 +185,13 @@ class NetDev(Dev): ipaddr = self._get_config_ipaddr(config) xd = get_component('xen.xend.XendDomain') - backendDomain = str(xd.domain_lookup(sxp.child_value(config, 'backend', '0')).id) + backendDomain = xd.domain_lookup_by_name(sxp.child_value(config, 'backend', '0')).id if (mac is not None) and (mac != self.mac): raise XendError("cannot change mac") if (be_mac is not None) and (be_mac != self.be_mac): raise XendError("cannot change backend mac") - if (backendDomain is not None) and (backendDomain != str(self.backendDomain)): + if (backendDomain is not None) and (backendDomain != self.backendDomain): raise XendError("cannot change backend") if (bridge is not None) and (bridge != self.bridge): changes['bridge'] = bridge @@ -199,7 +257,6 @@ class NetDev(Dev): val.append(['evtchn', self.evtchn['port1'], self.evtchn['port2']]) - val.append(['index', self.getIndex()]) return val def get_vifname(self): @@ -213,12 +270,12 @@ class NetDev(Dev): def get_mac(self): """Get the MAC address as a string. """ - return ':'.join(map(lambda x: "%02x" % x, self.mac)) + return macToString(self.mac) def get_be_mac(self): """Get the backend MAC address as a string. """ - return ':'.join(map(lambda x: "%02x" % x, self.be_mac)) + return macToString(self.be_mac) def vifctl_params(self, vmname=None): """Get the parameters to pass to vifctl. @@ -230,7 +287,7 @@ class NetDev(Dev): vm = xd.domain_lookup(dom) vmname = vm.name except: - vmname = 'DOM%d' % dom + vmname = 'Domain-%d' % dom return { 'domain': vmname, 'vif' : self.get_vifname(), 'mac' : self.get_mac(), diff --git a/tools/python/xen/xend/server/params.py b/tools/python/xen/xend/server/params.py index 5c7fdf7bad..2565c2dfcd 100644 --- a/tools/python/xen/xend/server/params.py +++ b/tools/python/xen/xend/server/params.py @@ -1,6 +1,34 @@ -# The following parameters could be placed in a configuration file. -XEND_PID_FILE = '/var/run/xend.pid' -XEND_TRACE_FILE = '/var/log/xend.trace' +import os + +def getenv(var, val, conv=None): + """Get a value from the environment, with optional conversion. -XEND_USER = 'root' + @param var name of environment variable + @param val default value + @param conv conversion function to apply to env value + @return converted value or default + """ + try: + v = os.getenv(var) + if v is None: + v = val + else: + print var, '=', v + if conv: + v = conv(v) + except: + v = val + return v + +# The following parameters could be placed in a configuration file. +XEND_PID_FILE = '/var/run/xend.pid' +XEND_TRACE_FILE = '/var/log/xend.trace' +XEND_DEBUG_LOG = '/var/log/xend-debug.log' +XEND_USER = 'root' +XEND_DEBUG = getenv("XEND_DEBUG", 0, conv=int) +XEND_DAEMONIZE = getenv("XEND_DAEMONIZE", not XEND_DEBUG, conv=int) +XENSTORED_PID_FILE = '/var/run/xenstored.pid' +XENSTORED_RUN_DIR = '/var/run/xenstored' +XENSTORED_LIB_DIR = '/var/lib/xenstored' +XENSTORED_DEBUG = getenv("XENSTORED_DEBUG", 0, conv=int) diff --git a/tools/python/xen/xend/server/usbif.py b/tools/python/xen/xend/server/usbif.py index 9535fdd202..d366985740 100644 --- a/tools/python/xen/xend/server/usbif.py +++ b/tools/python/xen/xend/server/usbif.py @@ -7,10 +7,11 @@ from xen.xend import sxp from xen.xend.XendLogging import log from xen.xend.XendError import XendError +from xen.xend.xenstore import DBVar -import channel -from controller import Dev, DevController -from messages import * +from xen.xend.server import channel +from xen.xend.server.controller import Dev, DevController +from xen.xend.server.messages import * class UsbBackend: """Handler for the 'back-end' channel to a USB device driver domain @@ -141,6 +142,11 @@ class UsbBackend: class UsbDev(Dev): + + __exports__ = Dev.__exports__ + [ + DBVar('port', ty='int'), + DBVar('path', ty='str'), + ] def __init__(self, controller, id, config, recreate=False): Dev.__init__(self, controller, id, config, recreate=recreate) @@ -186,7 +192,6 @@ class UsbDev(Dev): ['port', self.port], ['path', self.path], ] - val.append(['index', self.getIndex()]) return val def getBackend(self): diff --git a/tools/python/xen/xend/uuid.py b/tools/python/xen/xend/uuid.py new file mode 100644 index 0000000000..096fef7f9f --- /dev/null +++ b/tools/python/xen/xend/uuid.py @@ -0,0 +1,65 @@ +"""Universal(ly) Unique Identifiers (UUIDs). +""" +import commands +import random + +def uuidgen(random=True): + """Generate a UUID using the command uuidgen. + + If random is true (default) generates a random uuid. + If random is false generates a time-based uuid. + """ + cmd = "uuidgen" + if random: + cmd += " -r" + else: + cmd += " -t" + return commands.getoutput(cmd) + +class UuidFactoryUuidgen: + + """A uuid factory using uuidgen.""" + + def __init__(self): + pass + + def getUuid(self): + return uuidgen() + +class UuidFactoryRandom: + + """A random uuid factory.""" + + def __init__(self): + f = file("/dev/urandom", "r") + seed = f.read(16) + f.close() + self.rand = random.Random(seed) + + def randBytes(self, n): + return [ self.rand.randint(0, 255) for i in range(0, n) ] + + def getUuid(self): + bytes = self.randBytes(16) + # Encode the variant. + bytes[6] = (bytes[6] & 0x0f) | 0x40 + bytes[8] = (bytes[8] & 0x3f) | 0x80 + f = "%02x" + return ( "-".join([f*4, f*2, f*2, f*2, f*6]) % tuple(bytes) ) + +def getFactory(): + """Get the factory to use for creating uuids. + This is so it's easy to change the uuid factory. + For example, for testing we might want repeatable uuids + rather than the random ones we normally use. + """ + global uuidFactory + try: + uuidFactory + except: + #uuidFactory = UuidFactoryUuidgen() + uuidFactory = UuidFactoryRandom() + return uuidFactory + +def getUuid(): + return getFactory().getUuid() diff --git a/tools/python/xen/xend/xenstore/__init__.py b/tools/python/xen/xend/xenstore/__init__.py new file mode 100644 index 0000000000..6772d2ceca --- /dev/null +++ b/tools/python/xen/xend/xenstore/__init__.py @@ -0,0 +1,2 @@ +from xsnode import * +from xsobj import * diff --git a/tools/python/xen/xend/xenstore/xsnode.py b/tools/python/xen/xend/xenstore/xsnode.py new file mode 100644 index 0000000000..ae770219ab --- /dev/null +++ b/tools/python/xen/xend/xenstore/xsnode.py @@ -0,0 +1,382 @@ +import errno +import os +import os.path +import select +import sys +import time + +from xen.lowlevel import xs +from xen.xend import sxp +from xen.xend.PrettyPrint import prettyprint + +SELECT_TIMEOUT = 2.0 + +def getEventPath(event): + return os.path.join("/_event", event) + +def getEventIdPath(event): + return os.path.join(eventPath(event), "@eid") + +class Subscription: + + def __init__(self, event, fn, id): + self.event = event + self.watcher = None + self.fn = fn + self.id = id + + def watch(self, watcher): + self.watcher = watcher + watcher.addSubs(self) + + def unwatch(self): + watcher = self.watcher + if watcher: + self.watcher = None + watcher.delSubs(self) + + def notify(self, event): + try: + self.fn(event, id) + except SystemExitException: + raise + except: + pass + +class Watcher: + + def __init__(self, store, event): + self.path = getEventPath(event) + self.eidPath = getEventIdPath(event) + store.mkdirs(self.path) + if not store.exists(self.eidPath): + store.writeInt(self.eidPath, 0) + self.xs = None + self.subs = [] + + def __getattr__(self, k, v): + if k == "fileno": + if self.xs: + return self.xs.fileno + else: + return -1 + else: + return self.__dict__.get(k, v) + + def addSubs(self, subs): + self.subs.append(subs) + self.watch() + + def delSubs(self, subs): + self.subs.remove(subs) + if len(self.subs) == 0: + self.unwatch() + + def getEvent(self): + return self.event + + def watch(self): + if self.xs: return + self.xs = xs.open() + self.xs.watch(path) + + def unwatch(self): + if self.xs: + self.xs.unwatch(self.path) + self.xs.close() + self.xs = None + + def watching(self): + return self.xs is not None + + def getNotification(self): + p = self.xs.read_watch() + self.xs.acknowledge_watch() + eid = self.xs.readInt(self.eidPath) + return p + + def notify(self, subs): + p = self.getNotification() + for s in subs: + s.notify(p) + +class XenStore: + + def __init__(self): + self.xs = None + #self.xs = xs.open() + self.subscription = {} + self.subscription_id = 0 + self.events = {} + self.write("/", "") + + def getxs(self): + if self.xs is None: + ex = None + for i in range(0,20): + try: + self.xs = xs.open() + ex = None + break + except Exception, ex: + print >>stderr, "Exception connecting to xsdaemon:", ex + print >>stderr, "Trying again..." + time.sleep(1) + else: + raise ex + + #todo would like to reconnect if xs conn closes (e.g. daemon restart). + return self.xs + + def dump(self, path="/", out=sys.stdout): + print 'dump>', path + val = ['node'] + val.append(['path', path]) +## perms = ['perms'] +## for p in self.getPerms(path): +## l = ['perm'] +## l.append('dom', p.get['dom']) +## for k in ['read', 'write', 'create', 'owner']: +## v = p.get(k) +## l.append([k, v]) +## perms.append(l) +## val.append(perms) + data = self.read(path) + if data: + val.append(['data', data]) + children = ['children'] + for x in self.lsPaths(path): + print 'dump>', 'child=', x + children.append(self.dump(x)) + if len(children) > 1: + val.append(children) + prettyprint(val, out=out) + return val + + def getPerms(self, path): + return self.getxs().get_permissions(path) + + def ls(self, path="/"): + return self.getxs().ls(path) + + def lsPaths(self, path="/"): + return [ os.path.join(path, x) for x in self.ls(path) ] + + def lsr(self, path="/", list=None): + if list is None: + list = [] + list.append(path) + for x in self.lsPaths(path): + list.append(x) + self.lsr(x, list=list) + return list + + def rm(self, path): + try: + #for x in self.lsPaths(): + # self.getxs().rm(x) + self.getxs().rm(path) + except: + pass + + def exists(self, path): + try: + self.getxs().ls(path) + return True + except RuntimeError, ex: + if ex.args[0] == errno.ENOENT: + return False + else: + raise + + def mkdirs(self, path): + if self.exists(path): + return + elts = path.split("/") + p = "/" + for x in elts: + if x == "": continue + p = os.path.join(p, x) + if not self.exists(p): + self.getxs().write(p, "", create=True) + + def read(self, path): + try: + return self.getxs().read(path) + except RuntimeError, ex: + if ex.args[0] == errno.EISDIR: + return None + else: + raise + + def create(self, path, excl=False): + self.write(path, "", create=True, excl=excl) + + def write(self, path, data, create=True, excl=False): + self.mkdirs(path) + self.getxs().write(path, data, create=create, excl=excl) + + def begin(self, path): + self.getxs().begin_transaction(path) + + def commit(self, abandon=False): + self.getxs().end_transaction(abort=abandon) + + def subscribe(self, event, fn): + watcher = self.watchEvent(event) + self.subscription_id += 1 + subs = Subscription(event, fn, self.subscription_id) + self.subscription[subs.id] = subs + subs.watch(watcher) + return subs.id + + def unsubscribe(self, sid): + s = self.subscription.get(sid) + if not s: return + del self.subscription[s.id] + s.unwatch() + unwatchEvent(s.event) + + def sendEvent(self, event, data): + eventPath = getEventPath(event) + eidPath = getEventIdPath(event) + try: + self.begin(eventPath) + self.mkdirs(eventPath) + if self.exists(eidPath): + eid = self.readInt(eidPath) + eid += 1 + else: + eid = 1 + self.writeInt(eidPath, eid) + self.write(os.path.join(eventPath, str(eid)), data) + finally: + self.commit() + + def watchEvent(self, event): + if event in self.events: + return + watcher = Watcher(event) + self.watchers[watcher.getEvent()] = watcher + self.watchStart() + return watcher + + def unwatchEvent(self, event): + watcher = self.watchers.get(event) + if not watcher: + return + if not watcher.watching(): + del self.watchers[event] + + def watchStart(self): + if self.watchThread: return + + def watchMain(self): + try: + while True: + if self.watchThread is None: return + if not self.events: + return + rd = self.watchers.values() + try: + (rd, wr, er) = select.select(rd, [], [], SELECT_TIMEOUT) + for watcher in rd: + watcher.notify() + except socket.error, ex: + if ex.args[0] in (EAGAIN, EINTR): + pass + else: + raise + finally: + self.watchThread = None + + def introduceDomain(self, dom, page, evtchn, path): + self.getxs().introduce_domain(dom, page, evtchn.port1, path) + + def releaseDomain(self, dom): + self.getxs().release_domain(dom) + +def getXenStore(): + global xenstore + try: + return xenstore + except: + xenstore = XenStore() + return xenstore + +class XenNode: + + def __init__(self, path="/", create=True): + self.store = getXenStore() + self.path = path + if not self.store.exists(path): + if create: + self.store.create(path) + else: + raise ValueError("path does not exist: '%s'" % path) + + def relPath(self, path=""): + if not path: + return self.path + if path and path.startswith("/"): + path = path[1:] + return os.path.join(self.path, path) + + def delete(self, path=""): + self.store.rm(self.relPath(path)) + + def exists(self, path=""): + return self.store.exists(self.relPath(path)) + + def getNode(self, path="", create=True): + if path == "": + return self + else: + return XenNode(self.relPath(path=path), create=create) + + getChild = getNode + + def getData(self, path=""): + path = self.relPath(path) + try: + return self.store.read(path) + except: + return None + + def setData(self, data, path=""): + path = self.relPath(path) + #print 'XenNode>setData>', 'path=', path, 'data=', data + return self.store.write(path, data) + + def getLock(self): + return None + + def lock(self, lockid): + return None + + def unlock(self, lockid): + return None + + def deleteChild(self, name): + self.delete(name) + + def deleteChildren(self): + for name in self.ls(): + self.deleteChild(name) + + def getChildren(self): + return [ self.getNode(name) for name in self.ls() ] + + def ls(self): + return self.store.ls(self.path) + + def introduceDomain(self, dom, page, evtchn, path): + self.store.introduceDomain(dom, page, evtchn, path) + + def releaseDomain(self, dom): + self.store.releaseDomain(dom) + + def __repr__(self): + return "<XenNode %s>" % self.path + + diff --git a/tools/python/xen/xend/xenstore/xsobj.py b/tools/python/xen/xend/xenstore/xsobj.py new file mode 100644 index 0000000000..b1c9a4f1d1 --- /dev/null +++ b/tools/python/xen/xend/xenstore/xsobj.py @@ -0,0 +1,522 @@ +import string +import types + +from xen.xend import sxp +from xsnode import XenNode +from xen.util.mac import macToString, macFromString + +VALID_KEY_CHARS = string.ascii_letters + string.digits + "_-@" + +def hasAttr(obj, attr): + if isinstance(obj, dict): + return obj.contains(attr) + else: + return hasattr(obj, attr) + +def getAttr(obj, attr): + if isinstance(obj, dict): + return dict.get(attr) + else: + return getattr(obj, attr, None) + +def setAttr(obj, attr, val): + if isinstance(obj, dict): + dict[attr] = val + else: + setattr(obj, attr, val) + +class DBConverter: + """Conversion of values to and from strings in xenstore. + """ + + converters = {} + + def checkType(cls, ty): + if ty is None or ty in cls.converters: + return + raise ValueError("invalid converter type: '%s'" % ty) + + checkType = classmethod(checkType) + + def getConverter(cls, ty=None): + if ty is None: + ty = "str" + conv = cls.converters.get(ty) + if not conv: + raise ValueError("no converter for type: '%s'" % ty) + return conv + + getConverter = classmethod(getConverter) + + def convertToDB(cls, val, ty=None): + return cls.getConverter(ty).toDB(val) + + convertToDB = classmethod(convertToDB) + + def convertFromDB(cls, val, ty=None): + return cls.getConverter(ty).fromDB(val) + + convertFromDB = classmethod(convertFromDB) + + # Must define in subclass. + name = None + + def __init__(self): + self.register() + + def register(self): + if not self.name: + raise ValueError("invalid converter name: '%s'" % self.name) + self.converters[self.name] = self + + def toDB(self, val): + raise NotImplementedError() + + def fromDB(self, val): + raise NotImplementedError() + +class StrConverter(DBConverter): + + name = "str" + + def toDB(self, val): + # Convert True/False to 1/0, otherwise they convert to + # 'True' and 'False' rather than '1' and '0', even though + # isinstance(True/False, int) is true. + if isinstance(val, bool): + val = int(val) + return str(val) + + def fromDB(self, data): + return data + +StrConverter() + +class BoolConverter(DBConverter): + + name = "bool" + + def toDB(self, val): + return str(int(bool(val))) + + def fromDB(self, data): + return bool(int(data)) + +BoolConverter() + +class SxprConverter(DBConverter): + + name = "sxpr" + + def toDB(self, val): + return sxp.to_string(val) + + def fromDB(self, data): + return sxp.from_string(data) + +SxprConverter() + +class IntConverter(DBConverter): + + name = "int" + + def toDB(self, val): + return str(int(val)) + + def fromDB(self, data): + return int(data) + +IntConverter() + +class FloatConverter(DBConverter): + + name = "float" + + def toDB(self, val): + return str(float(val)) + + def fromDB(self, data): + return float(data) + +FloatConverter() + +class LongConverter(DBConverter): + + name = "long" + + def toDB(self, val): + return str(long(val)) + + def fromDB(self, data): + return long(data) + +LongConverter() + +class MacConverter(DBConverter): + + name = "mac" + + def toDB(self, val): + return macToString(val) + + def fromDB(self, data): + return macFromString(data) + +MacConverter() + +class DBVar: + + def __init__(self, var, ty=None, path=None): + DBConverter.checkType(ty) + if path is None: + path = var + self.var = var + self.ty = ty + self.path = path + varpath = filter(bool, self.var.split()) + self.attrpath = varpath[:-1] + self.attr = varpath[-1] + + def exportToDB(self, db, obj): + self.setDB(db, self.getObj(obj)) + + def importFromDB(self, db, obj): + self.setObj(obj, self.getDB(db)) + + def getObj(self, obj): + o = obj + for x in self.attrpath: + o = getAttr(o, x) + if o is None: + return None + return getAttr(o, self.attr) + + def setObj(self, obj, val): + o = obj + for x in self.attrpath: + o = getAttr(o, x) + # Don't set obj attr if val is None. + if val is None and hasAttr(o, self.attr): + return + setAttr(o, self.attr, val) + + def getDB(self, db): + try: + data = getattr(db, self.path) + except AttributeError: + return None + return DBConverter.convertFromDB(data, ty=self.ty) + + def setDB(self, db, val): + # Don't set in db if val is None. + #print 'DBVar>setDB>', self.path, 'val=', val + if val is None: + return + data = DBConverter.convertToDB(val, ty=self.ty) + #print 'DBVar>setDB>', self.path, 'data=', data + setattr(db, self.path, data) + + +class DBMap(dict): + """A persistent map. Extends dict with persistence. + Set and get values using the usual map syntax: + + m[k], m.get(k) + m[k] = v + + Also supports being treated as an object with attributes. + When 'k' is a legal identifier you may also use + + m.k, getattr(m, k) + m.k = v, setattr(m, k) + k in m, hasattr(m, k) + + When setting you can pass in a normal value, for example + + m.x = 3 + + Getting works too: + + m.x ==> 3 + + while m['x'] will return the map for x. + + m['x'].getData() ==> 3 + + To get values from subdirs use get() to get the subdir first: + + get(m, 'foo').x + m['foo'].x + + instead of m.foo.x, because m.foo will return the data for field foo, + not the directory. + + You can assign values into a subdir by passing a map: + + m.foo = {'x': 1, 'y':2 } + + You can also use paths as keys: + + m['foo/x'] = 1 + + sets field x in subdir foo. + + """ + + __db__ = None + __data__ = None + __perms__ = None + __parent__ = None + __name__ = "" + + __transaction__ = False + + # True if value set since saved (or never saved). + __dirty__ = True + + def __init__(self, parent=None, name="", db=None): + if parent is None: + self.__name__ = name + else: + if not isinstance(parent, DBMap): + raise ValueError("invalid parent") + self.__parent__ = parent + self.__name__ = name + db = self.__parent__.getChildDB(name) + self.setDB(db) + + def getName(self): + return self.__name__ + + def getPath(self): + return self.__db__ and self.__db__.relPath() + + def introduceDomain(self, dom, page, evtchn, path=None): + db = self.__db__ + if path is None: + path = db.relPath() + print 'DBMap>introduceDomain>', dom, page, evtchn, path + try: + db.introduceDomain(dom, page, evtchn, path) + except Exception, ex: + import traceback + traceback.print_exc() + print 'DBMap>introduceDomain>', ex + pass # todo: don't ignore + + def releaseDomain(self, dom): + db = self.__db__ + print 'DBMap>releaseDomain>', dom + try: + db.releaseDomain(dom) + except Exception, ex: + import traceback + traceback.print_exc() + print 'DBMap>releaseDomain>', ex + pass # todo: don't ignore + + def transactionBegin(self): + # Begin a transaction. + pass + + def transactionCommit(self): + # Commit writes to db. + pass + + def transactionFail(self): + # Fail a transaction. + # We have changed values, what do we do? + pass + + def watch(self, fn): + pass + + def unwatch(self, watch): + pass + + def checkName(self, k): + if k == "": + raise ValueError("invalid key, empty string") + for c in k: + if c in VALID_KEY_CHARS: continue + raise ValueError("invalid key char '%s'" % c) + + def _setData(self, v): + #print 'DBMap>_setData>', self.getPath(), 'data=', v + if v != self.__data__: + self.__dirty__ = True + self.__data__ = v + + def setData(self, v): + if isinstance(v, dict): + for (key, val) in v.items(): + self[key] = val + else: + self._setData(v) + + def getData(self): + return self.__data__ + + def _set(self, k, v): + dict.__setitem__(self, k, v) + + def _get(self, k): + try: + return dict.__getitem__(self, k) + except: + return None + + def _del(self, k, v): + try: + dict.__delitem__(self, k) + except: + pass + + def _contains(self, k): + return dict.__contains__(self, k) + + def __setitem__(self, k, v, save=False): + node = self.addChild(k) + node.setData(v) + if save: + node.saveDB() + + def __getitem__(self, k): + if self._contains(k): + v = self._get(k) + else: + v = self.readChildDB(k) + self._set(k, v) + return v + + def __delitem__(self, k): + self._del(k) + self.deleteChildDB(k) + + def __repr__(self): + if len(self): + return dict.__repr__(self) + else: + return repr(self.__data__) + + def __setattr__(self, k, v): + if k.startswith("__"): + object.__setattr__(self, k, v) + else: + self.__setitem__(k, v, save=True) + return v + + def __getattr__(self, k): + if k.startswith("__"): + v = object.__getattr__(self, k) + else: + try: + v = self.__getitem__(k).getData() + except LookupError, ex: + raise AttributeError(ex.args) + return v + + def __delattr__(self, k): + return self.__delitem__(k) + + def delete(self): + dict.clear(self) + self.__data__ = None + if self.__db__: + self.__db__.delete() + + def clear(self): + dict.clear(self) + if self.__db__: + self.__db__.deleteChildren() + + def getChild(self, k): + return self._get(k) + + def getChildDB(self, k): + self.checkName(k) + return self.__db__ and self.__db__.getChild(k) + + def deleteChildDB(self, k): + if self.__db__: + self.__db__.deleteChild(k) + + def _addChild(self, k): + kid = self._get(k) + if kid is None: + kid = DBMap(parent=self, name=k, db=self.getChildDB(k)) + self._set(k, kid) + return kid + + def addChild(self, path): + l = path.split("/") + n = self + for x in l: + if x == "": continue + n = n._addChild(x) + return n + + def setDB(self, db): + if (db is not None) and not isinstance(db, XenNode): + raise ValueError("invalid db") + self.__db__ = db + for (k, v) in self.items(): + if v is None: continue + if isinstance(v, DBMap): + v._setDB(self.addChild(k), restore) + + def readDB(self): + if self.__db__ is None: + return + self.__data__ = self.__db__.getData() + for k in self.__db__.ls(): + n = self.addChild(k) + n.readDB() + self.__dirty__ = False + + def readChildDB(self, k): + if self.__db__ and (k in self.__db__.ls()): + n = self.addChild(k) + n.readDB() + raise LookupError("invalid key '%s'" % k) + + def saveDB(self, sync=False, save=False): + """Save unsaved data to db. + If save or sync is true, saves whether dirty or not. + If sync is true, removes db entries not in the map. + """ + + if self.__db__ is None: + #print 'DBMap>saveDB>',self.getPath(), 'no db' + return + # Write data. + #print 'DBMap>saveDB>', self.getPath(), 'dirty=', self.__dirty__, 'data=', self.__data__ + if ((self.__data__ is not None) + and (sync or save or self.__dirty__)): + self.__db__.setData(self.__data__) + self.__dirty__ = False + else: + #print 'DBMap>saveDB>', self.getPath(), 'not written' + pass + # Write children. + for (name, node) in self.items(): + if not isinstance(node, DBMap): continue + node.saveDB(sync=sync, save=save) + # Remove db nodes not in children. + if sync: + for name in self.__db__.ls(): + if name not in self: + self.__db__.delete(name) + + def importFromDB(self, obj, fields): + """Set fields in obj from db fields. + """ + for f in fields: + f.importFromDB(self, obj) + + def exportToDB(self, obj, fields, save=False, sync=False): + """Set fields in db from obj fields. + """ + for f in fields: + f.exportToDB(self, obj) + self.saveDB(save=save, sync=sync) diff --git a/tools/python/xen/xend/xenstore/xsresource.py b/tools/python/xen/xend/xenstore/xsresource.py new file mode 100644 index 0000000000..37011bdea3 --- /dev/null +++ b/tools/python/xen/xend/xenstore/xsresource.py @@ -0,0 +1,136 @@ +#============================================================================ +# Copyright (C) 2005 Mike Wray <mike.wray@hp.com> +#============================================================================ +# HTTP interface onto xenstore (read-only). +# Mainly intended for testing. + +import os +import os.path + +from xen.web.httpserver import HttpServer, UnixHttpServer +from xen.web.SrvBase import SrvBase +from xen.web.SrvDir import SrvDir +from xen.xend.Args import FormFn +from xen.xend.xenstore import XenNode + +def pathurl(req): + url = req.prePathURL() + if not url.endswith('/'): + url += '/' + return url + +def writelist(req, l): + req.write('(') + for k in l: + req.write(' ' + k) + req.write(')') + +def lsData(dbnode, req, url): + v = dbnode.getData() + if v is None: + req.write('<p>No data') + else: + req.write('<p>Data: <pre>') + req.write(str(v)) + req.write('</pre>') + v = dbnode.getLock() + if v is None: + req.write("<p>Unlocked") + else: + req.write("<p>Lock = %s" % v) + +def lsChildren(dbnode, req, url): + l = dbnode.ls() + if l: + req.write('<p>Children: <ul>') + for key in l: + child = dbnode.getChild(key) + data = child.getData() + if data is None: data = "" + req.write('<li><a href="%(url)s%(key)s">%(key)s</a> %(data)s</li>' + % { "url": url, "key": key, "data": data }) + req.write('</ul>') + else: + req.write('<p>No children') + + +class DBDataResource(SrvBase): + """Resource for the node data. + """ + + def __init__(self, dbnode): + SrvBase.__init__(self) + self.dbnode = dbnode + + def render_GET(self, req): + req.write('<html><head></head><body>') + self.print_path(req) + req.write("<pre>") + req.write(self.getData() or self.getNoData()) + req.write("</pre>") + req.write('</body></html>') + + def getContentType(self): + # Use content-type from metadata. + return "text/plain" + + def getData(self): + v = self.dbnode.getData() + if v is None: return v + return str(v) + + def getNoData(self): + return "" + +class DBNodeResource(SrvDir): + """Resource for a DB node. + """ + + def __init__(self, dbnode): + SrvDir.__init__(self) + self.dbnode = dbnode + + def get(self, x): + val = None + if x == "__data__": + val = DBDataResource(self.dbnode) + else: + if self.dbnode.exists(x): + child = self.dbnode.getChild(x, create=False) + else: + child = None + if child is not None: + val = DBNodeResource(child) + return val + + def render_POST(self, req): + return self.perform(req) + + def ls(self, req, use_sxp=0): + if use_sxp: + writelist(req, self.dbnode.getChildren()) + else: + url = pathurl(req) + req.write("<fieldset>") + lsData(self.dbnode, req, url) + lsChildren(self.dbnode, req, url) + req.write("</fieldset>") + + def form(self, req): + url = req.prePathURL() + pass + +class DBRootResource(DBNodeResource): + """Resource for the root of a DB. + """ + + def __init__(self): + DBNodeResource.__init__(self, XenNode()) + +def main(argv): + root = SrvDir() + root.putChild('xenstore', DBRootResource()) + interface = '' + port = 8003 + server = HttpServer(root=root, interface=interface, port=port) + server.run() |