source: trunk/packages/xen-3.1/xen-3.1/tools/python/xen/xend/XendProtocol.py @ 34

Last change on this file since 34 was 34, checked in by hartmans, 18 years ago

Add xen and xen-common

File size: 7.0 KB
Line 
1#============================================================================
2# This library is free software; you can redistribute it and/or
3# modify it under the terms of version 2.1 of the GNU Lesser General Public
4# License as published by the Free Software Foundation.
5#
6# This library is distributed in the hope that it will be useful,
7# but WITHOUT ANY WARRANTY; without even the implied warranty of
8# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
9# Lesser General Public License for more details.
10#
11# You should have received a copy of the GNU Lesser General Public
12# License along with this library; if not, write to the Free Software
13# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
14#============================================================================
15# Copyright (C) 2004, 2005 Mike Wray <mike.wray@hp.com>
16# Copyright (C) 2005 XenSource Ltd.
17#============================================================================
18
19import socket
20import httplib
21import time
22import types
23
24from encode import *
25from xen.xend import sxp
26
27from xen.xend import XendOptions
28
29DEBUG = 0
30
31HTTP_OK                              = 200
32HTTP_CREATED                         = 201
33HTTP_ACCEPTED                        = 202
34HTTP_NO_CONTENT                      = 204
35
36
37xoptions = XendOptions.instance()
38
39
40class XendError(RuntimeError):
41    """Error class for 'expected errors' when talking to xend.
42    """
43    pass
44
45class XendRequest:
46    """A request to xend.
47    """
48
49    def __init__(self, url, method, args):
50        """Create a request. Sets up the headers, argument data, and the
51        url.
52
53        @param url:    the url to request
54        @param method: request method, GET or POST
55        @param args:   dict containing request args, if any
56        """
57        if url.proto != 'http':
58            raise ValueError('Invalid protocol: ' + url.proto)
59        (hdr, data) = encode_data(args)
60        if args and method == 'GET':
61            url.query = data
62            data = None
63        if method == "POST" and url.path.endswith('/'):
64            url.path = url.path[:-1]
65
66        self.headers = hdr
67        self.data = data
68        self.url = url
69        self.method = method
70
71class XendClientProtocol:
72    """Abstract class for xend clients.
73    """
74    def xendRequest(self, url, method, args=None):
75        """Make a request to xend.
76        Implement in a subclass.
77
78        @param url:    xend request url
79        @param method: http method: POST or GET
80        @param args:   request arguments (dict)
81        """
82        raise NotImplementedError()
83
84    def xendGet(self, url, args=None):
85        """Make a xend request using HTTP GET.
86        Requests using GET are usually 'safe' and may be repeated without
87        nasty side-effects.
88
89        @param url:    xend request url
90        @param data:   request arguments (dict)
91        """
92        return self.xendRequest(url, "GET", args)
93
94    def xendPost(self, url, args):
95        """Make a xend request using HTTP POST.
96        Requests using POST potentially cause side-effects, and should
97        not be repeated unless you really want to repeat the side
98        effect.
99
100        @param url:    xend request url
101        @param args:   request arguments (dict)
102        """
103        return self.xendRequest(url, "POST", args)
104
105    def handleStatus(self, _, status, message):
106        """Handle the status returned from the request.
107        """
108        status = int(status)
109        if status in [ HTTP_NO_CONTENT ]:
110            return None
111        if status not in [ HTTP_OK, HTTP_CREATED, HTTP_ACCEPTED ]:
112            return self.handleException(XendError(message))
113        return 'ok'
114
115    def handleResponse(self, data):
116        """Handle the data returned in response to the request.
117        """
118        if data is None: return None
119        typ = self.getHeader('Content-Type')
120        if typ != sxp.mime_type:
121            return data
122        try:
123            pin = sxp.Parser()
124            pin.input(data);
125            pin.input_eof()
126            val = pin.get_val()
127        except sxp.ParseError, err:
128            return self.handleException(err)
129        if isinstance(val, types.ListType) and sxp.name(val) == 'xend.err':
130            err = XendError(val[1])
131            return self.handleException(err)
132        return val
133
134    def handleException(self, err):
135        """Handle an exception during the request.
136        May be overridden in a subclass.
137        """
138        raise err
139
140    def getHeader(self, key):
141        """Get a header from the response.
142        Case is ignored in the key.
143
144        @param key: header key
145        @return: header
146        """
147        raise NotImplementedError()
148
149class HttpXendClientProtocol(XendClientProtocol):
150    """A synchronous xend client. This will make a request, wait for
151    the reply and return the result.
152    """
153
154    resp = None
155    request = None
156
157    def makeConnection(self, url):
158        return httplib.HTTPConnection(url.location())
159
160    def makeRequest(self, url, method, args):
161        return XendRequest(url, method, args)
162
163    def xendRequest(self, url, method, args=None):
164        """Make a request to xend.
165
166        @param url:    xend request url
167        @param method: http method: POST or GET
168        @param args:   request arguments (dict)
169        """
170        retries = 0
171        while retries < 2:
172            self.request = self.makeRequest(url, method, args)
173            conn = self.makeConnection(url)
174            try:
175                if DEBUG: conn.set_debuglevel(1)
176                conn.request(method, url.fullpath(), self.request.data,
177                             self.request.headers)
178                try:
179                    resp = conn.getresponse()
180                    self.resp = resp
181                    val = self.handleStatus(resp.version, resp.status,
182                                            resp.reason)
183                    if val is None:
184                        data = None
185                    else:
186                        data = resp.read()
187                    val = self.handleResponse(data)
188                    return val
189                except httplib.BadStatusLine:
190                    retries += 1
191                    time.sleep(5)
192            finally:
193                conn.close()
194
195        raise XendError("Received invalid response from Xend, twice.")
196
197
198    def getHeader(self, key):
199        return self.resp.getheader(key)
200
201
202class UnixConnection(httplib.HTTPConnection):
203    """Subclass of Python library HTTPConnection that uses a unix-domain socket.
204    """
205
206    def __init__(self, path):
207        httplib.HTTPConnection.__init__(self, 'localhost')
208        self.path = path
209
210    def connect(self):
211        sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
212        sock.connect(self.path)
213        self.sock = sock
214
215class UnixXendClientProtocol(HttpXendClientProtocol):
216    """A synchronous xend client using a unix-domain socket.
217    """
218
219    def __init__(self, path=None):
220        if path is None:
221            path = xoptions.get_xend_unix_path()
222        self.path = path
223
224    def makeConnection(self, _):
225        return UnixConnection(self.path)
Note: See TracBrowser for help on using the repository browser.