bitkeeper revision 1.946.1.3 (40c9c475sJLMEuc6TqUt-bX6hVFOpQ)
authormjw@wray-m-3.hpl.hp.com <mjw@wray-m-3.hpl.hp.com>
Fri, 11 Jun 2004 14:40:53 +0000 (14:40 +0000)
committermjw@wray-m-3.hpl.hp.com <mjw@wray-m-3.hpl.hp.com>
Fri, 11 Jun 2004 14:40:53 +0000 (14:40 +0000)
Add source tree for the management utils.

57 files changed:
.rootkeys
BitKeeper/etc/ignore
tools/Makefile
tools/examples/xm_dom_control.py [new file with mode: 0755]
tools/examples/xm_dom_create.py [new file with mode: 0755]
tools/examples/xm_vd_tool.py [new file with mode: 0755]
tools/xenctl/lib/ip.py [new file with mode: 0644]
tools/xenctl/lib/vdisk.py [new file with mode: 0644]
tools/xenctl/setup.py
tools/xend/lib/utils.c
tools/xenmgr/Makefile [new file with mode: 0644]
tools/xenmgr/lib/Args.py [new file with mode: 0644]
tools/xenmgr/lib/EventServer.py [new file with mode: 0644]
tools/xenmgr/lib/EventTypes.py [new file with mode: 0644]
tools/xenmgr/lib/PrettyPrint.py [new file with mode: 0644]
tools/xenmgr/lib/XendClient.py [new file with mode: 0644]
tools/xenmgr/lib/XendConsole.py [new file with mode: 0644]
tools/xenmgr/lib/XendDB.py [new file with mode: 0644]
tools/xenmgr/lib/XendDomain.py [new file with mode: 0644]
tools/xenmgr/lib/XendDomainConfig.py [new file with mode: 0644]
tools/xenmgr/lib/XendDomainInfo.py [new file with mode: 0644]
tools/xenmgr/lib/XendMigrate.py [new file with mode: 0644]
tools/xenmgr/lib/XendNode.py [new file with mode: 0644]
tools/xenmgr/lib/XendRoot.py [new file with mode: 0644]
tools/xenmgr/lib/XendVdisk.py [new file with mode: 0644]
tools/xenmgr/lib/XendVnet.py [new file with mode: 0644]
tools/xenmgr/lib/__init__.py [new file with mode: 0644]
tools/xenmgr/lib/encode.py [new file with mode: 0644]
tools/xenmgr/lib/server/SrvBase.py [new file with mode: 0644]
tools/xenmgr/lib/server/SrvConsole.py [new file with mode: 0644]
tools/xenmgr/lib/server/SrvConsoleDir.py [new file with mode: 0644]
tools/xenmgr/lib/server/SrvConsoleServer.py [new file with mode: 0644]
tools/xenmgr/lib/server/SrvDeviceDir.py [new file with mode: 0644]
tools/xenmgr/lib/server/SrvDir.py [new file with mode: 0644]
tools/xenmgr/lib/server/SrvDomain.py [new file with mode: 0644]
tools/xenmgr/lib/server/SrvDomainDir.py [new file with mode: 0644]
tools/xenmgr/lib/server/SrvEventDir.py [new file with mode: 0644]
tools/xenmgr/lib/server/SrvNode.py [new file with mode: 0644]
tools/xenmgr/lib/server/SrvRoot.py [new file with mode: 0644]
tools/xenmgr/lib/server/SrvServer.py [new file with mode: 0644]
tools/xenmgr/lib/server/SrvVdisk.py [new file with mode: 0644]
tools/xenmgr/lib/server/SrvVdiskDir.py [new file with mode: 0644]
tools/xenmgr/lib/server/SrvVnetDir.py [new file with mode: 0644]
tools/xenmgr/lib/server/__init__.py [new file with mode: 0644]
tools/xenmgr/lib/server/blkif.py [new file with mode: 0755]
tools/xenmgr/lib/server/channel.py [new file with mode: 0755]
tools/xenmgr/lib/server/console.py [new file with mode: 0755]
tools/xenmgr/lib/server/controller.py [new file with mode: 0755]
tools/xenmgr/lib/server/cstruct.py [new file with mode: 0755]
tools/xenmgr/lib/server/messages.py [new file with mode: 0644]
tools/xenmgr/lib/server/netif.py [new file with mode: 0755]
tools/xenmgr/lib/server/params.py [new file with mode: 0644]
tools/xenmgr/lib/sxp.py [new file with mode: 0644]
tools/xenmgr/netfix [new file with mode: 0644]
tools/xenmgr/setup.py [new file with mode: 0644]
tools/xenmgr/xend [new file with mode: 0644]
tools/xenmgr/xenmgrd [new file with mode: 0755]

index 25bb5eeb3cd06483d8d3154a674dda82057737d8..5f808adcecf4c14feb449d3d4292ceabe90eb52f 100644 (file)
--- a/.rootkeys
+++ b/.rootkeys
 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
 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
 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
index 2fafb882f584675ff3c5dacbe624561efbdb9773..f9801219a3010600ebd0f244762a344ab1ecf1fe 100644 (file)
@@ -30,3 +30,127 @@ 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~
index 9ddf5f25a21208151d98f361ad5558a840df1569..37e373c789d04b88a36fe03a98ee8800f493f230 100644 (file)
@@ -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 (executable)
index 0000000..da30244
--- /dev/null
@@ -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 (executable)
index 0000000..ce89426
--- /dev/null
@@ -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 (executable)
index 0000000..7207a59
--- /dev/null
@@ -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 (file)
index 0000000..0f7f61e
--- /dev/null
@@ -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 (file)
index 0000000..6c4d144
--- /dev/null
@@ -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
+
index 1b6a98c10a0606357a3efbd76cb0babea9c91673..1d67a843ef2bf53cac001cd86b5a0649479b80d9 100644 (file)
@@ -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.
index 918e74cc96fbaf6e86bc794ad94c91040c6bd9c8..903bb9c27493c8e91383b52494274b93f213acbf 100644 (file)
@@ -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 (file)
index 0000000..125f8cf
--- /dev/null
@@ -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 (file)
index 0000000..527e841
--- /dev/null
@@ -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 (file)
index 0000000..9749e11
--- /dev/null
@@ -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 (file)
index 0000000..3d4230e
--- /dev/null
@@ -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 (file)
index 0000000..9e91b11
--- /dev/null
@@ -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 (file)
index 0000000..aa88d99
--- /dev/null
@@ -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 (file)
index 0000000..041e6c4
--- /dev/null
@@ -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 (file)
index 0000000..6a27e65
--- /dev/null
@@ -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 (file)
index 0000000..60e207d
--- /dev/null
@@ -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 (file)
index 0000000..35db31f
--- /dev/null
@@ -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 (file)
index 0000000..efa56d7
--- /dev/null
@@ -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 (file)
index 0000000..1580ba8
--- /dev/null
@@ -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 (file)
index 0000000..9be3d64
--- /dev/null
@@ -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 (file)
index 0000000..6d9903a
--- /dev/null
@@ -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 (file)
index 0000000..241d29b
--- /dev/null
@@ -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 (file)
index 0000000..213408e
--- /dev/null
@@ -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 (file)
index 0000000..8d1c8b6
--- /dev/null
@@ -0,0 +1 @@
diff --git a/tools/xenmgr/lib/encode.py b/tools/xenmgr/lib/encode.py
new file mode 100644 (file)
index 0000000..38c9351
--- /dev/null
@@ -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 (file)
index 0000000..722b60f
--- /dev/null
@@ -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 (file)
index 0000000..ea25bb6
--- /dev/null
@@ -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 (file)
index 0000000..89b092c
--- /dev/null
@@ -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 (file)
index 0000000..88f3964
--- /dev/null
@@ -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 (file)
index 0000000..52f4285
--- /dev/null
@@ -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 (file)
index 0000000..f4310e2
--- /dev/null
@@ -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 (file)
index 0000000..0ef5676
--- /dev/null
@@ -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 (file)
index 0000000..7bb2996
--- /dev/null
@@ -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 (file)
index 0000000..eda5697
--- /dev/null
@@ -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 (file)
index 0000000..3c6168e
--- /dev/null
@@ -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 (file)
index 0000000..b002d2c
--- /dev/null
@@ -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 (file)
index 0000000..a42219b
--- /dev/null
@@ -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 (file)
index 0000000..4200b9b
--- /dev/null
@@ -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 (file)
index 0000000..a6a9e55
--- /dev/null
@@ -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 (file)
index 0000000..a8a8141
--- /dev/null
@@ -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 (file)
index 0000000..8b13789
--- /dev/null
@@ -0,0 +1 @@
+
diff --git a/tools/xenmgr/lib/server/blkif.py b/tools/xenmgr/lib/server/blkif.py
new file mode 100755 (executable)
index 0000000..0ef8ff0
--- /dev/null
@@ -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 (executable)
index 0000000..7678e18
--- /dev/null
@@ -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 (executable)
index 0000000..6db905d
--- /dev/null
@@ -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 (executable)
index 0000000..793ac85
--- /dev/null
@@ -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 (executable)
index 0000000..880931b
--- /dev/null
@@ -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 (file)
index 0000000..b45a500
--- /dev/null
@@ -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 (executable)
index 0000000..a6b1c99
--- /dev/null
@@ -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 (file)
index 0000000..d8f064c
--- /dev/null
@@ -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 (file)
index 0000000..dd4fece
--- /dev/null
@@ -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 (file)
index 0000000..fcde629
--- /dev/null
@@ -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 (file)
index 0000000..b08cf29
--- /dev/null
@@ -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 (file)
index 0000000..f2355f1
--- /dev/null
@@ -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 (executable)
index 0000000..c871d4d
--- /dev/null
@@ -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()