2828 'FixtureTransport' ,
2929]
3030
31+ REST_SPECIAL_METHODS = {
32+ 'deleteObject' : 'DELETE' ,
33+ 'createObject' : 'POST' ,
34+ 'createObjects' : 'POST' ,
35+ 'editObject' : 'PUT' ,
36+ 'editObjects' : 'PUT' ,
37+ }
38+
3139
3240class Request (object ):
3341 """Transport request object."""
@@ -76,6 +84,14 @@ def __init__(self):
7684 self .offset = None
7785
7886
87+ class SoftLayerListResult (list ):
88+ """A SoftLayer list result."""
89+
90+ def __init__ (self , items , total_count ):
91+ self .total_count = total_count
92+ super (SoftLayerListResult , self ).__init__ (items )
93+
94+
7995class XmlRpcTransport (object ):
8096 """XML-RPC transport."""
8197 def __init__ (self ,
@@ -104,7 +120,8 @@ def __call__(self, request):
104120 headers [header_name ] = {'id' : request .identifier }
105121
106122 if request .mask is not None :
107- headers .update (_format_object_mask (request .mask , request .service ))
123+ headers .update (_format_object_mask_xmlrpc (request .mask ,
124+ request .service ))
108125
109126 if request .filter is not None :
110127 headers ['%sObjectFilter' % request .service ] = request .filter
@@ -129,18 +146,23 @@ def __call__(self, request):
129146 LOGGER .debug (payload )
130147
131148 try :
132- response = requests .request ('POST' , url ,
133- data = payload ,
134- headers = request .transport_headers ,
135- timeout = self .timeout ,
136- verify = request .verify ,
137- cert = request .cert ,
138- proxies = _proxies_dict (self .proxy ))
149+ resp = requests .request ('POST' , url ,
150+ data = payload ,
151+ headers = request .transport_headers ,
152+ timeout = self .timeout ,
153+ verify = request .verify ,
154+ cert = request .cert ,
155+ proxies = _proxies_dict (self .proxy ))
139156 LOGGER .debug ("=== RESPONSE ===" )
140- LOGGER .debug (response .headers )
141- LOGGER .debug (response .content )
142- response .raise_for_status ()
143- return utils .xmlrpc_client .loads (response .content )[0 ][0 ]
157+ LOGGER .debug (resp .headers )
158+ LOGGER .debug (resp .content )
159+ resp .raise_for_status ()
160+ result = utils .xmlrpc_client .loads (resp .content )[0 ][0 ]
161+ if isinstance (result , list ):
162+ return SoftLayerListResult (
163+ result , int (resp .headers .get ('softlayer-total-items' , 0 )))
164+ else :
165+ return result
144166 except utils .xmlrpc_client .Fault as ex :
145167 # These exceptions are formed from the XML-RPC spec
146168 # http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php
@@ -190,26 +212,68 @@ def __call__(self, request):
190212
191213 :param request request: Request object
192214 """
215+ request .transport_headers .setdefault ('Content-Type' ,
216+ 'application/json' )
217+ request .transport_headers .setdefault ('User-Agent' , self .user_agent )
218+
219+ params = request .headers .copy ()
220+ if request .mask :
221+ params ['objectMask' ] = _format_object_mask (request .mask )
222+
223+ if request .limit :
224+ params ['limit' ] = request .limit
225+
226+ if request .offset :
227+ params ['offset' ] = request .offset
228+
229+ if request .filter :
230+ params ['objectFilter' ] = json .dumps (request .filter )
231+
232+ auth = None
233+ if request .transport_user :
234+ auth = requests .auth .HTTPBasicAuth (
235+ request .transport_user ,
236+ request .transport_password ,
237+ )
238+
239+ method = REST_SPECIAL_METHODS .get (request .method )
240+ is_special_method = True
241+ if method is None :
242+ is_special_method = False
243+ method = 'GET'
244+
245+ body = {}
246+ if request .args :
247+ # NOTE(kmcdonald): force POST when there are arguments because
248+ # the request body is ignored otherwise.
249+ method = 'POST'
250+ body ['parameters' ] = request .args
251+
252+ raw_body = None
253+ if body :
254+ raw_body = json .dumps (body )
255+
193256 url_parts = [self .endpoint_url , request .service ]
194257 if request .identifier is not None :
195258 url_parts .append (str (request .identifier ))
196- if request .method is not None :
197- url_parts .append (request .method )
198- for arg in request .args :
199- url_parts .append (str (arg ))
200259
201- request .transport_headers .setdefault ('Content-Type' ,
202- 'application/json' )
203- request .transport_headers .setdefault ('User-Agent' , self .user_agent )
260+ # Special methods (createObject, editObject, etc) use the HTTP verb
261+ # to determine the action on the resource
262+ if request .method is not None and not is_special_method :
263+ url_parts .append (request .method )
204264
205265 url = '%s.%s' % ('/' .join (url_parts ), 'json' )
206266
207267 LOGGER .debug ("=== REQUEST ===" )
208268 LOGGER .info (url )
209269 LOGGER .debug (request .transport_headers )
270+ LOGGER .debug (raw_body )
210271 try :
211- resp = requests .request ('GET' , url ,
272+ resp = requests .request (method , url ,
273+ auth = auth ,
212274 headers = request .transport_headers ,
275+ params = params ,
276+ data = raw_body ,
213277 timeout = self .timeout ,
214278 verify = request .verify ,
215279 cert = request .cert ,
@@ -218,7 +282,13 @@ def __call__(self, request):
218282 LOGGER .debug (resp .headers )
219283 LOGGER .debug (resp .content )
220284 resp .raise_for_status ()
221- return json .loads (resp .content )
285+ result = json .loads (resp .content )
286+
287+ if isinstance (result , list ):
288+ return SoftLayerListResult (
289+ result , int (resp .headers .get ('softlayer-total-items' , 0 )))
290+ else :
291+ return result
222292 except requests .HTTPError as ex :
223293 content = json .loads (ex .response .content )
224294 raise exceptions .SoftLayerAPIError (ex .response .status_code ,
@@ -279,7 +349,7 @@ def _proxies_dict(proxy):
279349 return {'http' : proxy , 'https' : proxy }
280350
281351
282- def _format_object_mask (objectmask , service ):
352+ def _format_object_mask_xmlrpc (objectmask , service ):
283353 """Format new and old style object masks into proper headers.
284354
285355 :param objectmask: a string- or dict-based object mask
@@ -290,10 +360,22 @@ def _format_object_mask(objectmask, service):
290360 mheader = '%sObjectMask' % service
291361 else :
292362 mheader = 'SoftLayer_ObjectMask'
293-
294- objectmask = objectmask .strip ()
295- if (not objectmask .startswith ('mask' ) and
296- not objectmask .startswith ('[' )):
297- objectmask = "mask[%s]" % objectmask
363+ objectmask = _format_object_mask (objectmask )
298364
299365 return {mheader : {'mask' : objectmask }}
366+
367+
368+ def _format_object_mask (objectmask ):
369+ """Format the new style object mask.
370+
371+ This wraps the user mask with mask[USER_MASK] if it does not already
372+ have one. This makes it slightly easier for users.
373+
374+ :param objectmask: a string-based object mask
375+
376+ """
377+ objectmask = objectmask .strip ()
378+ if (not objectmask .startswith ('mask' ) and
379+ not objectmask .startswith ('[' )):
380+ objectmask = "mask[%s]" % objectmask
381+ return objectmask
0 commit comments