diff options
author | kaf24@scramble.cl.cam.ac.uk <kaf24@scramble.cl.cam.ac.uk> | 2004-06-11 18:31:12 +0000 |
---|---|---|
committer | kaf24@scramble.cl.cam.ac.uk <kaf24@scramble.cl.cam.ac.uk> | 2004-06-11 18:31:12 +0000 |
commit | 69db76b274eb93f02c066e5451542b06e568d1dc (patch) | |
tree | 1c4d2d9fa676c7f5a0defd5874c840d7bf72cc81 | |
parent | 46dcd03da3242c5a0409b88f0b623ffaec127272 (diff) | |
parent | 8b980d633cd157fdea71de4e8db397181fa4b678 (diff) | |
download | xen-69db76b274eb93f02c066e5451542b06e568d1dc.tar.gz xen-69db76b274eb93f02c066e5451542b06e568d1dc.tar.bz2 xen-69db76b274eb93f02c066e5451542b06e568d1dc.zip |
bitkeeper revision 1.955.1.2 (40c9fa70tPu0dbYE7l64lI-VfVdUYQ)
Merge scramble.cl.cam.ac.uk:/auto/groups/xeno/BK/xeno.bk
into scramble.cl.cam.ac.uk:/local/scratch/kaf24/var/bk/xeno-unstable.bk
62 files changed, 9289 insertions, 143 deletions
@@ -102,6 +102,7 @@ 3e5a4e66mrtlmV75L1tjKDg8RaM5gA linux-2.4.26-xen-sparse/drivers/block/ll_rw_blk.c 3f108aeaLcGDgQdFAANLTUEid0a05w linux-2.4.26-xen-sparse/drivers/char/mem.c 3e5a4e66rw65CxyolW9PKz4GG42RcA linux-2.4.26-xen-sparse/drivers/char/tty_io.c +40c9c0c1pPwYE3-4i-oI3ubUu7UgvQ linux-2.4.26-xen-sparse/drivers/scsi/aic7xxx/Makefile 3e5a4e669uzIE54VwucPYtGwXLAbzA linux-2.4.26-xen-sparse/fs/exec.c 3e5a4e66wbeCpsJgVf_U8Jde-CNcsA linux-2.4.26-xen-sparse/include/asm-xen/bugs.h 4048c0ddxnIa2GpBAVR-mY6mNSdeJg linux-2.4.26-xen-sparse/include/asm-xen/ctrl_if.h @@ -163,6 +164,9 @@ 401d7e16RJj-lbtsVEjua6HYAIiKiA tools/examples/xc_dom_create.py 403b7cf7J7FsSSoEPGhx6gXR4pIdZg tools/examples/xc_physinfo.py 401d7e16X4iojyKopo_j63AyzYZd2A tools/examples/xc_vd_tool.py +40c9c4684Wfg8VgMKtRFa_ULi2e_tQ tools/examples/xm_dom_control.py +40c9c468pXANclL7slGaoD0kSrIwoQ tools/examples/xm_dom_create.py +40c9c468QKoqBHjb5Qwrm60pNVcVng tools/examples/xm_vd_tool.py 3f776bd2Xd-dUcPKlPN2vG89VGtfvQ tools/misc/Makefile 40ab2cfawIw8tsYo0dQKtp83h4qfTQ tools/misc/fakei386xen 3f6dc136ZKOjd8PIqLbFBl_v-rnkGg tools/misc/miniterm/Makefile @@ -205,8 +209,10 @@ 4055ee41IfFazrwadCH2J72nz-A9YA tools/xenctl/Makefile 4055ee4b_4Rvns_KzE12csI14EKK6Q tools/xenctl/lib/__init__.py 4055ee4dwy4l0MghZosxoiu6zmhc9Q tools/xenctl/lib/console_client.py +40c9c468IienauFHQ_xJIcqnPJ8giQ tools/xenctl/lib/ip.py 4059c6a0pnxhG8hwSOivXybbGOwuXw tools/xenctl/lib/tempfile.py 3fbd4bd6GtGwZGxYUJPOheYIR7bPaA tools/xenctl/lib/utils.py +40c9c468F36e06WH38kpYrD3JfXC0Q tools/xenctl/lib/vdisk.py 4055ee44Bu6oP7U0WxxXypbUt4dNPQ tools/xenctl/setup.py 40431ac64Hj4ixUnKmlugZKhXPFE_Q tools/xend/Makefile 4055ad95Se-FqttgxollqOAAHB94zA tools/xend/lib/__init__.py @@ -219,6 +225,53 @@ 40431ac8wrUEj-XM7B8smFtx_HA7lQ tools/xend/lib/utils.c 4054a2fdkdATEnRw-U7AUlgu-6JiUA tools/xend/setup.py 4056cd26Qyp09iNoOjrvzg8KYzSqOw tools/xend/xend +40c9c468icGyC5RAF1bRKsCXPDCvsA tools/xenmgr/Makefile +40c9c468SNuObE_YWARyS0hzTPSzKg tools/xenmgr/lib/Args.py +40c9c468Um_qc66OQeLEceIz1pgD5g tools/xenmgr/lib/EventServer.py +40c9c468U8EVl0d3G--8YXVg6VJD3g tools/xenmgr/lib/EventTypes.py +40c9c468QJTEuk9g4qHxGpmIi70PEQ tools/xenmgr/lib/PrettyPrint.py +40c9c4688m3eqnC8fhLu1APm36VOVA tools/xenmgr/lib/XendClient.py +40c9c468t6iIKTjwuYoe-UMCikDcOQ tools/xenmgr/lib/XendConsole.py +40c9c468WnXs6eOUSff23IIGI4kMfQ tools/xenmgr/lib/XendDB.py +40c9c468fSl3H3IypyT0ppkbb0ZT9A tools/xenmgr/lib/XendDomain.py +40c9c468bbKq3uC7_fuNUkiMMjArdw tools/xenmgr/lib/XendDomainConfig.py +40c9c4685ykq87_n1kVUbMr9flx9fg tools/xenmgr/lib/XendDomainInfo.py +40c9c46854nsHmuxHQHncKk5rAs5NA tools/xenmgr/lib/XendMigrate.py +40c9c468M96gA1EYDvNa5w5kQNYLFA tools/xenmgr/lib/XendNode.py +40c9c4686jruMyZIqiaZRMiMoqMJtg tools/xenmgr/lib/XendRoot.py +40c9c468P75aHqyIE156JXwc-5W92A tools/xenmgr/lib/XendVdisk.py +40c9c468xzANp6o2D_MeCYwNmOIUsQ tools/xenmgr/lib/XendVnet.py +40c9c468x191zetrVlMnExfsQWHxIQ tools/xenmgr/lib/__init__.py +40c9c468S2YnCEKmk4ey8XQIST7INg tools/xenmgr/lib/encode.py +40c9c468DCpMe542varOolW1Xc68ew tools/xenmgr/lib/server/SrvBase.py +40c9c468IxQabrKJSWs0aEjl-27mRQ tools/xenmgr/lib/server/SrvConsole.py +40c9c4689Io5bxfbYIfRiUvsiLX0EQ tools/xenmgr/lib/server/SrvConsoleDir.py +40c9c468woSmBByfeXA4o_jGf2gCgA tools/xenmgr/lib/server/SrvConsoleServer.py +40c9c468kACsmkqjxBWKHRo071L26w tools/xenmgr/lib/server/SrvDeviceDir.py +40c9c468EQZJVkCLds-OhesJVVyZbQ tools/xenmgr/lib/server/SrvDir.py +40c9c468TyHZUq8sk0FF_vxM6Sozrg tools/xenmgr/lib/server/SrvDomain.py +40c9c469WzajDjutou3X7FmL9hMf3g tools/xenmgr/lib/server/SrvDomainDir.py +40c9c469-8mYEJJTAR6w_ClrJRAfwQ tools/xenmgr/lib/server/SrvEventDir.py +40c9c4694eu5759Dehr4Uhakei0EMg tools/xenmgr/lib/server/SrvNode.py +40c9c469TaZ83ypsrktmPSHLEZiP5w tools/xenmgr/lib/server/SrvRoot.py +40c9c469W3sgDMbBJYQdz5wbQweL0Q tools/xenmgr/lib/server/SrvServer.py +40c9c469JlUVPkwGWZyVnqIsU8U6Bw tools/xenmgr/lib/server/SrvVdisk.py +40c9c469sG8iuyxjVH3zKw8XOAyxtQ tools/xenmgr/lib/server/SrvVdiskDir.py +40c9c469aq7oXrE1Ngqf3_lBqL0RoQ tools/xenmgr/lib/server/SrvVnetDir.py +40c9c469Y_aimoOFfUZoS-4eV8gEKg tools/xenmgr/lib/server/__init__.py +40c9c4692hckPol_EK0EGB16ZyDsyQ tools/xenmgr/lib/server/blkif.py +40c9c469N2-b3GqpLHHHPZykJPLVvA tools/xenmgr/lib/server/channel.py +40c9c469hJ_IlatRne-9QEa0-wlquw tools/xenmgr/lib/server/console.py +40c9c469UcNJh_NuLU0ytorM0Lk5Ow tools/xenmgr/lib/server/controller.py +40c9c469vHh-qLiiubdbKEQbJf18Zw tools/xenmgr/lib/server/cstruct.py +40c9c469yrm31i60pGKslTi2Zgpotg tools/xenmgr/lib/server/messages.py +40c9c46925x-Rjb0Cv2f1-l2jZrPYg tools/xenmgr/lib/server/netif.py +40c9c469ZqILEQ8x6yWy0_51jopiCg tools/xenmgr/lib/server/params.py +40c9c469LNxLVizOUpOjEaTKKCm8Aw tools/xenmgr/lib/sxp.py +40c9c469kT0H9COWzA4XzPBjWK0WsA tools/xenmgr/netfix +40c9c469n2RRwCmjWdjdyyVRWKmgWg tools/xenmgr/setup.py +40c9c4697z76HDfkCLdMhmaEwzFoNQ tools/xenmgr/xend +40c9c469JkN47d1oXi-e0RjAP-C6uQ tools/xenmgr/xenmgrd 403a3edbrr8RE34gkbR40zep98SXbg tools/xentrace/Makefile 40a107afN60pFdURgBv9KwEzgRl5mQ tools/xentrace/formats 4050c413PhhLNAYk3TEwP37i_iLw9Q tools/xentrace/xentrace.8 diff --git a/BitKeeper/etc/ignore b/BitKeeper/etc/ignore index 0d5cdf1d6b..f9801219a3 100644 --- a/BitKeeper/etc/ignore +++ b/BitKeeper/etc/ignore @@ -25,3 +25,132 @@ xen/include/xen/compile.h xen/tools/elf-reloc xen/tools/figlet/figlet TAGS +linux-2.4.26-xen-sparse/drivers/scsi/aic7xxx/Makefile~ +linux-2.4.26-xen/.config.old +linux-2.4.26-xen/.depend +linux-2.4.26-xen/.hdepend +linux-2.4.26-xen/.version +linux-2.4.26-xen/.config +linux-2.4.26-xen/COPYING +linux-2.4.26-xen/CREDITS +linux-2.4.26-xen/Documentation/00-INDEX +linux-2.4.26-xen/Documentation/BK-usage/00-INDEX +linux-2.4.26-xen/Documentation/BK-usage/bk-kernel-howto.txt +linux-2.4.26-xen/Documentation/BK-usage/bk-make-sum +linux-2.4.26-xen/Documentation/BK-usage/bksend +linux-2.4.26-xen/Documentation/BK-usage/bz64wrap +linux-2.4.26-xen/Documentation/BK-usage/cset-to-linus +linux-2.4.26-xen/Documentation/BK-usage/csets-to-patches +linux-2.4.26-xen/Documentation/BK-usage/unbz64wrap +linux-2.4.26-xen/Documentation/BUG-HUNTING +linux-2.4.26-xen/Documentation/Changes +linux-2.4.26-xen/Documentation/CodingStyle +linux-2.4.26-xen/Documentation/Configure.help +linux-2.4.26-xen/Documentation/DMA-mapping.txt +linux-2.4.26-xen/Documentation/DocBook/Makefile +linux-2.4.26-xen/Documentation/DocBook/deviceiobook.tmpl +linux-2.4.26-xen/Documentation/DocBook/journal-api.tmpl +linux-2.4.26-xen/Documentation/DocBook/kernel-api.tmpl +linux-2.4.26-xen/Documentation/DocBook/kernel-hacking.tmpl +linux-2.4.26-xen/Documentation/DocBook/kernel-locking.tmpl +linux-2.4.26-xen/Documentation/DocBook/mcabook.tmpl +linux-2.4.26-xen/Documentation/DocBook/mousedrivers.tmpl +linux-2.4.26-xen/Documentation/DocBook/parport-multi.fig +linux-2.4.26-xen/Documentation/DocBook/parport-share.fig +linux-2.4.26-xen/Documentation/DocBook/parport-structure.fig +linux-2.4.26-xen/Documentation/DocBook/parportbook.tmpl +linux-2.4.26-xen/Documentation/DocBook/procfs-guide.tmpl +linux-2.4.26-xen/Documentation/DocBook/procfs_example.c +linux-2.4.26-xen/Documentation/DocBook/sis900.tmpl +linux-2.4.26-xen/Documentation/DocBook/tulip-user.tmpl +linux-2.4.26-xen/Documentation/DocBook/via-audio.tmpl +linux-2.4.26-xen/Documentation/DocBook/videobook.tmpl +linux-2.4.26-xen/Documentation/DocBook/wanbook.tmpl +linux-2.4.26-xen/Documentation/DocBook/z8530book.tmpl +linux-2.4.26-xen/Documentation/IO-mapping.txt +linux-2.4.26-xen/Documentation/IPMI.txt +linux-2.4.26-xen/Documentation/IRQ-affinity.txt +linux-2.4.26-xen/Documentation/LVM-HOWTO +linux-2.4.26-xen/Documentation/README.DAC960 +linux-2.4.26-xen/Documentation/README.moxa +linux-2.4.26-xen/Documentation/README.nsp32_cb.eng +linux-2.4.26-xen/Documentation/README.nsp_cs.eng +linux-2.4.26-xen/Documentation/SAK.txt +linux-2.4.26-xen/Documentation/SubmittingDrivers +linux-2.4.26-xen/Documentation/SubmittingPatches +linux-2.4.26-xen/Documentation/VGA-softcursor.txt +linux-2.4.26-xen/Documentation/arm/Booting +linux-2.4.26-xen/Documentation/arm/ConfigVars +linux-2.4.26-xen/Documentation/arm/MEMC +linux-2.4.26-xen/Documentation/arm/Netwinder +linux-2.4.26-xen/Documentation/arm/README +linux-2.4.26-xen/Documentation/arm/SA1100/ADSBitsy +linux-2.4.26-xen/Documentation/arm/SA1100/Assabet +linux-2.4.26-xen/Documentation/arm/SA1100/Brutus +linux-2.4.26-xen/Documentation/arm/SA1100/CERF +linux-2.4.26-xen/Documentation/arm/SA1100/DMA +linux-2.4.26-xen/Documentation/arm/SA1100/FreeBird +linux-2.4.26-xen/Documentation/arm/SA1100/GraphicsClient +linux-2.4.26-xen/Documentation/arm/SA1100/GraphicsMaster +linux-2.4.26-xen/Documentation/arm/SA1100/HUW_WEBPANEL +linux-2.4.26-xen/Documentation/arm/SA1100/Itsy +linux-2.4.26-xen/Documentation/arm/SA1100/LART +linux-2.4.26-xen/Documentation/arm/SA1100/PCMCIA +linux-2.4.26-xen/Documentation/arm/SA1100/PLEB +linux-2.4.26-xen/Documentation/arm/SA1100/Pangolin +linux-2.4.26-xen/Documentation/arm/SA1100/SA1100_USB +linux-2.4.26-xen/Documentation/arm/SA1100/Tifon +linux-2.4.26-xen/Documentation/arm/SA1100/Victor +linux-2.4.26-xen/Documentation/arm/SA1100/Yopy +linux-2.4.26-xen/Documentation/arm/SA1100/empeg +linux-2.4.26-xen/Documentation/arm/SA1100/nanoEngine +linux-2.4.26-xen/Documentation/arm/SA1100/serial_UART +linux-2.4.26-xen/Documentation/arm/Setup +linux-2.4.26-xen/Documentation/arm/empeg/README +linux-2.4.26-xen/Documentation/arm/empeg/ir.txt +linux-2.4.26-xen/Documentation/arm/empeg/mkdevs +linux-2.4.26-xen/Documentation/arm/nwfpe/NOTES +linux-2.4.26-xen/Documentation/arm/nwfpe/README +linux-2.4.26-xen/Documentation/arm/nwfpe/README.FPE +linux-2.4.26-xen/Documentation/arm/nwfpe/TODO +linux-2.4.26-xen/Documentation/binfmt_misc.txt +linux-2.4.26-xen/Documentation/cachetlb.txt +linux-2.4.26-xen/Documentation/cciss.txt +linux-2.4.26-xen/Documentation/cdrom/00-INDEX +linux-2.4.26-xen/Documentation/cdrom/Makefile +linux-2.4.26-xen/Documentation/cdrom/aztcd +linux-2.4.26-xen/Documentation/cdrom/cdrom-standard.tex +linux-2.4.26-xen/Documentation/cdrom/cdu31a +linux-2.4.26-xen/Documentation/cdrom/cm206 +linux-2.4.26-xen/Documentation/cdrom/gscd +linux-2.4.26-xen/Documentation/cdrom/ide-cd +linux-2.4.26-xen/Documentation/cdrom/isp16 +linux-2.4.26-xen/Documentation/cdrom/mcd +linux-2.4.26-xen/Documentation/cdrom/mcdx +linux-2.4.26-xen/Documentation/cdrom/optcd +linux-2.4.26-xen/Documentation/cdrom/sbpcd +linux-2.4.26-xen/Documentation/cdrom/sjcd +linux-2.4.26-xen/Documentation/cdrom/sonycd535 +linux-2.4.26-xen/Documentation/computone.txt +linux-2.4.26-xen/Documentation/cpqarray.txt +linux-2.4.26-xen/Documentation/cris/README +linux-2.4.26-xen/Documentation/crypto/api-intro.txt +linux-2.4.26-xen/Documentation/crypto/descore-readme.txt +linux-2.4.26-xen/Documentation/devices.txt +linux-2.4.26-xen/Documentation/digiboard.txt +linux-2.4.26-xen/Documentation/digiepca.txt +linux-2.4.26-xen/Documentation/dnotify.txt +linux-2.4.26-xen/Documentation/exception.txt +linux-2.4.26-xen/Documentation/fb/00-INDEX +linux-2.4.26-xen/Documentation/fb/README-sstfb.txt +linux-2.4.26-xen/Documentation/fb/aty128fb.txt +linux-2.4.26-xen/Documentation/fb/clgenfb.txt +linux-2.4.26-xen/Documentation/fb/framebuffer.txt +linux-2.4.26-xen/Documentation/fb/internals.txt +linux-2.4.26-xen/Documentation/fb/matroxfb.txt +linux-2.4.26-xen/Documentation/fb/modedb.txt +linux-2.4.26-xen/Documentation/fb/pvr2fb.txt +linux-2.4.26-xen/Documentation/fb/sa1100fb.txt +tools/xenmgr/lib/server/blkif.py~ +tools/xenmgr/lib/server/messages.py~ +tools/xenmgr/lib/server/netif.py~ diff --git a/BitKeeper/etc/logging_ok b/BitKeeper/etc/logging_ok index 30f55a98cc..8674fa612d 100644 --- a/BitKeeper/etc/logging_ok +++ b/BitKeeper/etc/logging_ok @@ -23,6 +23,7 @@ kaf24@striker.cl.cam.ac.uk laudney@eclipse.(none) lynx@idefix.cl.cam.ac.uk maw48@labyrinth.cl.cam.ac.uk +mjw@wray-m-3.hpl.hp.com mwilli2@equilibrium.research.intel-research.net rac61@labyrinth.cl.cam.ac.uk rgr22@boulderdash.cl.cam.ac.uk diff --git a/linux-2.4.26-xen-sparse/arch/xen/drivers/netif/backend/Makefile b/linux-2.4.26-xen-sparse/arch/xen/drivers/netif/backend/Makefile index 9ffb0bd702..ba04eb449e 100644 --- a/linux-2.4.26-xen-sparse/arch/xen/drivers/netif/backend/Makefile +++ b/linux-2.4.26-xen-sparse/arch/xen/drivers/netif/backend/Makefile @@ -1,3 +1,4 @@ O_TARGET := drv.o +export-objs := interface.o obj-y := main.o control.o interface.o include $(TOPDIR)/Rules.make diff --git a/linux-2.4.26-xen-sparse/arch/xen/drivers/netif/backend/interface.c b/linux-2.4.26-xen-sparse/arch/xen/drivers/netif/backend/interface.c index 5a2da3d29b..09b4a1f0f0 100644 --- a/linux-2.4.26-xen-sparse/arch/xen/drivers/netif/backend/interface.c +++ b/linux-2.4.26-xen-sparse/arch/xen/drivers/netif/backend/interface.c @@ -71,13 +71,14 @@ void __netif_disconnect_complete(netif_t *netif) void netif_create(netif_be_create_t *create) { + int err = 0; domid_t domid = create->domid; unsigned int handle = create->netif_handle; struct net_device *dev; netif_t **pnetif, *netif; - char name[IFNAMSIZ]; + char name[IFNAMSIZ] = {}; - snprintf(name, IFNAMSIZ, "vif%u.%u", domid, handle); + snprintf(name, IFNAMSIZ - 1, "vif%u.%u", domid, handle); dev = alloc_netdev(sizeof(netif_t), name, ether_setup); if ( dev == NULL ) { @@ -123,9 +124,9 @@ void netif_create(netif_be_create_t *create) /* XXX In bridge mode we should force a different MAC from remote end. */ dev->dev_addr[2] ^= 1; - if ( register_netdev(dev) != 0 ) - { - DPRINTK("Could not register new net device\n"); + err = register_netdev(dev); + if (err) { + DPRINTK("Could not register new net device %s: err=%d\n", dev->name, err); create->status = NETIF_BE_STATUS_OUT_OF_MEMORY; kfree(dev); return; @@ -302,3 +303,13 @@ void netif_interface_init(void) bridge_br->bridge_forward_delay = bridge_br->forward_delay = 0; bridge_br->stp_enabled = 0; } + +#ifndef CONFIG_BRIDGE +#error Must configure Ethernet bridging in Network Options +#endif +EXPORT_SYMBOL(br_add_bridge); +EXPORT_SYMBOL(br_del_bridge); +EXPORT_SYMBOL(br_add_if); +EXPORT_SYMBOL(br_del_if); +EXPORT_SYMBOL(br_get_bridge_ifindices); +EXPORT_SYMBOL(br_get_port_ifindices); diff --git a/linux-2.4.26-xen-sparse/arch/xen/drivers/netif/frontend/main.c b/linux-2.4.26-xen-sparse/arch/xen/drivers/netif/frontend/main.c index 821ad1b0ed..3481788bf7 100644 --- a/linux-2.4.26-xen-sparse/arch/xen/drivers/netif/frontend/main.c +++ b/linux-2.4.26-xen-sparse/arch/xen/drivers/netif/frontend/main.c @@ -99,6 +99,72 @@ static struct net_device *find_dev_by_handle(unsigned int handle) return NULL; } +#define MULTIVIF +//#ifdef MULTIVIF + +/** Network interface info. */ +struct netif_ctrl { + /** Number of interfaces. */ + int interface_n; + /** Number of connected interfaces. */ + int connected_n; + /** Error code. */ + int err; +}; + +static struct netif_ctrl netctrl = {}; + +static void netctrl_init(void){ + netctrl = (struct netif_ctrl){}; + netctrl.interface_n = -1; +} + +/** Get or set a network interface error. + */ +static int netctrl_err(int err) +{ + if(err < 0 && !netctrl.err){ + netctrl.err = err; + printk(KERN_WARNING "%s> err=%d\n", __FUNCTION__, err); + } + return netctrl.err; +} + +/** Test if all network interfaces are connected. + * + * @return 1 if all connected, 0 if not, negative error code otherwise + */ +static int netctrl_connected(void) +{ + int ok = 0; + ok = (netctrl.err ? netctrl.err : (netctrl.connected_n == netctrl.interface_n)); + return ok; +} + +/** Count the connected network interfaces. + * + * @return connected count + */ +static int netctrl_connected_count(void) +{ + + struct list_head *ent; + struct net_private *np; + unsigned int connected; + + connected = 0; + + list_for_each(ent, &dev_list) { + np = list_entry(ent, struct net_private, list); + if ( np->state == NETIF_STATE_CONNECTED){ + connected++; + } + } + netctrl.connected_n = connected; + return connected; +} + +//#endif static int network_open(struct net_device *dev) { @@ -488,21 +554,100 @@ static struct net_device_stats *network_get_stats(struct net_device *dev) } +static void network_reconnect(struct net_device *dev, netif_fe_interface_status_changed_t *status) +{ + struct net_private *np; + int i, requeue_idx; + netif_tx_request_t *tx; + + np = dev->priv; + spin_lock_irq(&np->rx_lock); + spin_lock(&np->tx_lock); + + /* Recovery procedure: */ + + /* Step 1: Reinitialise variables. */ + np->rx_resp_cons = np->tx_resp_cons = np->tx_full = 0; + np->rx->event = 1; + + /* Step 2: Rebuild the RX and TX ring contents. + * NB. We could just free the queued TX packets now but we hope + * that sending them out might do some good. We have to rebuild + * the RX ring because some of our pages are currently flipped out + * so we can't just free the RX skbs. + * NB2. Freelist index entries are always going to be less than + * __PAGE_OFFSET, whereas pointers to skbs will always be equal or + * greater than __PAGE_OFFSET: we use this property to distinguish + * them. + */ + + /* Rebuild the TX buffer freelist and the TX ring itself. + * NB. This reorders packets. We could keep more private state + * to avoid this but maybe it doesn't matter so much given the + * interface has been down. + */ + for( requeue_idx = 0, i = 1; i <= NETIF_TX_RING_SIZE; i++ ){ + if( (unsigned long)np->tx_skbs[i] >= __PAGE_OFFSET ) { + struct sk_buff *skb = np->tx_skbs[i]; + + tx = &np->tx->ring[requeue_idx++].req; + + tx->id = i; + tx->addr = virt_to_machine(skb->data); + tx->size = skb->len; + + np->stats.tx_bytes += skb->len; + np->stats.tx_packets++; + } + } + wmb(); + np->tx->req_prod = requeue_idx; + + /* Rebuild the RX buffer freelist and the RX ring itself. */ + for ( requeue_idx = 0, i = 1; i <= NETIF_RX_RING_SIZE; i++ ) + if ( (unsigned long)np->rx_skbs[i] >= __PAGE_OFFSET ) + np->rx->ring[requeue_idx++].req.id = i; + wmb(); + np->rx->req_prod = requeue_idx; + + /* Step 3: All public and private state should now be sane. Get + * ready to start sending and receiving packets and give the driver + * domain a kick because we've probably just requeued some + * packets. + */ + netif_carrier_on(dev); + netif_start_queue(dev); + np->state = NETIF_STATE_ACTIVE; + + notify_via_evtchn(status->evtchn); + + network_tx_buf_gc(dev); + + printk(KERN_INFO "Recovery completed\n"); + + spin_unlock(&np->tx_lock); + spin_unlock_irq(&np->rx_lock); +} + static void netif_status_change(netif_fe_interface_status_changed_t *status) { ctrl_msg_t cmsg; netif_fe_interface_connect_t up; struct net_device *dev; struct net_private *np; - - if ( status->handle != 0 ) - { - printk(KERN_WARNING "Status change on unsupported netif %d\n", - status->handle); + +//#ifdef MULTIVIF + if(netctrl.interface_n <= 0){ + printk(KERN_WARNING "Status change: no interfaces\n"); return; } - - dev = find_dev_by_handle(0); +//#endif + dev = find_dev_by_handle(status->handle); + if(!dev){ + printk(KERN_WARNING "Status change: invalid netif handle %u\n", + status->handle); + return; + } np = dev->priv; switch ( status->status ) @@ -560,7 +705,7 @@ static void netif_status_change(netif_fe_interface_status_changed_t *status) cmsg.type = CMSG_NETIF_FE; cmsg.subtype = CMSG_NETIF_FE_INTERFACE_CONNECT; cmsg.length = sizeof(netif_fe_interface_connect_t); - up.handle = 0; + up.handle = status->handle; up.tx_shmem_frame = virt_to_machine(np->tx) >> PAGE_SHIFT; up.rx_shmem_frame = virt_to_machine(np->rx) >> PAGE_SHIFT; memcpy(cmsg.msg, &up, sizeof(up)); @@ -570,8 +715,7 @@ static void netif_status_change(netif_fe_interface_status_changed_t *status) break; case NETIF_INTERFACE_STATUS_CONNECTED: - if ( np->state == NETIF_STATE_CLOSED ) - { + if ( np->state == NETIF_STATE_CLOSED ){ printk(KERN_WARNING "Unexpected netif-CONNECTED message" " in state %d\n", np->state); break; @@ -579,87 +723,20 @@ static void netif_status_change(netif_fe_interface_status_changed_t *status) memcpy(dev->dev_addr, status->mac, ETH_ALEN); - if(netif_carrier_ok(dev)) + if(netif_carrier_ok(dev)){ np->state = NETIF_STATE_CONNECTED; - else - { - int i, requeue_idx; - netif_tx_request_t *tx; - - spin_lock_irq(&np->rx_lock); - spin_lock(&np->tx_lock); - - /* Recovery procedure: */ - - /* Step 1: Reinitialise variables. */ - np->rx_resp_cons = np->tx_resp_cons = np->tx_full = 0; - np->rx->event = 1; - - /* Step 2: Rebuild the RX and TX ring contents. - * NB. We could just free the queued TX packets now but we hope - * that sending them out might do some good. We have to rebuild - * the RX ring because some of our pages are currently flipped out - * so we can't just free the RX skbs. - * NB2. Freelist index entries are always going to be less than - * __PAGE_OFFSET, whereas pointers to skbs will always be equal or - * greater than __PAGE_OFFSET: we use this property to distinguish - * them. - */ - - /* Rebuild the TX buffer freelist and the TX ring itself. - * NB. This reorders packets. We could keep more private state - * to avoid this but maybe it doesn't matter so much given the - * interface has been down. - */ - for ( requeue_idx = 0, i = 1; i <= NETIF_TX_RING_SIZE; i++ ) - { - if ( (unsigned long)np->tx_skbs[i] >= __PAGE_OFFSET ) - { - struct sk_buff *skb = np->tx_skbs[i]; - - tx = &np->tx->ring[requeue_idx++].req; - - tx->id = i; - tx->addr = virt_to_machine(skb->data); - tx->size = skb->len; - - np->stats.tx_bytes += skb->len; - np->stats.tx_packets++; - } - } - wmb(); - np->tx->req_prod = requeue_idx; - - /* Rebuild the RX buffer freelist and the RX ring itself. */ - for ( requeue_idx = 0, i = 1; i <= NETIF_RX_RING_SIZE; i++ ) - if ( (unsigned long)np->rx_skbs[i] >= __PAGE_OFFSET ) - np->rx->ring[requeue_idx++].req.id = i; - wmb(); - np->rx->req_prod = requeue_idx; - - /* Step 3: All public and private state should now be sane. Get - * ready to start sending and receiving packets and give the driver - * domain a kick because we've probably just requeued some - * packets. - */ - netif_carrier_on(dev); - netif_start_queue(dev); - np->state = NETIF_STATE_ACTIVE; - - notify_via_evtchn(status->evtchn); - - network_tx_buf_gc(dev); - - printk(KERN_INFO "Recovery completed\n"); - - spin_unlock(&np->tx_lock); - spin_unlock_irq(&np->rx_lock); + } else { + network_reconnect(dev, status); } np->evtchn = status->evtchn; np->irq = bind_evtchn_to_irq(np->evtchn); (void)request_irq(np->irq, netif_int, SA_SAMPLE_RANDOM, dev->name, dev); + +#ifdef MULTIVIF + netctrl_connected_count(); +#endif break; default: @@ -669,27 +746,102 @@ static void netif_status_change(netif_fe_interface_status_changed_t *status) } } +/** Create a network devices. + * + * @param handle device handle + * @param val return parameter for created device + * @return 0 on success, error code otherwise + */ +static int create_netdev(int handle, struct net_device **val){ + int err = 0; + struct net_device *dev = NULL; + struct net_private *np = NULL; + + dev = alloc_etherdev(sizeof(struct net_private)); + if (!dev){ + printk(KERN_WARNING "%s> alloc_etherdev failed.\n", __FUNCTION__); + err = -ENOMEM; + goto exit; + } + np = dev->priv; + np->state = NETIF_STATE_CLOSED; + np->handle = handle; + + dev->open = network_open; + dev->hard_start_xmit = network_start_xmit; + dev->stop = network_close; + dev->get_stats = network_get_stats; + dev->poll = netif_poll; + dev->weight = 64; + + err = register_netdev(dev); + if (err){ + printk(KERN_WARNING "%s> register_netdev err=%d\n", __FUNCTION__, err); + goto exit; + } + np->dev = dev; + list_add(&np->list, &dev_list); + exit: + if(err){ + if(dev) kfree(dev); + dev = NULL; + } + if(val) *val = dev; + return err; +} + +//#ifdef MULTIVIF +/** Initialize the network control interface. Set the number of network devices + * and create them. + */ +static void netif_driver_status_change(netif_fe_driver_status_changed_t *status) +{ + int err = 0; + int i; + + netctrl.interface_n = status->nr_interfaces; + netctrl.connected_n = 0; + + for(i = 0; i < netctrl.interface_n; i++){ + err = create_netdev(i, NULL); + if(err){ + netctrl_err(err); + break; + } + } +} +//#endif + static void netif_ctrlif_rx(ctrl_msg_t *msg, unsigned long id) { + int respond = 1; switch ( msg->subtype ) { case CMSG_NETIF_FE_INTERFACE_STATUS_CHANGED: if ( msg->length != sizeof(netif_fe_interface_status_changed_t) ) - goto parse_error; + goto error; netif_status_change((netif_fe_interface_status_changed_t *) &msg->msg[0]); break; +//#ifdef MULTIVIF + case CMSG_NETIF_FE_DRIVER_STATUS_CHANGED: + if ( msg->length != sizeof(netif_fe_driver_status_changed_t) ) + goto error; + netif_driver_status_change((netif_fe_driver_status_changed_t *) + &msg->msg[0]); + // Message is a response, so do not respond. + respond = 0; + break; +//#endif + error: default: - goto parse_error; + msg->length = 0; + break; + } + if(respond){ + ctrl_if_send_response(msg); } - - ctrl_if_send_response(msg); - return; - - parse_error: - msg->length = 0; - ctrl_if_send_response(msg); } @@ -697,9 +849,11 @@ static int __init init_module(void) { ctrl_msg_t cmsg; netif_fe_driver_status_changed_t st; - int err; - struct net_device *dev; - struct net_private *np; + int err = 0; +#ifdef MULTIVIF + int wait_n = 20; + int wait_i; +#endif if ( (start_info.flags & SIF_INITDOMAIN) || (start_info.flags & SIF_NET_BE_DOMAIN) ) @@ -708,32 +862,9 @@ static int __init init_module(void) printk("Initialising Xen virtual ethernet frontend driver"); INIT_LIST_HEAD(&dev_list); - - if ( (dev = alloc_etherdev(sizeof(struct net_private))) == NULL ) - { - err = -ENOMEM; - goto fail; - } - - np = dev->priv; - np->state = NETIF_STATE_CLOSED; - np->handle = 0; - - dev->open = network_open; - dev->hard_start_xmit = network_start_xmit; - dev->stop = network_close; - dev->get_stats = network_get_stats; - dev->poll = netif_poll; - dev->weight = 64; - - if ( (err = register_netdev(dev)) != 0 ) - { - kfree(dev); - goto fail; - } - - np->dev = dev; - list_add(&np->list, &dev_list); +#ifdef MULTIVIF + netctrl_init(); +#endif (void)ctrl_if_register_receiver(CMSG_NETIF_FE, netif_ctrlif_rx, CALLBACK_IN_BLOCKING_CONTEXT); @@ -743,25 +874,30 @@ static int __init init_module(void) cmsg.subtype = CMSG_NETIF_FE_DRIVER_STATUS_CHANGED; cmsg.length = sizeof(netif_fe_driver_status_changed_t); st.status = NETIF_DRIVER_STATUS_UP; + st.nr_interfaces = 0; memcpy(cmsg.msg, &st, sizeof(st)); - ctrl_if_send_message_block(&cmsg, NULL, 0, TASK_UNINTERRUPTIBLE); - - /* - * We should read 'nr_interfaces' from response message and wait - * for notifications before proceeding. For now we assume that we - * will be notified of exactly one interface. - */ - while ( np->state != NETIF_STATE_CONNECTED ) - { + +#ifdef MULTIVIF + /* Wait for all interfaces to be connected. */ + for(wait_i = 0; 1; wait_i++) { + if(wait_i < wait_n){ + err = netctrl_connected(); + } else { + err = -ENETDOWN; + } + if(err < 0) goto exit; + if(err > 0){ + err = 0; + break; + } set_current_state(TASK_INTERRUPTIBLE); schedule_timeout(1); - } - - return 0; + } +#endif - fail: - cleanup_module(); + exit: + if(err) cleanup_module(); return err; } diff --git a/linux-2.4.26-xen-sparse/drivers/scsi/aic7xxx/Makefile b/linux-2.4.26-xen-sparse/drivers/scsi/aic7xxx/Makefile new file mode 100644 index 0000000000..16ac7f1a6d --- /dev/null +++ b/linux-2.4.26-xen-sparse/drivers/scsi/aic7xxx/Makefile @@ -0,0 +1,97 @@ +# +# drivers/scsi/aic7xxx/Makefile +# +# Makefile for the Linux aic7xxx SCSI driver. +# + +O_TARGET := aic7xxx_drv.o + +list-multi := aic7xxx.o aic79xx.o + +obj-$(CONFIG_SCSI_AIC7XXX) += aic7xxx.o +ifeq ($(CONFIG_PCI),y) +obj-$(CONFIG_SCSI_AIC79XX) += aic79xx.o +endif + +EXTRA_CFLAGS += -I$(TOPDIR)/drivers/scsi -Werror +#EXTRA_CFLAGS += -g + +# Platform Specific Files +obj-aic7xxx = aic7xxx_osm.o aic7xxx_proc.o + +# Core Files +obj-aic7xxx += aic7xxx_core.o aic7xxx_93cx6.o +ifeq ($(CONFIG_AIC7XXX_REG_PRETTY_PRINT),y) +obj-aic7xxx += aic7xxx_reg_print.o +endif + +#EISA Specific Files +AIC7XXX_EISA_ARCH = $(filter i386 alpha xen,$(ARCH)) +ifneq ($(AIC7XXX_EISA_ARCH),) +obj-aic7xxx += aic7770.o +# Platform Specific EISA Files +obj-aic7xxx += aic7770_osm.o +endif + +#PCI Specific Files +ifeq ($(CONFIG_PCI),y) +obj-aic7xxx += aic7xxx_pci.o +# Platform Specific PCI Files +obj-aic7xxx += aic7xxx_osm_pci.o +endif + +# Platform Specific U320 Files +obj-aic79xx = aic79xx_osm.o aic79xx_proc.o aic79xx_osm_pci.o +# Core Files +obj-aic79xx += aic79xx_core.o aic79xx_pci.o +ifeq ($(CONFIG_AIC79XX_REG_PRETTY_PRINT),y) +obj-aic79xx += aic79xx_reg_print.o +endif + +# Override our module desitnation +MOD_DESTDIR = $(shell cd .. && $(CONFIG_SHELL) $(TOPDIR)/scripts/pathdown.sh) + +include $(TOPDIR)/Rules.make + +aic7xxx_core.o: aic7xxx_seq.h +$(obj-aic7xxx): aic7xxx_reg.h +aic7xxx.o: aic7xxx_seq.h aic7xxx_reg.h $(obj-aic7xxx) + $(LD) $(LD_RFLAG) -r -o $@ $(obj-aic7xxx) + +aic79xx_core.o: aic79xx_seq.h +$(obj-aic79xx): aic79xx_reg.h +aic79xx.o: aic79xx_seq.h aic79xx_reg.h $(obj-aic79xx) + $(LD) $(LD_RFLAG) -r -o $@ $(obj-aic79xx) + +ifeq ($(CONFIG_AIC7XXX_BUILD_FIRMWARE),y) +aic7xxx_gen = aic7xxx_seq.h aic7xxx_reg.h +ifeq ($(CONFIG_AIC7XXX_REG_PRETTY_PRINT),y) +aic7xxx_gen += aic7xxx_reg_print.c +aic7xxx_asm_cmd = aicasm/aicasm -I. -r aic7xxx_reg.h \ + -p aic7xxx_reg_print.c -i aic7xxx_osm.h \ + -o aic7xxx_seq.h aic7xxx.seq +else +aic7xxx_asm_cmd = aicasm/aicasm -I. -r aic7xxx_reg.h \ + -o aic7xxx_seq.h aic7xxx.seq +endif +$(aic7xxx_gen): aic7xxx.seq aic7xxx.reg aicasm/aicasm + $(aic7xxx_asm_cmd) +endif + +ifeq ($(CONFIG_AIC79XX_BUILD_FIRMWARE),y) +aic79xx_gen = aic79xx_seq.h aic79xx_reg.h +ifeq ($(CONFIG_AIC79XX_REG_PRETTY_PRINT),y) +aic79xx_gen += aic79xx_reg_print.c +aic79xx_asm_cmd = aicasm/aicasm -I. -r aic79xx_reg.h \ + -p aic79xx_reg_print.c -i aic79xx_osm.h \ + -o aic79xx_seq.h aic79xx.seq +else +aic79xx_asm_cmd = aicasm/aicasm -I. -r aic79xx_reg.h \ + -o aic79xx_seq.h aic79xx.seq +endif +$(aic79xx_gen): aic79xx.seq aic79xx.reg aicasm/aicasm + $(aic79xx_asm_cmd) +endif + +aicasm/aicasm: aicasm/*.[chyl] + $(MAKE) -C aicasm diff --git a/tools/Makefile b/tools/Makefile index 9ddf5f25a2..37e373c789 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -7,6 +7,7 @@ all: $(MAKE) -C xentrace $(MAKE) -C xenctl $(MAKE) -C xend + $(MAKE) -C xenmgr install: all $(MAKE) -C balloon install @@ -16,6 +17,7 @@ install: all $(MAKE) -C xentrace install $(MAKE) -C xenctl install $(MAKE) -C xend install + $(MAKE) -C xenmgr install dist: $(TARGET) $(MAKE) prefix=`pwd`/../../install dist=yes install @@ -30,4 +32,5 @@ clean: $(MAKE) -C xentrace clean $(MAKE) -C xenctl clean $(MAKE) -C xend clean + $(MAKE) -C xenmgr install diff --git a/tools/examples/xm_dom_control.py b/tools/examples/xm_dom_control.py new file mode 100755 index 0000000000..da30244652 --- /dev/null +++ b/tools/examples/xm_dom_control.py @@ -0,0 +1,233 @@ +#!/usr/bin/env python + +import sys +import re +import string +import time +import os +import os.path +import signal + +from xenmgr.XendClient import server + +# usage: xc_dom_control [command] <params> +# +# this script isn't very smart, but it'll do for now. +# + +def usage (rc=0): + if rc: + out = sys.stderr + else: + out = sys.stdout + print >> out, """ +Usage: %s [command] <params> + + help -- print usage + stop [dom] -- pause a domain + start [dom] -- un-pause a domain + shutdown [dom] [[-w]] -- request a domain to shutdown (can specify 'all') + (optionally wait for complete shutdown) + destroy [dom] -- immediately terminate a domain + pincpu [dom] [cpu] -- pin a domain to the specified CPU + suspend [dom] [file] -- write domain's memory to a file and terminate + (resume by re-running xc_dom_create with -L option) + unwatch [dom] -- kill the auto-restart daemon for a domain + list -- print info about all domains + listvbds -- print info about all virtual block devs + cpu_bvtset [dom] [mcuadv] [warp] [warpl] [warpu] + -- set BVT scheduling parameters for domain + cpu_bvtslice [slice] -- set default BVT scheduler slice + cpu_atropos_set [dom] [period] [slice] [latency] [xtratime] + -- set Atropos scheduling parameters for domain + cpu_rrobin_slice [slice] -- set Round Robin scheduler slice + vif_stats [dom] [vif] -- get stats for a given network vif + vif_addip [dom] [vif] [ip] -- add an IP address to a given vif + vif_setsched [dom] [vif] [bytes] [usecs] -- rate limit vif bandwidth + vif_getsched [dom] [vif] -- print vif's scheduling parameters + vbd_add [dom] [uname] [dev] [mode] -- make disk/partition uname available to + domain as dev e.g. 'vbd_add 2 phy:sda3 hda1 w' + vbd_remove [dom] [dev] -- remove disk or partition attached as 'dev' +""" % sys.argv[0] + if rc: sys.exit(rc) + +if len(sys.argv) < 2: usage(1) +cmd = sys.argv[1] + +#todo: replace all uses of xc with the new api. +import Xc; xc = Xc.new() + +rc = '' +dom = None + + +def auto_restart_pid_file(dom): + # The auto-restart daemon's pid file. + return '/var/run/xendomains/%d.pid' % dom + +def auto_restart_pid(dom): + watcher = auto_restart_pid_file(dom) + if os.path.isfile(watcher): + fd = open(watcher,'r') + pid = int(fd.readline()) + else: + pid = None + return pid + +def auto_restart_kill(dom): + #todo: replace this - tell xend not to restart any more. + # Kill a domain's auto restart daemon. + pid = auto_restart_pid(dom) + if pid: + os.kill(pid, signal.SIGTERM) + + +if len( sys.argv ) > 2 and re.match('\d+$', sys.argv[2]): + dom = long(sys.argv[2]) + +if cmd == "help": + usage() + +elif cmd == 'stop': + rc = server.xend_domain_stop(dom) + +elif cmd == 'start': + rc = server.xend_domain_start(dom) + +elif cmd == 'shutdown': + doms = [] + shutdown = [] + if dom != None: + doms = [ dom ] + elif sys.argv[2] == 'all': + doms = server.xend_domains() + doms.remove('0') + for d in doms: + ret = server.xend_domain_shutdown(d) + if ret == 0: + shutdown.append(d) + else: + rc = ret + + wait = (len(sys.argv) == 4 and sys.argv[3] == "-w") + if wait: + # wait for all domains we shut down to terminate + for dom in shutdown: + while True: + info = server.xend_domain(dom) + if not info: break + time.sleep(1) + +elif cmd == 'destroy': + rc = server.xend_domain_halt(dom) + +elif cmd == 'pincpu': + if len(sys.argv) < 4: usage(1) + cpu = int(sys.argv[3]) + rc = server.xend_domain_pincpu(dom, cpu) + +elif cmd == 'list': + print 'Dom Name Mem(MB) CPU State Time(s)' + for dom in server.xend_domains(): + info = server.xend_domain(dom) + d = {} + d['dom'] = dom + d['name'] = sxp.get_child_value(info, 'name', '??') + d['mem'] = int(sxp.get_child_value(info, 'memory', '0')) + d['cpu'] = int(sxp.get_child_value(info, 'cpu', '0')) + d['state'] = sxp.get_child_value(info, 'state', '??') + d['cpu_time'] = sxp.get_child_value(info, 'cpu_time', '0') + print ("%(dom)-4d %(name)-16s %(mem)7d %(cpu)3d %(state)5s %(cpu_time)8d" + % d) + +elif cmd == 'unwatch': + auto_restart_kill(dom) + +elif cmd == 'listvbds': + print 'Dom Dev Mode Size(MB)' + for dom in server.xend_domains(): + for vbd in server.xend_domain_vbds(dom): + info = server.xend_domain_vbd(vbd) + v['vbd'] = vbd + v['size'] = int(sxp.get_child_value(info, 'size', '0')) + v['mode'] = sxp.get_child_value(info, 'mode', '??') + vbd['size_mb'] = vbd['nr_sectors'] / 2048 + print ('%(dom)-4d %(vbd)04x %(mode)-2s %(size)d' % v) + +elif cmd == 'suspend': + if len(sys.argv) < 4: usage(1) + file = os.path.abspath(sys.argv[3]) + auto_restart_kill(dom) + rc = server.xend_domain_save(dom, file, progress=1) + +elif cmd == 'cpu_bvtslice': + if len(sys.argv) < 3: usage(1) + slice = sys.argv[2] + rc = server.xend_node_cpu_bvt_slice_set(slice) + +elif cmd == 'cpu_bvtset': + if len(sys.argv) < 7: usage(1) + (mcuadv, warp, warpl, warpu) = map(int, sys.argv[3:7]) + + rc = server.xend_domain_cpu_bvt_set(dom, mcuadv, warp, warpl, warpu) + +elif cmd == 'vif_stats': + if len(sys.argv) < 4: usage(1) + vif = int(sys.argv[3]) + + print server.xend_domain_vif_stats(dom, vif) + +elif cmd == 'vif_addip': + if len(sys.argv) < 5: usage(1) + vif = int(sys.argv[3]) + ip = sys.argv[4] + rc = server.xend_domain_vif_addip(dom, vif, ip) + +elif cmd == 'vif_setsched': + if len(sys.argv) < 6: usage(1) + (vif, bytes, usecs) = map(int, sys.argv[3:6]) + rc = server.xend_domain_vif_scheduler_set(dom, vif, bytes, usecs) + +elif cmd == 'vif_getsched': + if len(sys.argv) < 4: usage(1) + vif = int(sys.argv[3]) + print server.xend_domain_vif_scheduler_get(dom, vif) + +elif cmd == 'vbd_add': + if len(sys.argv) < 6: usage(1) + uname = sys.argv[3] + dev = sys.argv[4] + mode = sys.argv[5] + try: + vbd = server.xend_domain_vbd_add(dom, uname, dev, mode) + except StandardError, ex: + print "Error:", ex + sys.exit(1) + print "Added disk/partition %s to domain %d as device %s (%x)" % (uname, dom, dev, vbd) + +elif cmd == 'vbd_remove': + if len(sys.argv) < 4: usage(1) + dev = sys.argv[3] + vbd = server.xend_domain_vbd_remove(dom, dev) + if vbd < 0: + print "Failed" + sys.exit(1) + else: + print "Removed disk/partition attached as device %s (%x) in domain %d" % (dev, vbd, dom) + +elif cmd == 'cpu_atropos_set': # args: dom period slice latency xtratime + if len(sys.argv) < 6: usage(1) + (period, slice, latency, xtratime) = map(int, sys.argv[3:7]) + rc = server.xend_domain_cpu_atropos_set( + dom, period, slice, latency, xtratime) + +elif cmd == 'cpu_rrobin_slice': + if len(sys.argv) < 3: usage(1) + slice = int(sys.argv[2]) + rc = server.xend_node_rrobin_set(slice=slice) + +else: + usage(1) + +if rc != '': + print "return code %d" % rc diff --git a/tools/examples/xm_dom_create.py b/tools/examples/xm_dom_create.py new file mode 100755 index 0000000000..ce89426f9d --- /dev/null +++ b/tools/examples/xm_dom_create.py @@ -0,0 +1,402 @@ +#!/usr/bin/env python + +import string +import sys +import os +import os.path +import time +import socket +import getopt +import signal +import syslog + +import xenctl.console_client + +from xenmgr import sxp +from xenmgr import PrettyPrint +from xenmgr.XendClient import server + +config_dir = '/etc/xc/' +config_file = xc_config_file = config_dir + 'defaults' + +def main_usage (): + print >>sys.stderr,""" +Usage: %s <args> + +This tool is used to create and start new domains. It reads defaults +from a file written in Python, having allowed variables to be set and +passed into the file. Further command line arguments allow the +defaults to be overridden. The defaults for each parameter are listed +in [] brackets. Arguments are as follows: + +Arguments to control the parsing of the defaults file: + -f config_file -- Use the specified defaults script. + Default: ['%s'] + -L state_file -- Load virtual machine memory state from state_file + -D foo=bar -- Set variable foo=bar before parsing config + E.g. '-D vmid=3;ip=1.2.3.4' + -h -- Print extended help message, including all arguments + -n -- Dry run only, don't actually create domain + Prints the config, suitable for -F. + -q -- Quiet - write output only to the system log + -F domain_config -- Build domain using the config in the file. + Suitable files can be made using '-n' to output a config. +""" % (sys.argv[0], xc_config_file) + +def extra_usage (): + print >>sys.stderr,""" +Arguments to override current config read from '%s': + -c -- Turn into console terminal after domain is created + -k image -- Path to kernel image ['%s'] + -r ramdisk -- Path to ramdisk (or empty) ['%s'] + -b builder_fn -- Function to use to build domain ['%s'] + -m mem_size -- Initial memory allocation in MB [%dMB] + -N domain_name -- Set textual name of domain ['%s'] + -a auto_restart -- Restart domain on exit, yes/no ['%d'] + -e vbd_expert -- Safety catch to avoid some disk accidents ['%s'] + -d udisk,dev,rw -- Add disk, partition, or virtual disk to domain. E.g. to + make partion sda4 available to the domain as hda1 with + read-write access: '-d phy:sda4,hda1,rw' To add + multiple disks use multiple -d flags or seperate with ';' + Default: ['%s'] + -i vfr_ipaddr -- Add IP address to the list which Xen will route to + the domain. Use multiple times to add more IP addrs. + Default: ['%s'] + +Args to override the kernel command line, which is concatenated from these: + -I cmdline_ip -- Override 'ip=ipaddr:nfsserv:gateway:netmask::eth0:off' + Default: ['%s'] + -R cmdline_root -- Override root device parameters. + Default: ['%s'] + -E cmdline_extra -- Override extra kernel args and rc script env vars. + Default: ['%s'] + +""" % (config_file, + image, ramdisk, builder_fn, mem_size, domain_name, auto_restart, + vbd_expert, + printvbds( vbd_list ), + reduce ( (lambda a,b: a+':'+b), vfr_ipaddr,'' )[1:], + cmdline_ip, cmdline_root, cmdline_extra) + +def config_usage (): pass + +def answer ( s ): + s = string.lower(s) + if s == 'yes' or s == 'true' or s == '1': return 1 + return 0 + +def printvbds ( v ): + s='' + for (a,b,c) in v: + s = s + '; %s,%s,%s' % (a,b,c) + return s[2:] + +def output(string): + global quiet + syslog.syslog(string) + if not quiet: + print string + return + +bail=False; dryrun=False; extrahelp=False; quiet = False +image=''; ramdisk=''; builder_fn='linux'; restore=0; state_file='' +mem_size=0; domain_name=''; vfr_ipaddr=[]; +vbd_expert='rr'; auto_restart=False; +vbd_list = []; cmdline_ip = ''; cmdline_root=''; cmdline_extra='' +pci_device_list = []; console_port = -1 +auto_console = False +config_from_file = False + +##### Determine location of defaults file +##### + +try: + opts, args = getopt.getopt(sys.argv[1:], "h?nqcf:F:D:k:r:b:m:N:a:e:d:i:I:R:E:L:" ) + + for opt in opts: + if opt[0] == '-f': config_file= opt[1] + if opt[0] == '-h' or opt[0] == '-?' : bail=True; extrahelp=True + if opt[0] == '-n': dryrun=True + if opt[0] == '-D': + for o in string.split( opt[1], ';' ): + (l,r) = string.split( o, '=' ) + exec "%s='%s'" % (l,r) + if opt[0] == '-q': quiet = True + if opt[0] == '-L': restore = True; state_file = opt[1] + if opt[0] == '-F': config_from_file = True; domain_config = opt[1] + + +except getopt.GetoptError: + bail=True + +if not config_from_file: + try: + os.stat( config_file ) + except: + try: + d = config_dir + config_file + os.stat( d ) + config_file = d + except: + print >> sys.stderr, "Unable to open config file '%s'" % config_file + bail = True + + +##### Parse the config file +##### + +if not config_from_file: + if not quiet: + print "Parsing config file '%s'" % config_file + + try: + execfile ( config_file ) + except (AssertionError,IOError): + print >>sys.stderr,"Exiting %s" % sys.argv[0] + bail = True + +##### Print out config if necessary +##### + +def bailout(): + global extrahelp + main_usage() + config_usage() + if extrahelp: extra_usage() + sys.exit(1) + +if bail: + bailout() + +##### Parse any command line overrides +##### + +x_vbd_list = [] +x_vfr_ipaddr = [] + +for opt in opts: + if opt[0] == '-k': image = opt[1] + if opt[0] == '-r': ramdisk = opt[1] + if opt[0] == '-b': builder_fn = opt[1] + if opt[0] == '-m': mem_size = int(opt[1]) + if opt[0] == '-C': cpu = int(opt[1]) + if opt[0] == '-N': domain_name = opt[1] + if opt[0] == '-a': auto_restart = answer(opt[1]) + if opt[0] == '-e': vbd_expert = opt[1] + if opt[0] == '-I': cmdline_ip = opt[1] + if opt[0] == '-R': cmdline_root = opt[1] + if opt[0] == '-E': cmdline_extra = opt[1] + if opt[0] == '-i': x_vfr_ipaddr.append(opt[1]) + if opt[0] == '-c': auto_console = True + if opt[0] == '-d': + try: + vv = string.split(opt[1],';') + for v in vv: + (udisk,dev,mode) = string.split(v,',') + x_vbd_list.append( (udisk,dev,mode) ) + except: + print >>sys.stderr, "Invalid block device specification : %s" % opt[1] + sys.exit(1) + +if x_vbd_list: vbd_list = x_vbd_list +if x_vfr_ipaddr: vfr_ipaddr = x_vfr_ipaddr + +syslog.openlog('xc_dom_create.py %s' % config_file, 0, syslog.LOG_DAEMON) + +def strip(pre, s): + if s.startswith(pre): + return s[len(pre):] + else: + return s + +def make_domain_config(): + global builder_fn, image, ramdisk, mem_size, domain_name + global cpu + global cmdline, cmdline_ip, cmdline_root + global vfr_ipaddr, vbd_list, vbd_expert + + config = ['config', + ['name', domain_name ], + ['memory', mem_size ], + ] + if cpu: + config.append(['cpu', cpu]) + + config_image = [ builder_fn ] + config_image.append([ 'kernel', os.path.abspath(image) ]) + if ramdisk: + config_image.append([ 'ramdisk', os.path.abspath(ramdisk) ]) + if cmdline_ip: + cmdline_ip = strip("ip=", cmdline_ip) + config_image.append(['ip', cmdline_ip]) + if cmdline_root: + cmdline_root = strip("root=", cmdline_root) + config_image.append(['root', cmdline_root]) + if cmdline_extra: + config_image.append(['args', cmdline_extra]) + config.append(['image', config_image ]) + + config_devs = [] + for (uname, dev, mode) in vbd_list: + config_vbd = ['vbd', + ['uname', uname], + ['dev', dev ], + ['mode', mode ] ] + if vbd_expert != 'rr': + config_vbd.append(['sharing', vbd_expert]) + config_devs.append(['device', config_vbd]) + + for (bus, dev, func) in pci_device_list: + config_pci = ['pci', + ['bus', bus ], + ['dev', dev ], + ['func', func] ] + config_devs.append(['device', config_pci]) + + config += config_devs + + config_vfr = ['vfr'] + idx = 0 # No way of saying which IP is for which vif? + for ip in vfr_ipaddr: + config_vfr.append(['vif', ['id', idx], ['ip', ip]]) + + config.append(config_vfr) + return config + +def parse_config_file(domain_file): + config = None + fin = None + try: + fin = file(domain_file, "rb") + config = sxp.parse(fin) + if len(config) >= 1: + config = config[0] + else: + raise StandardError("Invalid configuration") + except StandardError, ex: + print >> sys.stderr, "Error :", ex + sys.exit(1) + #finally: + if fin: fin.close() + return config + +# This function creates, builds and starts a domain, using the values +# in the global variables, set above. It is used in the subsequent +# code for starting the new domain and rebooting it if appropriate. +def make_domain(config): + """Create, build and start a domain. + Returns: [int] the ID of the new domain. + """ + global restore + + if restore: + dominfo = server.xend_domain_restore(state_file, config) + else: + dominfo = server.xend_domain_create(config) + + dom = int(sxp.child_value(dominfo, 'id')) + console_info = sxp.child(dominfo, 'console') + if console_info: + console_port = int(sxp.child_value(console_info, 'port')) + else: + console_port = None + + if server.xend_domain_start(dom) < 0: + print "Error starting domain" + server.xend_domain_halt(dom) + sys.exit() + + return (dom, console_port) + +PID_DIR = '/var/run/xendomains/' + +def pidfile(dom): + return PID_DIR + '%d.pid' % dom + +def mkpidfile(): + global current_id + if not os.path.isdir(PID_DIR): + os.mkdir(PID_DIR) + + fd = open(pidfile(current_id), 'w') + print >> fd, str(os.getpid()) + fd.close() + return + +def rmpidfile(): + global current_id + os.unlink(pidfile(current_id)) + +def death_handler(dummy1,dummy2): + global current_id + os.unlink(pidfile(current_id)) + output('Auto-restart daemon: daemon PID = %d for domain %d is now exiting' + % (os.getpid(), current_id)) + sys.exit(0) + return + +#============================================================================ +# The starting / monitoring of the domain actually happens here... + +if config_from_file: + config = parse_config_file(domain_config) +else: + config = make_domain_config() + +if dryrun: + print "# %s" % ' '.join(sys.argv) + PrettyPrint.prettyprint(config) + sys.exit(0) +elif quiet: + pass +else: + PrettyPrint.prettyprint(config) + +# start the domain and record its ID number +(current_id, current_port) = make_domain(config) + +def start_msg(prefix, dom, port): + output(prefix + "VM started in domain %d" % dom) + if port: + output(prefix + "Console I/O available on TCP port %d." % port) + +start_msg('', current_id, current_port) + +if current_port and auto_console: + xenctl.console_client.connect('127.0.0.1', current_port) + +# if the auto_restart flag is set then keep polling to see if the domain is +# alive - restart if it is not by calling make_domain() again (it's necessary +# to update the id variable, since the new domain may have a new ID) + +#todo: Replace this - get xend to watch them. +if auto_restart: + ARD = "Auto-restart daemon: " + # turn ourselves into a background daemon + try: + pid = os.fork() + if pid > 0: + sys.exit(0) + os.setsid() + pid = os.fork() + if pid > 0: + output(ARD + 'PID = %d' % pid) + sys.exit(0) + signal.signal(signal.SIGTERM,death_handler) + except OSError: + print >> sys.stderr, ARD+'Startup failed' + sys.exit(1) + + mkpidfile() + + while True: + time.sleep(1) + # todo: use new interface + info = xc.domain_getinfo(current_id, 1) + if info == [] or info[0]['dom'] != current_id: + output(ARD + "Domain %d terminated, restarting VM in new domain" + % current_id) + rmpidfile() + (current_id, current_port) = make_domain() + mkpidfile() + start_msg(ARD, current_id, current_port) diff --git a/tools/examples/xm_vd_tool.py b/tools/examples/xm_vd_tool.py new file mode 100755 index 0000000000..7207a594d3 --- /dev/null +++ b/tools/examples/xm_vd_tool.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python + +import sys +import re +import string + +from xenctl import vdisk + +def usage(): + + print >>sys.stderr,""" +Usage: %s command <params> + + initialise [dev] [[ext_size]] - init. a physcial partition to store vd's + create [size] [[expiry]] - allocate a vd of specified size (and expiry) + enlarge [vdid] [extra_size] - enlarge a specified vd by some amount + delete [vdid] - delete a vd + import [filename] [[expiry]] - create a vd and populate w/ image from file + export [vdid] [filename] - copy vd's contents to a file + setexpiry [vdid] [[expiry]] - update the expiry time for a vd + list - list all the unexpired virtual disks + undelete [vdid] [[expiry]] - attempts to recover an expired vd + freespace - print out the amount of space in free pool + +notes: + vdid - the virtual disk's identity string + size - measured in MB + expiry - is the expiry time of the virtual disk in seconds from now + (0 = don't expire) + device - physical partition to 'format' to hold vd's. e.g. hda4 + ext_size - extent size (default 64MB) +""" % sys.argv[0] + +if len(sys.argv) < 2: + usage() + sys.exit(-1) + +rc='' +src='' +expiry_time = 0 +cmd = sys.argv[1] + +if cmd == 'initialise': + + dev = sys.argv[2] + + if len(sys.argv) > 3: + extent_size = int(sys.argv[3]) + else: + print """No extent size specified - using default size of 64MB""" + extent_size = 64 + + print "Formatting for virtual disks" + print "Device: " + dev + print "Extent size: " + str(extent_size) + "MB" + + rc = vdisk.vd_format(dev, extent_size) + +elif cmd == 'create': + + size = int(sys.argv[2]) + + if len(sys.argv) > 3: + expiry_time = int(sys.argv[3]) + + print "Creating a virtual disk" + print "Size: %d" % size + print "Expiry time (seconds from now): %d" % expiry_time + + src = vdisk.vd_create(size, expiry_time) + +elif cmd == 'enlarge': + + id = sys.argv[2] + + extra_size = int(sys.argv[3]) + + rc = vdisk.vd_enlarge(id, extra_size) + +elif cmd == 'delete': + + id = sys.argv[2] + + print "Deleting a virtual disk with ID: " + id + + rc = vdisk.vd_delete(id) + +elif cmd == 'import': + + file = sys.argv[2] + + if len(sys.argv) > 3: + expiry_time = int(sys.argv[3]) + + print "Allocate new virtual disk and populate from file : %s" % file + + print vdisk.vd_read_from_file(file, expiry_time) + +elif cmd == 'export': + + id = sys.argv[2] + file = sys.argv[3] + + print "Dump contents of virtual disk to file : %s" % file + + rc = vdisk.vd_cp_to_file(id, file ) + +elif cmd == 'setexpiry': + + id = sys.argv[2] + + if len(sys.argv) > 3: + expiry_time = int(sys.argv[3]) + + print "Refreshing a virtual disk" + print "Id: " + id + print "Expiry time (seconds from now [or 0]): " + str(expiry_time) + + rc = vdisk.vd_refresh(id, expiry_time) + +elif cmd == 'list': + print 'ID Size(MB) Expiry' + + for vbd in vdisk.vd_list(): + vbd['size_mb'] = vbd['size'] / vdisk.VBD_SECTORS_PER_MB + vbd['expiry'] = (vbd['expires'] and vbd['expiry_time']) or 'never' + print '%(vdisk_id)-4s %(size_mb)-12d %(expiry)s' % vbd + +elif cmd == 'freespace': + + print vdisk.vd_freespace() + +elif cmd == 'undelete': + + id = sys.argv[2] + + if len(sys.argv) > 3: + expiry_time = int(sys.argv[3]) + + if vdisk.vd_undelete(id, expiry_time): + print "Undelete operation failed for virtual disk: " + id + else: + print "Undelete operation succeeded for virtual disk: " + id + +else: + usage() + sys.exit(-1) + + +if src != '': + print "Returned virtual disk id is : %s" % src + +if rc != '': + print "return code %d" % rc + + + diff --git a/tools/xenctl/lib/ip.py b/tools/xenctl/lib/ip.py new file mode 100644 index 0000000000..0f7f61e3f8 --- /dev/null +++ b/tools/xenctl/lib/ip.py @@ -0,0 +1,95 @@ +import os +import re +import socket +import struct + +##### Networking-related functions + +def get_current_ipaddr(dev='eth0'): + """Return a string containing the primary IP address for the given + network interface (default 'eth0'). + """ + fd = os.popen( '/sbin/ifconfig ' + dev + ' 2>/dev/null' ) + lines = readlines(fd) + for line in lines: + m = re.search( '^\s+inet addr:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+).*', + line ) + if m: + return m.group(1) + return None + +def get_current_ipmask(dev='eth0'): + """Return a string containing the primary IP netmask for the given + network interface (default 'eth0'). + """ + fd = os.popen( '/sbin/ifconfig ' + dev + ' 2>/dev/null' ) + lines = readlines(fd) + for line in lines: + m = re.search( '^.+Mask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+).*', + line ) + if m: + return m.group(1) + return None + +def get_current_ipgw(dev='eth0'): + """Return a string containing the IP gateway for the given + network interface (default 'eth0'). + """ + fd = os.popen( '/sbin/route -n' ) + lines = readlines(fd) + for line in lines: + m = re.search( '^\S+\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)' + + '\s+\S+\s+\S*G.*' + dev + '.*', line ) + if m: + return m.group(1) + return None + +def setup_vfr_rules_for_vif(dom,vif,addr): + """Takes a tuple ( domain-id, vif-id, ip-addr ), where the ip-addr + is expressed as a textual dotted quad, and set up appropriate routing + rules in Xen. No return value. + """ + fd = os.open( '/proc/xen/vfr', os.O_WRONLY ) + if ( re.search( '169\.254', addr) ): + os.write( fd, 'ADD ACCEPT srcaddr=' + addr + + ' srcaddrmask=255.255.255.255' + + ' srcdom=' + str(dom) + ' srcidx=' + str(vif) + + ' dstdom=0 dstidx=0 proto=any\n' ) + else: + os.write( fd, 'ADD ACCEPT srcaddr=' + addr + + ' srcaddrmask=255.255.255.255' + + ' srcdom=' + str(dom) + ' srcidx=' + str(vif) + + ' dst=PHYS proto=any\n' ) + os.write( fd, 'ADD ACCEPT dstaddr=' + addr + + ' dstaddrmask=255.255.255.255' + + ' src=ANY' + + ' dstdom=' + str(dom) + ' dstidx=' + str(vif) + + ' proto=any\n' ) + os.close( fd ) + return None + +def inet_aton(addr): + """Convert an IP addr in IPv4 dot notation into an int. + """ + b = socket.inet_aton(addr) + return struct.unpack('!I', b)[0] + +def inet_ntoa(n): + """Convert an int into an IP addr in IPv4 dot notation. + """ + b = struct.pack('!I', n) + return socket.inet_ntoa(b) + +def add_offset_to_ip(addr, offset): + """Add a numerical offset to an IP addr in IPv4 dot notation. + """ + n = inet_aton(addr) + n += offset + return inet_ntoa(n) + +def check_subnet( ip, network, netmask ): + n_ip = inet_aton(ip) + n_net = inet_aton(network) + n_mask = inet_aton(netmask) + return (n_ip & n_mask) == (n_net & n_mask) + diff --git a/tools/xenctl/lib/vdisk.py b/tools/xenctl/lib/vdisk.py new file mode 100644 index 0000000000..6c4d144f9f --- /dev/null +++ b/tools/xenctl/lib/vdisk.py @@ -0,0 +1,944 @@ +import os +import re +import socket +import string +import sys +import tempfile +import struct + +##### Module variables + +"""Location of the Virtual Disk management database. + defaults to /var/db/xen_vdisks.sqlite +""" +VD_DB_FILE = "/var/db/xen_vdisks.sqlite" + +"""VBD expertise level - determines the strictness of the sanity checking. + This mode determines the level of complaints when disk sharing occurs + through the current VBD mappings. + 0 - only allow shared mappings if both domains have r/o access (always OK) + 1 - also allow sharing with one dom r/w and the other r/o + 2 - allow sharing with both doms r/w +""" +VBD_SAFETY_RR = 0 +VBD_SAFETY_RW = 1 +VBD_SAFETY_WW = 2 + +VBD_SECTORS_PER_MB = 2048 + +##### Module initialisation + +try: + # try to import sqlite (not everyone will have it installed) + import sqlite +except ImportError: + # on failure, just catch the error, don't do anything + pass + + + +##### VBD-related Functions + +def blkdev_name_to_number(name): + """Take the given textual block-device name (e.g., '/dev/sda1', + 'hda') and return the device number used by the OS. """ + + if not re.match( '/dev/', name ): + name = '/dev/' + name + + return os.stat(name).st_rdev + +# lookup_blkdev_partn_info( '/dev/sda3' ) +def lookup_raw_partn(partition): + """Take the given block-device name (e.g., '/dev/sda1', 'hda') + and return a dictionary { device, start_sector, + nr_sectors, type } + device: Device number of the given partition + start_sector: Index of first sector of the partition + nr_sectors: Number of sectors comprising this partition + type: 'Disk' or identifying name for partition type + """ + + if not re.match( '/dev/', partition ): + partition = '/dev/' + partition + + drive = re.split( '[0-9]', partition )[0] + + if drive == partition: + fd = os.popen( '/sbin/sfdisk -s ' + drive + ' 2>/dev/null' ) + line = readline(fd) + if line: + return [ { 'device' : blkdev_name_to_number(drive), + 'start_sector' : long(0), + 'nr_sectors' : long(line) * 2, + 'type' : 'Disk' } ] + return None + + # determine position on disk + fd = os.popen( '/sbin/sfdisk -d ' + drive + ' 2>/dev/null' ) + + #['/dev/sda3 : start= 16948575, size=16836120, Id=83, bootable\012'] + lines = readlines(fd) + for line in lines: + m = re.search( '^' + partition + '\s*: start=\s*([0-9]+), ' + + 'size=\s*([0-9]+), Id=\s*(\S+).*$', line) + if m: + return [ { 'device' : blkdev_name_to_number(drive), + 'start_sector' : long(m.group(1)), + 'nr_sectors' : long(m.group(2)), + 'type' : m.group(3) } ] + + return None + +def lookup_disk_uname( uname ): + """Lookup a list of segments for either a physical or a virtual device. + uname [string]: name of the device in the format \'vd:id\' for a virtual + disk, or \'phy:dev\' for a physical device + returns [list of dicts]: list of extents that make up the named device + """ + ( type, d_name ) = string.split( uname, ':' ) + + if type == "phy": + segments = lookup_raw_partn( d_name ) + elif type == "vd": + segments = vd_lookup( d_name ) + + return segments + + +##### VD Management-related functions + +##### By Mark Williamson, <mark.a.williamson@intel.com> +##### (C) Intel Research Cambridge + +# TODO: +# +# Plenty of room for enhancement to this functionality (contributions +# welcome - and then you get to have your name in the source ;-)... +# +# vd_unformat() : want facilities to unallocate virtual disk +# partitions, possibly migrating virtual disks of them, with checks to see if +# it's safe and options to force it anyway +# +# vd_create() : should have an optional argument specifying a physical +# disk preference - useful to allocate for guest doms to do RAID +# +# vd_undelete() : add ability to "best effort" undelete as much of a +# vdisk as is left in the case that some of it has already been +# reallocated. Some people might still be able to recover some of +# their data this way, even if some of the disk has disappeared. +# +# It'd be nice if we could wipe virtual disks for security purposes - +# should be easy to do this using dev if=/dev/{zero,random} on each +# extent in turn. There could be another optional flag to vd_create +# in order to allow this. +# +# Error codes could be more expressive - i.e. actually tell why the +# error occurred rather than "it broke". Currently the code avoids +# using exceptions to make control scripting simpler and more +# accessible to beginners - therefore probably should just use more +# return codes. +# +# Enhancements / additions to the example scripts are also welcome: +# some people will interact with this code mostly through those +# scripts. +# +# More documentation of how this stuff should be used is always nice - +# if you have a novel configuration that you feel isn't discussed +# enough in the HOWTO (which is currently a work in progress), feel +# free to contribute a walkthrough, or something more substantial. +# + + +def __vd_no_database(): + """Called when no database found - exits with an error + """ + print >> sys.stderr, "ERROR: Could not locate the database file at " + VD_DB_FILE + sys.exit(1) + +def readlines(fd): + """Version of readlines safe against EINTR. + """ + import errno + + lines = [] + while 1: + try: + line = fd.readline() + except IOError, ex: + if ex.errno == errno.EINTR: + continue + else: + raise + if line == '': break + lines.append(line) + return lines + +def readline(fd): + """Version of readline safe against EINTR. + """ + while 1: + try: + return fd.readline() + except IOError, ex: + if ex.errno == errno.EINTR: + continue + else: + raise + + +def vd_format(partition, extent_size_mb): + """Format a partition or drive for use a virtual disk storage. + partition [string]: device file representing the partition + extent_size_mb [string]: extent size in megabytes to use on this disk + """ + + if not os.path.isfile(VD_DB_FILE): + vd_init_db(VD_DB_FILE) + + if not re.match( '/dev/', partition ): + partition = '/dev/' + partition + + cx = sqlite.connect(VD_DB_FILE) + cu = cx.cursor() + + cu.execute("select * from vdisk_part where partition = \'" + + partition + "\'") + row = cu.fetchone() + + extent_size = extent_size_mb * VBD_SECTORS_PER_MB # convert megabytes to sectors + + if not row: + part_info = lookup_raw_partn(partition)[0] + + cu.execute("INSERT INTO vdisk_part(partition, part_id, extent_size) " + + "VALUES ( \'" + partition + "\', " + + str(blkdev_name_to_number(partition)) + + ", " + str(extent_size) + ")") + + + cu.execute("SELECT max(vdisk_extent_no) FROM vdisk_extents " + + "WHERE vdisk_id = 0") + + max_id, = cu.fetchone() + + if max_id != None: + new_id = max_id + 1 + else: + new_id = 0 + + num_extents = part_info['nr_sectors'] / extent_size + + for i in range(num_extents): + sql ="""INSERT INTO vdisk_extents(vdisk_extent_no, vdisk_id, + part_id, part_extent_no) + VALUES ("""+ str(new_id + i) + ", 0, "\ + + str(blkdev_name_to_number(partition))\ + + ", " + str(num_extents - (i + 1)) + ")" + cu.execute(sql) + + cx.commit() + cx.close() + return 0 + + +def vd_create(size_mb, expiry): + """Create a new virtual disk. + size_mb [int]: size in megabytes for the new virtual disk + expiry [int]: expiry time in seconds from now + """ + + if not os.path.isfile(VD_DB_FILE): + __vd_no_database() + + cx = sqlite.connect(VD_DB_FILE) + cu = cx.cursor() + + size = size_mb * VBD_SECTORS_PER_MB + + cu.execute("SELECT max(vdisk_id) FROM vdisks") + max_id, = cu.fetchone() + new_id = int(max_id) + 1 + + # fetch a list of extents from the expired disks, along with information + # about their size + cu.execute("""SELECT vdisks.vdisk_id, vdisk_extent_no, part_extent_no, + vdisk_extents.part_id, extent_size + FROM vdisks NATURAL JOIN vdisk_extents + NATURAL JOIN vdisk_part + WHERE expires AND expiry_time <= datetime('now') + ORDER BY expiry_time ASC, vdisk_extent_no DESC + """) # aims to reuse the last extents + # from the longest-expired disks first + + allocated = 0 + + if expiry: + expiry_ts = "datetime('now', '" + str(expiry) + " seconds')" + expires = 1 + else: + expiry_ts = "NULL" + expires = 0 + + # we'll use this to build the SQL statement we want + building_sql = "INSERT INTO vdisks(vdisk_id, size, expires, expiry_time)" \ + +" VALUES ("+str(new_id)+", "+str(size)+ ", " \ + + str(expires) + ", " + expiry_ts + "); " + + counter = 0 + + while allocated < size: + row = cu.fetchone() + if not row: + print "ran out of space, having allocated %d meg of %d" % (allocated, size) + cx.close() + return -1 + + + (vdisk_id, vdisk_extent_no, part_extent_no, part_id, extent_size) = row + allocated += extent_size + building_sql += "UPDATE vdisk_extents SET vdisk_id = " + str(new_id) \ + + ", " + "vdisk_extent_no = " + str(counter) \ + + " WHERE vdisk_extent_no = " + str(vdisk_extent_no) \ + + " AND vdisk_id = " + str(vdisk_id) + "; " + + counter += 1 + + + # this will execute the SQL query we build to store details of the new + # virtual disk and allocate space to it print building_sql + cu.execute(building_sql) + + cx.commit() + cx.close() + return str(new_id) + + +def vd_lookup(id): + """Lookup a Virtual Disk by ID. + id [string]: a virtual disk identifier + Returns [list of dicts]: a list of extents as dicts, containing fields: + device : Linux device number of host disk + start_sector : within the device + nr_sectors : size of this extent + type : set to \'VD Extent\' + + part_device : Linux device no of host partition + part_start_sector : within the partition + """ + + if not os.path.isfile(VD_DB_FILE): + __vd_no_database() + + cx = sqlite.connect(VD_DB_FILE) + cu = cx.cursor() + + cu.execute("-- types int") + cu.execute("""SELECT COUNT(*) + FROM vdisks + WHERE (expiry_time > datetime('now') OR NOT expires) + AND vdisk_id = """ + id) + count, = cu.fetchone() + + if not count: + cx.close() + return None + + cu.execute("SELECT size from vdisks WHERE vdisk_id = " + id) + real_size, = cu.fetchone() + + # This query tells PySQLite how to convert the data returned from the + # following query - the use of the multiplication confuses it otherwise ;-) + # This row is significant to PySQLite but is syntactically an SQL comment. + + cu.execute("-- types str, int, int, int") + + # This SQL statement is designed so that when the results are fetched they + # will be in the right format to return immediately. + cu.execute("""SELECT partition, vdisk_part.part_id, + round(part_extent_no * extent_size) as start, + extent_size + + FROM vdisks NATURAL JOIN vdisk_extents + NATURAL JOIN vdisk_part + + WHERE vdisk_extents.vdisk_id = """ + id + + " ORDER BY vdisk_extents.vdisk_extent_no ASC" + ) + + extent_tuples = cu.fetchall() + + # use this function to map the results from the database into a dict + # list of extents, for consistency with the rest of the code + def transform ((partition, part_device, part_offset, nr_sectors)): + return { + # the disk device this extent is on - for passing to Xen + 'device' : lookup_raw_partn(partition)[0]['device'], + # the offset of this extent within the disk - for passing to Xen + 'start_sector' : long(part_offset + lookup_raw_partn(partition)[0]['start_sector']), + # extent size, in sectors + 'nr_sectors' : nr_sectors, + # partition device this extent is on (useful to know for xenctl.utils fns) + 'part_device' : part_device, + # start sector within this partition (useful to know for xenctl.utils fns) + 'part_start_sector' : part_offset, + # type of this extent - handy to know + 'type' : 'VD Extent' } + + cx.commit() + cx.close() + + extent_dicts = map(transform, extent_tuples) + + # calculate the over-allocation in sectors (happens because + # we allocate whole extents) + allocated_size = 0 + for i in extent_dicts: + allocated_size += i['nr_sectors'] + + over_allocation = allocated_size - real_size + + # trim down the last extent's length so the resulting VBD will be the + # size requested, rather than being rounded up to the nearest extent + extent_dicts[len(extent_dicts) - 1]['nr_sectors'] -= over_allocation + + return extent_dicts + + +def vd_enlarge(vdisk_id, extra_size_mb): + """Create a new virtual disk. + vdisk_id [string] : ID of the virtual disk to enlarge + extra_size_mb [int]: size in megabytes to increase the allocation by + returns [int] : 0 on success, otherwise non-zero + """ + + if not os.path.isfile(VD_DB_FILE): + __vd_no_database() + + cx = sqlite.connect(VD_DB_FILE) + cu = cx.cursor() + + extra_size = extra_size_mb * VBD_SECTORS_PER_MB + + cu.execute("-- types int") + cu.execute("SELECT COUNT(*) FROM vdisks WHERE vdisk_id = " + vdisk_id + + " AND (expiry_time > datetime('now') OR NOT expires)") + count, = cu.fetchone() + + if not count: # no such vdisk + cx.close() + return -1 + + cu.execute("-- types int") + cu.execute("""SELECT SUM(extent_size) + FROM vdisks NATURAL JOIN vdisk_extents + NATURAL JOIN vdisk_part + WHERE vdisks.vdisk_id = """ + vdisk_id) + + real_size, = cu.fetchone() # get the true allocated size + + cu.execute("-- types int") + cu.execute("SELECT size FROM vdisks WHERE vdisk_id = " + vdisk_id) + + old_size, = cu.fetchone() + + + cu.execute("--- types int") + cu.execute("""SELECT MAX(vdisk_extent_no) + FROM vdisk_extents + WHERE vdisk_id = """ + vdisk_id) + + counter = cu.fetchone()[0] + 1 # this stores the extent numbers + + + # because of the extent-based allocation, the VD may already have more + # allocated space than they asked for. Find out how much we really + # need to add. + add_size = extra_size + old_size - real_size + + # fetch a list of extents from the expired disks, along with information + # about their size + cu.execute("""SELECT vdisks.vdisk_id, vdisk_extent_no, part_extent_no, + vdisk_extents.part_id, extent_size + FROM vdisks NATURAL JOIN vdisk_extents + NATURAL JOIN vdisk_part + WHERE expires AND expiry_time <= datetime('now') + ORDER BY expiry_time ASC, vdisk_extent_no DESC + """) # aims to reuse the last extents + # from the longest-expired disks first + + allocated = 0 + + building_sql = "UPDATE vdisks SET size = " + str(old_size + extra_size)\ + + " WHERE vdisk_id = " + vdisk_id + "; " + + while allocated < add_size: + row = cu.fetchone() + if not row: + cx.close() + return -1 + + (dead_vd_id, vdisk_extent_no, part_extent_no, part_id, extent_size) = row + allocated += extent_size + building_sql += "UPDATE vdisk_extents SET vdisk_id = " + vdisk_id \ + + ", " + "vdisk_extent_no = " + str(counter) \ + + " WHERE vdisk_extent_no = " + str(vdisk_extent_no) \ + + " AND vdisk_id = " + str(dead_vd_id) + "; " + + counter += 1 + + + # this will execute the SQL query we build to store details of the new + # virtual disk and allocate space to it print building_sql + cu.execute(building_sql) + + cx.commit() + cx.close() + return 0 + + +def vd_undelete(vdisk_id, expiry_time): + """Create a new virtual disk. + vdisk_id [int]: size in megabytes for the new virtual disk + expiry_time [int]: expiry time, in seconds from now + returns [int]: zero on success, non-zero on failure + """ + + if not os.path.isfile(VD_DB_FILE): + __vd_no_database() + + if vdisk_id == '0': # undeleting vdisk 0 isn't sane! + return -1 + + cx = sqlite.connect(VD_DB_FILE) + cu = cx.cursor() + + cu.execute("-- types int") + cu.execute("SELECT COUNT(*) FROM vdisks WHERE vdisk_id = " + vdisk_id) + count, = cu.fetchone() + + if not count: + cx.close() + return -1 + + cu.execute("-- types int") + cu.execute("""SELECT SUM(extent_size) + FROM vdisks NATURAL JOIN vdisk_extents + NATURAL JOIN vdisk_part + WHERE vdisks.vdisk_id = """ + vdisk_id) + + real_size, = cu.fetchone() # get the true allocated size + + + cu.execute("-- types int") + cu.execute("SELECT size FROM vdisks WHERE vdisk_id = " + vdisk_id) + + old_size, = cu.fetchone() + + if real_size < old_size: + cx.close() + return -1 + + if expiry_time == 0: + expires = '0' + else: + expires = '1' + + # this will execute the SQL query we build to store details of the new + # virtual disk and allocate space to it print building_sql + cu.execute("UPDATE vdisks SET expiry_time = datetime('now','" + + str(expiry_time) + " seconds'), expires = " + expires + + " WHERE vdisk_id = " + vdisk_id) + + cx.commit() + cx.close() + return 0 + + + + +def vd_list(): + """Lists all the virtual disks registered in the system. + returns [list of dicts] + """ + + if not os.path.isfile(VD_DB_FILE): + __vd_no_database() + + cx = sqlite.connect(VD_DB_FILE) + cu = cx.cursor() + + cu.execute("""SELECT vdisk_id, size, expires, expiry_time + FROM vdisks + WHERE (NOT expires) OR expiry_time > datetime('now') + """) + + ret = cu.fetchall() + + cx.close() + + def makedicts((vdisk_id, size, expires, expiry_time)): + return { 'vdisk_id' : str(vdisk_id), 'size': size, + 'expires' : expires, 'expiry_time' : expiry_time } + + return map(makedicts, ret) + + +def vd_refresh(id, expiry): + """Change the expiry time of a virtual disk. + id [string] : a virtual disk identifier + expiry [int] : expiry time in seconds from now (0 = never expire) + returns [int]: zero on success, non-zero on failure + """ + + if not os.path.isfile(VD_DB_FILE): + __vd_no_database() + + cx = sqlite.connect(VD_DB_FILE) + cu = cx.cursor() + + cu.execute("-- types int") + cu.execute("SELECT COUNT(*) FROM vdisks WHERE vdisk_id = " + id + + " AND (expiry_time > datetime('now') OR NOT expires)") + count, = cu.fetchone() + + if not count: + cx.close() + return -1 + + if expiry: + expires = 1 + expiry_ts = "datetime('now', '" + str(expiry) + " seconds')" + else: + expires = 0 + expiry_ts = "NULL" + + cu.execute("UPDATE vdisks SET expires = " + str(expires) + + ", expiry_time = " + expiry_ts + + " WHERE (expiry_time > datetime('now') OR NOT expires)" + + " AND vdisk_id = " + id) + + cx.commit() + cx.close() + + return 0 + + +def vd_delete(id): + """Deletes a Virtual Disk, making its extents available for future VDs. + id [string] : identifier for the virtual disk to delete + returns [int] : 0 on success, -1 on failure (VD not found + or already deleted) + """ + + if not os.path.isfile(VD_DB_FILE): + __vd_no_database() + + cx = sqlite.connect(VD_DB_FILE) + cu = cx.cursor() + + cu.execute("-- types int") + cu.execute("SELECT COUNT(*) FROM vdisks WHERE vdisk_id = " + id + + " AND (expiry_time > datetime('now') OR NOT expires)") + count, = cu.fetchone() + + if not count: + cx.close() + return -1 + + cu.execute("UPDATE vdisks SET expires = 1, expiry_time = datetime('now')" + + " WHERE vdisk_id = " + id) + + cx.commit() + cx.close() + + return 0 + + +def vd_freespace(): + """Returns the amount of free space available for new virtual disks, in MB + returns [int] : free space for VDs in MB + """ + + if not os.path.isfile(VD_DB_FILE): + __vd_no_database() + + cx = sqlite.connect(VD_DB_FILE) + cu = cx.cursor() + + cu.execute("-- types int") + + cu.execute("""SELECT SUM(extent_size) + FROM vdisks NATURAL JOIN vdisk_extents + NATURAL JOIN vdisk_part + WHERE expiry_time <= datetime('now') AND expires""") + + sum, = cu.fetchone() + + cx.close() + + return sum / VBD_SECTORS_PER_MB + + +def vd_init_db(path): + """Initialise the VD SQLite database + path [string]: path to the SQLite database file + """ + + cx = sqlite.connect(path) + cu = cx.cursor() + + cu.execute( + """CREATE TABLE vdisk_extents + ( vdisk_extent_no INT, + vdisk_id INT, + part_id INT, + part_extent_no INT ) + """) + + cu.execute( + """CREATE TABLE vdisk_part + ( part_id INT, + partition VARCHAR, + extent_size INT ) + """) + + cu.execute( + """CREATE TABLE vdisks + ( vdisk_id INT, + size INT, + expires BOOLEAN, + expiry_time TIMESTAMP ) + """) + + + cu.execute( + """INSERT INTO vdisks ( vdisk_id, size, expires, expiry_time ) + VALUES ( 0, 0, 1, datetime('now') ) + """) + + cx.commit() + cx.close() + + VD_DB_FILE = path + + + +def vd_cp_to_file(vdisk_id,filename): + """Writes the contents of a specified vdisk out into a disk file, leaving + the original copy in the virtual disk pool.""" + + cx = sqlite.connect(VD_DB_FILE) + cu = cx.cursor() + + extents = vd_lookup(vdisk_id) + + if not extents: + return -1 + + file_idx = 0 # index into source file, in sectors + + for i in extents: + cu.execute("""SELECT partition, extent_size FROM vdisk_part + WHERE part_id = """ + str(i['part_device'])) + + (partition, extent_size) = cu.fetchone() + + os.system("dd bs=1b if=" + partition + " of=" + filename + + " skip=" + str(i['part_start_sector']) + + " seek=" + str(file_idx) + + " count=" + str(i['nr_sectors']) + + " > /dev/null") + + file_idx += i['nr_sectors'] + + cx.close() + + return 0 # should return -1 if something breaks + + +def vd_mv_to_file(vdisk_id,filename): + """Writes a vdisk out into a disk file and frees the space originally + taken within the virtual disk pool. + vdisk_id [string]: ID of the vdisk to write out + filename [string]: file to write vdisk contents out to + returns [int]: zero on success, nonzero on failure + """ + + if vd_cp_to_file(vdisk_id,filename): + return -1 + + if vd_delete(vdisk_id): + return -1 + + return 0 + + +def vd_read_from_file(filename,expiry): + """Reads the contents of a file directly into a vdisk, which is + automatically allocated to fit. + filename [string]: file to read disk contents from + returns [string] : vdisk ID for the destination vdisk + """ + + size_bytes = os.stat(filename).st_size + + (size_mb,leftover) = divmod(size_bytes,1048580) # size in megabytes + if leftover > 0: size_mb += 1 # round up if not an exact number of MB + + vdisk_id = vd_create(size_mb, expiry) + + if vdisk_id < 0: + return -1 + + cx = sqlite.connect(VD_DB_FILE) + cu = cx.cursor() + + cu.execute("""SELECT partition, extent_size, part_extent_no + FROM vdisk_part NATURAL JOIN vdisk_extents + WHERE vdisk_id = """ + vdisk_id + """ + ORDER BY vdisk_extent_no ASC""") + + extents = cu.fetchall() + + size_sectors = size_mb * VBD_SECTORS_PER_MB # for feeding to dd + + file_idx = 0 # index into source file, in sectors + + def write_extent_to_vd((partition, extent_size, part_extent_no), + file_idx, filename): + """Write an extent out to disk and update file_idx""" + + os.system("dd bs=512 if=" + filename + " of=" + partition + + " skip=" + str(file_idx) + + " seek=" + str(part_extent_no * extent_size) + + " count=" + str(min(extent_size, size_sectors - file_idx)) + + " > /dev/null") + + return extent_size + + for i in extents: + file_idx += write_extent_to_vd(i, file_idx, filename) + + cx.close() + + return vdisk_id + + + + +def vd_extents_validate(new_extents, new_writeable, safety=VBD_SAFETY_RR): + """Validate the extents against the existing extents. + Complains if the list supplied clashes against the extents that + are already in use in the system. + new_extents [list of dicts]: list of new extents, as dicts + new_writeable [int]: 1 if they are to be writeable, 0 otherwise + returns [int]: either the expertise level of the mapping if it doesn't + exceed VBD_EXPERT_MODE or -1 if it does (error) + """ + + import Xc # this is only needed in this function + + xc = Xc.new() + + ##### Probe for explicitly created virtual disks and build a list + ##### of extents for comparison with the ones that are being added + + probe = xc.vbd_probe() + + old_extents = [] # this will hold a list of all existing extents and + # their writeable status, as a list of (device, + # start, size, writeable?) tuples + + for vbd in probe: + this_vbd_extents = xc.vbd_getextents(vbd['dom'],vbd['vbd']) + for vbd_ext in this_vbd_extents: + vbd_ext['writeable'] = vbd['writeable'] + old_extents.append(vbd_ext) + + ##### Now scan /proc/mounts for compile a list of extents corresponding to + ##### any devices mounted in DOM0. This list is added on to old_extents + + regexp = re.compile("/dev/(\S*) \S* \S* (..).*") + fd = open('/proc/mounts', "r") + + while True: + line = readline(fd) + if not line: # if we've run out of lines then stop reading + break + + m = regexp.match(line) + + # if the regexp didn't match then it's probably a line we don't + # care about - skip to next line + if not m: + continue + + # lookup the device + ext_list = lookup_raw_partn(m.group(1)) + + # if lookup failed, skip to next mounted device + if not ext_list: + continue + + # set a writeable flag as appropriate + for ext in ext_list: + ext['writeable'] = m.group(2) == 'rw' + + # now we've got here, the contents of ext_list are in a + # suitable format to be added onto the old_extents list, ready + # for checking against the new extents + + old_extents.extend(ext_list) + + fd.close() # close /proc/mounts + + ##### By this point, old_extents contains a list of extents, in + ##### dictionary format corresponding to every extent of physical + ##### disk that's either part of an explicitly created VBD, or is + ##### mounted under DOM0. We now check these extents against the + ##### proposed additions in new_extents, to see if a conflict will + ##### happen if they are added with write status new_writeable + + level = 0 # this'll accumulate the max warning level + + # Search for clashes between the new extents and the old ones + # Takes time O(len(new_extents) * len(old_extents)) + for new_ext in new_extents: + for old_ext in old_extents: + if(new_ext['device'] == old_ext['device']): + + new_ext_start = new_ext['start_sector'] + new_ext_end = new_ext_start + new_ext['nr_sectors'] - 1 + + old_ext_start = old_ext['start_sector'] + old_ext_end = old_ext_start + old_ext['nr_sectors'] - 1 + + if((old_ext_start <= new_ext_start <= old_ext_end) or + (old_ext_start <= new_ext_end <= old_ext_end)): + if (not old_ext['writeable']) and new_writeable: + level = max(1,level) + elif old_ext['writeable'] and (not new_writeable): + level = max(1,level) + elif old_ext['writeable'] and new_writeable: + level = max(2,level) + + + ##### level now holds the warning level incurred by the current + ##### VBD setup and we complain appropriately to the user + + + if level == 1: + print >> sys.stderr, """Warning: one or more hard disk extents + writeable by one domain are also readable by another.""" + elif level == 2: + print >> sys.stderr, """Warning: one or more hard disk extents are + writeable by two or more domains simultaneously.""" + + if level > safety: + print >> sys.stderr, """ERROR: This kind of disk sharing is not allowed + at the current safety level (%d).""" % safety + level = -1 + + return level + diff --git a/tools/xenctl/setup.py b/tools/xenctl/setup.py index 1b6a98c10a..1d67a843ef 100644 --- a/tools/xenctl/setup.py +++ b/tools/xenctl/setup.py @@ -2,7 +2,7 @@ from distutils.core import setup, Extension import sys -modules = [ 'xenctl.console_client', 'xenctl.utils' ] +modules = [ 'xenctl.console_client', 'xenctl.utils', 'xenctl.ip' , 'xenctl.vdisk' ] # We need the 'tempfile' module from Python 2.3. We install this ourselves # if the installed Python is older than 2.3. diff --git a/tools/xend/lib/utils.c b/tools/xend/lib/utils.c index 918e74cc96..903bb9c274 100644 --- a/tools/xend/lib/utils.c +++ b/tools/xend/lib/utils.c @@ -412,7 +412,8 @@ static PyObject *xu_message_get_payload(PyObject *self, PyObject *args) C2P(netif_fe_interface_status_changed_t, evtchn, Int, Long); return dict; case TYPE(CMSG_NETIF_FE, CMSG_NETIF_FE_DRIVER_STATUS_CHANGED): - C2P(netif_fe_driver_status_changed_t, status, Int, Long); + C2P(netif_fe_driver_status_changed_t, status, Int, Long); + C2P(netif_fe_driver_status_changed_t, nr_interfaces, Int, Long); return dict; case TYPE(CMSG_NETIF_FE, CMSG_NETIF_FE_INTERFACE_CONNECT): C2P(netif_fe_interface_connect_t, handle, Int, Long); @@ -603,6 +604,9 @@ static PyObject *xu_message_new(PyObject *self, PyObject *args) P2C(netif_be_disconnect_t, domid, u32); P2C(netif_be_disconnect_t, netif_handle, u32); break; + case TYPE(CMSG_NETIF_FE, CMSG_NETIF_FE_DRIVER_STATUS_CHANGED): + P2C(netif_fe_driver_status_changed_t, status, u32); + P2C(netif_fe_driver_status_changed_t, nr_interfaces, u32); } if ( dict_items_parsed != PyDict_Size(payload) ) diff --git a/tools/xenmgr/Makefile b/tools/xenmgr/Makefile new file mode 100644 index 0000000000..125f8cf4e8 --- /dev/null +++ b/tools/xenmgr/Makefile @@ -0,0 +1,19 @@ + +all: + python setup.py build + +install: all + if [ "$(prefix)" = "" ]; then \ + python setup.py install; \ + elif [ "$(dist)" = "yes" ]; then \ + python setup.py install --home="$(prefix)"; \ + else \ + python setup.py install --root="$(prefix)"; \ + fi + mkdir -p $(prefix)/usr/sbin + install -m0755 xenmgrd $(prefix)/usr/sbin + install -m0755 xend $(prefix)/usr/sbin + install -m0755 netfix $(prefix)/usr/sbin + +clean: + rm -rf build *.pyc *.pyo *.o *.a *~ diff --git a/tools/xenmgr/lib/Args.py b/tools/xenmgr/lib/Args.py new file mode 100644 index 0000000000..527e841d3d --- /dev/null +++ b/tools/xenmgr/lib/Args.py @@ -0,0 +1,126 @@ +import sxp + +class ArgError(StandardError): + pass + +class Args: + """Argument encoding support for HTTP. + """ + + def __init__(self, paramspec, keyspec): + self.arg_ord = [] + self.arg_dict = {} + self.key_ord = [] + self.key_dict = {} + for (name, type) in paramspec: + self.arg_ord.append(name) + self.arg_dict[name] = type + for (name, type) in keyspec: + self.key_ord.append(name) + self.key_dict[name] = type + + def get_args(self, d, xargs=None): + args = {} + keys = {} + params = [] + if xargs: + self.split_args(xargs, args, keys) + self.split_args(d, args, keys) + for a in self.arg_ord: + if a in args: + params.append(args[a]) + else: + raise ArgError('Missing parameter: %s' % a) + return (params, keys) + + def split_args(self, d, args, keys): + for (k, v) in d.items(): + if k in self.arg_dict: + type = self.arg_dict[k] + val = self.coerce(type, v) + args[k] = val + elif k in self.key_dict: + type = self.key_dict[k] + val = self.coerce(type, v) + keys[k] = val + else: + raise ArgError('Invalid parameter: %s' % k) + + def get_form_args(self, f, xargs=None): + d = {} + for (k, v) in f.items(): + n = len(v) + if ((k not in self.arg_dict) and + (k not in self.key_dict)): + continue + if n == 0: + continue + elif n == 1: + d[k] = v[0] + else: + raise ArgError('Too many values for %s' % k) + return self.get_args(d, xargs=xargs) + + def coerce(self, type, v): + try: + if type == 'int': + return int(v) + if type == 'str': + return str(v) + if type == 'sxpr': + return self.sxpr(v) + except ArgError: + raise + except StandardError, ex: + raise ArgError(str(ex)) + + def sxpr(self, v): + if instanceof(v, types.ListType): + return v + if instanceof(v, types.File) or hasattr(v, 'readline'): + return sxpr_file(v) + if instanceof(v, types.StringType): + return sxpr_file(StringIO(v)) + return str(v) + + def sxpr_file(self, fin): + try: + vals = sxp.parse(fin) + except: + raise ArgError('Coercion to sxpr failed') + if len(vals) == 1: + return vals[0] + else: + raise ArgError('Too many sxprs') + + def call_with_args(self, fn, args, xargs=None): + (params, keys) = self.get_args(args, xargs=xargs) + fn(*params, **keys) + + def call_with_form_args(self, fn, fargs, xargs=None): + (params, keys) = self.get_form_args(fargs, xargs=xargs) + fn(*params, **keys) + +class ArgFn(Args): + """Represent a remote HTTP operation as a function. + Used on the client. + """ + + def __init__(self, fn, paramspec, keyspec={}): + Args.__init__(self, paramspec, keyspec) + self.fn = fn + + def __call__(self, fargs, xargs=None): + return self.call_with_args(self.fn, fargs, xargs=xargs) + +class FormFn(Args): + """Represent an operation as a function over a form. + Used in the HTTP server. + """ + + def __init__(self, fn, paramspec, keyspec={}): + Args.__init__(self, paramspec, keyspec) + self.fn = fn + + def __call__(self, fargs, xargs=None): + return self.call_with_form_args(self.fn, fargs, xargs=xargs) diff --git a/tools/xenmgr/lib/EventServer.py b/tools/xenmgr/lib/EventServer.py new file mode 100644 index 0000000000..9749e119fb --- /dev/null +++ b/tools/xenmgr/lib/EventServer.py @@ -0,0 +1,204 @@ +# Copyright (C) 2004 Mike Wray <mike.wray@hp.com> +"""Simple publish/subscribe event server. + +""" +import string + +# subscribe a.b.c h: map a.b.c -> h +# subscribe a.b.* h: map a.b.* -> h +# subscribe a.b.? h: map a.b.? -> h +# +# for event a.b.c.d: +# +# lookup a.b.c.d, call handlers +# +# lookup a.b.c.?, call handlers +# +# lookup a.b.c.d.*, call handlers +# lookup a.b.c.*, call handlers +# lookup a.b.*, call handlers +# lookup a.*, call handlers +# lookup *, call handlers + +# a.b.c.d = (a b c d) +# a.b.c.? = (a b c _) +# a.b.c.* = (a b c . _) + +class EventServer: + + DOT = '.' + QUERY = '?' + DOT_QUERY = DOT + QUERY + STAR = '*' + DOT_STAR = DOT + STAR + + def __init__(self, run=0): + self.handlers = {} + self.run = run + self.queue = [] + + def start(self): + """Enable event handling. Sends any queued events. + """ + self.run = 1 + for (e,v) in self.queue: + self.inject(e, v) + self.queue = [] + + def stop(self): + """Suspend event handling. Events injected while suspended + are queued until we are started again. + """ + self.run = 0 + + def subscribe(self, event, handler): + """Subscribe to an event. For example 'a.b.c.d'. + A subcription like 'a.b.c.?' ending in '?' matches any value + for the '?'. A subscription like 'a.b.c.*' ending in '*' matches + any event type with the same prefix, 'a.b.c' in this case. + + event event name + handler event handler fn(event, val) + """ + hl = self.handlers.get(event) + if hl is None: + self.handlers[event] = [handler] + else: + hl.append(handler) + + def unsubscribe_all(self, event=None): + """Unsubscribe all handlers for a given event, or all handlers. + + event event (optional) + """ + if event == None: + self.handlers.clear() + else: + del self.handlers[event] + + def unsubscribe(self, event, handler): + """Unsubscribe a given event and handler. + + event event + handler handler + """ + hl = self.handlers.get(event) + if hl is None: + return + hl.remove(handler) + + def inject(self, event, val): + """Inject an event. Handlers for it are called if runing, otherwise + it is queued. + + event event type + val event value + """ + if self.run: + #print ">event", event, val + self.call_event_handlers(event, event, val) + self.call_query_handlers(event, val) + self.call_star_handlers(event, val) + else: + self.queue.append( (event, val) ) + + def call_event_handlers(self, key, event, val): + """Call the handlers for an event. + It is safe for handlers to subscribe or unsubscribe. + + key key for handler list + event event type + val event value + """ + hl = self.handlers.get(key) + if hl is None: + return + # Copy the handler list so that handlers can call + # subscribe/unsubscribe safely - python list iteration + # is not safe against list modification. + for h in hl[:]: + try: + h(event, val) + except: + pass + + def call_query_handlers(self, event, val): + """Call regex handlers for events matching 'event' that end in '?'. + + event event type + val event value + """ + dot_idx = event.rfind(self.DOT) + if dot_idx == -1: + self.call_event_handlers(self.QUERY, event, val) + else: + event_query = event[0:dot_idx] + self.DOT_QUERY + self.call_event_handlers(event_query, event, val) + + def call_star_handlers(self, event, val): + """Call regex handlers for events matching 'event' that end in '*'. + + event event type + val event value + """ + etype = string.split(event, self.DOT) + for i in range(len(etype), 0, -1): + event_star = self.DOT.join(etype[0:i]) + self.DOT_STAR + self.call_event_handlers(event_star, event, val) + self.call_event_handlers(self.STAR, event, val) + +def instance(): + global inst + try: + inst + except: + inst = EventServer() + inst.start() + return inst + +def main(): + def sys_star(event, val): + print 'sys_star', event, val + + def sys_foo(event, val): + print 'sys_foo', event, val + s.unsubscribe('sys.foo', sys_foo) + + def sys_foo2(event, val): + print 'sys_foo2', event, val + + def sys_bar(event, val): + print 'sys_bar', event, val + + def sys_foo_bar(event, val): + print 'sys_foo_bar', event, val + + def foo_bar(event, val): + print 'foo_bar', event, val + + s = EventServer() + s.start() + s.subscribe('sys.*', sys_star) + s.subscribe('sys.foo', sys_foo) + s.subscribe('sys.foo', sys_foo2) + s.subscribe('sys.bar', sys_bar) + s.subscribe('sys.foo.bar', sys_foo_bar) + s.subscribe('foo.bar', foo_bar) + s.inject('sys.foo', 'hello') + print + s.inject('sys.bar', 'hello again') + print + s.inject('sys.foo.bar', 'hello again') + print + s.inject('foo.bar', 'hello again') + print + s.inject('foo', 'hello again') + print + s.start() + s.unsubscribe('sys.*', sys_star) + s.unsubscribe_all('sys.*') + s.inject('sys.foo', 'hello') + +if __name__ == "__main__": + main() + diff --git a/tools/xenmgr/lib/EventTypes.py b/tools/xenmgr/lib/EventTypes.py new file mode 100644 index 0000000000..3d4230e2ba --- /dev/null +++ b/tools/xenmgr/lib/EventTypes.py @@ -0,0 +1,34 @@ +# Copyright (C) 2004 Mike Wray <mike.wray@hp.com> + +## XEND_DOMAIN_CREATE = "xend.domain.create": dom +## create: +## xend.domain.destroy: dom, reason:died/crashed +## xend.domain.up ? + +## xend.domain.start: dom +## xend.domain.stop: dom +## xend.domain.shutdown: dom +## xend.domain.halt: dom + +## xend.domain.migrate.begin: dom, to +## Begin tells: src host, src domain uri, dst host. Dst id known? +## err: src host, src domain uri, dst host, dst id if known, status (of domain: ok, dead,...), reason +## end: src host, src domain uri, dst host, dst uri + +## Events for both ends of migrate: for exporter and importer? +## Include migrate id so can tie together. +## Have uri /xend/migrate/<id> for migrate info (migrations in progress). + +## (xend.domain.migrate.begin (src <host>) (src.domain <id>) +## (dst <host>) (id <migrate id>)) + +## xend.domain.migrate.end: +## (xend.domain.migrate.end (domain <id>) (to <host>) + +## xend.node.up: xend uri +## xend.node.down: xend uri + +## xend.error ? + +## format: + diff --git a/tools/xenmgr/lib/PrettyPrint.py b/tools/xenmgr/lib/PrettyPrint.py new file mode 100644 index 0000000000..9e91b11448 --- /dev/null +++ b/tools/xenmgr/lib/PrettyPrint.py @@ -0,0 +1,299 @@ +# Copyright (C) 2004 Mike Wray <mike.wray@hp.com> + +"""General pretty-printer, including support for SXP. + +""" +import sys +import types +import StringIO +import sxp + +class PrettyItem: + + def __init__(self, width): + self.width = width + + def insert(self, block): + block.addtoline(self) + + def get_width(self): + return self.width + + def output(self, out): + print '***PrettyItem>output>', self + pass + + def prettyprint(self, out, width): + print '***PrettyItem>prettyprint>', self + return width + +class PrettyString(PrettyItem): + + def __init__(self, x): + PrettyItem.__init__(self, len(x)) + self.value = x + + def output(self, out): + out.write(self.value) + + def prettyprint(self, line): + line.output(self) + + def show(self, out): + print >> out, ("(string (width %d) '%s')" % (self.width, self.value)) + +class PrettySpace(PrettyItem): + + def output(self, out): + out.write(' ' * self.width) + + def prettyprint(self, line): + line.output(self) + + def show(self, out): + print >> out, ("(space (width %d))" % self.width) + +class PrettyBreak(PrettyItem): + + def __init__(self, width, indent): + PrettyItem.__init__(self, width) + self.indent = indent + self.space = 0 + self.active = 0 + + def output(self, out): + out.write(' ' * self.width) + + def prettyprint(self, line): + if line.breaks(self.space): + self.active = 1 + line.newline(self.indent) + else: + line.output(self) + + def show(self, out): + print >> out, ("(break (width %d) (indent %d) (space %d) (active %d))" + % (self.width, self.indent, self.space, self.lspace, self.active)) + +class PrettyNewline(PrettySpace): + + def __init__(self, indent): + PrettySpace.__init__(self, indent) + + def insert(self, block): + block.newline() + block.addtoline(self) + + def output(self, out): + out.write(' ' * self.width) + + def prettyprint(self, line): + line.newline(0) + line.output(self) + + def show(self, out): + print >> out, ("(nl (indent %d))" % self.indent) + +class PrettyLine(PrettyItem): + def __init__(self): + PrettyItem.__init__(self, 0) + self.content = [] + + def write(self, x): + self.content.append(x) + + def end(self): + width = 0 + lastwidth = 0 + lastbreak = None + for x in self.content: + if isinstance(x, PrettyBreak): + if lastbreak: + lastbreak.space = (width - lastwidth) + lastbreak = x + lastwidth = width + width += x.get_width() + if lastbreak: + lastbreak.space = (width - lastwidth) + self.width = width + + def prettyprint(self, line): + for x in self.content: + x.prettyprint(line) + + def show(self, out): + print >> out, '(LINE (width %d)' % self.width + for x in self.content: + x.show(out) + print >> out, ')' + +class PrettyBlock(PrettyItem): + + def __init__(self, all=0, parent=None): + self.width = 0 + self.lines = [] + self.parent = parent + self.indent = 0 + self.all = all + self.broken = 0 + self.newline() + + def add(self, item): + item.insert(self) + + def end(self): + self.width = 0 + for l in self.lines: + l.end() + if self.width < l.width: + self.width = l.width + + def breaks(self, n): + return self.all and self.broken + + def newline(self): + self.lines.append(PrettyLine()) + + def addtoline(self, x): + self.lines[-1].write(x) + + def prettyprint(self, line): + self.indent = line.used + line.block = self + if not line.fits(self.width): + self.broken = 1 + for l in self.lines: + l.prettyprint(line) + line.block = self.parent + + def show(self, out): + print >> out, ('(BLOCK (width %d) (indent %d) (all %d) (broken %d)' % + (self.width, self.indent, self.all, self.broken)) + for l in self.lines: + l.show(out) + print >> out, ')' + +class Line: + + def __init__(self, out, width): + self.out = out + self.width = width + self.used = 0 + self.space = self.width + + def newline(self, indent): + indent += self.block.indent + self.out.write('\n') + self.out.write(' ' * indent) + self.used = indent + self.space = self.width - self.used + + def fits(self, n): + return self.space - n >= 0 + + def breaks(self, n): + return self.block.breaks(n) or not self.fits(n) + + def output(self, x): + n = x.get_width() + self.space -= n + self.used += n + if self.space < 0: + self.space = 0 + x.output(self.out) + +class PrettyPrinter: + """A prettyprinter based on what I remember of Derek Oppen's + prettyprint algorithm from TOPLAS way back. + """ + + def __init__(self, width=40): + self.width = width + self.block = None + self.top = None + + def write(self, x): + self.block.add(PrettyString(x)) + + def add(self, item): + self.block.add(item) + + def addbreak(self, width=1, indent=4): + self.add(PrettyBreak(width, indent)) + + def addspace(self, width=1): + self.add(PrettySpace(width)) + + def addnl(self, indent=0): + self.add(PrettyNewline(indent)) + + def begin(self, all=0): + block = PrettyBlock(all=all, parent=self.block) + self.block = block + + def end(self): + self.block.end() + if self.block.parent: + self.block.parent.add(self.block) + else: + self.top = self.block + self.block = self.block.parent + + def prettyprint(self, out=sys.stdout): + line = Line(out, self.width) + self.top.prettyprint(line) + +class SXPPrettyPrinter(PrettyPrinter): + """An SXP prettyprinter. + """ + + def pstring(self, x): + io = StringIO.StringIO() + sxp.show(x, out=io) + io.seek(0) + val = io.getvalue() + io.close() + return val + + def pprint(self, l): + if isinstance(l, types.ListType): + self.begin(all=1) + self.write('(') + i = 0 + for x in l: + if(i): self.addbreak() + self.pprint(x) + i += 1 + self.addbreak(width=0, indent=0) + self.write(')') + self.end() + else: + self.write(self.pstring(l)) + +def prettyprint(sxpr, out=sys.stdout, width=80): + """Prettyprint an SXP form. + + sxpr s-expression + out destination + width maximum output width + """ + if isinstance(sxpr, types.ListType): + pp = SXPPrettyPrinter(width=width) + pp.pprint(sxpr) + pp.prettyprint(out=out) + else: + sxp.show(sxpr, out=out) + print >> out + +def main(): + pin = sxp.Parser() + while 1: + buf = sys.stdin.read(100) + pin.input(buf) + if buf == '': break + l = pin.get_val() + prettyprint(l, width=80) + +if __name__ == "__main__": + main() + diff --git a/tools/xenmgr/lib/XendClient.py b/tools/xenmgr/lib/XendClient.py new file mode 100644 index 0000000000..aa88d99e46 --- /dev/null +++ b/tools/xenmgr/lib/XendClient.py @@ -0,0 +1,368 @@ +# Copyright (C) 2004 Mike Wray <mike.wray@hp.com> +"""Client API for the HTTP interface on xend. +Callable as a script - see main(). +""" +import sys +import httplib +import types +from StringIO import StringIO +import urlparse + +from encode import * +import sxp +import PrettyPrint + +DEBUG = 1 + +class Foo(httplib.HTTPResponse): + + def begin(self): + fin = self.fp + while(1): + buf = fin.readline() + print "***", buf + if buf == '': + print + sys.exit() + + +def sxprio(sxpr): + io = StringIO() + sxp.show(sxpr, out=io) + print >> io + io.seek(0) + return io + +def fileof(val): + """Converter for passing configs. + Handles lists, files directly. + Assumes a string is a file name and passes its contents. + """ + if isinstance(val, types.ListType): + return sxprio(val) + if isinstance(val, types.StringType): + return file(val) + if hasattr(val, 'readlines'): + return val + +# todo: need to sort of what urls/paths are using for objects. +# e.g. for domains at the moment return '0'. +# should probably return abs path w.r.t. server, e.g. /xend/domain/0. +# As an arg, assume abs path is obj uri, otherwise just id. + +# Function to convert to full url: Xend.uri(path), e.g. +# maps /xend/domain/0 to http://wray-m-3.hpl.hp.com:8000/xend/domain/0 +# And should accept urls for ids? + +def urljoin(location, root, prefix='', rest=''): + base = 'http://' + location + root + prefix + url = urlparse.urljoin(base, rest) + return url + +def nodeurl(location, root, id=''): + return urljoin(location, root, 'node/', id) + +def domainurl(location, root, id=''): + return urljoin(location, root, 'domain/', id) + +def consoleurl(location, root, id=''): + return urljoin(location, root, 'console/', id) + +def vbdurl(location, root, id=''): + return urljoin(location, root, 'vbd/', id) + +def deviceurl(location, root, id=''): + return urljoin(location, root, 'device/', id) + +def vneturl(location, root, id=''): + return urljoin(location, root, 'vnet/', id) + +def eventurl(location, root, id=''): + return urljoin(location, root, 'event/', id) + +def xend_request(url, method, data=None): + urlinfo = urlparse.urlparse(url) + (uproto, ulocation, upath, uparam, uquery, ufrag) = urlinfo + if DEBUG: print url, urlinfo + if uproto != 'http': + raise StandardError('Invalid protocol: ' + uproto) + if DEBUG: print '>xend_request', ulocation, upath, method, data + (hdr, args) = encode_data(data) + if data and method == 'GET': + upath += '?' + args + args = None + if method == "POST" and upath.endswith('/'): + upath = upath[:-1] + if DEBUG: print "ulocation=", ulocation, "upath=", upath, "args=", args + #hdr['User-Agent'] = 'Mozilla' + #hdr['Accept'] = 'text/html,text/plain' + conn = httplib.HTTPConnection(ulocation) + #conn.response_class = Foo + conn.set_debuglevel(1) + conn.request(method, upath, args, hdr) + resp = conn.getresponse() + if DEBUG: print resp.status, resp.reason + if DEBUG: print resp.msg.headers + if resp.status in [204, 404]: + return None + if resp.status not in [200, 201, 202, 203]: + raise RuntimeError(resp.reason) + pin = sxp.Parser() + data = resp.read() + if DEBUG: print "***data" , data + if DEBUG: print "***" + pin.input(data); + pin.input_eof() + conn.close() + val = pin.get_val() + #if isinstance(val, types.ListType) and sxp.name(val) == 'val': + # val = val[1] + if isinstance(val, types.ListType) and sxp.name(val) == 'err': + raise RuntimeError(val[1]) + if DEBUG: print '**val=' + #sxp.show(val); print + PrettyPrint.prettyprint(val) + if DEBUG: print '**' + return val + +def xend_get(url, args=None): + return xend_request(url, "GET", args) + +def xend_call(url, data): + return xend_request(url, "POST", data) + +class Xend: + + SRV_DEFAULT = "localhost:8000" + ROOT_DEFAULT = "/xend/" + + def __init__(self, srv=None, root=None): + self.bind(srv, root) + + def bind(self, srv=None, root=None): + if srv is None: srv = self.SRV_DEFAULT + if root is None: root = self.ROOT_DEFAULT + if not root.endswith('/'): root += '/' + self.location = srv + self.root = root + + def nodeurl(self, id=''): + return nodeurl(self.location, self.root, id) + + def domainurl(self, id=''): + return domainurl(self.location, self.root, id) + + def consoleurl(self, id=''): + return consoleurl(self.location, self.root, id) + + def vbdurl(self, id=''): + return vbdurl(self.location, self.root, id) + + def deviceurl(self, id=''): + return deviceurl(self.location, self.root, id) + + def vneturl(self, id=''): + return vneturl(self.location, self.root, id) + + def eventurl(self, id=''): + return eventurl(self.location, self.root, id) + + def xend(self): + return xend_get(urljoin(self.location, self.root)) + + def xend_node(self): + return xend_get(self.nodeurl()) + + def xend_node_cpu_rrobin_slice_set(self, slice): + return xend_call(self.nodeurl(), + {'op' : 'cpu_rrobin_slice_set', + 'slice' : slice }) + + def xend_node_cpu_bvt_slice_set(self, slice): + return xend_call(self.nodeurl(), + {'op' : 'cpu_bvt_slice_set', + 'slice' : slice }) + + def xend_domains(self): + return xend_get(self.domainurl()) + + def xend_domain_create(self, conf): + return xend_call(self.domainurl(), + {'op' : 'create', + 'config' : fileof(conf) }) + + def xend_domain(self, id): + return xend_get(self.domainurl(id)) + + def xend_domain_start(self, id): + return xend_call(self.domainurl(id), + {'op' : 'start'}) + + def xend_domain_stop(self, id): + return xend_call(self.domainurl(id), + {'op' : 'stop'}) + + def xend_domain_shutdown(self, id): + return xend_call(self.domainurl(id), + {'op' : 'shutdown'}) + + def xend_domain_halt(self, id): + return xend_call(self.domainurl(id), + {'op' : 'halt'}) + + def xend_domain_save(self, id, filename): + return xend_call(self.domainurl(id), + {'op' : 'save', + 'file' : filename}) + + def xend_domain_restore(self, id, filename, conf): + return xend_call(self.domainurl(id), + {'op' : 'restore', + 'file' : filename, + 'config' : fileof(conf) }) + + def xend_domain_migrate(self, id, dst): + return xend_call(self.domainurl(id), + {'op' : 'migrate', + 'dst' : dst}) + + def xend_domain_pincpu(self, id, cpu): + return xend_call(self.domainurl(id), + {'op' : 'pincpu', + 'cpu' : cpu}) + + def xend_domain_cpu_bvt_set(self, id, mcuadv, warp, warpl, warpu): + return xend_call(self.domainurl(id), + {'op' : 'cpu_bvt_set', + 'mcuadv' : mvuadv, + 'warp' : warp, + 'warpl' : warpl, + 'warpu' : warpu }) + + def xend_domain_cpu_atropos_set(self, id, period, slice, latency, xtratime): + return xend_call(self.domainurl(id), + {'op' : 'cpu_atropos_set', + 'period' : period, + 'slice' : slice, + 'latency' : latency, + 'xtratime': xtratime }) + + def xend_domain_vifs(self, id): + return xend_get(self.domainurl(id), + { 'op' : 'vifs' }) + + def xend_domain_vif_stats(self, id, vif): + return xend_get(self.domainurl(id), + { 'op' : 'vif_stats', + 'vif' : vif}) + + def xend_domain_vif_ip_add(self, id, vif, ipaddr): + return xend_call(self.domainurl(id), + {'op' : 'vif_ip_add', + 'vif' : vif, + 'ip' : ipaddr }) + + def xend_domain_vif_scheduler_set(id, vif, bytes, usecs): + return xend_call(self.domainurl(id), + {'op' : 'vif_scheduler_set', + 'vif' : vif, + 'bytes' : bytes, + 'usecs' : usecs }) + + def xend_domain_vif_scheduler_get(id, vif): + return xend_get(self.domainurl(id), + {'op' : 'vif_scheduler_get', + 'vif' : vif}) + + def xend_domain_vbds(self, id): + return xend_get(self.domainurl(id), + {'op' : 'vbds'}) + + def xend_domain_vbd(self, id, vbd): + return xend_get(self.domainurl(id), + {'op' : 'vbd', + 'vbd' : vbd}) + + def xend_domain_vbd_add(self, id, uname, dev, mode): + return xend_call(self.domainurl(id), + {'op' : 'vbd_add', + 'uname' : uname, + 'dev' : dev, + 'mode' : mode}) + + def xend_domain_vbd_remove(self, id, dev): + return xend_call(self.domainurl(id), + {'op' : 'vbd_remove', + 'dev' : dev}) + + def xend_consoles(self): + return xend_get(self.consoleurl()) + + def xend_console(self, id): + return xend_get(self.consoleurl(id)) + + def xend_vbds(self): + return xend_get(self.vbdurl()) + + def xend_vbd_create(self, conf): + return xend_call(self.vbdurl(), + {'op': 'create', 'config': fileof(conf) }) + + def xend_vbd(self, id): + return xend_get(self.vbdurl(id)) + + def xend_vbd_delete(self, id): + return xend_call(self.vbdurl(id), + {'op': 'delete'}) + + def xend_vbd_refresh(self, id, expiry): + return xend_call(self.vbdurl(id), + {'op': 'refresh', 'expiry': expiry }) + + def xend_vbd_expand(self, id, size): + return xend_call(self.vbdurl(id), + {'op': 'expand', 'size': size}) + + def xend_vnets(self): + return xend_get(self.vneturl()) + + def xend_vnet_create(self, conf): + return xend_call(self.vneturl(), + {'op': 'create', 'config': fileof(conf) }) + + def xend_vnet(self, id): + return xend_get(self.vneturl(id)) + + def xend_vnet_delete(self, id): + return xend_call(self.vneturl(id), + {'op': 'delete'}) + + def xend_event_inject(self, sxpr): + val = xend_call(self.eventurl(), + {'op': 'inject', 'event': fileof(sxpr) }) + + +def main(argv): + """Call an API function: + + python XendClient.py fn args... + + The leading 'xend_' on the function can be omitted. + Example: + + > python XendClient.py domains + (domain 0 8) + > python XendClient.py domain 0 + (domain (id 0) (name Domain-0) (memory 128)) + """ + server = Xend() + fn = argv[1] + if not fn.startswith('xend'): + fn = 'xend_' + fn + args = argv[2:] + getattr(server, fn)(*args) + +if __name__ == "__main__": + main(sys.argv) +else: + server = Xend() diff --git a/tools/xenmgr/lib/XendConsole.py b/tools/xenmgr/lib/XendConsole.py new file mode 100644 index 0000000000..041e6c4d44 --- /dev/null +++ b/tools/xenmgr/lib/XendConsole.py @@ -0,0 +1,172 @@ +# Copyright (C) 2004 Mike Wray <mike.wray@hp.com> + +import socket +import Xc +xc = Xc.new() + +import sxp +import XendRoot +xroot = XendRoot.instance() +import XendDB + +import EventServer +eserver = EventServer.instance() + +from xenmgr.server import SrvConsoleServer +xcd = SrvConsoleServer.instance() + +class XendConsoleInfo: + """Console information record. + """ + + def __init__(self, console, dom1, port1, dom2, port2, conn=None): + self.console = console + self.dom1 = dom1 + self.port1 = port1 + self.dom2 = dom2 + self.port2 = port2 + self.conn = conn + #self.id = "%d.%d-%d.%d" % (self.dom1, self.port1, self.dom2, self.port2) + self.id = str(port1) + + def __str__(self): + s = "console" + s += " id=%s" % self.id + s += " src=%d.%d" % (self.dom1, self.port1) + s += " dst=%d.%d" % (self.dom2, self.port2) + s += " port=%s" % self.console + if self.conn: + s += " conn=%s:%s" % (self.conn[0], self.conn[1]) + return s + + def sxpr(self): + sxpr = ['console', + ['id', self.id], + ['src', self.dom1, self.port1], + ['dst', self.dom2, self.port2], + ['port', self.console], + ] + if self.conn: + sxpr.append(['connected', self.conn[0], self.conn[1]]) + return sxpr + + def connection(self): + return self.conn + + def update(self, consinfo): + conn = sxp.child(consinfo, 'connected') + if conn: + self.conn = conn[1:] + else: + self.conn = None + + def uri(self): + """Get the uri to use to connect to the console. + This will be a telnet: uri. + + return uri + """ + host = socket.gethostname() + return "telnet://%s:%s" % (host, self.console) + +class XendConsole: + + dbpath = "console" + + def __init__(self): + self.db = XendDB.XendDB(self.dbpath) + self.console = {} + self.console_db = self.db.fetchall("") + if xroot.get_rebooted(): + print 'XendConsole> rebooted: removing all console info' + self.rm_all() + eserver.subscribe('xend.domain.died', self.onDomainDied) + + def rm_all(self): + """Remove all console info. Used after reboot. + """ + for (k, v) in self.console_db.items(): + self._delete_console(k) + + def refresh(self): + consoles = xcd.consoles() + cons = {} + for consinfo in consoles: + id = str(sxp.child_value(consinfo, 'id')) + cons[id] = consinfo + if id not in self.console: + self._new_console(consinfo) + for c in self.console.values(): + consinfo = cons.get(c.id) + if consinfo: + c.update(consinfo) + else: + self._delete_console(c.id) + + def onDomainDied(self, event, val): + dom = int(val) + for c in self.consoles(): + if (c.dom1 == dom) or (c.dom2 == dom): + self._delete_console(c.id) + + def sync(self): + self.db.saveall("", self.console_db) + + def sync_console(self, id): + self.db.save(id, self.console_db[id]) + + def _new_console(self, consinfo): + # todo: xen needs a call to get current domain id. + dom1 = 0 + port1 = sxp.child_value(consinfo, 'local_port') + dom2 = sxp.child_value(consinfo, 'domain') + port2 = sxp.child_value(consinfo, 'remote_port') + console = sxp.child_value(consinfo, 'console_port') + info = XendConsoleInfo(console, dom1, int(port1), int(dom2), int(port2)) + info.update(consinfo) + self._add_console(info.id, info) + return info + + def _add_console(self, id, info): + self.console[id] = info + self.console_db[id] = info.sxpr() + self.sync_console(id) + + def _delete_console(self, id): + if id in self.console: + del self.console[id] + if id in self.console_db: + del self.console_db[id] + self.db.delete(id) + + def console_ls(self): + self.refresh() + return self.console.keys() + + def consoles(self): + self.refresh() + return self.console.values() + + def console_create(self, dom): + consinfo = xcd.console_create(dom) + info = self._new_console(consinfo) + return info + + def console_get(self, id): + self.refresh() + return self.console.get(id) + + def console_delete(self, id): + self._delete_console(id) + + def console_disconnect(self, id): + id = int(id) + xcd.console_disconnect(id) + +def instance(): + global inst + try: + inst + except: + inst = XendConsole() + return inst diff --git a/tools/xenmgr/lib/XendDB.py b/tools/xenmgr/lib/XendDB.py new file mode 100644 index 0000000000..6a27e65b58 --- /dev/null +++ b/tools/xenmgr/lib/XendDB.py @@ -0,0 +1,91 @@ +# Copyright (C) 2004 Mike Wray <mike.wray@hp.com> + +import os +import os.path +import errno +import dircache +import time + +import sxp +import XendRoot +xroot = XendRoot.instance() + +class XendDB: + """Persistence for Xend. Stores data in files and directories. + """ + + def __init__(self, path=None): + self.dbpath = xroot.get_dbroot() + if path: + self.dbpath = os.path.join(self.dbpath, path) + pass + + def filepath(self, path): + return os.path.join(self.dbpath, path) + + def fetch(self, path): + fpath = self.filepath(path) + return self.fetchfile(fpath) + + def fetchfile(self, fpath): + pin = sxp.Parser() + fin = file(fpath, "rb") + try: + while 1: + try: + buf = fin.read(1024) + except IOError, ex: + if ex.errno == errno.EINTR: + continue + else: + raise + pin.input(buf) + if buf == '': + pin.input_eof() + break + finally: + fin.close() + return pin.get_val() + + def save(self, path, sxpr): + fpath = self.filepath(path) + return self.savefile(fpath, sxpr) + + def savefile(self, fpath, sxpr): + fdir = os.path.dirname(fpath) + if not os.path.isdir(fdir): + os.makedirs(fdir) + fout = file(fpath, "wb+") + try: + t = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + fout.write("# %s %s\n" % (fpath, t)) + sxp.show(sxpr, out=fout) + finally: + fout.close() + + def fetchall(self, path): + dpath = self.filepath(path) + d = {} + for k in dircache.listdir(dpath): + try: + v = self.fetchfile(os.path.join(dpath, k)) + d[k] = v + except: + pass + return d + + def saveall(self, path, d): + for (k, v) in d.items(): + self.save(os.path.join(path, k), v) + + def delete(self, path): + dpath = self.filepath(path) + os.unlink(dpath) + + def ls(self, path): + dpath = self.filepath(path) + return dircache.listdir(dpath) + + + + diff --git a/tools/xenmgr/lib/XendDomain.py b/tools/xenmgr/lib/XendDomain.py new file mode 100644 index 0000000000..60e207da65 --- /dev/null +++ b/tools/xenmgr/lib/XendDomain.py @@ -0,0 +1,347 @@ +# Copyright (C) 2004 Mike Wray <mike.wray@hp.com> + +"""Handler for domain operations. + Nothing here is persistent (across reboots). + Needs to be persistent for one uptime. +""" +import sys + +import Xc; xc = Xc.new() +import xenctl.ip +import xenctl.vdisk + +import sxp +import XendRoot +xroot = XendRoot.instance() +import XendDB +import XendDomainInfo +import XendConsole +import EventServer + +eserver = EventServer.instance() + +__all__ = [ "XendDomain" ] + +class XendDomain: + """Index of all domains. Singleton. + """ + + dbpath = "domain" + domain = {} + + def __init__(self): + self.xconsole = XendConsole.instance() + # Table of domain info indexed by domain id. + self.db = XendDB.XendDB(self.dbpath) + #self.domain = {} + self.domain_db = self.db.fetchall("") + if xroot.get_rebooted(): + print 'XendDomain> rebooted: removing all domain info' + self.rm_all() + self.initial_refresh() + + def rm_all(self): + """Remove all domain info. Used after reboot. + """ + for (k, v) in self.domain_db.items(): + self._delete_domain(k, notify=0) + + def initial_refresh(self): + """Refresh initial domain info from domain_db. + """ + domlist = xc.domain_getinfo() + doms = {} + for d in domlist: + domid = str(d['dom']) + doms[domid] = d + for config in self.domain_db.values(): + domid = int(sxp.child_value(config, 'id')) + if domid in doms: + self._new_domain(config) + else: + self._delete_domain(domid) + self.refresh() + + def sync(self): + """Sync domain db to disk. + """ + self.db.saveall("", self.domain_db) + + def sync_domain(self, dom): + """Sync info for a domain to disk. + + dom domain id (string) + """ + self.db.save(dom, self.domain_db[dom]) + + def close(self): + pass + + def _new_domain(self, info): + """Create a domain entry from saved info. + """ + console = None + kernel = None + id = sxp.child_value(info, 'id') + dom = int(id) + name = sxp.child_value(info, 'name') + memory = int(sxp.child_value(info, 'memory')) + consoleinfo = sxp.child(info, 'console') + if consoleinfo: + consoleid = sxp.child_value(consoleinfo, 'id') + console = self.xconsole.console_get(consoleid) + if dom and console is None: + # Try to connect a console. + console = self.xconsole.console_create(dom) + config = sxp.child(info, 'config') + if config: + image = sxp.child(info, 'image') + if image: + image = sxp.child0(image) + kernel = sxp.child_value(image, 'kernel') + dominfo = XendDomainInfo.XendDomainInfo( + config, dom, name, memory, kernel, console) + self.domain[id] = dominfo + + def _add_domain(self, id, info, notify=1): + self.domain[id] = info + self.domain_db[id] = info.sxpr() + self.sync_domain(id) + if notify: eserver.inject('xend.domain.created', id) + + def _delete_domain(self, id, notify=1): + if id in self.domain: + if notify: eserver.inject('xend.domain.died', id) + del self.domain[id] + if id in self.domain_db: + del self.domain_db[id] + self.db.delete(id) + + def refresh(self): + """Refresh domain list from Xen. + """ + domlist = xc.domain_getinfo() + # Index the domlist by id. + # Add entries for any domains we don't know about. + doms = {} + for d in domlist: + id = str(d['dom']) + doms[id] = d + if id not in self.domain: + config = None + image = None + newinfo = XendDomainInfo.XendDomainInfo( + config, d['dom'], d['name'], d['mem_kb']/1024, image) + self._add_domain(id, newinfo) + # Remove entries for domains that no longer exist. + for d in self.domain.values(): + dominfo = doms.get(d.id) + if dominfo: + d.update(dominfo) + else: + self._delete_domain(d.id) + + def refresh_domain(self, id): + dom = int(id) + dominfo = xc.domain_getinfo(dom, 1) + if dominfo == [] or dominfo[0]['dom'] != dom: + try: + self._delete_domain(id) + except: + pass + else: + d = self.domain.get(id) + if d: + d.update(dominfo) + + def domain_ls(self): + # List domains. + # Update info from kernel first. + self.refresh() + return self.domain.keys() + + def domains(self): + self.refresh() + return self.domain.values() + + def domain_create(self, config): + # Create domain, log it. + deferred = XendDomainInfo.vm_create(config) + def fn(dominfo): + self._add_domain(dominfo.id, dominfo) + return dominfo + deferred.addCallback(fn) + return deferred + + def domain_get(self, id): + id = str(id) + self.refresh_domain(id) + return self.domain[id] + + def domain_start(self, id): + """Start domain running. + """ + dom = int(id) + eserver.inject('xend.domain.start', id) + return xc.domain_start(dom=dom) + + def domain_stop(self, id): + """Stop domain running. + """ + dom = int(id) + return xc.domain_stop(dom=dom) + + def domain_shutdown(self, id): + """Shutdown domain (nicely). + """ + dom = int(id) + if dom <= 0: + return 0 + eserver.inject('xend.domain.shutdown', id) + val = xc.domain_destroy(dom=dom, force=0) + self.refresh() + return val + + def domain_halt(self, id): + """Shutdown domain immediately. + """ + dom = int(id) + if dom <= 0: + return 0 + eserver.inject('xend.domain.halt', id) + val = xc.domain_destroy(dom=dom, force=1) + self.refresh() + return val + + def domain_migrate(self, id, dst): + """Start domain migration. + """ + # Need a cancel too? + pass + + def domain_save(self, id, dst, progress=0): + """Save domain state to file, halt domain. + """ + dom = int(id) + self.domain_stop(id) + eserver.inject('xend.domain.save', id) + rc = xc.linux_save(dom=dom, state_file=dst, progress=progress) + if rc == 0: + self.domain_halt(id) + return rc + + def domain_restore(self, src, config, progress=0): + """Restore domain from file. + """ + dominfo = XendDomainInfo.dom_restore(dom, config) + self._add_domain(dominfo.id, dominfo) + return dominfo + + def domain_device_add(self, id, info): + """Add a device to a domain. + """ + pass + + def domain_device_remove(self, id, dev): + """Delete a device from a domain. + """ + pass + + def domain_device_configure(self, id, dev, info): + """Configure a domain device. + """ + pass + + #============================================================================ + # Backward compatibility stuff from here on. + + def domain_pincpu(self, dom, cpu): + dom = int(dom) + return xc.domain_pincpu(dom, cpu) + + def domain_cpu_bvt_set(self, dom, mcuadv, warp, warpl, warpu): + dom = int(dom) + return xc.bvtsched_domain_set(dom=dom, mcuadv=mcuadv, + warp=warp, warpl=warpl, warpu=warpu) + + def domain_cpu_bvt_get(self, dom): + dom = int(dom) + return xc.bvtsched_domain_get(dom) + + def domain_cpu_atropos_set(self, dom, period, slice, latency, xtratime): + dom = int(dom) + return xc.atropos_domain_set(dom, period, slice, latency, xtratime) + + def domain_cpu_atropos_get(self, dom): + dom = int(dom) + return xc.atropos_domain_get(dom) + + def domain_vif_ls(self, dom): + dominfo = self.domain_get(dom) + if not dominfo: return None + devs = dominfo.get_devices('vif') + return range(0, len(devs)) + + def domain_vif_get(self, dom, vif): + dominfo = self.domain_get(dom) + if not dominfo: return None + return dominfo.get_device_by_index(vif) + + def domain_vif_stats(self, dom, vif): + dom = int(dom) + return xc.vif_stats_get(dom=dom, vif=vif) + + def domain_vif_ip_add(self, dom, vif, ip): + dom = int(dom) + return xenctl.ip.setup_vfr_rules_for_vif(dom, vif, ip) + + def domain_vif_scheduler_set(self, dom, vif, bytes, usecs): + dom = int(dom) + return xc.xc_vif_scheduler_set(dom=dom, vif=vif, + credit_bytes=bytes, credit_usecs=usecs) + + def domain_vif_scheduler_get(self, dom, vif): + dom = int(dom) + return xc.vif_scheduler_get(dom=dom, vif=vif) + + def domain_vbd_ls(self, dom): + dominfo = self.domain_get(dom) + if not dominfo: return [] + devs = dominfo.get_devices('vbd') + return [ sxp.child_value(v, 'dev') for v in devs ] + + def domain_vbd_get(self, dom, vbd): + dominfo = self.domain_get(dom) + if not dominfo: return None + devs = dominfo.get_devices('vbd') + for v in devs: + if sxp.child_value(v, 'dev') == vbd: + return v + return None + + def domain_vbd_add(self, dom, uname, dev, mode): + dom = int(dom) + vbd = vm.make_disk(dom, uname, dev, mode) + return vbd + + def domain_vbd_remove(self, dom, dev): + dom = int(dom) + vbd = xenctl.vdisk.blkdev_name_to_number(dev) + if vbd < 0: return vbd + err = xc.vbd_destroy(dom, vbd) + if err < 0: return err + return vbd + + def domain_shadow_control(self, dom, op): + dom = int(dom) + return xc.shadow_control(dom, op) + + #============================================================================ + +def instance(): + global inst + try: + inst + except: + inst = XendDomain() + return inst diff --git a/tools/xenmgr/lib/XendDomainConfig.py b/tools/xenmgr/lib/XendDomainConfig.py new file mode 100644 index 0000000000..35db31ff51 --- /dev/null +++ b/tools/xenmgr/lib/XendDomainConfig.py @@ -0,0 +1,44 @@ +# Copyright (C) 2004 Mike Wray <mike.wray@hp.com> + +"""Handler for persistent domain configs. + +""" + +import sxp +import XendDB +import XendDomain + +__all__ = [ "XendDomainConfig" ] + +class XendDomainConfig: + + dbpath = 'config' + + def __init__(self): + self.db = XendDB.XendDB(self.dbpath) + + def domain_config_ls(self, path): + return self.db.ls(path) + + def domain_config_create(self, path, sxpr): + self.db.save(path, sxpr) + pass + + def domain_config_delete(self, path): + self.db.delete(path) + + def domain_config_instance(self, path): + """Create a domain from a config. + """ + config = self.db.fetch(path) + xd = XendDomain.instance() + newdom = xd.domain_create(config) + return newdom + +def instance(): + global inst + try: + inst + except: + inst = XendDomainConfig() + return inst diff --git a/tools/xenmgr/lib/XendDomainInfo.py b/tools/xenmgr/lib/XendDomainInfo.py new file mode 100644 index 0000000000..efa56d7f32 --- /dev/null +++ b/tools/xenmgr/lib/XendDomainInfo.py @@ -0,0 +1,697 @@ +#!/usr/bin/python +# Copyright (C) 2004 Mike Wray <mike.wray@hp.com> + +"""Representation of a single domain. +Includes support for domain construction, using +open-ended configurations. + +Author: Mike Wray <mike.wray@hpl.hp.com> + +""" + +import sys +import os + +from twisted.internet import defer + +import Xc; xc = Xc.new() + +import xenctl.ip +import xenctl.vdisk + +import sxp + +import XendConsole +xendConsole = XendConsole.instance() + +import server.SrvConsoleServer +xend = server.SrvConsoleServer.instance() + +class VmError(ValueError): + """Vm construction error.""" + + def __init__(self, value): + self.value = value + + def __str__(self): + return self.value + + +class XendDomainInfo: + """Virtual machine object.""" + + def __init__(self, config, dom, name, memory, image=None, console=None): + """Construct a virtual machine object. + + config configuration + dom domain id + name name + memory memory size (in MB) + image image object + """ + #todo: add info: runtime, state, ... + self.config = config + self.id = str(dom) + self.dom = dom + self.name = name + self.memory = memory + self.image = image + self.console = console + self.devices = {} + self.configs = [] + self.info = None + + #todo: state: running, suspended + self.state = 'running' + #todo: set to migrate info if migrating + self.migrate = None + + def update(self, info): + """Update with info from xc.domain_getinfo(). + """ + self.info = info + + def __str__(self): + s = "domain" + s += " id=" + self.id + s += " name=" + self.name + s += " memory=" + str(self.memory) + if self.console: + s += " console=" + self.console.id + if self.image: + s += " image=" + self.image + s += "" + return s + + __repr__ = __str__ + + def sxpr(self): + sxpr = ['domain', + ['id', self.id], + ['name', self.name], + ['memory', self.memory] ] + if self.info: + run = (self.info['running'] and 'r') or '-' + stop = (self.info['stopped'] and 's') or '-' + state = run + state + sxpr.append(['cpu', self.info['cpu']]) + sxpr.append(['state', state]) + sxpr.append(['cpu_time', self.info['cpu_time']/1e8]) + if self.console: + sxpr.append(self.console.sxpr()) + if self.config: + sxpr.append(['config', self.config]) + return sxpr + + def add_device(self, type, dev): + """Add a device to a virtual machine. + + dev device to add + """ + dl = self.devices.get(type, []) + dl.append(dev) + self.devices[type] = dl + + def get_devices(self, type): + val = self.devices.get(type, []) + print 'get_devices', type; sxp.show(val); print + return val + + def get_device_by_id(self, type, id): + """Get the device with the given id. + + id device id + + returns device or None + """ + return sxp.child_with_id(self.get_devices(type), id) + + def get_device_by_index(self, type, idx): + dl = self.get_devices(type) + if 0 <= idx < len(dl): + return dl[idx] + else: + return None + + def add_config(self, val): + """Add configuration data to a virtual machine. + + val data to add + """ + self.configs.append(val) + + def destroy(self): + if self.dom <= 0: + return 0 + return xc.domain_destroy(dom=self.dom, force=1) + + def show(self): + """Print virtual machine info. + """ + print "[VM dom=%d name=%s memory=%d" % (self.dom, self.name, self.memory) + print "image:" + sxp.show(self.image) + print + for dl in self.devices: + for dev in dl: + print "device:" + sxp.show(dev) + print + for val in self.configs: + print "config:" + sxp.show(val) + print + print "]" + +def safety_level(sharing): + if sharing == 'rw': + return xenctl.vdisk.VBD_SAFETY_RW + if sharing == 'ww': + return xenctl.vdisk.VBD_SAFETY_WW + return xenctl.vdisk.VBD_SAFETY_RR + + +def make_disk_old(dom, uname, dev, mode, sharing): + writeable = ('w' in mode) + safety = safety_level(sharing) + vbd = xenctl.vdisk.blkdev_name_to_number(dev) + extents = xenctl.vdisk.lookup_disk_uname(uname) + if not extents: + raise VmError("vbd: Extents not found: uname=%s" % uname) + + # check that setting up this VBD won't violate the sharing + # allowed by the current VBD expertise level + if xenctl.vdisk.vd_extents_validate(extents, writeable, safety=safety) < 0: + raise VmError("vbd: Extents invalid: uname=%s" % uname) + + if xc.vbd_create(dom=dom, vbd=vbd, writeable=writeable): + raise VmError("vbd: Creating device failed: dom=%d uname=%s vbd=%d mode=%s" + % (dom, uname, vbdmode)) + + if xc.vbd_setextents(dom=dom, vbd=vbd, extents=extents): + raise VMError("vbd: Setting extents failed: dom=%d uname=%s vbd=%d" + % (dom, uname, vbd)) + return vbd + +def make_disk(dom, uname, dev, mode, sharing): + """Create a virtual disk device for a domain. + + @returns Deferred + """ + segments = xenctl.vdisk.lookup_disk_uname(uname) + if not segments: + raise VmError("vbd: Segments not found: uname=%s" % uname) + if len(segments) > 1: + raise VmError("vbd: Multi-segment vdisk: uname=%s" % uname) + segment = segments[0] + vdev = xenctl.vdisk.blkdev_name_to_number(dev) + ctrl = xend.blkif_create(dom) + + def fn(ctrl): + return xend.blkif_dev_create(dom, vdev, mode, segment) + ctrl.addCallback(fn) + return ctrl + +def make_vif_old(dom, vif, vmac, vnet): + return # todo: Not supported yet. + err = xc.vif_setinfo(dom=dom, vif=vif, vmac=vmac, vnet=vnet) + if err < 0: + raise VmError('vnet: Error %d setting vif mac dom=%d vif=%d vmac=%s vnet=%d' % + (err, dom, vif, vmac, vnet)) + +def make_vif(dom, vif, vmac): + """Create a virtual network device for a domain. + + + @returns Deferred + """ + xend.netif_create(dom) + d = xend.netif_dev_create(dom, vif, vmac) + return d + +def vif_up(iplist): + #todo: Need a better way. + # send an unsolicited ARP reply for all non link-local IPs + + IP_NONLOCAL_BIND = '/proc/sys/net/ipv4/ip_nonlocal_bind' + + def get_ip_nonlocal_bind(): + return int(open(IP_NONLOCAL_BIND, 'r').read()[0]) + + def set_ip_nonlocal_bind(v): + print >> open(IP_NONLOCAL_BIND, 'w'), str(v) + + def link_local(ip): + return xenctl.ip.check_subnet(ip, '169.254.0.0', '255.255.0.0') + + def arping(ip, gw): + cmd = '/usr/sbin/arping -A -b -I eth0 -c 1 -s %s %s' % (ip, gw) + print cmd + os.system(cmd) + + gateway = xenctl.ip.get_current_ipgw() or '255.255.255.255' + nlb = get_ip_nonlocal_bind() + if not nlb: set_ip_nonlocal_bind(1) + try: + for ip in iplist: + if not link_local(ip): + arping(ip, gateway) + finally: + if not nlb: set_ip_nonlocal_bind(0) + +def xen_domain_create(config, ostype, name, memory, kernel, ramdisk, cmdline, vifs_n): + 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) + + cpu = int(sxp.child_value(config, 'cpu', '-1')) + dom = xc.domain_create(mem_kb= memory * 1024, name= name, cpu= cpu) + if dom <= 0: + raise VmError('Creating domain failed: name=%s memory=%d kernel=%s' + % (name, memory, kernel)) + console = xendConsole.console_create(dom) + buildfn = getattr(xc, '%s_build' % ostype) + err = buildfn(dom = dom, + image = kernel, + control_evtchn = console.port2, + cmdline = cmdline, + ramdisk = ramdisk) + if err != 0: + raise VmError('Building domain failed: type=%s dom=%d err=%d' + % (ostype, dom, err)) + vm = XendDomainInfo(config, dom, name, memory, kernel, console) + return vm + +config_handlers = {} + +def add_config_handler(name, h): + """Add a handler for a config field. + + name field name + h handler: fn(vm, config, field, index) + """ + config_handlers[name] = h + +def get_config_handler(name): + """Get a handler for a config field. + + returns handler or None + """ + 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 + name image type + h handler: fn(config, name, memory, image) + """ + image_handlers[name] = h + +def get_image_handler(name): + """Get the handler for an image type. + name image type + + returns handler or None + """ + return image_handlers.get(name) + +"""Table of handlers for devices. +Indexed by device type. +""" +device_handlers = {} + +def add_device_handler(name, h): + """Add a handler for a device type. + + name device type + h handler: fn(vm, dev) + """ + device_handlers[name] = h + +def get_device_handler(name): + """Get the handler for a device type. + + name device type + + returns handler or None + """ + return device_handlers.get(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. + + config configuration + + returns Deferred + raises VmError for invalid configuration + """ + # todo - add support for scheduling params? + print 'vm_create>' + xenctl.vdisk.VBD_EXPERT_MODE = 0 + vm = None + try: + name = sxp.child_value(config, 'name') + memory = int(sxp.child_value(config, 'memory', '128')) + image = sxp.child_value(config, 'image') + + image_name = sxp.name(image) + image_handler = get_image_handler(image_name) + if image_handler is None: + raise VmError('unknown image type: ' + image_name) + vm = image_handler(config, name, memory, image) + deferred = vm_configure(vm, config) + except StandardError, ex: + # Catch errors, cleanup and re-raise. + if vm: + vm.destroy() + raise + def cbok(x): + print 'vm_create> cbok', x + return x + deferred.addCallback(cbok) + print 'vm_create<' + return deferred + +def vm_restore(src, config, progress=0): + ostype = "linux" #todo set from config + restorefn = getattr(xc, "%s_restore" % ostype) + dom = restorefn(state_file=src, progress=progress) + if dom < 0: return dom + deferred = dom_configure(dom, config) + return deferred + +def dom_get(dom): + domlist = xc.domain_getinfo(dom=dom) + if domlist and dom == domlist[0]['dom']: + return domlist[0] + return None + +def dom_configure(dom, config): + d = dom_get(dom) + if not d: + raise VMError("Domain not found: %d" % dom) + try: + name = d['name'] + memory = d['memory']/1024 + image = None + vm = VM(config, dom, name, memory, image) + deferred = vm_configure(vm, config) + except StandardError, ex: + if vm: + vm.destroy() + raise + return deferred + +def append_deferred(dlist, v): + if isinstance(v, defer.Deferred): + dlist.append(v) + +def vm_create_devices(vm, config): + """Create the devices for a vm. + + vm virtual machine + config configuration + + returns Deferred + raises VmError for invalid devices + """ + print '>vm_create_devices' + dlist = [] + devices = sxp.children(config, 'device') + index = {} + for d in devices: + dev = sxp.child0(d) + if dev is None: + raise VmError('invalid device') + dev_name = sxp.name(dev) + dev_index = index.get(dev_name, 0) + dev_handler = get_device_handler(dev_name) + if dev_handler is None: + raise VmError('unknown device type: ' + dev_name) + v = dev_handler(vm, dev, dev_index) + append_deferred(dlist, v) + index[dev_name] = dev_index + 1 + deferred = defer.DeferredList(dlist, fireOnOneErrback=1) + print '<vm_create_devices' + return deferred + +def vm_configure(vm, config): + """Configure a vm. + + vm virtual machine + config configuration + + returns Deferred - calls callback with vm + """ + d = xend.blkif_create(vm.dom) + d.addCallback(_vm_configure1, vm, config) + return d + +def _vm_configure1(val, vm, config): + d = vm_create_devices(vm, config) + print '_vm_configure1> made devices...' + def cbok(x): + print '_vm_configure1> cbok', x + return x + d.addCallback(cbok) + d.addCallback(_vm_configure2, vm, config) + print '_vm_configure1<' + return d + +def _vm_configure2(val, vm, config): + print '>callback _vm_configure2...' + dlist = [] + index = {} + for field in sxp.children(config): + field_name = sxp.name(field) + field_index = index.get(field_name, 0) + field_handler = get_config_handler(field_name) + # Ignore unknown fields. Warn? + if field_handler: + v = field_handler(vm, config, field, field_index) + append_deferred(dlist, v) + index[field_name] = field_index + 1 + d = defer.DeferredList(dlist, fireOnOneErrback=1) + def cbok(results): + print '_vm_configure2> cbok', results + return vm + def cberr(err): + print '_vm_configure2> cberr', err + vm.destroy() + return err + d.addCallback(cbok) + d.addErrback(cberr) + print '<_vm_configure2' + return d + +def config_devices(config, name): + """Get a list of the 'device' nodes of a given type from a config. + + config configuration + name device type + return list of device configs + """ + devices = [] + for d in sxp.children(config, 'device'): + dev = sxp.child0(d) + if dev is None: continue + if name == sxp.name(dev): + devices.append(dev) + return devices + +def vm_image_linux(config, name, memory, image): + """Create a VM for a linux 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", '') + vifs = config_devices(config, "vif") + vm = xen_domain_create(config, "linux", name, memory, kernel, + ramdisk, cmdline, len(vifs)) + return vm + +def vm_image_netbsd(config, name, memory, image): + """Create a VM for a bsd image. + + name vm name + memory vm memory + image image config + + returns vm + """ + #todo: Same as for linux. Is that right? If so can unify them. + 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") + vifs = config_devices(config, "vif") + vm = xen_domain_create(config, "netbsd", name, memory, kernel, + ramdisk, cmdline, len(vifs)) + return vm + + +def vm_dev_vif(vm, val, index): + """Create a virtual network interface (vif). + + vm virtual machine + val vif config + index vif index + """ + vif = index #todo + vmac = sxp.child_value(val, "mac") + defer = make_vif(vm.dom, vif, vmac) + def fn(id): + dev = val + ['vif', vif] + vm.add_device('vif', dev) + print 'vm_dev_vif> created', dev + return id + defer.addCallback(fn) + return defer + +def vm_dev_vbd(vm, val, index): + """Create a virtual block device (vbd). + + vm virtual machine + val vbd config + index vbd index + """ + uname = sxp.child_value(val, 'uname') + if not uname: + raise VMError('vbd: Missing uname') + dev = sxp.child_value(val, 'dev') + if not dev: + raise VMError('vbd: Missing dev') + mode = sxp.child_value(val, 'mode', 'r') + sharing = sxp.child_value(val, 'sharing', 'rr') + defer = make_disk(vm.dom, uname, dev, mode, sharing) + def fn(vbd): + vm.add_device('vbd', val) + return vbd + defer.addCallback(fn) + return defer + +def vm_dev_pci(vm, val, index): + bus = sxp.child_value(val, 'bus') + if not bus: + raise VMError('pci: Missing bus') + dev = sxp.child_value(val, 'dev') + if not dev: + raise VMError('pci: Missing dev') + func = sxp.child_value(val, 'func') + if not func: + raise VMError('pci: Missing func') + rc = xc.physdev_pci_access_modify(dom=vm.dom, bus=bus, dev=dev, func=func, enable=1) + if rc < 0: + #todo non-fatal + raise VMError('pci: Failed to configure device: bus=%s dev=%s func=%s' % + (bus, dev, func)) + return rc + + +def vm_field_vfr(vm, config, val, index): + """Handle a vfr field in a config. + + vm virtual machine + config vm config + val vfr field + """ + # Get the rules and add them. + # (vfr (vif (id foo) (ip x.x.x.x)) ... ) + list = sxp.children(val, 'vif') + for v in list: + id = sxp.child_value(v, 'id') + if id is None: + raise VmError('vfr: missing vif id') + id = int(id) + dev = vm.get_device_by_index('vif', id) + if not dev: + raise VmError('vfr: invalid vif id %d' % id) + vif = sxp.child_value(dev, 'vif') + ip = sxp.child_value(v, 'ip') + if not ip: + raise VmError('vfr: missing ip address') + #Don't do this in new i/o model. + #print 'vm_field_vfr> add rule', 'dom=', vm.dom, 'vif=', vif, 'ip=', ip + #xenctl.ip.setup_vfr_rules_for_vif(vm.dom, vif, ip) + +def vnet_bridge(vnet, vmac, dom, idx): + """Add the device for the vif to the bridge for its vnet. + """ + vif = "vif%d.%d" % (dom, idx) + try: + cmd = "(vif.conn (vif %s) (vnet %s) (vmac %s))" % (vif, vnet, vmac) + print "*** vnet_bridge>", cmd + out = file("/proc/vnet/policy", "wb") + out.write(cmd) + err = out.close() + print "vnet_bridge>", "err=", err + except IOError, ex: + print "vnet_bridge>", ex + +def vm_field_vnet(vm, config, val, index): + """Handle a vnet field in a config. + + vm virtual machine + config vm config + val vnet field + index index + """ + # Get the vif children. For each vif look up the vif device + # with the given id and configure its vnet. + # (vnet (vif (id foo) (vnet 2) (mac x:x:x:x:x:x)) ... ) + vif_vnets = sxp.children(val, 'vif') + for v in vif_vnets: + id = sxp.child_value(v, 'id') + if id is None: + raise VmError('vnet: missing vif id') + dev = vm.get_device_by_id('vif', id) + if not sxp.elementp(dev, 'vif'): + raise VmError('vnet: invalid vif id %s' % id) + vnet = sxp.child_value(v, 'vnet', 1) + mac = sxp.child_value(dev, 'mac') + vif = sxp.child_value(dev, 'vif') + vnet_bridge(vnet, mac, vm.dom, 0) + vm.add_config([ 'vif.vnet', ['id', id], ['vnet', vnet], ['mac', mac]]) + +# Register image handlers for linux and bsd. +add_image_handler('linux', vm_image_linux) +add_image_handler('netbsd', vm_image_netbsd) + +# Register device handlers for vifs and vbds. +add_device_handler('vif', vm_dev_vif) +add_device_handler('vbd', vm_dev_vbd) +add_device_handler('pci', vm_dev_pci) + +# Register config handlers for vfr and vnet. +add_config_handler('vfr', vm_field_vfr) +add_config_handler('vnet', vm_field_vnet) diff --git a/tools/xenmgr/lib/XendMigrate.py b/tools/xenmgr/lib/XendMigrate.py new file mode 100644 index 0000000000..1580ba83ed --- /dev/null +++ b/tools/xenmgr/lib/XendMigrate.py @@ -0,0 +1,103 @@ +# Copyright (C) 2004 Mike Wray <mike.wray@hp.com> + +import sys +import socket + +import sxp +import XendDB +import EventServer; eserver = EventServer.instance() + +class XendMigrateInfo: + + # states: begin, active, failed, succeeded? + + def __init__(self, id, dom, dst): + self.id = id + self.state = 'begin' + self.src_host = socket.gethostname() + self.src_dom = dom + self.dst_host = dst + self.dst_dom = None + + def set_state(self, state): + self.state = state + + def get_state(self): + return self.state + + def sxpr(self): + sxpr = ['migrate', ['id', self.id], ['state', self.state] ] + sxpr_src = ['src', ['host', self.src_host], ['domain', self.src_dom] ] + sxpr.append(sxpr_src) + sxpr_dst = ['dst', ['host', self.dst] ] + if self.dst_dom: + sxpr_dst.append(['domain', self.dst_dom]) + sxpr.append(sxpr_dst) + return sxpr + + +class XendMigrate: + # Represents migration in progress. + # Use log for indications of begin/end/errors? + # Need logging of: domain create/halt, migrate begin/end/fail + # Log via event server? + + dbpath = "migrate" + + def __init__(self): + self.db = XendDB.XendDB(self.dbpath) + self.migrate = {} + self.migrate_db = self.db.fetchall("") + self.id = 0 + + def nextid(self): + self.id += 1 + return "%d" % self.id + + def sync(self): + self.db.saveall("", self.migrate_db) + + def sync_migrate(self, id): + self.db.save(id, self.migrate_db[id]) + + def close(self): + pass + + def _add_migrate(self, id, info): + self.migrate[id] = info + self.migrate_db[id] = info.sxpr() + self.sync_migrate(id) + #eserver.inject('xend.migrate.begin', info.sxpr()) + + def _delete_migrate(self, id): + #eserver.inject('xend.migrate.end', id) + del self.migrate[id] + del self.migrate_db[id] + self.db.delete(id) + + def migrate_ls(self): + return self.migrate.keys() + + def migrates(self): + return self.migrate.values() + + def migrate_get(self, id): + return self.migrate.get(id) + + def migrate_begin(self, dom, dst): + # Check dom for existence, not migrating already. + # Create migrate info, tell xend to migrate it? + # - or fork migrate command ourselves? + # Subscribe to migrate notifications (for updating). + id = self.nextid() + info = XenMigrateInfo(id, dom, dst) + self._add_migrate(id, info) + return id + +def instance(): + global inst + try: + inst + except: + inst = XendMigrate() + return inst diff --git a/tools/xenmgr/lib/XendNode.py b/tools/xenmgr/lib/XendNode.py new file mode 100644 index 0000000000..9be3d642bc --- /dev/null +++ b/tools/xenmgr/lib/XendNode.py @@ -0,0 +1,55 @@ +# Copyright (C) 2004 Mike Wray <mike.wray@hp.com> + +"""Handler for node operations. + Has some persistent state: + - logs + - notification urls + +""" + +import Xc + +class XendNodeInfo: + """Node information record. + """ + + def __init__(self): + pass + +class XendNode: + + def __init__(self): + self.xc = Xc.new() + + def shutdown(self): + return 0 + + def reboot(self): + return 0 + + def notify(self, uri): + return 0 + + def cpu_bvt_slice_set(self, slice): + ret = 0 + #ret = self.xc.bvtsched_global_set(ctx_allow=slice) + return ret + + def cpu_bvt_slice_get(self, slice): + ret = 0 + #ret = self.xc.bvtsched_global_get() + return ret + + def cpu_rrobin_slice_set(self, slice): + ret = 0 + #ret = self.xc.rrobin_global_set(slice) + return ret + +def instance(): + global inst + try: + inst + except: + inst = XendNode() + return inst + diff --git a/tools/xenmgr/lib/XendRoot.py b/tools/xenmgr/lib/XendRoot.py new file mode 100644 index 0000000000..6d9903a91d --- /dev/null +++ b/tools/xenmgr/lib/XendRoot.py @@ -0,0 +1,156 @@ +# Copyright (C) 2004 Mike Wray <mike.wray@hp.com> + +"""Xend root class. +Creates the event server and handles configuration. +""" + +import os +import os.path +import sys +import EventServer + +# Initial create of the event server. +eserver = EventServer.instance() + +import sxp + +def reboots(): + """Get a list of system reboots from wtmp. + """ + out = os.popen('/usr/bin/last reboot', 'r') + list = [ x.strip() for x in out if x.startswith('reboot') ] + return list + +def last_reboot(): + """Get the last known system reboot. + """ + l = reboots() + return (l and l[-1]) or None + +class XendRoot: + """Root of the management classes.""" + + lastboot_default = "/etc/xen/xend/lastboot" + + """Default path to the root of the database.""" + dbroot_default = "/etc/xen/xend/xenmgr-db" + + """Default path to the config file.""" + config_default = "/etc/xen/xenmgr-config.sxp" + + """Environment variable used to override config_default.""" + config_var = "XEND_CONFIG" + + def __init__(self): + self.rebooted = 0 + self.last_reboot = None + self.dbroot = None + self.config_path = None + self.config = None + self.configure() + self.check_lastboot() + eserver.subscribe('xend.*', self.event_handler) + #eserver.subscribe('xend.domain.created', self.event_handler) + #eserver.subscribe('xend.domain.died', self.event_handler) + + def start(self): + eserver.inject('xend.start', self.rebooted) + + def event_handler(self, event, val): + print >> sys.stderr, "EVENT>", event, val + + def read_lastboot(self): + try: + val = file(self.lastboot, 'rb').readlines()[0] + except StandardError, ex: + print 'warning: Error reading', self.lastboot, ex + val = None + return val + + def write_lastboot(self, val): + if not val: return + try: + fdir = os.path.dirname(self.lastboot) + if not os.path.isdir(fdir): + os.makedirs(fdir) + out = file(self.lastboot, 'wb+') + out.write(val) + out.close() + except IOError, ex: + print 'warning: Error writing', self.lastboot, ex + pass + + def check_lastboot(self): + """Check if there has been a system reboot since we saved lastboot. + """ + last_val = self.read_lastboot() + this_val = last_reboot() + if this_val == last_val: + self.rebooted = 0 + else: + self.rebooted = 1 + self.write_lastboot(this_val) + self.last_reboot = this_val + + def get_last_reboot(self): + return self.last_reboot + + def get_rebooted(self): + return self.rebooted + + def configure(self): + self.set_config() + self.dbroot = self.get_config_value("dbroot", self.dbroot_default) + self.lastboot = self.get_config_value("lastboot", self.lastboot_default) + + 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. + + The config file is a sequence of sxp forms. + """ + self.config_path = os.getenv(self.config_var, self.config_default) + if os.path.exists(self.config_path): + fin = file(self.config_path, 'rb') + try: + config = sxp.parse(fin) + config.insert(0, 'config') + self.config = config + finally: + fin.close() + else: + self.config = ['config'] + + def get_config(self, name=None): + """Get the configuration element with the given name, or + the whole configuration if no name is given. + + name element name (optional) + returns config or none + """ + if name is None: + val = self.config + else: + val = sxp.child(self.config, name) + return val + + def get_config_value(self, name, val=None): + """Get the value of an atomic configuration element. + + name element name + val default value (optional, defaults to None) + returns value + """ + return sxp.child_value(self.config, name, val=val) + +def instance(): + global inst + try: + inst + except: + inst = XendRoot() + return inst diff --git a/tools/xenmgr/lib/XendVdisk.py b/tools/xenmgr/lib/XendVdisk.py new file mode 100644 index 0000000000..241d29be87 --- /dev/null +++ b/tools/xenmgr/lib/XendVdisk.py @@ -0,0 +1,111 @@ +# Copyright (C) 2004 Mike Wray <mike.wray@hp.com> + +"""Handler for vdisk operations. + +""" + +import os +import os.path + +from xenctl import vdisk + +import sxp + +class XendVdiskInfo: + + def __init__(self, info): + self.info = info + self.id = info['vdisk_id'] + + def __str__(self): + return ("vdisk id=%(vdisk_id)s size=%(size)d expires=%(expires)d expiry_time=%(expiry_time)d" + % self.info) + + def sxpr(self): + val = ['vdisk'] + for (k,v) in self.info.items(): + val.append([k, str(v)]) + return val + +class XendVdisk: + """Index of all vdisks. Singleton. + """ + + dbpath = "vdisk" + + def __init__(self): + # Table of vdisk info indexed by vdisk id. + self.vdisk = {} + if not os.path.isfile(vdisk.VD_DB_FILE): + vdisk.vd_init_db(vdisk.VD_DB_FILE) + self.vdisk_refresh() + + def vdisk_refresh(self): + # vdisk = {vdisk_id, size, expires, expiry_time} + try: + vdisks = vdisk.vd_list() + except: + vdisks = [] + for vdisk in vdisks: + vdiskinfo = XendVdiskInfo(vdisk) + self.vdisk[vdiskinfo.id] = vdiskinfo + + def vdisk_ls(self): + """List all vdisk ids. + """ + return self.vdisk.keys() + + def vdisks(self): + return self.vdisk.values() + + def vdisk_get(self, id): + """Get a vdisk. + + id vdisk id + """ + return self.vdisk.get(id) + + def vdisk_create(self, info): + """Create a vdisk. + + info config + """ + # Need to configure for real. + # vdisk.vd_create(size, expiry) + + def vdisk_configure(self, info): + """Configure a vdisk. + id vdisk id + info config + """ + # Need to configure for real. + # Make bigger: vdisk.vd_enlarge(id, extra_size) + # Update expiry time: vdisk.vd_refresh(id, expiry) + # Try to recover an expired vdisk : vdisk.vd_undelete(id, expiry) + + + def vdisk_delete(self, id): + """Delete a vdisk. + + id vdisk id + """ + # Need to delete vdisk for real. What if fails? + del self.vdisk[id] + vdisk.vd_delete(id) + + # def vdisk_copy: copy contents to file, vdisk still exists + # def vdisk_export: copy contents to file then delete the vdisk + # def vdisk_import: create a vdisk from a file + # def vdisk_space: space left for new vdisks + + # def vdisk_recover: recover an expired vdisk + + # def vdisk_init_partition: setup a physical partition for vdisks + +def instance(): + global inst + try: + inst + except: + inst = XendVdisk() + return inst diff --git a/tools/xenmgr/lib/XendVnet.py b/tools/xenmgr/lib/XendVnet.py new file mode 100644 index 0000000000..213408e111 --- /dev/null +++ b/tools/xenmgr/lib/XendVnet.py @@ -0,0 +1,69 @@ +# Copyright (C) 2004 Mike Wray <mike.wray@hp.com> + +"""Handler for vnet operations. +""" + +import sxp +import XendDB + +class XendVnet: + """Index of all vnets. Singleton. + """ + + dbpath = "vnet" + + def __init__(self): + # Table of vnet info indexed by vnet id. + self.vnet = {} + self.db = XendDB.XendDB(self.dbpath) + self.vnet = self.db.fetchall("") + + def vnet_ls(self): + """List all vnets. + """ + return self.vnet.keys() + + def vnets(self): + return self.vnet.values() + + def vnet_get(self, id): + """Get a vnet. + + id vnet id + """ + return self.vnet.get(id) + + def vnet_create(self, info): + """Create a vnet. + + info config + """ + self.vnet_configure(info) + + def vnet_configure(self, info): + """Configure a vnet. + id vnet id + info config + """ + # Need to configure for real. + # Only sync if succeeded - otherwise need to back out. + self.vnet[info.id] = info + self.db.save(info.id, info) + + def vnet_delete(self, id): + """Delete a vnet. + + id vnet id + """ + # Need to delete for real. What if fails? + if id in self.vnet: + del self.vnet[id] + self.db.delete(id) + +def instance(): + global inst + try: + inst + except: + inst = XendVnet() + return inst diff --git a/tools/xenmgr/lib/__init__.py b/tools/xenmgr/lib/__init__.py new file mode 100644 index 0000000000..8d1c8b69c3 --- /dev/null +++ b/tools/xenmgr/lib/__init__.py @@ -0,0 +1 @@ + diff --git a/tools/xenmgr/lib/encode.py b/tools/xenmgr/lib/encode.py new file mode 100644 index 0000000000..38c9351db7 --- /dev/null +++ b/tools/xenmgr/lib/encode.py @@ -0,0 +1,165 @@ +# Copyright (C) 2004 Mike Wray <mike.wray@hp.com> +"""Encoding for arguments to HTTP calls. + Uses the url-encoding with MIME type 'application/x-www-form-urlencoded' + if the data does not include files. Otherwise it uses the encoding with + MIME type 'multipart/form-data'. See the HTML4 spec for details. + + """ +import sys +import types +from StringIO import StringIO + +import urllib +import httplib +import random +import md5 + +# Extract from HTML4 spec. +## The following example illustrates "multipart/form-data" +## encoding. Suppose we have the following form: + +## <FORM action="http://server.com/cgi/handle" +## enctype="multipart/form-data" +## method="post"> +## <P> +## What is your name? <INPUT type="text" name="submit-name"><BR> +## What files are you sending? <INPUT type="file" name="files"><BR> +## <INPUT type="submit" value="Send"> <INPUT type="reset"> +## </FORM> + +## If the user enters "Larry" in the text input, and selects the text +## file "file1.txt", the user agent might send back the following data: + +## Content-Type: multipart/form-data; boundary=AaB03x + +## --AaB03x +## Content-Disposition: form-data; name="submit-name" + +## Larry +## --AaB03x +## Content-Disposition: form-data; name="files"; filename="file1.txt" +## Content-Type: text/plain + +## ... contents of file1.txt ... +## --AaB03x-- + +## If the user selected a second (image) file "file2.gif", the user agent +## might construct the parts as follows: + +## Content-Type: multipart/form-data; boundary=AaB03x + +## --AaB03x +## Content-Disposition: form-data; name="submit-name" + +## Larry +## --AaB03x +## Content-Disposition: form-data; name="files" +## Content-Type: multipart/mixed; boundary=BbC04y + +## --BbC04y +## Content-Disposition: file; filename="file1.txt" +## Content-Type: text/plain + +## ... contents of file1.txt ... +## --BbC04y +## Content-Disposition: file; filename="file2.gif" +## Content-Type: image/gif +## Content-Transfer-Encoding: binary + +## ...contents of file2.gif... +## --BbC04y-- +## --AaB03x-- + +__all__ = ['encode_data', 'encode_multipart', 'encode_form', 'mime_boundary' ] + +def data_values(d): + if isinstance(d, types.DictType): + return d.items() + else: + return d + +def encode_data(d): + """Encode some data for HTTP transport. + The encoding used is stored in 'Content-Type' in the headers. + + d data - sequence of tuples or dictionary + returns a 2-tuple of the headers and the encoded data + """ + val = ({}, None) + if d is None: return val + multipart = 0 + for (k, v) in data_values(d): + if encode_isfile(v): + multipart = 1 + break + if multipart: + val = encode_multipart(d) + else: + val = encode_form(d) + return val + +def encode_isfile(v): + if isinstance(v, types.FileType): + return 1 + if hasattr(v, 'readlines'): + return 1 + return 0 + +def encode_multipart(d): + boundary = mime_boundary() + hdr = { 'Content-Type': 'multipart/form-data; boundary=' + boundary } + out = StringIO() + for (k,v) in data_values(d): + out.write('--') + out.write(boundary) + out.write('\r\n') + if encode_isfile(v): + out.write('Content-Disposition: form-data; name="') + out.write(k) + if hasattr(v, 'name'): + out.write('"; filename="') + out.write(v.name) + out.write('"\r\n') + out.write('Content-Type: application/octet-stream\r\n') + out.write('\r\n') + for l in v.readlines(): + out.write(l) + else: + out.write('Content-Disposition: form-data; name="') + out.write(k) + out.write('"\r\n') + out.write('\r\n') + out.write(str(v)) + out.write('\r\n') + out.write('--') + out.write(boundary) + out.write('--') + out.write('\r\n') + return (hdr, out.getvalue()) + +def mime_boundary(): + random.seed() + m = md5.new() + for i in range(0, 10): + c = chr(random.randint(1, 255)) + m.update(c) + b = m.hexdigest() + return b[0:16] + +def encode_form(d): + hdr = { 'Content-Type': 'application/x-www-form-urlencoded' } + val = urllib.urlencode(d) + return (hdr, val) + +def main(): + #d = {'a': 1, 'b': 'x y', 'c': file('conf.sxp') } + #d = {'a': 1, 'b': 'x y' } + d = [ ('a', 1), ('b', 'x y'), ('c', file('conf.sxp')) ] + #d = [ ('a', 1), ('b', 'x y')] + v = encode_data(d) + print v[0] + sys.stdout.write(v[1]) + print + +if __name__ == "__main__": + main() diff --git a/tools/xenmgr/lib/server/SrvBase.py b/tools/xenmgr/lib/server/SrvBase.py new file mode 100644 index 0000000000..722b60f49d --- /dev/null +++ b/tools/xenmgr/lib/server/SrvBase.py @@ -0,0 +1,137 @@ +# Copyright (C) 2004 Mike Wray <mike.wray@hp.com> + +import cgi + +import os +import sys +import types +import StringIO + +from twisted.internet import defer +from twisted.internet import reactor +from twisted.web import error +from twisted.web import resource +from twisted.web import server + +from xenmgr import sxp +from xenmgr import PrettyPrint + +def uri_pathlist(p): + """Split a path into a list. + p path + return list of path elements + """ + l = [] + for x in p.split('/'): + if x == '': continue + l.append(x) + return l + +class SrvBase(resource.Resource): + """Base class for services. + """ + + def parse_form(self, req, method): + """Parse the data for a request, GET using the URL, POST using encoded data. + Posts should use enctype='multipart/form-data' in the <form> tag, + rather than 'application/x-www-form-urlencoded'. Only 'multipart/form-data' + handles file upload. + + req request + returns a cgi.FieldStorage instance + """ + env = {} + env['REQUEST_METHOD'] = method + if self.query: + env['QUERY_STRING'] = self.query + val = cgi.FieldStorage(fp=req.rfile, headers=req.headers, environ=env) + return val + + def use_sxp(self, req): + """Determine whether to send an SXP response to a request. + Uses SXP if there is no User-Agent, no Accept, or application/sxp is in Accept. + + req request + returns 1 for SXP, 0 otherwise + """ + ok = 0 + user_agent = req.getHeader('User-Agent') + accept = req.getHeader('Accept') + if (not user_agent) or (not accept) or (accept.find(sxp.mime_type) >= 0): + ok = 1 + return ok + + def get_op_method(self, op): + """Get the method for an operation. + For operation 'foo' looks for 'op_foo'. + + op operation name + returns method or None + """ + op_method_name = 'op_' + op + return getattr(self, op_method_name, None) + + def perform(self, req): + """General operation handler for posted operations. + For operation 'foo' looks for a method op_foo and calls + it with op_foo(op, req). Replies with code 500 if op_foo + is not found. + + The method must return a list when req.use_sxp is true + and an HTML string otherwise (or list). + Methods may also return a Deferred (for incomplete processing). + + req request + """ + op = req.args.get('op') + if op is None or len(op) != 1: + req.setResponseCode(404, "Invalid") + return '' + op = op[0] + op_method = self.get_op_method(op) + if op_method is None: + req.setResponseCode(501, "Not implemented") + req.setHeader("Content-Type", "text/plain") + req.write("Not implemented: " + op) + return '' + else: + val = op_method(op, req) + if isinstance(val, defer.Deferred): + val.addCallback(self._cb_perform, req, 1) + return server.NOT_DONE_YET + else: + self._cb_perform(val, req, 0) + return '' + + def _cb_perform(self, val, req, dfr): + """Callback to complete the request. + May be called from a Deferred. + """ + if isinstance(val, error.ErrorPage): + req.write(val.render(req)) + elif self.use_sxp(req): + req.setHeader("Content-Type", sxp.mime_type) + sxp.show(val, req) + else: + req.write('<html><head></head><body>') + self.print_path(req) + if isinstance(val, types.ListType): + req.write('<code><pre>') + PrettyPrint.prettyprint(val, out=req) + req.write('</pre></code>') + else: + req.write(str(val)) + req.write('</body></html>') + if dfr: + req.finish() + + def print_path(self, req): + """Print the path with hyperlinks. + """ + pathlist = [x for x in req.prepath if x != '' ] + s = "/" + req.write('<h1><a href="/">/</a>') + for x in pathlist: + s += x + "/" + req.write(' <a href="%s">%s</a>/' % (s, x)) + req.write("</h1>") diff --git a/tools/xenmgr/lib/server/SrvConsole.py b/tools/xenmgr/lib/server/SrvConsole.py new file mode 100644 index 0000000000..ea25bb6113 --- /dev/null +++ b/tools/xenmgr/lib/server/SrvConsole.py @@ -0,0 +1,42 @@ +# Copyright (C) 2004 Mike Wray <mike.wray@hp.com> + +from xenmgr import sxp +from xenmgr import XendConsole +from SrvDir import SrvDir + +class SrvConsole(SrvDir): + """An individual console. + """ + + def __init__(self, info): + SrvDir.__init__(self) + self.info = info + self.xc = XendConsole.instance() + + def op_disconnect(self, op, req): + val = self.xc.console_disconnect(self.info.id) + return val + + def render_POST(self, req): + return self.perform(req) + + def render_GET(self, req): + if self.use_sxp(req): + req.setHeader("Content-Type", sxp.mime_type) + sxp.show(self.info.sxpr(), out=req) + else: + req.write('<html><head></head><body>') + self.print_path(req) + #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.dom2)) + self.form(req) + req.write('</body></html>') + return '' + + def form(self, req): + req.write('<form method="post" action="%s">' % req.prePathURL()) + if self.info.connection(): + req.write('<input type="submit" name="op" value="disconnect">') + req.write('</form>') diff --git a/tools/xenmgr/lib/server/SrvConsoleDir.py b/tools/xenmgr/lib/server/SrvConsoleDir.py new file mode 100644 index 0000000000..89b092c18d --- /dev/null +++ b/tools/xenmgr/lib/server/SrvConsoleDir.py @@ -0,0 +1,58 @@ +# Copyright (C) 2004 Mike Wray <mike.wray@hp.com> + +from SrvDir import SrvDir +from SrvConsole import SrvConsole +from xenmgr import XendConsole +from xenmgr import sxp + +class SrvConsoleDir(SrvDir): + """Console directory. + """ + + def __init__(self): + SrvDir.__init__(self) + self.xconsole = XendConsole.instance() + + def console(self, x): + val = None + try: + info = self.xconsole.console_get(x) + val = SrvConsole(info) + except KeyError: + pass + return val + + def get(self, x): + v = SrvDir.get(self, x) + if v is not None: + return v + v = self.console(x) + return v + + def render_GET(self, req): + if self.use_sxp(req): + req.setHeader("Content-Type", sxp.mime_type) + self.ls_console(req, 1) + else: + req.write("<html><head></head><body>") + self.print_path(req) + self.ls(req) + self.ls_console(req) + #self.form(req.wfile) + req.write("</body></html>") + return '' + + def ls_console(self, req, use_sxp=0): + url = req.prePathURL() + if not url.endswith('/'): + url += '/' + if use_sxp: + consoles = self.xconsole.console_ls() + sxp.show(consoles, out=req) + else: + consoles = self.xconsole.consoles() + consoles.sort(lambda x, y: cmp(x.id, y.id)) + req.write('<ul>') + for c in consoles: + req.write('<li><a href="%s%s"> %s</a></li>' % (url, c.id, c)) + req.write('</ul>') diff --git a/tools/xenmgr/lib/server/SrvConsoleServer.py b/tools/xenmgr/lib/server/SrvConsoleServer.py new file mode 100644 index 0000000000..88f3964811 --- /dev/null +++ b/tools/xenmgr/lib/server/SrvConsoleServer.py @@ -0,0 +1,631 @@ +########################################################### +## Xen controller daemon +## Copyright (c) 2004, K A Fraser (University of Cambridge) +## Copyright (C) 2004, Mike Wray <mike.wray@hp.com> +########################################################### + +import os +import os.path +import signal +import sys +import socket +import pwd +import re +import StringIO + +from twisted.internet import pollreactor +pollreactor.install() + +from twisted.internet import reactor +from twisted.internet import protocol +from twisted.internet import abstract +from twisted.internet import defer + +import xend.utils + +from xenmgr import sxp +from xenmgr import PrettyPrint +from xenmgr import EventServer +eserver = EventServer.instance() + +from xenmgr.server import SrvServer + +import channel +import blkif +import netif +import console +from params import * + +DEBUG = 1 + +class MgmtProtocol(protocol.DatagramProtocol): + """Handler for the management socket (unix-domain). + """ + + def __init__(self, daemon): + #protocol.DatagramProtocol.__init__(self) + self.daemon = daemon + + def write(self, data, addr): + return self.transport.write(data, addr) + + def datagramReceived(self, data, addr): + if DEBUG: print 'datagramReceived> addr=', addr, 'data=', data + io = StringIO.StringIO(data) + try: + vals = sxp.parse(io) + res = self.dispatch(vals[0]) + self.send_result(addr, res) + except SystemExit: + raise + except: + if DEBUG: + raise + else: + self.send_error(addr) + + def send_reply(self, addr, sxpr): + io = StringIO.StringIO() + sxp.show(sxpr, out=io) + io.seek(0) + self.write(io.getvalue(), addr) + + def send_result(self, addr, res): + + def fn(res, self=self, addr=addr): + self.send_reply(addr, ['ok', res]) + + if isinstance(res, defer.Deferred): + res.addCallback(fn) + else: + fn(res) + + def send_error(self, addr): + (extype, exval) = sys.exc_info()[:2] + self.send_reply(addr, ['err', + ['type', str(extype) ], + ['value', str(exval) ] ] ) + + def opname(self, name): + """Get the name of the method for an operation. + """ + return 'op_' + name.replace('.', '_') + + def operror(self, name, v): + """Default operation handler - signals an error. + """ + raise NotImplementedError('Invalid operation: ' +name) + + def dispatch(self, req): + """Dispatch a request to its handler. + """ + op_name = sxp.name(req) + op_method_name = self.opname(op_name) + op_method = getattr(self, op_method_name, self.operror) + return op_method(op_name, req) + + def op_console_create(self, name, req): + """Create a new control interface - console for a domain. + """ + print name, req + dom = sxp.child_value(req, 'domain') + if not dom: raise ValueError('Missing domain') + dom = int(dom) + console_port = sxp.child_value(req, 'console_port') + if console_port: + console_port = int(console_port) + resp = self.daemon.console_create(dom, console_port) + print name, resp + return resp + + def op_consoles(self, name, req): + """Get a list of the consoles. + """ + return self.daemon.consoles() + + def op_console_disconnect(self, name, req): + id = sxp.child_value(req, 'id') + if not id: + raise ValueError('Missing console id') + id = int(id) + console = self.daemon.get_console(id) + if not console: + raise ValueError('Invalid console id') + if console.conn: + console.conn.loseConnection() + return ['ok'] + + def op_blkifs(self, name, req): + pass + + def op_blkif_devs(self, name, req): + pass + + def op_blkif_create(self, name, req): + pass + + def op_blkif_dev_create(self, name, req): + pass + + def op_netifs(self, name, req): + pass + + def op_netif_devs(self, name, req): + pass + + def op_netif_create(self, name, req): + pass + + def op_netif_dev_create(self, name, req): + pass + +class NotifierProtocol(protocol.Protocol): + """Asynchronous handler for i/o on the notifier (event channel). + """ + + def __init__(self, channelFactory): + self.channelFactory = channelFactory + + def notificationReceived(self, idx, type): + #print 'NotifierProtocol>notificationReceived>', idx, type + channel = self.channelFactory.getChannel(idx) + if not channel: + return + #print 'NotifierProtocol>notificationReceived> channel', channel + channel.notificationReceived(type) + + def connectionLost(self, reason=None): + pass + + def doStart(self): + pass + + def doStop(self): + pass + + def startProtocol(self): + pass + + def stopProtocol(self): + pass + +class NotifierPort(abstract.FileDescriptor): + """Transport class for the event channel. + """ + + def __init__(self, daemon, notifier, proto, reactor=None): + assert isinstance(proto, NotifierProtocol) + abstract.FileDescriptor.__init__(self, reactor) + self.daemon = daemon + self.notifier = notifier + self.protocol = proto + + def startListening(self): + self._bindNotifier() + self._connectToProtocol() + + def stopListening(self): + if self.connected: + result = self.d = defer.Deferred() + else: + result = None + self.loseConnection() + return result + + def fileno(self): + return self.notifier.fileno() + + def _bindNotifier(self): + self.connected = 1 + + def _connectToProtocol(self): + self.protocol.makeConnection(self) + self.startReading() + + def loseConnection(self): + if self.connected: + self.stopReading() + self.disconnecting = 1 + reactor.callLater(0, self.connectionLost) + + def connectionLost(self, reason=None): + abstract.FileDescriptor.connectionLost(self, reason) + if hasattr(self, 'protocol'): + self.protocol.doStop() + self.connected = 0 + #self.notifier.close() # Not implemented. + os.close(self.fileno()) + del self.notifier + if hasattr(self, 'd'): + self.d.callback(None) + del self.d + + def doRead(self): + #print 'NotifierPort>doRead>', self + count = 0 + while 1: + #print 'NotifierPort>doRead>', count + notification = self.notifier.read() + if not notification: + break + (idx, type) = notification + self.protocol.notificationReceived(idx, type) + self.notifier.unmask(idx) + count += 1 + #print 'NotifierPort>doRead<' + +class EventProtocol(protocol.Protocol): + """Asynchronous handler for a connected event socket. + """ + + def __init__(self, daemon): + #protocol.Protocol.__init__(self) + self.daemon = daemon + # Event queue. + self.queue = [] + # Subscribed events. + self.events = [] + self.parser = sxp.Parser() + self.pretty = 0 + + # For debugging subscribe to everything and make output pretty. + self.subscribe(['*']) + self.pretty = 1 + + def dataReceived(self, data): + try: + self.parser.input(data) + if self.parser.ready(): + val = self.parser.get_val() + res = self.dispatch(val) + self.send_result(res) + if self.parser.at_eof(): + self.loseConnection() + except SystemExit: + raise + except: + if DEBUG: + raise + else: + self.send_error() + + def connectionLost(self, reason=None): + self.unsubscribe() + + def send_reply(self, sxpr): + io = StringIO.StringIO() + if self.pretty: + PrettyPrint.prettyprint(sxpr, out=io) + else: + sxp.show(sxpr, out=io) + print >> io + io.seek(0) + return self.transport.write(io.getvalue()) + + def send_result(self, res): + return self.send_reply(['ok', res]) + + def send_error(self): + (extype, exval) = sys.exc_info()[:2] + return self.send_reply(['err', + ['type', str(extype)], + ['value', str(exval)]]) + + def send_event(self, val): + return self.send_reply(['event', val[0], val[1]]) + + def unsubscribe(self): + for event in self.events: + eserver.unsubscribe(event, self.queue_event) + + def subscribe(self, events): + self.unsubscribe() + for event in events: + eserver.subscribe(event, self.queue_event) + self.events = events + + def queue_event(self, name, v): + # Despite the name we dont' queue the event here. + # We send it because the transport will queue it. + self.send_event([name, v]) + + def opname(self, name): + return 'op_' + name.replace('.', '_') + + def operror(self, name, req): + raise NotImplementedError('Invalid operation: ' +name) + + def dispatch(self, req): + op_name = sxp.name(req) + op_method_name = self.opname(op_name) + op_method = getattr(self, op_method_name, self.operror) + return op_method(op_name, req) + + def op_help(self, name, req): + def nameop(x): + if x.startswith('op_'): + return x[3:].replace('_', '.') + else: + return x + + l = [ nameop(k) for k in dir(self) if k.startswith('op_') ] + return l + + def op_quit(self, name, req): + self.loseConnection() + + def op_exit(self, name, req): + sys.exit(0) + + def op_pretty(self, name, req): + self.pretty = 1 + return ['ok'] + + def op_console_disconnect(self, name, req): + id = sxp.child_value(req, 'id') + if not id: + raise ValueError('Missing console id') + self.daemon.console_disconnect(id) + return ['ok'] + + def op_info(self, name, req): + val = self.daemon.consoles() + return val + + def op_sys_subscribe(self, name, v): + # (sys.subscribe event*) + # Subscribe to the events: + self.subscribe(v[1:]) + return ['ok'] + + def op_sys_inject(self, name, v): + # (sys.inject event) + event = v[1] + eserver.inject(sxp.name(event), event) + return ['ok'] + + +class EventFactory(protocol.Factory): + """Asynchronous handler for the event server socket. + """ + protocol = EventProtocol + service = None + + def __init__(self, daemon): + #protocol.Factory.__init__(self) + self.daemon = daemon + + def buildProtocol(self, addr): + proto = self.protocol(self.daemon) + proto.factory = self + return proto + +class Daemon: + """The xend daemon. + """ + def __init__(self): + self.shutdown = 0 + + def daemon_pids(self): + pids = [] + pidex = '(?P<pid>\d+)' + pythonex = '(?P<python>\S*python\S*)' + cmdex = '(?P<cmd>.*)' + procre = re.compile('^\s*' + pidex + '\s*' + pythonex + '\s*' + cmdex + '$') + xendre = re.compile('^/usr/sbin/xend\s*(start|restart)\s*.*$') + procs = os.popen('ps -e -o pid,args 2>/dev/null') + for proc in procs: + pm = procre.match(proc) + if not pm: continue + xm = xendre.match(pm.group('cmd')) + if not xm: continue + #print 'pid=', pm.group('pid'), 'cmd=', pm.group('cmd') + pids.append(int(pm.group('pid'))) + return pids + + def new_cleanup(self, kill=0): + err = 0 + pids = self.daemon_pids() + if kill: + for pid in pids: + print "Killing daemon pid=%d" % pid + os.kill(pid, signal.SIGHUP) + elif pids: + err = 1 + print "Daemon already running: ", pids + return err + + def cleanup(self, kill=False): + # No cleanup to do if PID_FILE is empty. + if not os.path.isfile(PID_FILE) or not os.path.getsize(PID_FILE): + return 0 + # Read the pid of the previous invocation and search active process list. + pid = open(PID_FILE, 'r').read() + lines = os.popen('ps ' + pid + ' 2>/dev/null').readlines() + for line in lines: + if re.search('^ *' + pid + '.+xend', line): + if not kill: + print "Daemon is already running (pid %d)" % int(pid) + return 1 + # Old daemon is still active: terminate it. + os.kill(int(pid), 1) + # Delete the stale PID_FILE. + os.remove(PID_FILE) + return 0 + + def install_child_reaper(self): + #signal.signal(signal.SIGCHLD, self.onSIGCHLD) + # Ensure that zombie children are automatically reaped. + xend.utils.autoreap() + + def onSIGCHLD(self, signum, frame): + code = 1 + while code > 0: + code = os.waitpid(-1, os.WNOHANG) + + def start(self): + if self.cleanup(kill=False): + return 1 + + # Detach from TTY. + if not DEBUG: + os.setsid() + + if self.set_user(): + return 1 + + self.install_child_reaper() + + # Fork -- parent writes PID_FILE and exits. + pid = os.fork() + if pid: + # Parent + pidfile = open(PID_FILE, 'w') + pidfile.write(str(pid)) + pidfile.close() + return 0 + # Child + logfile = self.open_logfile() + self.redirect_output(logfile) + self.run() + return 0 + + def open_logfile(self): + if not os.path.exists(CONTROL_DIR): + os.makedirs(CONTROL_DIR) + + # Open log file. Truncate it if non-empty, and request line buffering. + if os.path.isfile(LOG_FILE): + os.rename(LOG_FILE, LOG_FILE+'.old') + logfile = open(LOG_FILE, 'w+', 1) + return logfile + + def set_user(self): + # Set the UID. + try: + os.setuid(pwd.getpwnam(USER)[2]) + return 0 + except KeyError, error: + print "Error: no such user '%s'" % USER + return 1 + + def redirect_output(self, logfile): + if DEBUG: return + # Close down standard file handles + try: + os.close(0) # stdin + os.close(1) # stdout + os.close(2) # stderr + except: + pass + # Redirect output to log file. + sys.stdout = sys.stderr = logfile + + def stop(self): + return self.cleanup(kill=True) + + def run(self): + self.createFactories() + self.listenMgmt() + self.listenEvent() + self.listenNotifier() + SrvServer.create() + reactor.run() + + def createFactories(self): + self.channelF = channel.channelFactory() + self.blkifCF = blkif.BlkifControllerFactory() + self.netifCF = netif.NetifControllerFactory() + self.consoleCF = console.ConsoleControllerFactory() + + def listenMgmt(self): + protocol = MgmtProtocol(self) + s = os.path.join(CONTROL_DIR, MGMT_SOCK) + if os.path.exists(s): + os.unlink(s) + return reactor.listenUNIXDatagram(s, protocol) + + def listenEvent(self): + protocol = EventFactory(self) + return reactor.listenTCP(EVENT_PORT, protocol) + + def listenNotifier(self): + protocol = NotifierProtocol(self.channelF) + p = NotifierPort(self, self.channelF.notifier, protocol, reactor) + p.startListening() + return p + + def exit(self): + reactor.diconnectAll() + sys.exit(0) + + def blkif_create(self, dom): + """Create a block device interface controller. + + Returns Deferred + """ + d = self.blkifCF.createInstance(dom) + return d + + def blkif_dev_create(self, dom, vdev, mode, segment): + """Create a block device. + + Returns Deferred + """ + ctrl = self.blkifCF.getInstanceByDom(dom) + if not ctrl: + raise ValueError('No blkif controller: %d' % dom) + print 'blkif_dev_create>', dom, vdev, mode, segment + d = ctrl.attach_device(vdev, mode, segment) + return d + + def netif_create(self, dom): + """Create a network interface controller. + + """ + return self.netifCF.createInstance(dom) + + def netif_dev_create(self, dom, vif, vmac): + """Create a network device. + + todo + """ + ctrl = self.netifCF.getInstanceByDom(dom) + if not ctrl: + raise ValueError('No netif controller: %d' % dom) + d = ctrl.attach_device(vif, vmac) + return d + + def console_create(self, dom, console_port=None): + """Create a console for a domain. + """ + console = self.consoleCF.getInstanceByDom(dom) + if console is None: + console = self.consoleCF.createInstance(dom, console_port) + return console.sxpr() + + def consoles(self): + return [ c.sxpr() for c in self.consoleCF.getInstances() ] + + def get_console(self, id): + return self.consoleCF.getInstance(id) + + def get_domain_console(self, dom): + return self.consoleCF.getInstanceByDom(dom) + + def console_disconnect(self, id): + """Disconnect any connected console client. + """ + console = self.get_console(id) + if not console: + raise ValueError('Invalid console id') + if console.conn: + console.conn.loseConnection() + +def instance(): + global inst + try: + inst + except: + inst = Daemon() + return inst diff --git a/tools/xenmgr/lib/server/SrvDeviceDir.py b/tools/xenmgr/lib/server/SrvDeviceDir.py new file mode 100644 index 0000000000..52f428540d --- /dev/null +++ b/tools/xenmgr/lib/server/SrvDeviceDir.py @@ -0,0 +1,9 @@ +# Copyright (C) 2004 Mike Wray <mike.wray@hp.com> + +from SrvDir import SrvDir + +class SrvDeviceDir(SrvDir): + """Device directory. + """ + + pass diff --git a/tools/xenmgr/lib/server/SrvDir.py b/tools/xenmgr/lib/server/SrvDir.py new file mode 100644 index 0000000000..f4310e279c --- /dev/null +++ b/tools/xenmgr/lib/server/SrvDir.py @@ -0,0 +1,91 @@ +# Copyright (C) 2004 Mike Wray <mike.wray@hp.com> + +from twisted.web import error +from xenmgr import sxp +from SrvBase import SrvBase + +class SrvConstructor: + """Delayed constructor for sub-servers. + Does not import the sub-server class or create the object until needed. + """ + + def __init__(self, klass): + """Create a constructor. It is assumed that the class + should be imported as 'import klass from klass'. + + klass name of its class + """ + self.klass = klass + self.obj = None + + def getobj(self): + """Get the sub-server object, importing its class and instantiating it if + necessary. + """ + if not self.obj: + exec 'from %s import %s' % (self.klass, self.klass) + klassobj = eval(self.klass) + self.obj = klassobj() + return self.obj + +class SrvDir(SrvBase): + """Base class for directory servlets. + """ + isLeaf = False + + def __init__(self): + SrvBase.__init__(self) + self.table = {} + self.order = [] + + def getChild(self, x, req): + if x == '': return self + val = self.get(x) + if val is None: + return error.NoResource('Not found') + else: + return val + + def get(self, x): + val = self.table.get(x) + if val is not None: + val = val.getobj() + return val + + def add(self, x, xclass = None): + if xclass is None: + xclass = 'SrvDir' + self.table[x] = SrvConstructor(xclass) + self.order.append(x) + + def render_GET(self, 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() + if not url.endswith('/'): + url += '/' + if use_sxp: + req.write('(ls ') + for k in self.order: + req.write(' ' + k) + req.write(')') + else: + req.write('<ul>') + for k in self.order: + v = self.get(k) + req.write('<li><a href="%s%s">%s</a></li>' + % (url, k, k)) + req.write('</ul>') + + def form(self, req): + pass diff --git a/tools/xenmgr/lib/server/SrvDomain.py b/tools/xenmgr/lib/server/SrvDomain.py new file mode 100644 index 0000000000..0ef5676941 --- /dev/null +++ b/tools/xenmgr/lib/server/SrvDomain.py @@ -0,0 +1,202 @@ +# Copyright (C) 2004 Mike Wray <mike.wray@hp.com> + +from xenmgr import sxp +from xenmgr import XendDomain +from xenmgr import XendConsole +from xenmgr import PrettyPrint +from xenmgr.Args import FormFn + +from SrvDir import SrvDir + +class SrvDomain(SrvDir): + """Service managing a single domain. + """ + + def __init__(self, dom): + SrvDir.__init__(self) + self.dom = dom + self.xd = XendDomain.instance() + self.xconsole = XendConsole.instance() + + def op_start(self, op, req): + val = self.xd.domain_start(self.dom.id) + return val + + def op_stop(self, op, req): + val = self.xd.domain_stop(self.dom.id) + return val + + def op_shutdown(self, op, req): + val = self.xd.domain_shutdown(self.dom.id) + req.setResponseCode(202) + req.setHeader("Location", "%s/.." % req.prePathURL()) + return val + + def op_halt(self, op, req): + val = self.xd.domain_halt(self.dom.id) + req.setHeader("Location", "%s/.." % req.prePathURL()) + return val + + def op_save(self, op, req): + fn = FormFn(self.xd.domain_save, + [['dom', 'int'], + ['dst', 'str']]) + val = fn(req.args, {'dom': self.dom.id}) + return val + + def op_restore(self, op, req): + fn = FormFn(self.xd.domain_restore, + [['dom', 'int'], + ['src', 'str']]) + val = fn(req.args, {'dom': self.dom.id}) + return val + + def op_migrate(self, op, req): + fn = FormFn(self.xd.domain_migrate, + [['dom', 'int'], + ['destination', 'str']]) + val = fn(req.args, {'dom': self.dom.id}) + val = 0 # Some migrate id. + req.setResponseCode(202) + #req.send_header("Location", "%s/.." % self.path) # Some migrate url. + return val + + def op_pincpu(self, op, req): + fn = FormFn(self.xd.domain_migrate, + [['dom', 'int'], + ['cpu', 'int']]) + val = fn(req.args, {'dom': self.dom.id}) + return val + + def op_cpu_bvt_set(self, op, req): + fn = FormFn(self.xd.domain_cpu_bvt_set, + [['dom', 'int'], + ['mcuadv', 'int'], + ['warp', 'int'], + ['warpl', 'int'], + ['warpu', 'int']]) + val = fn(req.args, {'dom': self.dom.id}) + return val + + def op_cpu_atropos_set(self, op, req): + fn = FormFn(self.xd.domain_cpu_atropos_set, + [['dom', 'int'], + ['period', 'int'], + ['slice', 'int'], + ['latency', 'int'], + ['xtratime', 'int']]) + val = fn(req.args, {'dom': self.dom.id}) + return val + + def op_vifs(self, op, req): + return self.xd.domain_vif_ls(self.dom.id) + + def op_vif(self, op, req): + fn = FormFn(self.xd.domain_vif_get, + [['dom', 'int'], + ['vif', 'int']]) + val = fn(req.args, {'dom': self.dom.id}) + return val + + def op_vif_stats(self, op, req): + #todo + fn = FormFn(self.xd.domain_vif_stats, + [['dom', 'int'], + ['vif', 'int']]) + #val = fn(req.args, {'dom': self.dom.id}) + val = 999 + #return val + return val + + def op_vif_ip_add(self, op, req): + fn = FormFn(self.xd.domain_vif_ip_add, + [['dom', 'int'], + ['vif', 'int'], + ['ip', 'str']]) + val = fn(req.args, {'dom': self.dom.id}) + return val + + def op_vif_scheduler_set(self, op, req): + fn = FormFn(self.xd.domain_vif_scheduler_set, + [['dom', 'int'], + ['vif', 'int'], + ['bytes', 'int'], + ['usecs', 'int']]) + val = fn(req.args, {'dom': self.dom.id}) + return val + + def op_vif_scheduler_get(self, op, req): + fn = FormFn(self.xd.domain_vif_scheduler_set, + [['dom', 'int'], + ['vif', 'int']]) + val = fn(req.args, {'dom': self.dom.id}) + return val + + def op_vbds(self, op, req): + return self.xd.domain_vbd_ls(self.dom.id) + + def op_vbd(self, op, req): + fn = FormFn(self.xd.domain_vbd_get, + [['dom', 'int'], + ['vbd', 'int']]) + val = fn(req.args, {'dom': self.dom.id}) + return val + + def op_vbd_add(self, op, req): + fn = FormFn(self.xd.domain_vbd_add, + [['dom', 'int'], + ['uname', 'str'], + ['dev', 'str'], + ['mode', 'str']]) + val = fn(req.args, {'dom': self.dom.id}) + return val + + def op_vbd_remove(self, op, req): + fn = FormFn(self.xd.domain_vbd_remove, + [['dom', 'int'], + ['dev', 'str']]) + val = fn(req.args, {'dom': self.dom.id}) + return val + + def render_POST(self, req): + return self.perform(req) + + def render_GET(self, req): + op = req.args.get('op') + if op and op[0] in ['vifs', 'vif', 'vif_stats', 'vbds', 'vbd']: + return self.perform(req) + if self.use_sxp(req): + req.setHeader("Content-Type", sxp.mime_type) + sxp.show(self.dom.sxpr(), out=req) + else: + req.write('<html><head></head><body>') + self.print_path(req) + #self.ls() + req.write('<p>%s</p>' % self.dom) + if self.dom.console: + cinfo = self.dom.console + cid = cinfo.id + #todo: Local xref: need to know server prefix. + req.write('<p><a href="/xend/console/%s">Console %s</a></p>' + % (cid, cid)) + req.write('<p><a href="%s">Connect to console</a></p>' + % cinfo.uri()) + if self.dom.config: + req.write("<code><pre>") + PrettyPrint.prettyprint(self.dom.config, out=req) + req.write("</pre></code>") + req.write('<a href="%s?op=vif_stats&vif=0">vif 0 stats</a>' + % req.prePathURL()) + self.form(req) + req.write('</body></html>') + return '' + + def form(self, req): + req.write('<form method="post" action="%s">' % req.prePathURL()) + req.write('<input type="submit" name="op" value="start">') + req.write('<input type="submit" name="op" value="stop">') + req.write('<input type="submit" name="op" value="shutdown">') + req.write('<input type="submit" name="op" value="halt">') + req.write('<br><input type="submit" name="op" value="migrate">') + req.write('To: <input type="text" name="destination">') + req.write('</form>') diff --git a/tools/xenmgr/lib/server/SrvDomainDir.py b/tools/xenmgr/lib/server/SrvDomainDir.py new file mode 100644 index 0000000000..7bb2996d9b --- /dev/null +++ b/tools/xenmgr/lib/server/SrvDomainDir.py @@ -0,0 +1,130 @@ +# Copyright (C) 2004 Mike Wray <mike.wray@hp.com> + +from StringIO import StringIO + +from twisted.protocols import http +from twisted.web import error + +from xenmgr import sxp +from xenmgr import XendDomain + +from SrvDir import SrvDir +from SrvDomain import SrvDomain + +class SrvDomainDir(SrvDir): + """Service that manages the domain directory. + """ + + def __init__(self): + SrvDir.__init__(self) + self.xd = XendDomain.instance() + + def domain(self, x): + val = None + try: + dom = self.xd.domain_get(x) + val = SrvDomain(dom) + except KeyError: + pass + return val + + def get(self, x): + v = SrvDir.get(self, x) + if v is not None: + return v + v = self.domain(x) + return v + + def op_create(self, op, req): + ok = 0 + try: + configstring = req.args.get('config')[0] + print 'config:', configstring + pin = sxp.Parser() + pin.input(configstring) + pin.input_eof() + config = pin.get_val() + ok = 1 + except Exception, ex: + print ex + if not ok: + req.setResponseCode(http.BAD_REQUEST, "Invalid configuration") + return "Invalid configuration" + return error.ErrorPage(http.BAD_REQUEST, + "Invalid", + "Invalid configuration") + try: + deferred = self.xd.domain_create(config) + deferred.addCallback(self._cb_op_create, configstring, req) + return deferred + except Exception, ex: + raise + #return ['err', str(ex) ] + #req.setResponseCode(http.BAD_REQUEST, "Error creating domain") + #return str(ex) + #return error.ErrorPage(http.BAD_REQUEST, + # "Error creating domain", + # str(ex)) + + + def _cb_op_create(self, dominfo, configstring, req): + """Callback to handle deferred domain creation. + """ + dom = dominfo.id + domurl = "%s/%s" % (req.prePathURL(), dom) + req.setResponseCode(201, "created") + req.setHeader("Location", domurl) + if self.use_sxp(req): + return dominfo.sxpr() + else: + out = StringIO() + print >> out, ('<p> Created <a href="%s">Domain %s</a></p>' + % (domurl, dom)) + print >> out, '<p><pre>' + print >> out, configstring + print >> out, '</pre></p>' + val = out.getvalue() + out.close() + return val + + def render_POST(self, req): + return self.perform(req) + + def render_GET(self, req): + if self.use_sxp(req): + req.setHeader("Content-Type", sxp.mime_type) + self.ls_domain(req, 1) + else: + req.write("<html><head></head><body>") + self.print_path(req) + self.ls(req) + self.ls_domain(req) + self.form(req) + req.write("</body></html>") + return '' + + def ls_domain(self, req, use_sxp=0): + url = req.prePathURL() + if not url.endswith('/'): + url += '/' + if use_sxp: + domains = self.xd.domain_ls() + sxp.show(domains, out=req) + else: + domains = self.xd.domains() + domains.sort(lambda x, y: cmp(x.id, y.id)) + req.write('<ul>') + for d in domains: + req.write('<li><a href="%s%s"> Domain %s</a>' + % (url, d.id, d.id)) + req.write('name=%s' % d.name) + req.write('memory=%d'% d.memory) + req.write('</li>') + req.write('</ul>') + + def form(self, req): + req.write('<form method="post" action="%s" enctype="multipart/form-data">' + % req.prePathURL()) + req.write('<button type="submit" name="op" value="create">Create Domain</button>') + req.write('Config <input type="file" name="config"><br>') + req.write('</form>') diff --git a/tools/xenmgr/lib/server/SrvEventDir.py b/tools/xenmgr/lib/server/SrvEventDir.py new file mode 100644 index 0000000000..eda56972da --- /dev/null +++ b/tools/xenmgr/lib/server/SrvEventDir.py @@ -0,0 +1,41 @@ +# Copyright (C) 2004 Mike Wray <mike.wray@hp.com> + +from xenmgr import sxp +from xenmgr import EventServer +from SrvDir import SrvDir + +class SrvEventDir(SrvDir): + """Event directory. + """ + + def __init__(self): + SrvDir.__init__(self) + self.eserver = EventServer.instance() + + def op_inject(self, op, req): + eventstring = req.args.get('event') + pin = sxp.Parser() + pin.input(eventstring) + pin.input_eof() + sxpr = pin.get_val() + self.eserver.inject(sxp.name(sxpr), sxpr) + if req.use_sxp: + sxp.name(sxpr) + else: + return '<code>' + eventstring + '</code>' + + def render_POST(self, req): + return self.perform(req) + + def form(self, req): + action = req.prePathURL() + req.write('<form method="post" action="%s" enctype="multipart/form-data">' + % action) + req.write('<button type="submit" name="op" value="inject">Inject</button>') + req.write('Event <input type="text" name="event" size="40"><br>') + req.write('</form>') + req.write('<form method="post" action="%s" enctype="multipart/form-data">' + % action) + req.write('<button type="submit" name="op" value="inject">Inject</button>') + req.write('Event file<input type="file" name="event"><br>') + req.write('</form>') diff --git a/tools/xenmgr/lib/server/SrvNode.py b/tools/xenmgr/lib/server/SrvNode.py new file mode 100644 index 0000000000..3c6168e337 --- /dev/null +++ b/tools/xenmgr/lib/server/SrvNode.py @@ -0,0 +1,59 @@ +# Copyright (C) 2004 Mike Wray <mike.wray@hp.com> + +import os +from SrvDir import SrvDir +from xenmgr import sxp +from xenmgr import XendNode + +class SrvNode(SrvDir): + """Information about the node. + """ + + def __init__(self): + SrvDir.__init__(self) + self.xn = XendNode.instance() + + def op_shutdown(self, op, req): + val = self.xn.shutdown() + return val + + def op_reboot(self, op, req): + val = self.xn.reboot() + return val + + def op_cpu_rrobin_slice_set(self, op, req): + fn = FormFn(self.xn.cpu_rrobin_slice_set, + [['slice', 'int']]) + val = fn(req.args, {}) + return val + + def op_cpu_bvt_slice_set(self, op, req): + fn = FormFn(self.xn.cpu_bvt_slice_set, + [['slice', 'int']]) + val = fn(req.args, {}) + return val + + def render_POST(self, req): + return self.perform(req) + + def render_GET(self, req): + if self.use_sxp(req): + req.setHeader("Content-Type", sxp.mime_type) + sxp.show(['node'] + self.info(), out=req) + else: + req.write('<html><head></head><body>') + self.print_path(req) + req.write('<ul>') + for d in self.info(): + req.write('<li> %10s: %s' % (d[0], d[1])) + req.write('</ul>') + req.write('</body></html>') + return '' + + def info(self): + (sys, host, rel, ver, mch) = os.uname() + return [['system', sys], + ['host', host], + ['release', rel], + ['version', ver], + ['machine', mch]] diff --git a/tools/xenmgr/lib/server/SrvRoot.py b/tools/xenmgr/lib/server/SrvRoot.py new file mode 100644 index 0000000000..b002d2cf76 --- /dev/null +++ b/tools/xenmgr/lib/server/SrvRoot.py @@ -0,0 +1,31 @@ +# Copyright (C) 2004 Mike Wray <mike.wray@hp.com> + +from xenmgr import XendRoot +xroot = XendRoot.instance() +from SrvDir import SrvDir + +class SrvRoot(SrvDir): + """The root of the xend server. + """ + + """Server sub-components. Each entry is (name, class), where + 'name' is the entry name and 'class' is the name of its class. + """ + #todo Get this list from the XendRoot config. + subdirs = [ + ('node', 'SrvNode' ), + ('domain', 'SrvDomainDir' ), + ('console', 'SrvConsoleDir' ), + ('event', 'SrvEventDir' ), + ('vdisk', 'SrvVdiskDir' ), + ('device', 'SrvDeviceDir' ), + ('vnet', 'SrvVnetDir' ), + ] + + def __init__(self): + SrvDir.__init__(self) + for (name, klass) in self.subdirs: + self.add(name, klass) + for (name, klass) in self.subdirs: + self.get(name) + xroot.start() diff --git a/tools/xenmgr/lib/server/SrvServer.py b/tools/xenmgr/lib/server/SrvServer.py new file mode 100644 index 0000000000..a42219b620 --- /dev/null +++ b/tools/xenmgr/lib/server/SrvServer.py @@ -0,0 +1,53 @@ +#!/usr/bin/python2 +# Copyright (C) 2004 Mike Wray <mike.wray@hp.com> + +"""Example xend HTTP and console server. + + Can be accessed from a browser or from a program. + Do 'python SrvServer.py' to run the server. + Then point a web browser at http://localhost:8000/xend and follow the links. + Most are stubs, except /domain which has a list of domains and a 'create domain' + button. + + You can also access the server from a program. + Do 'python XendClient.py' to run a few test operations. + + The data served differs depending on the client (as defined by User-Agent + and Accept in the HTTP headers). If the client is a browser, data + is returned in HTML, with interactive forms. If the client is a program, + data is returned in SXP format, with no forms. + + The server serves to the world by default. To restrict it to the local host + change 'interface' in main(). + + Mike Wray <mike.wray@hp.com> +""" +# todo Support security settings etc. in the config file. +# todo Support command-line args. + +from twisted.web import server +from twisted.web import resource +from twisted.internet import reactor + +from xenmgr import XendRoot +xroot = XendRoot.instance() + +from SrvRoot import SrvRoot + +def create(port=None, interface=None): + if port is None: port = 8000 + if interface is None: interface = '' + root = resource.Resource() + xend = SrvRoot() + root.putChild('xend', xend) + site = server.Site(root) + reactor.listenTCP(port, site, interface=interface) + + +def main(port=None, interface=None): + create(port, interface) + reactor.run() + + +if __name__ == '__main__': + main() diff --git a/tools/xenmgr/lib/server/SrvVdisk.py b/tools/xenmgr/lib/server/SrvVdisk.py new file mode 100644 index 0000000000..4200b9b0e5 --- /dev/null +++ b/tools/xenmgr/lib/server/SrvVdisk.py @@ -0,0 +1,12 @@ +# Copyright (C) 2004 Mike Wray <mike.wray@hp.com> + +from xenmgr import XendVdisk +from SrvVdiskDir import SrvVdiskDir + +class SrvVdisk(SrvDir): + """A virtual disk. + """ + + def __init__(self): + SrvDir.__init__(self) + self.xvdisk = XendVdisk.instance() diff --git a/tools/xenmgr/lib/server/SrvVdiskDir.py b/tools/xenmgr/lib/server/SrvVdiskDir.py new file mode 100644 index 0000000000..a6a9e55782 --- /dev/null +++ b/tools/xenmgr/lib/server/SrvVdiskDir.py @@ -0,0 +1,28 @@ +# Copyright (C) 2004 Mike Wray <mike.wray@hp.com> + +from xenmgr import XendVdisk +from SrvDir import SrvDir + +class SrvVdiskDir(SrvDir): + """Virtual disk directory. + """ + + def __init__(self): + SrvDir.__init__(self) + self.xvdisk = XendVdisk.instance() + + def vdisk(self, x): + val = None + try: + dom = self.xvdisk.vdisk_get(x) + val = SrvVdisk(dom) + except KeyError: + pass + return val + + def get(self, x): + v = SrvDir.get(self, x) + if v is not None: + return v + v = self.vdisk(x) + return v diff --git a/tools/xenmgr/lib/server/SrvVnetDir.py b/tools/xenmgr/lib/server/SrvVnetDir.py new file mode 100644 index 0000000000..a8a814192d --- /dev/null +++ b/tools/xenmgr/lib/server/SrvVnetDir.py @@ -0,0 +1,9 @@ +# Copyright (C) 2004 Mike Wray <mike.wray@hp.com> + +from SrvDir import SrvDir + +class SrvVnetDir(SrvDir): + """Vnet directory. + """ + + pass diff --git a/tools/xenmgr/lib/server/__init__.py b/tools/xenmgr/lib/server/__init__.py new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/tools/xenmgr/lib/server/__init__.py @@ -0,0 +1 @@ + diff --git a/tools/xenmgr/lib/server/blkif.py b/tools/xenmgr/lib/server/blkif.py new file mode 100755 index 0000000000..0ef8ff0ff3 --- /dev/null +++ b/tools/xenmgr/lib/server/blkif.py @@ -0,0 +1,232 @@ +import channel +import controller +from messages import * + +class BlkifControllerFactory(controller.ControllerFactory): + """Factory for creating block device interface controllers. + Also handles the 'back-end' channel to dom0. + """ + + # todo: add support for setting dom controlling blkifs (don't assume 0). + # todo: add support for 'recovery'. + + def __init__(self): + controller.ControllerFactory.__init__(self) + + self.majorTypes = [ CMSG_BLKIF_BE ] + + self.subTypes = { + CMSG_BLKIF_BE_CREATE : self.recv_be_create, + CMSG_BLKIF_BE_CONNECT : self.recv_be_connect, + CMSG_BLKIF_BE_VBD_CREATE : self.recv_be_vbd_create, + CMSG_BLKIF_BE_VBD_GROW : self.recv_be_vbd_grow, + CMSG_BLKIF_BE_DRIVER_STATUS_CHANGED: self.recv_be_driver_status_changed, + } + self.attached = 1 + self.registerChannel() + + def createInstance(self, dom): + d = self.addDeferred() + blkif = self.getInstanceByDom(dom) + if blkif: + self.callDeferred(blkif) + else: + blkif = BlkifController(self, dom) + self.addInstance(blkif) + blkif.send_be_create() + return d + + def setControlDomain(self, dom): + if self.channel: + self.deregisterChannel() + self.attached = 0 + self.dom = dom + self.registerChannel() + # + #if xend.blkif.be_port: + # xend.blkif.recovery = True + #xend.blkif.be_port = xend.main.port_from_dom(dom) + + def recv_be_create(self, msg, req): + #print 'recv_be_create>' + val = unpackMsg('blkif_be_create_t', msg) + blkif = self.getInstanceByDom(val['domid']) + self.callDeferred(blkif) + + def recv_be_connect(self, msg, req): + #print 'recv_be_create>' + val = unpackMsg('blkif_be_connect_t', msg) + blkif = self.getInstanceByDom(val['domid']) + if blkif: + blkif.send_fe_interface_status_changed() + else: + pass + + def recv_be_vbd_create(self, msg, req): + #print 'recv_be_vbd_create>' + val = unpackMsg('blkif_be_vbd_create_t', msg) + blkif = self.getInstanceByDom(val['domid']) + if blkif: + blkif.send_be_vbd_grow(val['vdevice']) + else: + pass + + def recv_be_vbd_grow(self, msg, req): + #print 'recv_be_vbd_grow>' + val = unpackMsg('blkif_be_vbd_grow_t', msg) + # Check status? + if self.attached: + self.callDeferred(0) + else: + self.reattach_device(val['domid'], val['vdevice']) + + def reattach_device(self, dom, vdev): + blkif = self.getInstanceByDom(dom) + if blkif: + blkif.reattach_device(vdev) + attached = 1 + for blkif in self.getInstances(): + if not blkif.attached: + attached = 0 + break + self.attached = attached + if self.attached: + self.reattached() + + def reattached(self): + for blkif in self.getInstances(): + blkif.reattached() + + def recv_be_driver_status_changed(self, msg, req): + val = unpackMsg('blkif_be_driver_status_changed_t'. msg) + status = val['status'] + if status == BLKIF_DRIVER_STATUS_UP and not self.attached: + for blkif in self.getInstances(): + blkif.detach() + +class BlkDev: + """Info record for a block device. + """ + + def __init__(self, vdev, mode, segment): + self.vdev = vdev + self.mode = mode + self.device = segment['device'] + self.start_sector = segment['start_sector'] + self.nr_sectors = segment['nr_sectors'] + self.attached = 1 + + def readonly(self): + return 'w' not in self.mode + +class BlkifController(controller.Controller): + """Block device interface controller. Handles all block devices + for a domain. + """ + + def __init__(self, factory, dom): + #print 'BlkifController> dom=', dom + controller.Controller.__init__(self, factory, dom) + self.devices = {} + + self.majorTypes = [ CMSG_BLKIF_FE ] + + self.subTypes = { + CMSG_BLKIF_FE_DRIVER_STATUS_CHANGED: + self.recv_fe_driver_status_changed, + CMSG_BLKIF_FE_INTERFACE_CONNECT : + self.recv_fe_interface_connect, + } + self.attached = 1 + self.registerChannel() + #print 'BlkifController<', 'dom=', self.dom, 'idx=', self.idx + + def attach_device(self, vdev, mode, segment): + """Attach a device to the specified interface. + """ + #print 'BlkifController>attach_device>', self.dom, vdev, mode, segment + if vdev in self.devices: return -1 + dev = BlkDev(vdev, mode, segment) + self.devices[vdev] = dev + self.send_be_vbd_create(vdev) + return self.factory.addDeferred() + + def detach(self): + self.attached = 0 + for dev in self.devices.values(): + dev.attached = 0 + self.send_be_vbd_create(vdev) + + def reattach_device(self, vdev): + dev = self.devices[vdev] + dev.attached = 1 + attached = 1 + for dev in self.devices.values(): + if not dev.attached: + attached = 0 + break + self.attached = attached + return self.attached + + def reattached(self): + msg = packMsg('blkif_fe_interface_status_changed_t', + { 'handle' : 0, + 'status' : BLKIF_INTERFACE_STATUS_DISCONNECTED}) + self.writeRequest(msg) + + def recv_fe_driver_status_changed(self, msg, req): + msg = packMsg('blkif_fe_interface_status_changed_t', + { 'handle' : 0, + 'status' : BLKIF_INTERFACE_STATUS_DISCONNECTED, + 'evtchn' : 0 }) + self.writeRequest(msg) + + def recv_fe_interface_connect(self, msg, req): + val = unpackMsg('blkif_fe_interface_connect_t', msg) + self.evtchn = channel.eventChannel(0, self.dom) + msg = packMsg('blkif_be_connect_t', + { 'domid' : self.dom, + 'blkif_handle' : val['handle'], + 'evtchn' : self.evtchn['port1'], + 'shmem_frame' : val['shmem_frame'] }) + self.factory.writeRequest(msg) + pass + + #def recv_fe_interface_status_changed(self, msg, req): + # (hnd, status, chan) = unpackMsg('blkif_fe_interface_status_changed_t', msg) + # print 'recv_fe_interface_status_changed>', hnd, status, chan + # pass + + def send_fe_interface_status_changed(self): + msg = packMsg('blkif_fe_interface_status_changed_t', + { 'handle' : 0, + 'status' : BLKIF_INTERFACE_STATUS_CONNECTED, + 'evtchn' : self.evtchn['port2'] }) + self.writeRequest(msg) + + def send_be_create(self): + msg = packMsg('blkif_be_create_t', + { 'domid' : self.dom, + 'blkif_handle' : 0 }) + self.factory.writeRequest(msg) + + def send_be_vbd_create(self, vdev): + dev = self.devices[vdev] + msg = packMsg('blkif_be_vbd_create_t', + { 'domid' : self.dom, + 'blkif_handle' : 0, + 'vdevice' : dev.vdev, + 'readonly' : dev.readonly() }) + self.factory.writeRequest(msg) + + def send_be_vbd_grow(self, vdev): + dev = self.devices[vdev] + msg = packMsg('blkif_be_vbd_grow_t', + { 'domid' : self.dom, + 'blkif_handle' : 0, + 'vdevice' : dev.vdev, + 'extent.device' : dev.device, + 'extent.sector_start' : dev.start_sector, + 'extent.sector_length' : dev.nr_sectors }) + self.factory.writeRequest(msg) + diff --git a/tools/xenmgr/lib/server/channel.py b/tools/xenmgr/lib/server/channel.py new file mode 100755 index 0000000000..7678e1807f --- /dev/null +++ b/tools/xenmgr/lib/server/channel.py @@ -0,0 +1,259 @@ +import Xc; xc = Xc.new() +import xend.utils +from messages import msgTypeName + +def eventChannel(dom1, dom2): + return xc.evtchn_bind_interdomain(dom1=dom1, dom2=dom2) + +class ChannelFactory: + """Factory for creating channels. + Maintains a table of channels. + """ + + channels = {} + + def __init__(self): + self.notifier = xend.utils.notifier() + + def addChannel(self, channel): + idx = channel.idx + self.channels[idx] = channel + self.notifier.bind(idx) + # Try to wake it up + #self.notifier.unmask(idx) + #channel.notify() + + def getChannel(self, idx): + return self.channels.get(idx) + + def delChannel(self, idx): + if idx in self.channels: + del self.channels[idx] + self.notifier.unbind(idx) + + def domChannel(self, dom): + for chan in self.channels.values(): + if chan.dom == dom: + return chan + chan = Channel(self, dom) + self.addChannel(chan) + return chan + + def channelClosed(self, channel): + self.delChannel(channel.idx) + + def createPort(self, dom): + return xend.utils.port(dom) + +def channelFactory(): + global inst + try: + inst + except: + inst = ChannelFactory() + return inst + +class Channel: + """A control channel to a domain. Messages for the domain device controllers + are multiplexed over the channel (console, block devs, net devs). + """ + + def __init__(self, factory, dom): + self.factory = factory + self.dom = dom + self.port = self.factory.createPort(dom) + self.idx = self.port.local_port + self.devs = [] + self.devs_by_type = {} + self.closed = 0 + self.queue = [] + + def getIndex(self): + return self.idx + + def getLocalPort(self): + return self.port.local_port + + def getRemotePort(self): + return self.port.remote_port + + def close(self): + for d in self.devs: + d.lostChannel() + self.factory.channelClosed(self) + del self.devs + del self.devs_by_type + + def registerDevice(self, types, dev): + """Register a device controller. + + @param types message types the controller handles + @param dev device controller + """ + self.devs.append(dev) + for ty in types: + self.devs_by_type[ty] = dev + + def unregisterDevice(self, dev): + """Remove the registration for a device controller. + + @param dev device controller + """ + self.devs.remove(dev) + types = [ ty for (ty, d) in self.devs_by_type.items() + if d == dev ] + for ty in types: + del devs_by_type[ty] + + def getDevice(self, type): + """Get the device controller handling a message type. + + @param type message type + @returns controller or None + """ + return self.devs_by_type.get(type) + + def getMessageType(self, msg): + hdr = msg.get_header() + return (hdr['type'], hdr.get('subtype')) + + def __repr__(self): + return ('<Channel dom=%d ports=%d:%d>' + % (self.dom, + self.port.local_port, + self.port.remote_port)) + + def notificationReceived(self, type): + #print 'notificationReceived> type=', type, self + if self.closed: return + if type == self.factory.notifier.EXCEPTION: + print 'notificationReceived> EXCEPTION' + info = xc.evtchn_status(self.idx) + if info['status'] == 'unbound': + print 'notificationReceived> EXCEPTION closing...' + self.close() + return + work = 0 + work += self.handleRequests() + work += self.handleResponses() + work += self.handleWrites() + if work: + self.notify() + #print 'notificationReceived<', work + + def notify(self): + #print 'notify>', self + self.port.notify() + + def handleRequests(self): + #print 'handleRequests>' + work = 0 + while 1: + #print 'handleRequests>', work + msg = self.readRequest() + #print 'handleRequests> msg=', msg + if not msg: break + self.requestReceived(msg) + work += 1 + #print 'handleRequests<', work + return work + + def requestReceived(self, msg): + (ty, subty) = self.getMessageType(msg) + #print 'requestReceived>', ty, subty, self + #todo: Must respond before writing any more messages. + #todo: Should automate this (respond on write) + self.port.write_response(msg) + dev = self.getDevice(ty) + if dev: + dev.requestReceived(msg, ty, subty) + else: + print ("requestReceived> No device: Message type %s %d:%d" + % (msgTypeName(ty, subty), ty, subty)), self + + def handleResponses(self): + #print 'handleResponses>', self + work = 0 + while 1: + #print 'handleResponses>', work + msg = self.readResponse() + #print 'handleResponses> msg=', msg + if not msg: break + self.responseReceived(msg) + work += 1 + #print 'handleResponses<', work + return work + + def responseReceived(self, msg): + (ty, subty) = self.getMessageType(msg) + #print 'responseReceived>', ty, subty + dev = self.getDevice(ty) + if dev: + dev.responseReceived(msg, ty, subty) + else: + print ("responseReceived> No device: Message type %d:%d" + % (msgTypeName(ty, subty), ty, subty)), self + + def handleWrites(self): + #print 'handleWrites>', self + work = 0 + # Pull data from producers. + #print 'handleWrites> pull...' + for dev in self.devs: + work += dev.produceRequests() + # Flush the queue. + #print 'handleWrites> flush...' + while self.queue and self.port.space_to_write_request(): + msg = self.queue.pop(0) + self.port.write_request(msg) + work += 1 + #print 'handleWrites<', work + return work + + def writeRequest(self, msg, notify=1): + #print 'writeRequest>', self + if self.closed: + val = -1 + elif self.writeReady(): + self.port.write_request(msg) + if notify: self.notify() + val = 1 + else: + self.queue.append(msg) + val = 0 + #print 'writeRequest<', val + return val + + def writeResponse(self, msg): + #print 'writeResponse>', self + if self.closed: return -1 + self.port.write_response(msg) + return 1 + + def writeReady(self): + if self.closed or self.queue: return 0 + return self.port.space_to_write_request() + + def readRequest(self): + #print 'readRequest>', self + if self.closed: + #print 'readRequest> closed' + return None + if self.port.request_to_read(): + val = self.port.read_request() + else: + val = None + #print 'readRequest< ', val + return val + + def readResponse(self): + #print 'readResponse>', self + if self.closed: + #print 'readResponse> closed' + return None + if self.port.response_to_read(): + val = self.port.read_response() + else: + val = None + #print 'readResponse<', val + return val diff --git a/tools/xenmgr/lib/server/console.py b/tools/xenmgr/lib/server/console.py new file mode 100755 index 0000000000..6db905dc0b --- /dev/null +++ b/tools/xenmgr/lib/server/console.py @@ -0,0 +1,220 @@ + +from twisted.internet import reactor +from twisted.internet import protocol +from twisted.protocols import telnet + +import xend.utils + +from xenmgr import EventServer +eserver = EventServer.instance() + +import controller +from messages import * +from params import * + +"""Telnet binary option.""" +TRANSMIT_BINARY = '0' +WILL = chr(251) +IAC = chr(255) + +class ConsoleProtocol(protocol.Protocol): + """Asynchronous handler for a console TCP socket. + """ + + def __init__(self, controller, idx): + self.controller = controller + self.idx = idx + self.addr = None + self.binary = 0 + + def connectionMade(self): + peer = self.transport.getPeer() + self.addr = (peer.host, peer.port) + if self.controller.connect(self.addr, self): + self.transport.write("Cannot connect to console %d on domain %d\n" + % (self.idx, self.controller.dom)) + self.loseConnection() + return + else: + self.transport.write("Connected to console %d on domain %d\n" + % (self.idx, self.controller.dom)) + self.setTelnetTransmitBinary() + eserver.inject('xend.console.connect', + [self.idx, self.addr[0], self.addr[1]]) + + def setTelnetTransmitBinary(self): + """Send the sequence to set the telnet TRANSMIT-BINARY option. + """ + self.write(IAC + WILL + TRANSMIT_BINARY) + + def dataReceived(self, data): + if self.controller.handleInput(self, data): + self.loseConnection() + + def write(self, data): + #if not self.connected: return -1 + self.transport.write(data) + return len(data) + + def connectionLost(self, reason=None): + eserver.inject('xend.console.disconnect', + [self.idx, self.addr[0], self.addr[1]]) + self.controller.disconnect() + + def loseConnection(self): + self.transport.loseConnection() + +class ConsoleFactory(protocol.ServerFactory): + """Asynchronous handler for a console server socket. + """ + protocol = ConsoleProtocol + + def __init__(self, controller, idx): + #protocol.ServerFactory.__init__(self) + self.controller = controller + self.idx = idx + + def buildProtocol(self, addr): + proto = self.protocol(self.controller, self.idx) + proto.factory = self + return proto + +class ConsoleControllerFactory(controller.ControllerFactory): + """Factory for creating console controllers. + """ + + def createInstance(self, dom, console_port=None): + if console_port is None: + console_port = CONSOLE_PORT_BASE + dom + console = ConsoleController(self, dom, console_port) + self.addInstance(console) + eserver.inject('xend.console.create', + [console.idx, console.dom, console.console_port]) + return console + + def consoleClosed(self, console): + eserver.inject('xend.console.close', console.idx) + self.delInstance(console) + +class ConsoleController(controller.Controller): + """Console controller for a domain. + Does not poll for i/o itself, but relies on the notifier to post console + output and the connected TCP sockets to post console input. + """ + + def __init__(self, factory, dom, console_port): + #print 'ConsoleController> dom=', dom + controller.Controller.__init__(self, factory, dom) + self.majorTypes = [ CMSG_CONSOLE ] + self.status = "new" + self.addr = None + self.conn = None + self.rbuf = xend.utils.buffer() + self.wbuf = xend.utils.buffer() + self.console_port = console_port + + self.registerChannel() + self.listener = None + self.listen() + #print 'ConsoleController<', 'dom=', self.dom, 'idx=', self.idx + + def sxpr(self): + val =['console', + ['id', self.idx ], + ['domain', self.dom ], + ['local_port', self.channel.getLocalPort() ], + ['remote_port', self.channel.getRemotePort() ], + ['console_port', self.console_port ] ] + if self.addr: + val.append(['connected', self.addr[0], self.addr[1]]) + return val + + def ready(self): + return not (self.closed() or self.rbuf.empty()) + + def closed(self): + return self.status == 'closed' + + def connected(self): + return self.status == 'connected' + + def close(self): + self.status = "closed" + self.listener.stopListening() + self.deregisterChannel() + self.lostChannel() + + def listen(self): + """Listen for TCP connections to the console port.. + """ + if self.closed(): return + self.status = "listening" + if self.listener: + #self.listener.startListening() + pass + else: + f = ConsoleFactory(self, self.idx) + self.listener = reactor.listenTCP(self.console_port, f) + + def connect(self, addr, conn): + if self.closed(): return -1 + if self.connected(): return -1 + self.addr = addr + self.conn = conn + self.status = "connected" + self.handleOutput() + return 0 + + def disconnect(self): + self.addr = None + self.conn = None + self.listen() + + def requestReceived(self, msg, type, subtype): + #print '***Console', self.dom, msg.get_payload() + self.rbuf.write(msg.get_payload()) + self.handleOutput() + + def responseReceived(self, msg, type, subtype): + pass + + def produceRequests(self): + # Send as much pending console data as there is room for. + work = 0 + while not self.wbuf.empty() and self.channel.writeReady(): + msg = xend.utils.message(CMSG_CONSOLE, 0, 0) + msg.append_payload(self.wbuf.read(msg.MAX_PAYLOAD)) + work += self.channel.writeRequest(msg, notify=0) + return work + + def handleInput(self, conn, data): + """Handle some external input aimed at the console. + Called from a TCP connection (conn). + """ + if self.closed(): return -1 + if conn != self.conn: return 0 + self.wbuf.write(data) + if self.produceRequests(): + self.channel.notify() + return 0 + + def handleOutput(self): + """Handle buffered output from the console. + Sends it to the connected console (if any). + """ + if self.closed(): + #print 'Console>handleOutput> closed' + return -1 + if not self.conn: + #print 'Console>handleOutput> not connected' + return 0 + while not self.rbuf.empty(): + try: + #print 'Console>handleOutput> writing...' + bytes = self.conn.write(self.rbuf.peek()) + if bytes > 0: + self.rbuf.discard(bytes) + except socket.error, error: + pass + #print 'Console>handleOutput<' + return 0 diff --git a/tools/xenmgr/lib/server/controller.py b/tools/xenmgr/lib/server/controller.py new file mode 100755 index 0000000000..793ac85968 --- /dev/null +++ b/tools/xenmgr/lib/server/controller.py @@ -0,0 +1,133 @@ +from twisted.internet import defer + +import channel +from messages import msgTypeName + +class CtrlMsgRcvr: + """Abstract class for things that deal with a control interface to a domain. + """ + + + def __init__(self): + self.channelFactory = channel.channelFactory() + self.majorTypes = [ ] + self.subTypes = {} + self.dom = None + self.channel = None + self.idx = None + + def requestReceived(self, msg, type, subtype): + method = self.subTypes.get(subtype) + if method: + method(msg, 1) + else: + print ('requestReceived> No handler: Message type %s %d:%d' + % (msgTypeName(type, subtype), type, subtype)), self + + def responseReceived(self, msg, type, subtype): + method = self.subTypes.get(subtype) + if method: + method(msg, 0) + else: + print ('responseReceived> No handler: Message type %s %d:%d' + % (msgTypeName(type, subtype), type, subtype)), self + + def lostChannel(self): + pass + + def registerChannel(self): + self.channel = self.channelFactory.domChannel(self.dom) + #print 'registerChannel> channel=', self.channel, self + self.idx = self.channel.getIndex() + #print 'registerChannel> idx=', self.idx + if self.majorTypes: + self.channel.registerDevice(self.majorTypes, self) + + def deregisterChannel(self): + if self.channel: + self.channel.deregisterDevice(self) + del self.channel + + def produceRequests(self): + return 0 + + def writeRequest(self, msg): + if self.channel: + self.channel.writeRequest(msg) + else: + print 'CtrlMsgRcvr>writeRequest>', 'no channel!', self + + def writeResponse(self, msg): + if self.channel: + self.channel.writeResponse(msg) + else: + print 'CtrlMsgRcvr>writeResponse>', 'no channel!', self + +class ControllerFactory(CtrlMsgRcvr): + """Abstract class for factories creating controllers. + Maintains a table of instances. + """ + + def __init__(self): + CtrlMsgRcvr.__init__(self) + self.instances = {} + self.dlist = [] + self.dom = 0 + + def addInstance(self, instance): + self.instances[instance.idx] = instance + + def getInstance(self, idx): + return self.instances.get(idx) + + def getInstances(self): + return self.instances.values() + + def getInstanceByDom(self, dom): + for inst in self.instances.values(): + if inst.dom == dom: + return inst + return None + + def delInstance(self, instance): + if instance in self.instances: + del self.instances[instance.idx] + + def createInstance(self, dom): + raise NotImplementedError() + + def instanceClosed(self, instance): + self.delInstance(instance) + + def addDeferred(self): + d = defer.Deferred() + self.dlist.append(d) + return d + + def callDeferred(self, *args): + if self.dlist: + d = self.dlist.pop(0) + d.callback(*args) + + def errDeferred(self, *args): + if self.dlist: + d = self.dlist.pop(0) + d.errback(*args) + +class Controller(CtrlMsgRcvr): + """Abstract class for a device controller attached to a domain. + """ + + def __init__(self, factory, dom): + CtrlMsgRcvr.__init__(self) + self.factory = factory + self.dom = dom + self.channel = None + self.idx = None + + def close(self): + self.deregisterChannel() + self.lostChannel(self) + + def lostChannel(self): + self.factory.instanceClosed(self) diff --git a/tools/xenmgr/lib/server/cstruct.py b/tools/xenmgr/lib/server/cstruct.py new file mode 100755 index 0000000000..880931b41f --- /dev/null +++ b/tools/xenmgr/lib/server/cstruct.py @@ -0,0 +1,269 @@ +import struct + +class Struct: + + maxDepth = 10 + + base = ['x', 'B', 'H', 'I', 'L', 'Q', 'c', 'h', 'i', 'l', 'q', ] + + sizes = {'B': 1, + 'H': 2, + 'I': 4, + 'L': 4, + 'Q': 8, + 'c': 1, + 'h': 2, + 'i': 4, + 'l': 4, + 'q': 8, + 'x': 1, + } + + formats = { + 'int8' : 'B', + 'int16' : 'H', + 'int32' : 'I', + 'int64' : 'Q', + 'u8' : 'B', + 'u16' : 'H', + 'u32' : 'I', + 'u64' : 'Q' + } + + def typedef(self, name, val): + self.formats[name] = val + + def struct(self, name, *f): + self.typedef(name, StructInfo(self, f)) + + def getType(self, name): + return self.formats[name] + + def format(self, ty): + d = 0 + f = ty + while d < self.maxDepth: + d += 1 + f = self.formats[f] + if isinstance(f, StructInfo): + return f.format() + if f in self.base: + return f + return -1 + + def alignedformat(self, ty): + fmt = self.format(ty) + #print 'alignedformat> %s |%s|' %(ty, fmt) + afmt = self.align(fmt) + #print 'alignedformat< %s |%s| |%s|' % (ty, fmt, afmt) + return afmt + + def align(self, fmt): + n1 = 0 + afmt = '' + for a in fmt: + n2 = self.getSize(a) + m = n1 % n2 + if m: + d = (n2 - m) + afmt += 'x' * d + n1 += d + afmt += a + n1 += n2 + return afmt + + def fmtsize(self, fmt): + s = 0 + for f in fmt: + s += self.getSize(f) + return s + + def getSize(self, f): + return self.sizes[f] + + def pack(self, ty, data): + return self.getType(ty).pack(data) + + def unpack(self, ty, data): + return self.getType(ty).unpack(data) + + def show(self): + l = self.formats.keys() + l.sort() + for v in l: + print "%-35s %-10s %s" % (v, self.format(v), self.alignedformat(v)) + + +class StructInfo: + + def __init__(self, s, f): + self.fmt = None + self.structs = s + self.fields = f + + def alignedformat(self): + if self.afmt: return self.afmt + self.afmt = self.structs.align(self.format()) + return self.afmt + + def format(self): + if self.fmt: return self.fmt + fmt = "" + for (ty, name) in self.fields: + fmt += self.formatString(ty) + self.fmt = fmt + return fmt + + def formatString(self, ty): + if ty in self.fields: + ty = self.fields[ty] + return self.structs.format(ty) + + def pack(self, *args): + return struct.pack(self.alignedformat(), *args) + + def unpack(self, data): + return struct.unpack(self.alignedformat(), data) + +types = Struct() + +types.typedef('short' , 'h') +types.typedef('int' , 'i') +types.typedef('long' , 'l') +types.typedef('unsigned short', 'H') +types.typedef('unsigned int' , 'I') +types.typedef('unsigned long' , 'L') +types.typedef('domid_t' , 'u64') +types.typedef('blkif_vdev_t' , 'u16') +types.typedef('blkif_pdev_t' , 'u16') +types.typedef('blkif_sector_t', 'u64') + +types.struct('u8[6]', + ('u8', 'a1'), + ('u8', 'a2'), + ('u8', 'a3'), + ('u8', 'a4'), + ('u8', 'a5'), + ('u8', 'a6')) + +types.struct('blkif_fe_interface_status_changed_t', + ('unsigned int', 'handle'), + ('unsigned int', 'status'), + ('unsigned int', 'evtchn')) + +types.struct('blkif_fe_driver_status_changed_t', + ('unsigned int', 'status'), + ('unsigned int', 'nr_interfaces')) + +types.struct('blkif_fe_interface_connect_t', + ('unsigned int' , 'handle'), + ('unsigned long', 'shmem_frame')) + +types.struct('blkif_fe_interface_disconnect_t', + ('unsigned int', 'handle')) + +types.struct('blkif_extent_t', + ('blkif_pdev_t' , 'device'), + ('blkif_sector_t', 'sector_start'), + ('blkif_sector_t', 'sector_length')) + +types.struct('blkif_be_create_t', + ('domid_t' , 'domid'), + ('unsigned int', 'blkif_handle'), + ('unsigned int', 'status')) + +types.struct('blkif_be_destroy_t', + ('domid_t' , 'domid'), + ('unsigned int', 'blkif_handle'), + ('unsigned int', 'status')) + +types.struct('blkif_be_connect_t', + ('domid_t' , 'domid'), + ('unsigned int' , 'blkif_handle'), + ('unsigned int' , 'evtchn'), + ('unsigned long', 'shmem_frame'), + ('unsigned int' , 'status')) + +types.struct('blkif_be_disconnect_t', + ('domid_t' , 'domid'), + ('unsigned int', 'blkif_handle'), + ('unsigned int', 'status')) + +types.struct('blkif_be_vbd_create_t', + ('domid_t' , 'domid'), #Q + ('unsigned int', 'blkif_handle'), #I + ('blkif_vdev_t', 'vdevice'), #H + ('int' , 'readonly'), #i + ('unsigned int', 'status')) #I + +types.struct('blkif_be_vbd_destroy_t', + ('domid_t' , 'domid'), + ('unsigned int', 'blkif_handle'), + ('blkif_vdev_t', 'vdevice'), + ('unsigned int', 'status')) + +types.struct('blkif_be_vbd_grow_t', + ('domid_t' , 'domid'), #Q + ('unsigned int' , 'blkif_handle'), #I + ('blkif_vdev_t' , 'vdevice'), #H + ('blkif_extent_t', 'extent'), #HQQ + ('unsigned int' , 'status')) #I + +types.struct('blkif_be_vbd_shrink_t', + ('domid_t' , 'domid'), + ('unsigned int', 'blkif_handle'), + ('blkif_vdev_t', 'vdevice'), + ('unsigned int', 'status')) + +types.struct('blkif_be_driver_status_changed_t', + ('unsigned int', 'status'), + ('unsigned int', 'nr_interfaces')) + +types.struct('netif_fe_interface_status_changed_t', + ('unsigned int', 'handle'), + ('unsigned int', 'status'), + ('unsigned int', 'evtchn'), + ('u8[6]', 'mac')) + +types.struct('netif_fe_driver_status_changed_t', + ('unsigned int', 'status'), + ('unsigned int', 'nr_interfaces')) + +types.struct('netif_fe_interface_connect_t', + ('unsigned int', 'handle'), + ('unsigned long', 'tx_shmem_frame'), + ('unsigned long', 'rx_shmem_frame')) + +types.struct('netif_fe_interface_disconnect_t', + ('unsigned int', 'handle')) + +types.struct('netif_be_create_t', + ('domid_t' , 'domid'), + ('unsigned int', 'netif_handle'), + ('u8[6]' , 'mac'), + ('unsigned int', 'status')) + +types.struct('netif_be_destroy_t', + ('domid_t' , 'domid'), + ('unsigned int', 'netif_handle'), + ('unsigned int', 'status')) + +types.struct('netif_be_connect_t', + ('domid_t' , 'domid'), + ('unsigned int' , 'netif_handle'), + ('unsigned int' , 'evtchn'), + ('unsigned long', 'tx_shmem_frame'), + ('unsigned long', 'rx_shmem_frame'), + ('unsigned int' , 'status')) + +types.struct('netif_be_disconnect_t', + ('domid_t' , 'domid'), + ('unsigned int', 'netif_handle'), + ('unsigned int', 'status')) + +types.struct('netif_be_driver_status_changed_t', + ('unsigned int', 'status'), + ('unsigned int', 'nr_interfaces')) + +if 1 or __name__ == "__main__": + types.show() diff --git a/tools/xenmgr/lib/server/messages.py b/tools/xenmgr/lib/server/messages.py new file mode 100644 index 0000000000..b45a5004de --- /dev/null +++ b/tools/xenmgr/lib/server/messages.py @@ -0,0 +1,186 @@ +import struct + +import xend.utils + +""" All message formats. +Added to incrementally for the various message types. +See below. +""" +msg_formats = {} + +#============================================================================ +# Console message types. +#============================================================================ + +CMSG_CONSOLE = 0 + +console_formats = { 'console_data': (CMSG_CONSOLE, 0, "?") } + +msg_formats.update(console_formats) + +#============================================================================ +# Block interface message types. +#============================================================================ + +CMSG_BLKIF_BE = 1 +CMSG_BLKIF_FE = 2 + +CMSG_BLKIF_FE_INTERFACE_STATUS_CHANGED = 0 +CMSG_BLKIF_FE_DRIVER_STATUS_CHANGED = 32 +CMSG_BLKIF_FE_INTERFACE_CONNECT = 33 +CMSG_BLKIF_FE_INTERFACE_DISCONNECT = 34 + +CMSG_BLKIF_BE_CREATE = 0 +CMSG_BLKIF_BE_DESTROY = 1 +CMSG_BLKIF_BE_CONNECT = 2 +CMSG_BLKIF_BE_DISCONNECT = 3 +CMSG_BLKIF_BE_VBD_CREATE = 4 +CMSG_BLKIF_BE_VBD_DESTROY = 5 +CMSG_BLKIF_BE_VBD_GROW = 6 +CMSG_BLKIF_BE_VBD_SHRINK = 7 +CMSG_BLKIF_BE_DRIVER_STATUS_CHANGED = 32 + +BLKIF_DRIVER_STATUS_DOWN = 0 +BLKIF_DRIVER_STATUS_UP = 1 + +BLKIF_INTERFACE_STATUS_DESTROYED = 0 #/* Interface doesn't exist. */ +BLKIF_INTERFACE_STATUS_DISCONNECTED = 1 #/* Exists but is disconnected. */ +BLKIF_INTERFACE_STATUS_CONNECTED = 2 #/* Exists and is connected. */ + +BLKIF_BE_STATUS_OKAY = 0 +BLKIF_BE_STATUS_ERROR = 1 +BLKIF_BE_STATUS_INTERFACE_EXISTS = 2 +BLKIF_BE_STATUS_INTERFACE_NOT_FOUND = 3 +BLKIF_BE_STATUS_INTERFACE_CONNECTED = 4 +BLKIF_BE_STATUS_VBD_EXISTS = 5 +BLKIF_BE_STATUS_VBD_NOT_FOUND = 6 +BLKIF_BE_STATUS_OUT_OF_MEMORY = 7 +BLKIF_BE_STATUS_EXTENT_NOT_FOUND = 8 +BLKIF_BE_STATUS_MAPPING_ERROR = 9 + +blkif_formats = { + 'blkif_be_connect_t': + (CMSG_BLKIF_BE, CMSG_BLKIF_BE_CONNECT, "QIILI"), + + 'blkif_be_create_t': + (CMSG_BLKIF_BE, CMSG_BLKIF_BE_CREATE, "QII"), + + 'blkif_be_destroy_t': + (CMSG_BLKIF_BE, CMSG_BLKIF_BE_DESTROY, "QII"), + + 'blkif_be_vbd_create_t': + (CMSG_BLKIF_BE, CMSG_BLKIF_BE_VBD_CREATE, "QIHII"), + + 'blkif_be_vbd_grow_t': + (CMSG_BLKIF_BE, CMSG_BLKIF_BE_VBD_GROW , "QIHHHQQI"), + + 'blkif_fe_interface_status_changed_t': + (CMSG_BLKIF_FE, CMSG_BLKIF_FE_INTERFACE_STATUS_CHANGED, "III"), + + 'blkif_fe_driver_status_changed_t': + (CMSG_BLKIF_FE, CMSG_BLKIF_FE_DRIVER_STATUS_CHANGED, "?"), + + 'blkif_fe_interface_connect_t': + (CMSG_BLKIF_FE, CMSG_BLKIF_FE_INTERFACE_CONNECT, "IL"), +} + +msg_formats.update(blkif_formats) + +#============================================================================ +# Network interface message types. +#============================================================================ + +CMSG_NETIF_BE = 3 +CMSG_NETIF_FE = 4 + +CMSG_NETIF_FE_INTERFACE_STATUS_CHANGED = 0 +CMSG_NETIF_FE_DRIVER_STATUS_CHANGED = 32 +CMSG_NETIF_FE_INTERFACE_CONNECT = 33 +CMSG_NETIF_FE_INTERFACE_DISCONNECT = 34 + +CMSG_NETIF_BE_CREATE = 0 +CMSG_NETIF_BE_DESTROY = 1 +CMSG_NETIF_BE_CONNECT = 2 +CMSG_NETIF_BE_DISCONNECT = 3 +CMSG_NETIF_BE_DRIVER_STATUS_CHANGED = 32 + +NETIF_INTERFACE_STATUS_DESTROYED = 0 #/* Interface doesn't exist. */ +NETIF_INTERFACE_STATUS_DISCONNECTED = 1 #/* Exists but is disconnected. */ +NETIF_INTERFACE_STATUS_CONNECTED = 2 #/* Exists and is connected. */ + +NETIF_DRIVER_STATUS_DOWN = 0 +NETIF_DRIVER_STATUS_UP = 1 + +netif_formats = { + 'netif_be_connect_t': + (CMSG_NETIF_BE, CMSG_NETIF_BE_CONNECT, "QIILLI"), + + 'netif_be_create_t': + (CMSG_NETIF_BE, CMSG_NETIF_BE_CREATE, "QIBBBBBBBBI"), + + 'netif_be_destroy_t': + (CMSG_NETIF_BE, CMSG_NETIF_BE_DESTROY, "QII"), + + 'netif_be_disconnect_t': + (CMSG_NETIF_BE, CMSG_NETIF_BE_DISCONNECT, "QII"), + + 'netif_be_driver_status_changed_t': + (CMSG_NETIF_BE, CMSG_NETIF_BE_DRIVER_STATUS_CHANGED, "QII"), + + 'netif_fe_driver_status_changed_t': + (CMSG_NETIF_FE, CMSG_NETIF_FE_DRIVER_STATUS_CHANGED, "II"), + + 'netif_fe_interface_connect_t': + (CMSG_NETIF_FE, CMSG_NETIF_FE_INTERFACE_CONNECT, "ILL"), + + 'netif_fe_interface_status_changed_t': + (CMSG_NETIF_FE, CMSG_NETIF_FE_INTERFACE_STATUS_CHANGED, "IIIBBBBBBBB"), + } + +msg_formats.update(netif_formats) + +#============================================================================ + +class Msg: + pass + +def packMsg(ty, params): + print '>packMsg', ty, params + (major, minor, packing) = msg_formats[ty] + args = {} + for (k, v) in params.items(): + if k == 'mac': + for i in range(0, 6): + args['mac[%d]' % i] = v[i] + else: + args[k] = v + for (k, v) in args.items(): + print 'packMsg>', k, v, type(v) + msgid = 0 + msg = xend.utils.message(major, minor, msgid, args) + return msg + +def unpackMsg(ty, msg): + args = msg.get_payload() + mac = [0, 0, 0, 0, 0, 0] + macs = [] + for (k, v) in args.items(): + if k.startswith('mac['): + macs += k + i = int(k[4:5]) + mac[i] = v + else: + pass + if macs: + args['mac'] = mac + for k in macs: + del args[k] + print '<unpackMsg', ty, args + return args + +def msgTypeName(ty, subty): + for (name, info) in msg_formats.items(): + if info[0] == ty and info[1] == subty: + return name + return None + diff --git a/tools/xenmgr/lib/server/netif.py b/tools/xenmgr/lib/server/netif.py new file mode 100755 index 0000000000..a6b1c99b19 --- /dev/null +++ b/tools/xenmgr/lib/server/netif.py @@ -0,0 +1,205 @@ +import random + +import channel +import controller +from messages import * + +class NetifControllerFactory(controller.ControllerFactory): + """Factory for creating network interface controllers. + Also handles the 'back-end' channel to dom0. + """ + # todo: add support for setting dom controlling blkifs (don't assume 0). + # todo: add support for 'recovery'. + + def __init__(self): + controller.ControllerFactory.__init__(self) + + self.majorTypes = [ CMSG_NETIF_BE ] + + self.subTypes = { + CMSG_NETIF_BE_CREATE : self.recv_be_create, + CMSG_NETIF_BE_CONNECT: self.recv_be_connect, + CMSG_NETIF_BE_DRIVER_STATUS_CHANGED: self.recv_be_driver_status_changed, + } + self.attached = 1 + self.registerChannel() + + def createInstance(self, dom): + #print 'netif>createInstance> dom=', dom + netif = self.getInstanceByDom(dom) + if netif is None: + netif = NetifController(self, dom) + self.addInstance(netif) + return netif + + def setControlDomain(self, dom): + self.deregisterChannel() + self.attached = 0 + self.dom = dom + self.registerChannel() + # + #if xend.netif.be_port.remote_dom != 0: + # xend.netif.recovery = True + # xend.netif.be_port = xend.main.port_from_dom(dom) + # + pass + + def recv_be_create(self, msg, req): + self.callDeferred(0) + + def recv_be_connect(self, msg, req): + val = unpackMsg('netif_be_connect_t', msg) + dom = val['domid'] + vif = val['netif_handle'] + netif = self.getInstanceByDom(dom) + if netif: + netif.send_interface_connected(vif) + else: + print "recv_be_connect> unknown vif=", vif + pass + + def recv_be_driver_status_changed(self, msg, req): + val = unpackMsg('netif_be_driver_status_changed_t', msg) + status = val['status'] + if status == NETIF_DRIVER_STATUS_UP and not self.attached: + for netif in self.getInstances(): + netif.reattach_devices() + self.attached = 1 + +## pl = msg.get_payload() +## status = pl['status'] +## if status == NETIF_DRIVER_STATUS_UP: +## if xend.netif.recovery: +## print "New netif backend now UP, notifying guests:" +## for netif_key in interface.list.keys(): +## netif = interface.list[netif_key] +## netif.create() +## print " Notifying %d" % netif.dom +## msg = xend.utils.message( +## CMSG_NETIF_FE, +## CMSG_NETIF_FE_INTERFACE_STATUS_CHANGED, 0, +## { 'handle' : 0, 'status' : 1 }) +## netif.ctrlif_tx_req(xend.main.port_from_dom(netif.dom),msg) +## print "Done notifying guests" +## recovery = False + +class NetDev: + """Info record for a network device. + """ + + def __init__(self, vif, mac): + self.vif = vif + self.mac = mac + self.evtchn = None + +class NetifController(controller.Controller): + """Network interface controller. Handles all network devices for a domain. + """ + + def __init__(self, factory, dom): + #print 'NetifController> dom=', dom + controller.Controller.__init__(self, factory, dom) + self.devices = {} + + self.majorTypes = [ CMSG_NETIF_FE ] + + self.subTypes = { + CMSG_NETIF_FE_DRIVER_STATUS_CHANGED: + self.recv_fe_driver_status_changed, + CMSG_NETIF_FE_INTERFACE_CONNECT : + self.recv_fe_interface_connect, + } + self.registerChannel() + #print 'NetifController<', 'dom=', self.dom, 'idx=', self.idx + + + def randomMAC(self): + # VIFs get a random MAC address with a "special" vendor id. + # + # NB. The vendor is currently an "obsolete" one that used to belong + # to DEC (AA-00-00). Using it is probably a bit rude :-) + # + # NB2. The first bit of the first random octet is set to zero for + # all dynamic MAC addresses. This may allow us to manually specify + # MAC addresses for some VIFs with no fear of clashes. + mac = [ 0xaa, 0x00, 0x00, + random.randint(0x00, 0x7f), + random.randint(0x00, 0xff), + random.randint(0x00, 0xff) ] + return mac + + def attach_device(self, vif, vmac): + if vmac is None: + mac = self.randomMAC() + else: + mac = [ int(x, 16) for x in vmac.split(':') ] + if len(mac) != 6: raise ValueError("invalid mac") + #print "attach_device>", "vif=", vif, "mac=", mac + self.devices[vif] = NetDev(vif, mac) + d = self.factory.addDeferred() + self.send_be_create(vif) + return d + + def reattach_devices(self): + d = self.factory.addDeferred() + self.send_be_create(vif) + self.attach_fe_devices(0) + + def attach_fe_devices(self): + for dev in self.devices.values(): + msg = packMsg('netif_fe_interface_status_changed_t', + { 'handle' : dev.vif, + 'status' : NETIF_INTERFACE_STATUS_DISCONNECTED, + 'evtchn' : 0, + 'mac' : dev.mac }) + self.writeRequest(msg) + + def recv_fe_driver_status_changed(self, msg, req): + if not req: return + msg = packMsg('netif_fe_driver_status_changed_t', + { 'status' : NETIF_DRIVER_STATUS_UP, + 'nr_interfaces' : len(self.devices) }) + self.writeRequest(msg) + self.attach_fe_devices() + + def recv_fe_interface_connect(self, msg, req): + val = unpackMsg('netif_fe_interface_connect_t', msg) + dev = self.devices[val['handle']] + dev.evtchn = channel.eventChannel(0, self.dom) + msg = packMsg('netif_be_connect_t', + { 'domid' : self.dom, + 'netif_handle' : dev.vif, + 'evtchn' : dev.evtchn['port1'], + 'tx_shmem_frame' : val['tx_shmem_frame'], + 'rx_shmem_frame' : val['rx_shmem_frame'] }) + self.factory.writeRequest(msg) + + #def recv_fe_interface_status_changed(self): + # print 'recv_fe_interface_status_changed>' + # pass + + def send_interface_connected(self, vif): + dev = self.devices[vif] + msg = packMsg('netif_fe_interface_status_changed_t', + { 'handle' : dev.vif, + 'status' : NETIF_INTERFACE_STATUS_CONNECTED, + 'evtchn' : dev.evtchn['port2'], + 'mac' : dev.mac }) + self.writeRequest(msg) + + def send_be_create(self, vif): + dev = self.devices[vif] + msg = packMsg('netif_be_create_t', + { 'domid' : self.dom, + 'netif_handle' : dev.vif, + 'mac' : dev.mac }) + self.factory.writeRequest(msg) + + def send_be_destroy(self, vif): + print 'send_be_destroy>', 'dom=', self.dom, 'vif=', vif + dev = self.devices[vif] + del self.devices[vif] + msg = packMsg('netif_be_destroy_t', + { 'domid' : self.dom, + 'netif_handle' : vif }) + self.factory.writeRequest(msg) diff --git a/tools/xenmgr/lib/server/params.py b/tools/xenmgr/lib/server/params.py new file mode 100644 index 0000000000..d8f064cf0c --- /dev/null +++ b/tools/xenmgr/lib/server/params.py @@ -0,0 +1,10 @@ +# The following parameters could be placed in a configuration file. +PID_FILE = '/var/run/xend.pid' +LOG_FILE = '/var/log/xend.log' +USER = 'root' +CONTROL_DIR = '/var/run/xend' +MGMT_SOCK = 'xenmgrsock' # relative to CONTROL_DIR +EVENT_PORT = 8001 + +CONSOLE_PORT_BASE = 9600 + diff --git a/tools/xenmgr/lib/sxp.py b/tools/xenmgr/lib/sxp.py new file mode 100644 index 0000000000..dd4fece6f0 --- /dev/null +++ b/tools/xenmgr/lib/sxp.py @@ -0,0 +1,557 @@ +#!/usr/bin/python2 +# Copyright (C) 2004 Mike Wray <mike.wray@hp.com> +""" +Input-driven parsing for s-expression (sxp) format. +Create a parser: pin = Parser(); +Then call pin.input(buf) with your input. +Call pin.input_eof() when done. +Use pin.read() to see if a value has been parsed, pin.get_val() +to get a parsed value. You can call ready and get_val at any time - +you don't have to wait until after calling input_eof. + +""" +from __future__ import generators + +import sys +import types +import errno +import string + +__all__ = [ + "mime_type", + "ParseError", + "Parser", + "atomp", + "show", + "show_xml", + "elementp", + "name", + "attributes", + "attribute", + "children", + "child", + "child_at", + "child0", + "child1", + "child2", + "child3", + "child4", + "child_value", + "has_id", + "with_id", + "child_with_id", + "elements", + "parse", + ] + +mime_type = "application/sxp" + +escapes = { + 'a': '\a', + 'b': '\b', + 't': '\t', + 'n': '\n', + 'v': '\v', + 'f': '\f', + 'r': '\r', + '\\': '\\', + '\'': '\'', + '\"': '\"'} + +k_list_open = "(" +k_list_close = ")" +k_attr_open = "@" +k_eval = "!" + +escapes_rev = {} +for k in escapes: + escapes_rev[escapes[k]] = k + +class ParseError(StandardError): + + def __init__(self, parser, value): + self.parser = parser + self.value = value + + def __str__(self): + return self.value + +class ParserState: + + def __init__(self, fn, parent=None): + self.parent = parent + self.buf = '' + self.val = [] + self.delim = None + self.fn = fn + + def push(self, fn): + return ParserState(fn, parent=self) + +class Parser: + + def __init__(self): + self.error = sys.stderr + self.reset() + + def reset(self): + self.val = [] + self.eof = 0 + self.err = 0 + self.line_no = 0 + self.char_no = 0 + self.state = None + + def push_state(self, fn): + self.state = self.state.push(fn) + + def pop_state(self): + val = self.state + self.state = self.state.parent + if self.state and self.state.fn == self.state_start: + # Return to start state - produce the value. + self.val += self.state.val + self.state.val = [] + return val + + def in_class(self, c, s): + return s.find(c) >= 0 + + def in_space_class(self, c): + return self.in_class(c, ' \t\n\v\f\r') + + def is_separator(self, c): + return self.in_class(c, '{}()<>[]!;') + + def in_comment_class(self, c): + return self.in_class(c, '#') + + def in_string_quote_class(self, c): + return self.in_class(c, '"\'') + + def in_printable_class(self, c): + return self.in_class(c, string.printable) + + def set_error_stream(self, error): + self.error = error + + def has_error(self): + return self.err > 0 + + def at_eof(self): + return self.eof + + def input_eof(self): + self.eof = 1 + self.input_char(-1) + + def input(self, buf): + if not buf or len(buf) == 0: + self.input_eof() + else: + for c in buf: + self.input_char(c) + + def input_char(self, c): + if self.at_eof(): + pass + elif c == '\n': + self.line_no += 1 + self.char_no = 0 + else: + self.char_no += 1 + + if self.state is None: + self.begin_start(None) + self.state.fn(c) + + def ready(self): + return len(self.val) > 0 + + def get_val(self): + v = self.val[0] + self.val = self.val[1:] + return v + + def get_all(self): + return self.val + + def begin_start(self, c): + self.state = ParserState(self.state_start) + + def end_start(self): + self.val += self.state.val + self.pop_state() + + def state_start(self, c): + if self.at_eof(): + self.end_start() + elif self.in_space_class(c): + pass + elif self.in_comment_class(c): + self.begin_comment(c) + elif c == k_list_open: + self.begin_list(c) + elif c == k_list_close: + raise ParseError(self, "syntax error: "+c) + elif self.in_string_quote_class(c): + self.begin_string(c) + elif self.in_printable_class(c): + self.begin_atom(c) + elif c == chr(4): + # ctrl-D, EOT: end-of-text. + self.input_eof() + else: + raise ParseError(self, "invalid character: code %d" % ord(c)) + + def begin_comment(self, c): + self.push_state(self.state_comment) + self.state.buf += c + + def end_comment(self): + self.pop_state() + + def state_comment(self, c): + if c == '\n' or self.at_eof(): + self.end_comment() + else: + self.state.buf += c + + def begin_string(self, c): + self.push_state(self.state_string) + self.state.delim = c + + def end_string(self): + val = self.state.buf + self.state.parent.val.append(val) + self.pop_state() + + def state_string(self, c): + if self.at_eof(): + raise ParseError(self, "unexpected EOF") + elif c == self.state.delim: + self.end_string() + elif c == '\\': + self.push_state(self.state_escape) + else: + self.state.buf += c + + def state_escape(self, c): + if self.at_eof(): + raise ParseError(self, "unexpected EOF") + d = escapes.get(c) + if d: + self.state.parent.buf += d + self.pop_state() + elif c == 'x': + self.state.fn = self.state_hex + self.state.val = 0 + else: + self.state.fn = self.state_octal + self.state.val = 0 + self.input_char(c) + + def state_octal(self, c): + def octaldigit(c): + self.state.val *= 8 + self.state.val += ord(c) - ord('0') + self.state.buf += c + if self.state.val < 0 or self.state.val > 0xff: + raise ParseError(self, "invalid octal escape: out of range " + self.state.buf) + if len(self.state.buf) == 3: + octaldone() + + def octaldone(): + d = chr(self.state.val) + self.state.parent.buf += d + self.pop_state() + + if self.at_eof(): + raise ParseError(self, "unexpected EOF") + elif '0' <= c <= '7': + octaldigit(c) + elif len(self.buf): + octaldone() + self.input_char(c) + + def state_hex(self, c): + def hexdone(): + d = chr(self.state.val) + self.state.parent.buf += d + self.pop_state() + + def hexdigit(c, d): + self.state.val *= 16 + self.state.val += ord(c) - ord(d) + self.state.buf += c + if self.state.val < 0 or self.state.val > 0xff: + raise ParseError(self, "invalid hex escape: out of range " + self.state.buf) + if len(self.state.buf) == 2: + hexdone() + + if self.at_eof(): + raise ParseError(self, "unexpected EOF") + elif '0' <= c <= '9': + hexdigit(c, '0') + elif 'A' <= c <= 'F': + hexdigit(c, 'A') + elif 'a' <= c <= 'f': + hexdigit(c, 'a') + elif len(buf): + hexdone() + self.input_char(c) + + def begin_atom(self, c): + self.push_state(self.state_atom) + self.state.buf = c + + def end_atom(self): + val = self.state.buf + self.state.parent.val.append(val) + self.pop_state() + + def state_atom(self, c): + if self.at_eof(): + self.end_atom() + elif (self.is_separator(c) or + self.in_space_class(c) or + self.in_comment_class(c)): + self.end_atom() + self.input_char(c) + else: + self.state.buf += c + + def begin_list(self, c): + self.push_state(self.state_list) + + def end_list(self): + val = self.state.val + self.state.parent.val.append(val) + self.pop_state() + + def state_list(self, c): + if self.at_eof(): + raise ParseError(self, "unexpected EOF") + elif c == k_list_close: + self.end_list() + else: + self.state_start(c) + +def atomp(sxpr): + if sxpr.isalnum() or sxpr == '@': + return 1 + for c in sxpr: + if c in string.whitespace: return 0 + if c in '"\'\\(){}[]<>$#&%^': return 0 + if c in string.ascii_letters: continue + if c in string.digits: continue + if c in '.-_:/~': continue + return 0 + return 1 + +def show(sxpr, out=sys.stdout): + if isinstance(sxpr, types.ListType): + out.write(k_list_open) + i = 0 + for x in sxpr: + if i: out.write(' ') + show(x, out) + i += 1 + out.write(k_list_close) + elif isinstance(sxpr, types.StringType) and atomp(sxpr): + out.write(sxpr) + else: + #out.write("'" + str(sxpr) + "'") + out.write(repr(str(sxpr))) + +def show_xml(sxpr, out=sys.stdout): + if isinstance(sxpr, types.ListType): + element = name(sxpr) + out.write('<%s' % element) + for attr in attributes(sxpr): + out.write(' %s=%s' % (attr[0], attr[1])) + out.write('>') + i = 0 + for x in children(sxpr): + if i: out.write(' ') + show_xml(x, out) + i += 1 + out.write('</%s>' % element) + elif isinstance(sxpr, types.StringType) and atomp(sxpr): + out.write(sxpr) + else: + out.write(str(sxpr)) + +def elementp(sxpr, elt=None): + return (isinstance(sxpr, types.ListType) + and len(sxpr) + and (None == elt or sxpr[0] == elt)) + +def name(sxpr): + val = None + if isinstance(sxpr, types.StringType): + val = sxpr + elif isinstance(sxpr, types.ListType) and len(sxpr): + val = sxpr[0] + return val + +def attributes(sxpr): + val = [] + if isinstance(sxpr, types.ListType) and len(sxpr) > 1: + attr = sxpr[1] + if elementp(attr, k_attr_open): + val = attr[1:] + return val + +def attribute(sxpr, key, val=None): + for x in attributes(sxpr): + if x[0] == key: + val = x[1] + break + return val + +def children(sxpr, elt=None): + val = [] + if isinstance(sxpr, types.ListType) and len(sxpr) > 1: + i = 1 + x = sxpr[i] + if elementp(x, k_attr_open): + i += 1 + val = sxpr[i : ] + if elt: + def iselt(x): + return elementp(x, elt) + val = filter(iselt, val) + return val + +def child(sxpr, elt, val=None): + for x in children(sxpr): + if elementp(x, elt): + val = x + break + return val + +def child_at(sxpr, index, val=None): + kids = children(sxpr) + if len(kids) > index: + val = kids[index] + return val + +def child0(sxpr, val=None): + return child_at(sxpr, 0, val) + +def child1(sxpr, val=None): + return child_at(sxpr, 1, val) + +def child2(sxpr, val=None): + return child_at(sxpr, 2, val) + +def child3(sxpr, val=None): + return child_at(sxpr, 3, val) + +def child4(sxpr, val=None): + return child_at(sxpr, 4, val) + +def child_value(sxpr, elt, val=None): + kid = child(sxpr, elt) + if kid: + val = child_at(kid, 0, val) + return val + +def has_id(sxpr, id): + """Test if an s-expression has a given id. + """ + return attribute(sxpr, 'id') == id + +def with_id(sxpr, id, val=None): + """Find the first s-expression with a given id, at any depth. + + sxpr s-exp or list + id id + val value if not found (default None) + + return s-exp or val + """ + if isinstance(sxpr, types.ListType): + for n in sxpr: + if has_id(n, id): + val = n + break + v = with_id(n, id) + if v is None: continue + val = v + break + return val + +def child_with_id(sxpr, id, val=None): + """Find the first child with a given id. + + sxpr s-exp or list + id id + val value if not found (default None) + + return s-exp or val + """ + if isinstance(sxpr, types.ListType): + for n in sxpr: + if has_id(n, id): + val = n + break + return val + +def elements(sxpr, ctxt=None): + """Generate elements (at any depth). + Visit elements in pre-order. + Values generated are (node, context) + The context is None if there is no parent, otherwise + (index, parent, context) where index is the node's index w.r.t its parent, + and context is the parent's context. + + sxpr s-exp + + returns generator + """ + yield (sxpr, ctxt) + i = 0 + for n in children(sxpr): + if isinstance(n, types.ListType): + # Calling elements() recursively does not generate recursively, + # it just returns a generator object. So we must iterate over it. + for v in elements(n, (i, sxpr, ctxt)): + yield v + i += 1 + +def parse(io): + """Completely parse all input from 'io'. + + io input file object + returns list of values, None if incomplete + raises ParseError on parse error + """ + pin = Parser() + while 1: + buf = io.readline() + pin.input(buf) + if len(buf) == 0: + break + if pin.ready(): + val = pin.get_all() + else: + val = None + return val + + +if __name__ == '__main__': + print ">main" + pin = Parser() + while 1: + buf = sys.stdin.read(1024) + #buf = sys.stdin.readline() + pin.input(buf) + while pin.ready(): + val = pin.get_val() + print + print '****** val=', val + if len(buf) == 0: + break + diff --git a/tools/xenmgr/netfix b/tools/xenmgr/netfix new file mode 100644 index 0000000000..fcde6292cb --- /dev/null +++ b/tools/xenmgr/netfix @@ -0,0 +1,150 @@ +#!/usr/bin/python +# Copyright (C) 2004 Mike Wray <mike.wray@hp.com> +#============================================================================ +# Move the IP address from eth0 onto the Xen bridge (nbe-br). +# Works best if the bridge control utils (brctl) have been installed. +#============================================================================ +import os +import os.path +import re +import sys + +from getopt import getopt + +CMD_IFCONFIG = '/sbin/ifconfig' +CMD_ROUTE = '/sbin/route' +CMD_BRCTL = '/usr/local/sbin/brctl' + +def routes(): + """Return a list of the routes. + """ + fin = os.popen(CMD_ROUTE + ' -n', 'r') + routes = [] + for x in fin: + if x.startswith('Kernel'): continue + if x.startswith('Destination'): continue + x = x.strip() + y = x.split() + z = { 'destination': y[0], + 'gateway' : y[1], + 'mask' : y[2], + 'flags' : y[3], + 'metric' : y[4], + 'ref' : y[5], + 'use' : y[6], + 'interface' : y[7] } + routes.append(z) + return routes + +def cmd(p, s): + """Print and execute command 'p' with args 's'. + """ + global opts + c = p + ' ' + s + if opts.verbose: print c + if not opts.dryrun: + os.system(c) + +def ifconfig(interface): + """Return the ip config for an interface, + """ + fin = os.popen(CMD_IFCONFIG + ' %s' % interface, 'r') + inetre = re.compile('\s*inet\s*addr:(?P<address>\S*)\s*Bcast:(?P<broadcast>\S*)\s*Mask:(?P<mask>\S*)') + info = None + for x in fin: + m = inetre.match(x) + if not m: continue + info = m.groupdict() + info['interface'] = interface + break + return info + +def reconfigure(interface, bridge): + """Reconfigure an interface to be attached to a bridge, and give the bridge + the IP address etc. from interface. Move the default route to the interface + to the bridge. + """ + intf_info = ifconfig(interface) + if not intf_info: + print 'Interface not found:', interface + return + #bridge_info = ifconfig(bridge) + #if not bridge_info: + # print 'Bridge not found:', bridge + # return + route_info = routes() + intf_info['bridge'] = bridge + intf_info['gateway'] = None + for r in route_info: + if (r['destination'] == '0.0.0.0' and + 'G' in r['flags'] and + r['interface'] == interface): + intf_info['gateway'] = r['gateway'] + if not intf_info['gateway']: + print 'Gateway not found: ', interface + return + cmd(CMD_IFCONFIG, '%(interface)s 0.0.0.0' % intf_info) + cmd(CMD_IFCONFIG, '%(bridge)s %(address)s netmask %(mask)s broadcast %(broadcast)s up' % intf_info) + cmd(CMD_ROUTE, 'add default gateway %(gateway)s dev %(bridge)s' % intf_info) + if os.path.exists(CMD_BRCTL): + cmd(CMD_BRCTL, 'addif %(bridge)s %(interface)s' % intf_info) + +defaults = { + 'interface': 'eth0', + 'bridge' : 'nbe-br', + 'verbose' : 1, + 'dryrun' : 0, + } + +short_options = 'hvqni:b:' +long_options = ['help', 'verbose', 'quiet', 'interface=', 'bridge='] + +def usage(): + print """Usage: + %s [options] + + Reconfigure routing so that <bridge> has the IP address from + <interface>. This lets IP carry on working when <interface> + is attached to <bridge> for virtual networking. + If brctl is available, <interface> is added to <bridge>, + so this can be run before any domains have been created. + """ % sys.argv[0] + print """ + -i, --interface <interface> interface, default %(interface)s. + -b, --bridge <bridge> bridge, default %(bridge)s. + -v, --verbose Print commands. + -q, --quiet Don't print commands. + -n, --dry-run Don't execute commands. + -h, --help Print this help. + """ % defaults + sys.exit(1) + +class Opts: + + def __init__(self, defaults): + for (k, v) in defaults.items(): + setattr(self, k, v) + pass + +def main(): + global opts + opts = Opts(defaults) + (options, args) = getopt(sys.argv[1:], short_options, long_options) + if args: usage() + for k, v in options: + if k in ['-h', '--help']: + usage() + elif k in ['-i', '--interface']: + opts.interface = v + elif k in ['-b', '--bridge']: + opts.bridge = v + elif k in ['-q', '--quiet']: + opts.verbose = 0 + elif k in ['-v', '--verbose']: + opts.verbose = 1 + elif k in ['-n', '--dry-run']: + opts.dryrun = 1 + reconfigure(opts.interface, opts.bridge) + +if __name__ == '__main__': + main() diff --git a/tools/xenmgr/setup.py b/tools/xenmgr/setup.py new file mode 100644 index 0000000000..b08cf29c31 --- /dev/null +++ b/tools/xenmgr/setup.py @@ -0,0 +1,14 @@ + +from distutils.core import setup, Extension + +PACKAGE = 'xenmgr' +VERSION = '1.0' + +setup(name = PACKAGE, + version = VERSION, + description = 'Xen Management API', + author = 'Mike Wray', + author_email = 'mike.wray@hp.com', + packages = [ PACKAGE, PACKAGE + '.server' ], + package_dir = { PACKAGE: 'lib' }, + ) diff --git a/tools/xenmgr/xend b/tools/xenmgr/xend new file mode 100644 index 0000000000..f2355f1e51 --- /dev/null +++ b/tools/xenmgr/xend @@ -0,0 +1,40 @@ +#!/usr/bin/python +# Copyright (C) 2004 Mike Wray <mike.wray@hp.com> + +"""Xen management daemon. Lives in /usr/sbin. + Provides console server and HTTP management api. + + Run: + + xend start + + The daemon is stopped with: + + xend stop + + Unfortunately restarting it upsets the channel to dom0 and + domain management stops working - needs a reboot to fix. +""" +import os +import sys +from xenmgr.server import SrvConsoleServer + +def main(): + daemon = SrvConsoleServer.instance() + if not sys.argv[1:]: + print 'usage: %s {start|stop|restart}' % sys.argv[0] + elif os.fork(): + pid, status = os.wait() + return status >> 8 + elif sys.argv[1] == 'start': + return daemon.start() + elif sys.argv[1] == 'stop': + return daemon.stop() + elif sys.argv[1] == 'restart': + return daemon.stop() or daemon.start() + else: + print 'not an option:', sys.argv[1] + return 1 + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/xenmgr/xenmgrd b/tools/xenmgr/xenmgrd new file mode 100755 index 0000000000..c871d4dece --- /dev/null +++ b/tools/xenmgr/xenmgrd @@ -0,0 +1,10 @@ +#!/usr/bin/python +# Copyright (C) 2004 Mike Wray <mike.wray@hp.com> + +"""Xen management daemon. Lives in /usr/sbin. + Run after running xend to provide http access to the management API. + + NO LONGER NEEDED. +""" +from xenmgr.server import SrvServer +SrvServer.main() |