[34] | 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 | |
---|
| 19 | import socket |
---|
| 20 | import httplib |
---|
| 21 | import time |
---|
| 22 | import types |
---|
| 23 | |
---|
| 24 | from encode import * |
---|
| 25 | from xen.xend import sxp |
---|
| 26 | |
---|
| 27 | from xen.xend import XendOptions |
---|
| 28 | |
---|
| 29 | DEBUG = 0 |
---|
| 30 | |
---|
| 31 | HTTP_OK = 200 |
---|
| 32 | HTTP_CREATED = 201 |
---|
| 33 | HTTP_ACCEPTED = 202 |
---|
| 34 | HTTP_NO_CONTENT = 204 |
---|
| 35 | |
---|
| 36 | |
---|
| 37 | xoptions = XendOptions.instance() |
---|
| 38 | |
---|
| 39 | |
---|
| 40 | class XendError(RuntimeError): |
---|
| 41 | """Error class for 'expected errors' when talking to xend. |
---|
| 42 | """ |
---|
| 43 | pass |
---|
| 44 | |
---|
| 45 | class 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 | |
---|
| 71 | class 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 | |
---|
| 149 | class 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 | |
---|
| 202 | class 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 | |
---|
| 215 | class 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) |
---|