ia64/xen-unstable

changeset 1850:e50c97665b67

bitkeeper revision 1.1108.1.4 (40fe51ffLxtDXSQ_TOJZQcpn_HcB3g)

Rework xend client to support synchronous and asynchronous
connections.
author mjw@wray-m-3.hpl.hp.com
date Wed Jul 21 11:22:39 2004 +0000 (2004-07-21)
parents f26582ec895e
children fd5d2b14cf2a
files tools/python/xen/xend/XendClient.py
line diff
     1.1 --- a/tools/python/xen/xend/XendClient.py	Tue Jul 20 14:55:40 2004 +0000
     1.2 +++ b/tools/python/xen/xend/XendClient.py	Wed Jul 21 11:22:39 2004 +0000
     1.3 @@ -1,6 +1,8 @@
     1.4 +#!/usr/bin/env python
     1.5  # Copyright (C) 2004 Mike Wray <mike.wray@hp.com>
     1.6  """Client API for the HTTP interface on xend.
     1.7  Callable as a script - see main().
     1.8 +Supports synchronous or asynchronous connection to xend.
     1.9  
    1.10  This API is the 'control-plane' for xend.
    1.11  The 'data-plane' is done separately. For example, consoles
    1.12 @@ -11,9 +13,12 @@ import sys
    1.13  import httplib
    1.14  import types
    1.15  from StringIO import StringIO
    1.16 -import urlparse
    1.17 +
    1.18  
    1.19  from twisted.protocols import http
    1.20 +from twisted.internet.protocol import ClientCreator
    1.21 +from twisted.internet.defer import Deferred
    1.22 +from twisted.internet import reactor
    1.23  
    1.24  from encode import *
    1.25  import sxp
    1.26 @@ -22,40 +27,22 @@ import PrettyPrint
    1.27  DEBUG = 0
    1.28  
    1.29  class XendError(RuntimeError):
    1.30 +    """Error class for 'expected errors' when talking to xend.
    1.31 +    """
    1.32      pass
    1.33  
    1.34 -class Foo(httplib.HTTPResponse):
    1.35 -
    1.36 -    def begin(self):
    1.37 -        fin = self.fp
    1.38 -        while(1):
    1.39 -            buf = fin.readline()
    1.40 -            print "***", buf
    1.41 -            if buf == '':
    1.42 -                print
    1.43 -                sys.exit()
    1.44 -
    1.45 -
    1.46 -def sxprio(sxpr):
    1.47 -    """Convert an sxpr to a string.
    1.48 -    """
    1.49 -    io = StringIO()
    1.50 -    sxp.show(sxpr, out=io)
    1.51 -    print >> io
    1.52 -    io.seek(0)
    1.53 -    return io
    1.54 -
    1.55  def fileof(val):
    1.56 -    """Converter for passing configs.
    1.57 +    """Converter for passing configs or other 'large' data.
    1.58      Handles lists, files directly.
    1.59      Assumes a string is a file name and passes its contents.
    1.60      """
    1.61      if isinstance(val, types.ListType):
    1.62 -        return sxprio(val)
    1.63 +        return sxp.to_string(val)
    1.64      if isinstance(val, types.StringType):
    1.65          return file(val)
    1.66      if hasattr(val, 'readlines'):
    1.67          return val
    1.68 +    raise XendError('cannot convert value')
    1.69  
    1.70  # todo: need to sort of what urls/paths are using for objects.
    1.71  # e.g. for domains at the moment return '0'.
    1.72 @@ -66,98 +53,251 @@ def fileof(val):
    1.73  # maps /xend/domain/0 to http://wray-m-3.hpl.hp.com:8000/xend/domain/0
    1.74  # And should accept urls for ids?
    1.75  
    1.76 -def urljoin(location, root, prefix='', rest=''):
    1.77 -    prefix = str(prefix)
    1.78 -    rest = str(rest)
    1.79 -    base = 'http://' + location + root + prefix
    1.80 -    url = urlparse.urljoin(base, rest)
    1.81 -    return url
    1.82 +class URL:
    1.83 +    """A URL.
    1.84 +    """
    1.85 +
    1.86 +    def __init__(self, proto='http', host='localhost', port=None, path='', query=None, frag=None):
    1.87 +        self.proto = proto
    1.88 +        self.host = host
    1.89 +        if port: port = int(port)
    1.90 +        self.port = port
    1.91 +        self.path = path
    1.92 +        self.query = query
    1.93 +        self.frag = frag
    1.94 +
    1.95 +    def url(self):
    1.96 +        """Get the full URL string including protocol, location and the full path.
    1.97 +        """
    1.98 +        return self.proto + '://' + self.location() + self.fullpath()
    1.99  
   1.100 -def nodeurl(location, root, id=''):
   1.101 -    return urljoin(location, root, 'node/', id)
   1.102 +    def location(self):
   1.103 +        """Get the location part of the URL, including host and port, if present.
   1.104 +        """
   1.105 +        if self.port:
   1.106 +            return self.host + ':' + str(self.port)
   1.107 +        else:
   1.108 +            return self.host
   1.109  
   1.110 -def domainurl(location, root, id=''):
   1.111 -    return urljoin(location, root, 'domain/', id)
   1.112 -
   1.113 -def consoleurl(location, root, id=''):
   1.114 -    return urljoin(location, root, 'console/', id)
   1.115 +    def fullpath(self):
   1.116 +        """Get the full path part of the URL, including query and fragment if present.
   1.117 +        """
   1.118 +        u = [ self.path ]
   1.119 +        if self.query:
   1.120 +            u.append('?')
   1.121 +            u.append(self.query)
   1.122 +        if self.frag:
   1.123 +            u.append('#')
   1.124 +            u.append(self.frag)
   1.125 +        return ''.join(u)
   1.126  
   1.127 -def deviceurl(location, root, id=''):
   1.128 -    return urljoin(location, root, 'device/', id)
   1.129 +    def relative(self, path='', query=None, frag=None):
   1.130 +        """Create a URL relative to this one.
   1.131 +        """
   1.132 +        return URL(proto=self.proto,
   1.133 +                   host=self.host,
   1.134 +                   port=self.port,
   1.135 +                   path=self.path + path,
   1.136 +                   query=query,
   1.137 +                   frag=frag)
   1.138  
   1.139 -def vneturl(location, root, id=''):
   1.140 -    return urljoin(location, root, 'vnet/', id)
   1.141 +class XendRequest:
   1.142 +    """A request to xend.
   1.143 +    """
   1.144  
   1.145 -def eventurl(location, root, id=''):
   1.146 -    return urljoin(location, root, 'event/', id)
   1.147 +    def __init__(self, url, method, args):
   1.148 +        """Create a request. Sets up the headers, argument data, and the
   1.149 +        url.
   1.150  
   1.151 -def dmesgurl(location, root, id=''):
   1.152 -    return urljoin(location, root, 'node/dmesg/', id)
   1.153 -
   1.154 -def xend_request(url, method, data=None):
   1.155 -    """Make a request to xend.
   1.156 -
   1.157 -    url    xend request url
   1.158 -    method http method: POST or GET
   1.159 -    data   request argument data (dict)
   1.160 +        @param url:    the url to request
   1.161 +        @param method: request method, GET or POST
   1.162 +        @param args:   dict containing request args, if any
   1.163 +        """
   1.164 +        if url.proto != 'http':
   1.165 +            raise ValueError('Invalid protocol: ' + url.proto)
   1.166 +        (hdr, data) = encode_data(args)
   1.167 +        if args and method == 'GET':
   1.168 +            url.query = data
   1.169 +            data = None
   1.170 +        if method == "POST" and url.path.endswith('/'):
   1.171 +            url.path = url.path[:-1]
   1.172 +            
   1.173 +        self.headers = hdr
   1.174 +        self.data = data
   1.175 +        self.url = url
   1.176 +        self.method = method
   1.177 +            
   1.178 +class XendClientProtocol:
   1.179 +    """Abstract class for xend clients.
   1.180      """
   1.181 -    urlinfo = urlparse.urlparse(url)
   1.182 -    (uproto, ulocation, upath, uparam, uquery, ufrag) = urlinfo
   1.183 -    if DEBUG: print url, urlinfo
   1.184 -    if uproto != 'http':
   1.185 -        raise StandardError('Invalid protocol: ' + uproto)
   1.186 -    if DEBUG: print '>xend_request', ulocation, upath, method, data
   1.187 -    (hdr, args) = encode_data(data)
   1.188 -    if data and method == 'GET':
   1.189 -        upath += '?' + args
   1.190 -        args = None
   1.191 -    if method == "POST" and upath.endswith('/'):
   1.192 -        upath = upath[:-1]
   1.193 -    if DEBUG: print "ulocation=", ulocation, "upath=", upath, "args=", args
   1.194 -    #hdr['User-Agent'] = 'Mozilla'
   1.195 -    #hdr['Accept'] = 'text/html,text/plain'
   1.196 -    conn = httplib.HTTPConnection(ulocation)
   1.197 -    #conn.response_class = Foo
   1.198 -    if DEBUG: conn.set_debuglevel(1)
   1.199 -    conn.request(method, upath, args, hdr)
   1.200 -    resp = conn.getresponse()
   1.201 -    if DEBUG: print resp.status, resp.reason
   1.202 -    if DEBUG: print resp.msg.headers
   1.203 -    if resp.status in [ http.NO_CONTENT ]:
   1.204 -        return None
   1.205 -    if resp.status not in [ http.OK, http.CREATED, http.ACCEPTED ]:
   1.206 -        raise XendError(resp.reason)
   1.207 -    pin = sxp.Parser()
   1.208 -    data = resp.read()
   1.209 -    if DEBUG: print "***data" , data
   1.210 -    if DEBUG: print "***"
   1.211 -    pin.input(data);
   1.212 -    pin.input_eof()
   1.213 -    conn.close()
   1.214 -    val = pin.get_val()
   1.215 -    #if isinstance(val, types.ListType) and sxp.name(val) == 'val':
   1.216 -    #    val = val[1]
   1.217 -    if isinstance(val, types.ListType) and sxp.name(val) == 'xend.err':
   1.218 -        raise XendError(val[1])
   1.219 -    if DEBUG: print '**val='; sxp.show(val); print
   1.220 -    return val
   1.221 +
   1.222 +    def xendRequest(self, url, method, args=None):
   1.223 +        """Make a request to xend.
   1.224 +        Implement in a subclass.
   1.225 +        
   1.226 +        @param url:    xend request url
   1.227 +        @param method: http method: POST or GET
   1.228 +        @param args:   request arguments (dict)
   1.229 +        """
   1.230 +        raise NotImplementedError()
   1.231 +    
   1.232 +    def xendGet(self, url, args=None):
   1.233 +        """Make a xend request using HTTP GET.
   1.234 +        Requests using GET are usually 'safe' and may be repeated without
   1.235 +        nasty side-effects.
   1.236 +        
   1.237 +        @param url:    xend request url
   1.238 +        @param data:   request arguments (dict)
   1.239 +        """
   1.240 +        return self.xendRequest(url, "GET", args)
   1.241 +
   1.242 +    def xendPost(self, url, args):
   1.243 +        """Make a xend request using HTTP POST.
   1.244 +        Requests using POST potentially cause side-effects, and should
   1.245 +        not be repeated unless you really want to repeat the side
   1.246 +        effect.
   1.247 +        
   1.248 +        @param url:    xend request url
   1.249 +        @param args:   request arguments (dict)
   1.250 +        """
   1.251 +        return self.xendRequest(url, "POST", args)
   1.252 +
   1.253 +    def handleStatus(self, version, status, message):
   1.254 +        """Handle the status returned from the request.
   1.255 +        """
   1.256 +        status = int(status)
   1.257 +        if status in [ http.NO_CONTENT ]:
   1.258 +            return None
   1.259 +        if status not in [ http.OK, http.CREATED, http.ACCEPTED ]:
   1.260 +            return self.handleException(XendError(message))
   1.261 +        return 'ok'
   1.262 +
   1.263 +    def handleResponse(self, data):
   1.264 +        """Handle the data returned in response to the request.
   1.265 +        """
   1.266 +        if data is None: return None
   1.267 +        try:
   1.268 +            pin = sxp.Parser()
   1.269 +            pin.input(data);
   1.270 +            pin.input_eof()
   1.271 +            val = pin.get_val()
   1.272 +        except sxp.ParseError, err:
   1.273 +            return self.handleException(err)
   1.274 +        if isinstance(val, types.ListType) and sxp.name(val) == 'xend.err':
   1.275 +            err = XendError(val[1])
   1.276 +            return self.handleException(err)
   1.277 +        return val
   1.278 +
   1.279 +    def handleException(self, err):
   1.280 +        """Handle an exception during the request.
   1.281 +        May be overridden in a subclass.
   1.282 +        """
   1.283 +        raise err
   1.284 +
   1.285 +class SynchXendClientProtocol(XendClientProtocol):
   1.286 +    """A synchronous xend client. This will make a request, wait for
   1.287 +    the reply and return the result.
   1.288 +    """
   1.289 +    
   1.290 +    def xendRequest(self, url, method, args=None):
   1.291 +        """Make a request to xend.
   1.292  
   1.293 -def xend_get(url, args=None):
   1.294 -    """Make a xend request using GET.
   1.295 -    Requests using GET are 'safe' and may be repeated without
   1.296 -    nasty side-effects.
   1.297 +        @param url:    xend request url
   1.298 +        @param method: http method: POST or GET
   1.299 +        @param args:   request arguments (dict)
   1.300 +        """
   1.301 +        self.request = XendRequest(url, method, args)
   1.302 +        conn = httplib.HTTPConnection(url.location())
   1.303 +        if DEBUG: conn.set_debuglevel(1)
   1.304 +        conn.request(method, url.fullpath(), self.request.data, self.request.headers)
   1.305 +        resp = conn.getresponse()
   1.306 +        val = self.handleStatus(resp.version, resp.status, resp.reason)
   1.307 +        if val is None:
   1.308 +            data = None
   1.309 +        else:
   1.310 +            data = resp.read()
   1.311 +        conn.close()
   1.312 +        val = self.handleResponse(data)
   1.313 +        return val
   1.314 +
   1.315 +class AsynchXendClient(http.HTTPClient):
   1.316 +    """A subclass of twisted's HTTPClient to deal with a connection to xend.
   1.317 +    Makes the request when connected, and delegates handling responses etc.
   1.318 +    to its protocol (usually an AsynchXendClientProtocol instance).
   1.319      """
   1.320 -    return xend_request(url, "GET", args)
   1.321 +    def __init__(self, protocol, request):
   1.322 +        self.protocol = protocol
   1.323 +        self.request = request
   1.324 +
   1.325 +    def connectionMade(self):
   1.326 +        request = self.request
   1.327 +        url = self.request.url
   1.328 +        self.sendCommand(request.method, url.fullpath())
   1.329 +        self.sendHeader('Host', url.location())
   1.330 +        for (k, v) in request.headers.items():
   1.331 +            self.sendHeader(k, v)
   1.332 +        self.endHeaders()
   1.333 +        if request.data:
   1.334 +            self.transport.write(request.data)
   1.335  
   1.336 -def xend_call(url, data):
   1.337 -    """Make xend request using POST.
   1.338 -    Requests using POST potentially cause side-effects and should
   1.339 -    not be repeated unless it really is wanted to do the side
   1.340 -    effect again.
   1.341 +    def handleStatus(self, version, status, message):
   1.342 +        return self.protocol.handleStatus(version, status, message)
   1.343 +    
   1.344 +    def handleResponse(self, data):
   1.345 +        return self.protocol.handleResponse(data)
   1.346 +        
   1.347 +class AsynchXendClientProtocol(XendClientProtocol):
   1.348 +    """An asynchronous xend client. Uses twisted to connect to xend
   1.349 +    and make the request. It does not block waiting for the result,
   1.350 +    but sets up a deferred that is called when the result becomes available.
   1.351 +
   1.352 +    Uses AsynchXendClient to manage the connection.
   1.353      """
   1.354 -    return xend_request(url, "POST", data)
   1.355 +
   1.356 +    def __init__(self):
   1.357 +        self.err = None
   1.358 +
   1.359 +    def xendRequest(self, url, method, args=None):
   1.360 +        """Make a request to xend. The returned deferred is called when
   1.361 +        the result is available.
   1.362 +        
   1.363 +        @param url:    xend request url
   1.364 +        @param method: http method: POST or GET
   1.365 +        @param args:   request arguments (dict)
   1.366 +        @return: deferred
   1.367 +        """
   1.368 +        request = XendRequest(url, method, args)
   1.369 +        self.deferred = Deferred()
   1.370 +        clientCreator = ClientCreator(reactor, AsynchXendClient, self, request)
   1.371 +        clientCreator.connectTCP(url.host, url.port)
   1.372 +        return self.deferred
   1.373  
   1.374 +    def callErrback(self, err):
   1.375 +        if not self.deferred.called:
   1.376 +            self.err = err
   1.377 +            self.deferred.errback(err)
   1.378 +        return err
   1.379 +
   1.380 +    def callCallback(self, val):
   1.381 +        if not self.deferred.called:
   1.382 +            self.deferred.callback(val)
   1.383 +        return val
   1.384 +
   1.385 +    def handleException(self, err):
   1.386 +        return self.callErrback(err)
   1.387 +
   1.388 +    def handleResponse(self, data):
   1.389 +        if self.err: return self.err
   1.390 +        val = XendClientProtocol.handleResponse(self, data)
   1.391 +        if isinstance(val, Exception):
   1.392 +            self.callErrback(val)
   1.393 +        else:
   1.394 +            self.callCallback(val)
   1.395 +        return val
   1.396 +        
   1.397  class Xend:
   1.398 +    """Client interface to Xend.
   1.399 +    """
   1.400  
   1.401      """Default location of the xend server."""
   1.402      SRV_DEFAULT = "localhost:8000"
   1.403 @@ -165,199 +305,262 @@ class Xend:
   1.404      """Default path to the xend root on the server."""
   1.405      ROOT_DEFAULT = "/xend/"
   1.406  
   1.407 -    def __init__(self, srv=None, root=None):
   1.408 +    def __init__(self, client=None, srv=None, root=None):
   1.409 +        """Create a xend client interface.
   1.410 +        If the client protocol is not specified, the default
   1.411 +        is to use a synchronous protocol.
   1.412 +
   1.413 +        @param client:  client protocol to use
   1.414 +        @param srv:     server host, and optional port (format host:port)
   1.415 +        @param root:    xend root path on the server
   1.416 +        """
   1.417 +        if client is None:
   1.418 +            client = SynchXendClientProtocol()
   1.419 +        self.client = client
   1.420          self.bind(srv, root)
   1.421  
   1.422      def bind(self, srv=None, root=None):
   1.423          """Bind to a given server.
   1.424  
   1.425 -        srv  server location (host:port)
   1.426 -        root server xend root path
   1.427 +        @param srv:  server location (host:port)
   1.428 +        @param root: xend root path on the server
   1.429          """
   1.430          if srv is None: srv = self.SRV_DEFAULT
   1.431          if root is None: root = self.ROOT_DEFAULT
   1.432          if not root.endswith('/'): root += '/'
   1.433 -        self.location = srv
   1.434 -        self.root = root
   1.435 +        (host, port) = srv.split(':', 1)
   1.436 +        self.url = URL(host=host, port=port, path=root)
   1.437 +
   1.438 +    def xendGet(self, url, args=None):
   1.439 +        return self.client.xendGet(url, args)
   1.440 +
   1.441 +    def xendPost(self, url, data):
   1.442 +        return self.client.xendPost(url, data)
   1.443  
   1.444      def nodeurl(self, id=''):
   1.445 -        return nodeurl(self.location, self.root, id)
   1.446 +        return self.url.relative('node/' + id)
   1.447  
   1.448      def domainurl(self, id=''):
   1.449 -        return domainurl(self.location, self.root, id)
   1.450 +        return self.url.relative('domain/' + id)
   1.451  
   1.452      def consoleurl(self, id=''):
   1.453 -        return consoleurl(self.location, self.root, id)
   1.454 +        return self.url.relative('console/' + id)
   1.455  
   1.456      def deviceurl(self, id=''):
   1.457 -        return deviceurl(self.location, self.root, id)
   1.458 +        return self.url.relative('device/' + id)
   1.459  
   1.460      def vneturl(self, id=''):
   1.461 -        return vneturl(self.location, self.root, id)
   1.462 +        return self.url.relative('vnet/' + id)
   1.463  
   1.464      def eventurl(self, id=''):
   1.465 -        return eventurl(self.location, self.root, id)
   1.466 +        return self.url.relative('event/' + id)
   1.467  
   1.468      def dmesgurl(self, id=''):
   1.469 -        return dmesgurl(self.location, self.root, id)
   1.470 +        return self.url.relative('node/dmesg/' + id)
   1.471  
   1.472      def xend(self):
   1.473 -        return xend_get(urljoin(self.location, self.root))
   1.474 +        return self.xendGet(self.url)
   1.475  
   1.476      def xend_node(self):
   1.477 -        return xend_get(self.nodeurl())
   1.478 +        return self.xendGet(self.nodeurl())
   1.479  
   1.480      def xend_node_cpu_rrobin_slice_set(self, slice):
   1.481 -        return xend_call(self.nodeurl(),
   1.482 -                         {'op'      : 'cpu_rrobin_slice_set',
   1.483 -                          'slice'   : slice })
   1.484 +        return self.xendPost(self.nodeurl(),
   1.485 +                             {'op'      : 'cpu_rrobin_slice_set',
   1.486 +                              'slice'   : slice })
   1.487      
   1.488      def xend_node_cpu_bvt_slice_set(self, ctx_allow):
   1.489 -        return xend_call(self.nodeurl(),
   1.490 -                         {'op'      : 'cpu_bvt_slice_set',
   1.491 -                          'ctx_allow' : ctx_allow })
   1.492 +        return self.xendPost(self.nodeurl(),
   1.493 +                             {'op'      : 'cpu_bvt_slice_set',
   1.494 +                              'ctx_allow' : ctx_allow })
   1.495      
   1.496      def xend_node_cpu_fbvt_slice_set(self, ctx_allow):
   1.497 -        return xend_call(self.nodeurl(),
   1.498 -                         {'op'      : 'cpu_fbvt_slice_set',
   1.499 -                          'ctx_allow' : ctx_allow })
   1.500 +        return self.xendPost(self.nodeurl(),
   1.501 +                             {'op'      : 'cpu_fbvt_slice_set',
   1.502 +                              'ctx_allow' : ctx_allow })
   1.503  
   1.504      def xend_domains(self):
   1.505 -        return xend_get(self.domainurl())
   1.506 +        return self.xendGet(self.domainurl())
   1.507  
   1.508      def xend_domain_create(self, conf):
   1.509 -        return xend_call(self.domainurl(),
   1.510 -                         {'op'      : 'create',
   1.511 -                          'config'  : fileof(conf) })
   1.512 +        return self.xendPost(self.domainurl(),
   1.513 +                             {'op'      : 'create',
   1.514 +                              'config'  : fileof(conf) })
   1.515  
   1.516      def xend_domain_restore(self, filename):
   1.517 -        return xend_call(self.domainurl(),
   1.518 -                         {'op'      : 'restore',
   1.519 -                          'file'    : filename })
   1.520 +        return self.xendPost(self.domainurl(),
   1.521 +                             {'op'      : 'restore',
   1.522 +                              'file'    : filename })
   1.523  
   1.524 -    def xend_domain_configure(self, id, config):
   1.525 -        return xend_call(self.domainurl(id),
   1.526 -                         {'op'      : 'configure',
   1.527 -                          'config'  : fileof(conf) })
   1.528 +    def xend_domain_configure(self, id, conf):
   1.529 +        return self.xendPost(self.domainurl(id),
   1.530 +                             {'op'      : 'configure',
   1.531 +                              'config'  : fileof(conf) })
   1.532  
   1.533      def xend_domain(self, id):
   1.534 -        return xend_get(self.domainurl(id))
   1.535 +        return self.xendGet(self.domainurl(id))
   1.536  
   1.537      def xend_domain_unpause(self, id):
   1.538 -        return xend_call(self.domainurl(id),
   1.539 -                         {'op'      : 'unpause' })
   1.540 +        return self.xendPost(self.domainurl(id),
   1.541 +                             {'op'      : 'unpause' })
   1.542  
   1.543      def xend_domain_pause(self, id):
   1.544 -        return xend_call(self.domainurl(id),
   1.545 -                         {'op'      : 'pause' })
   1.546 +        return self.xendPost(self.domainurl(id),
   1.547 +                             {'op'      : 'pause' })
   1.548  
   1.549      def xend_domain_shutdown(self, id, reason):
   1.550 -        return xend_call(self.domainurl(id),
   1.551 -                         {'op'      : 'shutdown',
   1.552 -                          'reason'  : reason })
   1.553 +        return self.xendPost(self.domainurl(id),
   1.554 +                             {'op'      : 'shutdown',
   1.555 +                              'reason'  : reason })
   1.556  
   1.557      def xend_domain_destroy(self, id, reason):
   1.558 -        return xend_call(self.domainurl(id),
   1.559 -                         {'op'      : 'destroy',
   1.560 -                          'reason'  : reason })
   1.561 +        return self.xendPost(self.domainurl(id),
   1.562 +                             {'op'      : 'destroy',
   1.563 +                              'reason'  : reason })
   1.564  
   1.565      def xend_domain_save(self, id, filename):
   1.566 -        return xend_call(self.domainurl(id),
   1.567 -                         {'op'      : 'save',
   1.568 -                          'file'    : filename })
   1.569 +        return self.xendPost(self.domainurl(id),
   1.570 +                             {'op'      : 'save',
   1.571 +                              'file'    : filename })
   1.572  
   1.573      def xend_domain_migrate(self, id, dst):
   1.574 -        return xend_call(self.domainurl(id),
   1.575 -                         {'op'      : 'migrate',
   1.576 -                          'destination': dst })
   1.577 +        return self.xendPost(self.domainurl(id),
   1.578 +                             {'op'      : 'migrate',
   1.579 +                              'destination': dst })
   1.580  
   1.581      def xend_domain_pincpu(self, id, cpu):
   1.582 -        return xend_call(self.domainurl(id),
   1.583 -                         {'op'      : 'pincpu',
   1.584 -                          'cpu'     : cpu })
   1.585 +        return self.xendPost(self.domainurl(id),
   1.586 +                             {'op'      : 'pincpu',
   1.587 +                              'cpu'     : cpu })
   1.588  
   1.589      def xend_domain_cpu_bvt_set(self, id, mcuadv, warp, warpl, warpu):
   1.590 -        return xend_call(self.domainurl(id),
   1.591 -                         {'op'      : 'cpu_bvt_set',
   1.592 -                          'mcuadv'  : mcuadv,
   1.593 -                          'warp'    : warp,
   1.594 -                          'warpl'   : warpl,
   1.595 -                          'warpu'   : warpu })
   1.596 +        return self.xendPost(self.domainurl(id),
   1.597 +                             {'op'      : 'cpu_bvt_set',
   1.598 +                              'mcuadv'  : mcuadv,
   1.599 +                              'warp'    : warp,
   1.600 +                              'warpl'   : warpl,
   1.601 +                              'warpu'   : warpu })
   1.602      
   1.603      def xend_domain_cpu_fbvt_set(self, id, mcuadv, warp, warpl, warpu):
   1.604 -        return xend_call(self.domainurl(id),
   1.605 -                         {'op'      : 'cpu_fbvt_set',
   1.606 -                          'mcuadv'  : mcuadv,
   1.607 -                          'warp'    : warp,
   1.608 -                          'warpl'   : warpl,
   1.609 -                          'warpu'   : warpu })
   1.610 -
   1.611 +        return self.xendPost(self.domainurl(id),
   1.612 +                             {'op'      : 'cpu_fbvt_set',
   1.613 +                              'mcuadv'  : mcuadv,
   1.614 +                              'warp'    : warp,
   1.615 +                              'warpl'   : warpl,
   1.616 +                              'warpu'   : warpu })
   1.617 +    
   1.618  
   1.619      def xend_domain_cpu_atropos_set(self, id, period, slice, latency, xtratime):
   1.620 -        return xend_call(self.domainurl(id),
   1.621 -                         {'op'      : 'cpu_atropos_set',
   1.622 -                          'period'  : period,
   1.623 -                          'slice'   : slice,
   1.624 -                          'latency' : latency,
   1.625 -                          'xtratime': xtratime })
   1.626 -
   1.627 +        return self.xendPost(self.domainurl(id),
   1.628 +                             {'op'      : 'cpu_atropos_set',
   1.629 +                              'period'  : period,
   1.630 +                              'slice'   : slice,
   1.631 +                              'latency' : latency,
   1.632 +                              'xtratime': xtratime })
   1.633 +    
   1.634      def xend_domain_vifs(self, id):
   1.635 -        return xend_get(self.domainurl(id),
   1.636 -                        { 'op'      : 'vifs' })
   1.637 +        return self.xendGet(self.domainurl(id),
   1.638 +                            { 'op'      : 'vifs' })
   1.639      
   1.640      def xend_domain_vif(self, id, vif):
   1.641 -        return xend_get(self.domainurl(id),
   1.642 -                        { 'op'      : 'vif',
   1.643 -                          'vif'     : vif })
   1.644 +        return self.xendGet(self.domainurl(id),
   1.645 +                            { 'op'      : 'vif',
   1.646 +                              'vif'     : vif })
   1.647      
   1.648      def xend_domain_vbds(self, id):
   1.649 -        return xend_get(self.domainurl(id),
   1.650 -                        {'op'       : 'vbds'})
   1.651 -
   1.652 +        return self.xendGet(self.domainurl(id),
   1.653 +                            {'op'       : 'vbds'})
   1.654 +    
   1.655      def xend_domain_vbd(self, id, vbd):
   1.656 -        return xend_get(self.domainurl(id),
   1.657 -                        {'op'       : 'vbd',
   1.658 -                         'vbd'      : vbd })
   1.659 +        return self.xendGet(self.domainurl(id),
   1.660 +                            {'op'       : 'vbd',
   1.661 +                             'vbd'      : vbd })
   1.662  
   1.663      def xend_domain_device_create(self, id, config):
   1.664 -        return xend_call(self.domainurl(id),
   1.665 -                         {'op'      : 'device_create',
   1.666 -                          'config'  : fileof(config) })
   1.667 +        return self.xendPost(self.domainurl(id),
   1.668 +                             {'op'      : 'device_create',
   1.669 +                              'config'  : fileof(config) })
   1.670  
   1.671      def xend_domain_device_destroy(self, id, type, idx):
   1.672 -        return xend_call(self.domainurl(id),
   1.673 -                         {'op'      : 'device_destroy',
   1.674 -                          'type'    : type,
   1.675 -                          'index'   : idx })
   1.676 -
   1.677 +        return self.xendPost(self.domainurl(id),
   1.678 +                             {'op'      : 'device_destroy',
   1.679 +                              'type'    : type,
   1.680 +                              'index'   : idx })
   1.681 +    
   1.682      def xend_consoles(self):
   1.683 -        return xend_get(self.consoleurl())
   1.684 +        return self.xendGet(self.consoleurl())
   1.685  
   1.686      def xend_console(self, id):
   1.687 -        return xend_get(self.consoleurl(id))
   1.688 +        return self.xendGet(self.consoleurl(id))
   1.689  
   1.690      def xend_vnets(self):
   1.691 -        return xend_get(self.vneturl())
   1.692 +        return self.xendGet(self.vneturl())
   1.693  
   1.694      def xend_vnet_create(self, conf):
   1.695 -        return xend_call(self.vneturl(),
   1.696 -                         {'op': 'create', 'config': fileof(conf) })
   1.697 +        return self.xendPost(self.vneturl(),
   1.698 +                             {'op'      : 'create',
   1.699 +                              'config'  : fileof(conf) })
   1.700  
   1.701      def xend_vnet(self, id):
   1.702 -        return xend_get(self.vneturl(id))
   1.703 +        return self.xendGet(self.vneturl(id))
   1.704  
   1.705      def xend_vnet_delete(self, id):
   1.706 -        return xend_call(self.vneturl(id),
   1.707 -                         {'op': 'delete' })
   1.708 +        return self.xendPost(self.vneturl(id),
   1.709 +                              {'op'     : 'delete' })
   1.710  
   1.711      def xend_event_inject(self, sxpr):
   1.712 -        val = xend_call(self.eventurl(),
   1.713 -                        {'op': 'inject', 'event': fileof(sxpr) })
   1.714 +        val = self.xendPost(self.eventurl(),
   1.715 +                             {'op'      : 'inject',
   1.716 +                              'event'   : fileof(sxpr) })
   1.717  
   1.718      def xend_dmesg(self):
   1.719 -        return xend_get(self.dmesgurl())
   1.720 +        return self.xendGet(self.dmesgurl())
   1.721      
   1.722  
   1.723 +def synchmain(srv, argv):
   1.724 +    xend = Xend(srv=srv)
   1.725 +    if len(argv) > 1:
   1.726 +        fn = argv[0]
   1.727 +    else:
   1.728 +        fn = 'xend'
   1.729 +    if not fn.startswith('xend'):
   1.730 +        fn = 'xend_' + fn
   1.731 +    args = argv[1:]
   1.732 +    try:
   1.733 +        val = getattr(xend, fn)(*args)
   1.734 +        PrettyPrint.prettyprint(val)
   1.735 +    except XendError, err:
   1.736 +        print 'ERROR:', err
   1.737 +        sys.exit(1)
   1.738 +
   1.739 +def xendmain(srv, asynch, fn, args):
   1.740 +    if asynch:
   1.741 +        client = AsynchXendClientProtocol()
   1.742 +    else:
   1.743 +        client = None
   1.744 +    xend = Xend(srv=srv, client=client)
   1.745 +    xend.rc = 0
   1.746 +    try:
   1.747 +        v = getattr(xend, fn)(*args)
   1.748 +    except XendError, err:
   1.749 +        print 'ERROR:', err
   1.750 +        return 1
   1.751 +    if asynch:
   1.752 +        def cbok(val):
   1.753 +            PrettyPrint.prettyprint(val)
   1.754 +            reactor.stop()
   1.755 +        def cberr(err):
   1.756 +            print 'ERROR:', err
   1.757 +            xend.rc = 1
   1.758 +            reactor.stop()
   1.759 +        v.addCallback(cbok)
   1.760 +        v.addErrback(cberr)
   1.761 +        reactor.run()
   1.762 +        return xend.rc
   1.763 +    else:
   1.764 +        PrettyPrint.prettyprint(v)
   1.765 +        return 0
   1.766 +
   1.767  def main(argv):
   1.768      """Call an API function:
   1.769      
   1.770 @@ -367,18 +570,33 @@ def main(argv):
   1.771      Example:
   1.772  
   1.773      > python XendClient.py domains
   1.774 -    (domain 0 8)
   1.775 +    (0 8)
   1.776      > python XendClient.py domain 0
   1.777      (domain (id 0) (name Domain-0) (memory 128))
   1.778      """
   1.779 -    server = Xend()
   1.780 -    fn = argv[1]
   1.781 +    global DEBUG
   1.782 +    from getopt import getopt
   1.783 +    short_options = 'x:ad'
   1.784 +    long_options = ['xend=', 'asynch', 'debug']
   1.785 +    (options, args) = getopt(argv[1:], short_options, long_options)
   1.786 +    srv = None
   1.787 +    asynch = 0
   1.788 +    for k, v in options:
   1.789 +        if k in ['-x', '--xend']:
   1.790 +            srv = v
   1.791 +        elif k in ['-a', '--asynch']:
   1.792 +            asynch = 1
   1.793 +        elif k in ['-d', '--DEBUG']:
   1.794 +            DEBUG = 1
   1.795 +    if len(args):
   1.796 +        fn = args[0]
   1.797 +        args = args[1:]
   1.798 +    else:
   1.799 +        fn = 'xend'
   1.800 +        args = []
   1.801      if not fn.startswith('xend'):
   1.802          fn = 'xend_' + fn
   1.803 -    args = argv[2:]
   1.804 -    val = getattr(server, fn)(*args)
   1.805 -    PrettyPrint.prettyprint(val)
   1.806 -    print
   1.807 +    sys.exit(xendmain(srv, asynch, fn, args))
   1.808  
   1.809  if __name__ == "__main__":
   1.810      main(sys.argv)