forked from oVirt/python-ovirt-engine-sdk4
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathservice.py
320 lines (272 loc) · 10.8 KB
/
service.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
# -*- coding: utf-8 -*-
#
# Copyright (c) 2016 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from ovirtsdk4 import AuthError
from ovirtsdk4 import Error
from ovirtsdk4 import NotFoundError
from ovirtsdk4 import http
from ovirtsdk4 import reader
from ovirtsdk4 import types
from ovirtsdk4 import writer
class Future(object):
"""
Instances of this class are returned for operations that specify the
`wait=False` parameter.
"""
def __init__(self, connection, context, code):
"""
Creates a new future result.
`connection` \n
The connection to be used by this future.
`context` \n
The request that this future will wait for when the `wait`
method is called.
`code` \n
The function that will be executed to check the response, and
to convert its body into the right type of object.
"""
self._connection = connection
self._context = context
self._code = code
def wait(self):
"""
Waits till the result of the operation that created this future is
available.
"""
response = self._connection.wait(self._context)
return self._code(response)
class Service(object):
"""
This is the base class for all the services of the SDK. It contains the
utility methods used by all of them.
"""
def __init__(self, connection, path):
"""
Creates a new service that will use the given connection and path.
"""
self._connection = connection
self._path = path
@staticmethod
def _raise_error(response, detail=None):
"""
Creates and raises an error containing the details of the given HTTP
response and fault.
This method is intended for internal use by other components of the
SDK. Refrain from using it directly, as backwards compatibility isn't
guaranteed.
"""
fault = detail if isinstance(detail, types.Fault) else None
msg = ''
if fault:
if fault.reason:
if msg:
msg += ' '
msg = msg + 'Fault reason is "%s".' % fault.reason
if fault.detail:
if msg:
msg += ' '
msg = msg + 'Fault detail is "%s".' % fault.detail
if response:
if response.code:
if msg:
msg += ' '
msg = msg + 'HTTP response code is %s.' % response.code
if response.message:
if msg:
msg += ' '
msg = msg + 'HTTP response message is "%s".' % response.message
if isinstance(detail, str):
if msg:
msg += ' '
msg = msg + detail + '.'
class_ = Error
if response is not None:
if response.code in [401, 403]:
class_ = AuthError
elif response.code == 404:
class_ = NotFoundError
error = class_(msg)
error.code = response.code if response else None
error.fault = fault
raise error
def _check_fault(self, response):
"""
Reads the response body assuming that it contains a fault message,
converts it to an exception and raises it.
This method is intended for internal use by other
components of the SDK. Refrain from using it directly,
as backwards compatibility isn't guaranteed.
"""
body = self._internal_read_body(response)
if isinstance(body, types.Fault):
self._raise_error(response, body)
elif isinstance(body, types.Action) and body.fault:
self._raise_error(response, body.fault)
raise Error("Expected a fault, but got %s" % type(body).__name__)
def _check_action(self, response):
"""
Reads the response body assuming that it contains an action, checks if
it contains an fault, and if it does converts it to an exception and
raises it.
This method is intended for internal use by other
components of the SDK. Refrain from using it directly,
as backwards compatibility isn't guaranteed.
"""
body = self._internal_read_body(response)
if isinstance(body, types.Fault):
self._raise_error(response, body)
elif isinstance(body, types.Action) and body.fault is not None:
self._raise_error(response, body.fault)
elif isinstance(body, types.Action):
return body
else:
raise Error(
"Expected a fault or action, but got %s" % (
type(body).__name__
)
)
return body
@staticmethod
def _check_types(tuples):
"""
Receives a list of tuples with three elements: the name of a
parameter, its value and its expected type. For each tuple it
checks that the value is either `None` or a valid value of
the given types. If any of the checks fails, it raises an
exception.
This method is intended for internal use by other
components of the SDK. Refrain from using it directly,
as backwards compatibility isn't guaranteed.
"""
messages = []
for name, value, expected in tuples:
if value is not None:
actual = type(value)
if actual != expected:
messages.append((
"The '{name}' parameter should be of type "
"'{expected}', but it is of type \'{actual}\'."
).format(
name=name,
expected=expected.__name__,
actual=actual.__name__,
))
if len(messages) > 0:
raise TypeError(' '.join(messages))
def _internal_get(self, headers=None, query=None, wait=None):
"""
Executes an `get` method.
"""
# Populate the headers:
headers = headers or {}
# Send the request:
request = http.Request(method='GET', path=self._path, query=query, headers=headers)
context = self._connection.send(request)
def callback(response):
if response.code in [200]:
return self._internal_read_body(response)
else:
self._check_fault(response)
future = Future(self._connection, context, callback)
return future.wait() if wait else future
def _internal_add(self, object, headers=None, query=None, wait=None):
"""
Executes an `add` method.
"""
# Populate the headers:
headers = headers or {}
# Send the request and wait for the response:
request = http.Request(method='POST', path=self._path, query=query, headers=headers)
request.body = writer.Writer.write(object, indent=True)
context = self._connection.send(request)
def callback(response):
if response.code in [200, 201, 202]:
return self._internal_read_body(response)
else:
self._check_fault(response)
future = Future(self._connection, context, callback)
return future.wait() if wait else future
def _internal_update(self, object, headers=None, query=None, wait=None):
"""
Executes an `update` method.
"""
# Populate the headers:
headers = headers or {}
# Send the request and wait for the response:
request = http.Request(method='PUT', path=self._path, query=query, headers=headers)
request.body = writer.Writer.write(object, indent=True)
context = self._connection.send(request)
def callback(response):
if response.code in [200]:
return self._internal_read_body(response)
else:
self._check_fault(response)
future = Future(self._connection, context, callback)
return future.wait() if wait else future
def _internal_remove(self, headers=None, query=None, wait=None):
"""
Executes an `remove` method.
"""
# Populate the headers:
headers = headers or {}
# Send the request and wait for the response:
request = http.Request(method='DELETE', path=self._path, query=query, headers=headers)
context = self._connection.send(request)
def callback(response):
if response.code not in [200]:
self._check_fault(response)
future = Future(self._connection, context, callback)
return future.wait() if wait else future
def _internal_action(self, action, path, member=None, headers=None, query=None, wait=None):
"""
Executes an action method.
"""
# Populate the headers:
headers = headers or {}
# Send the request and wait for the response:
request = http.Request(
method='POST',
path='%s/%s' % (self._path, path),
query=query,
headers=headers,
)
request.body = writer.Writer.write(action, indent=True)
context = self._connection.send(request)
def callback(response):
if response.code in [200, 201, 202]:
result = self._check_action(response)
if member:
return getattr(result, member)
else:
self._check_fault(response)
future = Future(self._connection, context, callback)
return future.wait() if wait else future
def _internal_read_body(self, response):
"""
Checks the content type of the given response, and if it is XML, as
expected, reads the body and converts it to an object. If it isn't
XML, then it raises an exception.
`response`:: The HTTP response to check.
"""
# First check if the response body is empty, as it makes no sense to check the content type if there is
# no body:
if not response.body:
self._raise_error(response)
# Check the content type, as otherwise the parsing will fail, and the resulting error message won't be explicit
# about the cause of the problem:
self._connection.check_xml_content_type(response)
# Parse the XML and generate the SDK object:
return reader.Reader.read(response.body)