rubywbem 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,571 @@
1
+ #
2
+ # Copyright 2006, Red Hat, Inc
3
+ # Scott Seago <sseago@redhat.com>
4
+ #
5
+ # derived from pywbem, written by Tim Potter <tpot@hp.com>, Martin Pool <mbp@hp.com>
6
+ #
7
+ # This program is free software; you can redistribute it and/or modify
8
+ # it under the terms of the GNU General Public License as published by
9
+ # the Free Software Foundation; either version 2 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # You should have received a copy of the GNU General Public License
13
+ # along with this program; if not, write to the Free Software
14
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15
+ #
16
+
17
+ require "rexml/document"
18
+ require "wbem/cim_obj"
19
+ require "wbem/cim_xml"
20
+ require "wbem/cim_http"
21
+ require "wbem/tupletree"
22
+ require "wbem/tupleparse"
23
+
24
+ #"""CIM-XML/HTTP operations.
25
+ #
26
+ #The WBEMConnection class opens a connection to a remote WBEM server.
27
+ #Across this you can run various CIM operations. Each method of this
28
+ #object corresponds fairly directly to a single CIM method call.
29
+ #"""
30
+
31
+ module WBEM
32
+ DEFAULT_NAMESPACE = 'root/cimv2'
33
+
34
+ # TODO: Many methods have more parameters that aren't set yet.
35
+
36
+ # helper functions for validating arguments
37
+
38
+ def WBEM._check_classname(val)
39
+ unless val.is_a?(String)
40
+ raise TypeError, "string expected for classname, not #{val}"
41
+ end
42
+ end
43
+
44
+ class CIMError < Exception
45
+ #"""Raised when something bad happens. The associated value is a
46
+ #tuple of (error_code, description). An error code of zero
47
+ #indicates an XML parsing error in RubyWBEM."""
48
+ attr :code
49
+ def initialize(code)
50
+ @code = code
51
+ end
52
+ end
53
+
54
+ class WBEMConnection
55
+ #"""Class representing a client's connection to a WBEM server.
56
+
57
+ #At the moment there is no persistent TCP connection; the
58
+ #connectedness is only conceptual.
59
+
60
+ #After creating a connection, various methods may be called on the
61
+ #object, which causes a remote call to the server. All these
62
+ #operations take regular Ruby or cim_types values for parameters,
63
+ #and return the same. The caller should not need to know about
64
+ #the XML encoding. (It should be possible to use a different
65
+ #transport below this layer without disturbing any clients.)
66
+
67
+ #The connection remembers the XML for the last request and last
68
+ #reply. This may be useful in debugging: if a problem occurs, you
69
+ #can examine the last_request and last_reply fields of the
70
+ #connection. These are the prettified request and response; the
71
+ #real request is sent without indents so as not to corrupt whitespace.
72
+
73
+ #The caller may also register callback functions which are passed
74
+ #the request before it is sent, and the reply before it is
75
+ #unpacked.
76
+ #"""
77
+
78
+ attr_reader :url, :creds, :x509, :last_request, :last_raw_request, :last_reply, :default_namespace
79
+ def initialize(url, creds = nil, default_namespace = DEFAULT_NAMESPACE,
80
+ x509 = nil)
81
+ @url = url
82
+ @creds = creds
83
+ @x509 = x509
84
+ @last_request = @last_reply = ''
85
+ @default_namespace = default_namespace
86
+ end
87
+
88
+ def to_s
89
+ "#{self.class}(#{self.url}, user=#{self.creds[0]})"
90
+ end
91
+
92
+ def imethodcall(methodname, params)
93
+ #"""Make an intrinsic method call.
94
+
95
+ #Returns a tupletree with a IRETURNVALUE element at the root.
96
+ #A CIMError exception is thrown if there was an error parsing
97
+ #the call response, or an ERROR element was returned.
98
+
99
+ #The parameters are automatically converted to the right
100
+ #CIM_XML objects.
101
+
102
+ #In general clients should call one of the method-specific
103
+ #methods of the connection, such as EnumerateInstanceNames,
104
+ #etc."""
105
+
106
+ # If a LocalNamespacePath wasn't specified, use the default one
107
+
108
+ localnamespacepath = params.delete(:LocalNamespacePath)
109
+ localnamespacepath = self.default_namespace if localnamespacepath.nil?
110
+
111
+ # Create HTTP headers
112
+
113
+ headers = ["CIMOperation: MethodCall",
114
+ "CIMMethod: #{methodname}",
115
+ WBEM.get_object_header(localnamespacepath)]
116
+
117
+ # Create parameter list
118
+ plist = params.to_a.collect do |x|
119
+ IPARAMVALUE.new(x[0].to_s, WBEM.tocimxml(x[1]))
120
+ end
121
+
122
+ # Build XML request
123
+
124
+ req_xml = CIM.new(MESSAGE.new(SIMPLEREQ.new(IMETHODCALL.new(methodname,
125
+ LOCALNAMESPACEPATH.new(localnamespacepath.split("/").collect do |ns|
126
+ NAMESPACE.new(ns)
127
+ end
128
+ ),
129
+ plist)),
130
+ '1001', '1.0'),
131
+ '2.0', '2.0')
132
+
133
+ @last_raw_request = ""
134
+ @last_request = ""
135
+ req_xml.write(@last_raw_request)
136
+ req_xml.write(@last_request, 2)
137
+ # Get XML response
138
+
139
+ begin
140
+ resp_xml = WBEM.wbem_request(self.url, @last_raw_request, self.creds,
141
+ headers, 0, self.x509)
142
+ rescue AuthError =>
143
+ raise
144
+ rescue CIMHttpError => arg
145
+ # Convert cim_http exceptions to CIMError exceptions
146
+ raise CIMError.new(0), arg.to_s
147
+ end
148
+ ## TODO: Perhaps only compute this if it's required? Should not be
149
+ ## all that expensive.
150
+
151
+ reply_dom = REXML::Document.new(resp_xml)
152
+
153
+ ## We want to not insert any newline characters, because
154
+ ## they're already present and we don't want them duplicated.
155
+ @last_reply = ""
156
+ @last_raw_reply = ""
157
+ reply_dom.write(@last_reply, 2)
158
+ reply_dom.write(@last_raw_reply)
159
+ # STDOUT << "response: #{@last_reply}\n"
160
+
161
+ # Parse response
162
+ tmptt = WBEM.dom_to_tupletree(reply_dom)
163
+ # STDOUT << "tmp tt: #{WBEM.tupletree_to_s(tmptt)}\n"
164
+ tt = WBEM.parse_cim(tmptt)
165
+
166
+ if (tt[0] != "CIM")
167
+ raise CIMError.new(0), "Expecting CIM element, got #{tt[0]}"
168
+ end
169
+ tt = tt[2]
170
+
171
+ if (tt[0] != "MESSAGE")
172
+ raise CIMError.new(0), "Expecting MESSAGE element, got #{tt[0]}"
173
+ end
174
+ tt = tt[2]
175
+
176
+ if (tt.length != 1)
177
+ raise CIMError.new(0), "Expecting one SIMPLERSP element: nelements: #{tt.length}"
178
+ end
179
+ if (tt[0][0] != "SIMPLERSP")
180
+ raise CIMError.new(0), "Expecting one SIMPLERSP element, found #{tt[0][0]}"
181
+ end
182
+ tt = tt[0][2]
183
+
184
+ if (tt[0] != "IMETHODRESPONSE")
185
+ raise CIMError.new(0), "Expecting IMETHODRESPONSE element, got #{tt[0]}"
186
+ end
187
+
188
+ if (tt[1]["NAME"] != methodname)
189
+ raise CIMError.new(0), "Expecting attribute NAME=#{methodname}, got #{tt[1]['NAME']}"
190
+ end
191
+ tt = tt[2]
192
+
193
+ # At this point we either have a IRETURNVALUE, ERROR element
194
+ # or None if there was no child nodes of the IMETHODRESPONSE
195
+ # element.
196
+
197
+ if (tt.nil?)
198
+ return nil
199
+ end
200
+ if (tt[0] == "ERROR")
201
+ code = tt[1]['CODE'].to_i
202
+ if tt[1].has_key?("DESCRIPTION")
203
+ raise CIMError.new(code), tt[1]["DESCRIPTION"]
204
+ end
205
+ raise CIMError.new(code), "Error code #{tt[1]['CODE']}"
206
+
207
+ if (tt[0] != "IRETURNVALUE")
208
+ raise CIMError,new(0), "Expecting IRETURNVALUE element, got #{tt[0]}"
209
+ end
210
+ end
211
+ return tt
212
+ end
213
+
214
+ def methodcall(methodname, localobject, params)
215
+ #"""Make an extrinsic method call.
216
+
217
+ #Returns a tupletree with a RETURNVALUE element at the root.
218
+ #A CIMError exception is thrown if there was an error parsing
219
+ #the call response, or an ERROR element was returned.
220
+
221
+ #The parameters are automatically converted to the right
222
+ #CIM_XML objects."""
223
+
224
+ # Create HTTP headers
225
+
226
+ headers = ["CIMOperation: MethodCall",
227
+ "CIMMethod: #{methodname}",
228
+ WBEM.get_object_header(localobject)]
229
+ # Create parameter list
230
+
231
+
232
+ plist = params.to_a.collect do |x|
233
+ PARAMVALUE.new(x[0].to_s, WBEM.tocimxml(x[1], true), WBEM.cimtype(x[1]))
234
+ end
235
+
236
+ # Build XML request
237
+
238
+ req_xml = CIM.new(MESSAGE.new(SIMPLEREQ.new(METHODCALL.new(methodname,
239
+ localobject.tocimxml(),
240
+ plist)),
241
+ '1001', '1.0'),
242
+ '2.0', '2.0')
243
+
244
+ @last_raw_request = ""
245
+ @last_request = ""
246
+ req_xml.write(@last_raw_request)
247
+ req_xml.write(@last_request, 2)
248
+
249
+ # Get XML response
250
+
251
+ begin
252
+ resp_xml = WBEM.wbem_request(self.url, @last_raw_request, self.creds,
253
+ headers)
254
+ rescue CIMHttpError => arg
255
+ # Convert cim_http exceptions to CIMError exceptions
256
+ raise CIMError.new(0), arg.to_s
257
+ end
258
+
259
+ @last_reply = resp_xml
260
+
261
+ tt = WBEM.parse_cim(WBEM.xml_to_tupletree(resp_xml))
262
+
263
+ if (tt[0] != "CIM")
264
+ raise CIMError.new(0), "Expecting CIM element, got #{tt[0]}"
265
+ end
266
+ tt = tt[2]
267
+
268
+ if (tt[0] != "MESSAGE")
269
+ raise CIMError.new(0), "Expecting MESSAGE element, got #{tt[0]}"
270
+ end
271
+ tt = tt[2]
272
+
273
+ if (tt.length != 1 or tt[0][0] != "SIMPLERSP")
274
+ raise CIMError.new(0), "Expecting one SIMPLERSP element"
275
+ end
276
+ tt = tt[0][2]
277
+
278
+ if (tt[0] != "METHODRESPONSE")
279
+ raise CIMError.new(0), "Expecting METHODRESPONSE element, got #{tt[0]}"
280
+ end
281
+
282
+ if (tt[1]["NAME"] != methodname)
283
+ raise CIMError.new(0), "Expecting attribute NAME=#{methodname}, got #{tt[1]['NAME']}"
284
+ end
285
+ tt = tt[2]
286
+
287
+ # At this point we have an optional RETURNVALUE and zero or more PARAMVALUE
288
+ # elements representing output parameters.
289
+ if (!tt.empty? and tt[0][0] == "ERROR")
290
+ code = tt[0][1]["CODE"].to_i
291
+ if tt[0][1].has_key?("DESCRIPTION")
292
+ raise CIMError.new(code), tt[0][1]['DESCRIPTION']
293
+ end
294
+ raise CIMError.new(code), "Error code #{tt[0][1]['CODE']}"
295
+ end
296
+
297
+ return tt
298
+ end
299
+
300
+ #
301
+ # Instance provider API
302
+ #
303
+ def EnumerateInstanceNames(className, params = {})
304
+ #"""Enumerate instance names of a given classname. Returns a
305
+ #list of CIMInstanceName objects."""
306
+ result = self.imethodcall("EnumerateInstanceNames",
307
+ params.merge(Hash[:ClassName => CIMClassName.new(className)]))
308
+
309
+ return result[2] unless result.nil?
310
+ return []
311
+ end
312
+
313
+ def EnumerateInstances(className, params = {})
314
+ #"""Enumerate instances of a given classname. Returns a list
315
+ #of CIMInstance objects."""
316
+
317
+ result = self.imethodcall('EnumerateInstances',
318
+ params.merge(Hash[:ClassName => CIMClassName.new(className)]))
319
+ return result[2] unless result.nil?
320
+ return []
321
+ end
322
+
323
+ def GetInstance(instancename, params = {})
324
+ #"""Fetch an instance given by instancename. Returns a
325
+ #CIMInstance object."""
326
+
327
+ # Strip off host and namespace to make this a "local" object
328
+ iname = instancename.clone
329
+ iname.host = nil
330
+ iname.namespace = nil
331
+
332
+ result = self.imethodcall("GetInstance",
333
+ params.merge(Hash[:InstanceName => iname]))
334
+ return result[2][0]
335
+ end
336
+
337
+ def DeleteInstance(instancename, params = {})
338
+ #"""Delete the instance given by instancename."""
339
+
340
+ # Strip off host and namespace to make this a "local" object
341
+ iname = instancename.clone
342
+ iname.host = nil
343
+ iname.namespace = nil
344
+
345
+ self.imethodcall("DeleteInstance",
346
+ params.merge(Hash[:InstanceName => iname]))
347
+ end
348
+
349
+ def CreateInstance(newinstance, params = {})
350
+ #"""Create an instance. Returns the name for the instance."""
351
+
352
+ # Strip off path to avoid producing a VALUE.NAMEDINSTANCE
353
+ # element instead of an INSTANCE element.
354
+
355
+ instance = newinstance.clone
356
+ instance.path = nil
357
+
358
+ result = self.imethodcall("CreateInstance",
359
+ params.merge(Hash[:NewInstance => instance]))
360
+ return result[2][0]
361
+ end
362
+
363
+ def ModifyInstance(modifiedinstance, params = {})
364
+ #"""Modify properties of a named instance."""
365
+ # last arg is hash
366
+
367
+ if modifiedinstance.path.nil?
368
+ raise ArgumentError, 'modifiedinstance parameter must have path attribute set'
369
+ end
370
+
371
+ return self.imethodcall("ModifyInstance",
372
+ params.merge(Hash[:ModifiedInstance => modifiedinstance]))
373
+ end
374
+
375
+ #
376
+ # Schema management API
377
+ #
378
+
379
+ def EnumerateClassNames(params = {})
380
+ #"""Return a list of CIM class names. Names are returned as strings."""
381
+
382
+ result = self.imethodcall("EnumerateClassNames",
383
+ params)
384
+
385
+ return [] if result.nil?
386
+ return result[2].collect { |x| x.classname}
387
+ end
388
+
389
+ def EnumerateClasses(params = {})
390
+ #"""Return a list of CIM class objects."""
391
+
392
+ result = self.imethodcall("EnumerateClasses",
393
+ params)
394
+
395
+ return [] if result.nil?
396
+
397
+ return result[2]
398
+ end
399
+
400
+ def GetClass(className, params = {})
401
+ #"""Return a CIMClass representing the named class."""
402
+
403
+ result = self.imethodcall("GetClass",
404
+ params.merge(Hash[:ClassName => CIMClassName.new(className)]))
405
+
406
+ return result[2][0]
407
+ end
408
+
409
+ def DeleteClass(className, params = {})
410
+ #"""Delete a class by class name."""
411
+
412
+ # UNSUPPORTED (but actually works)
413
+
414
+ self.imethodcall("DeleteClass",
415
+ params.merge(Hash[:ClassName => CIMClassName.new(className)]))
416
+ end
417
+
418
+ def ModifyClass(modifiedClass, params = {})
419
+ #"""Modify a CIM class."""
420
+
421
+ # UNSUPPORTED
422
+
423
+ self.imethodcall('ModifyClass',
424
+ params.merge(Hash[:ModifiedClass => modifiedClass]))
425
+ end
426
+
427
+ def CreateClass(newClass, params = {})
428
+ #"""Create a CIM class."""
429
+
430
+ # UNSUPPORTED
431
+
432
+ self.imethodcall('CreateClass',
433
+ params.merge(Hash[:NewClass => newClass]))
434
+ end
435
+ #
436
+ # Association provider API
437
+ #
438
+
439
+ def _add_objectname_param(params, object)
440
+ #"""Add an object name (either a class name or an instance
441
+ #name) to a dictionary of parameter names."""
442
+
443
+ if (object.is_a?(CIMClassName) or object.is_a?(CIMInstanceName))
444
+ params[:ObjectName] = object
445
+ elsif (object.is_a?(String))
446
+ params[:ObjectName] = CIMClassName.new(object)
447
+ else
448
+ raise TypeError, "Expecting a classname, CIMClassName or CIMInstanceName object"
449
+ end
450
+ return params
451
+ end
452
+
453
+ def _map_association_params(params = {})
454
+ #"""Convert various convenience parameters and types into their
455
+ #correct form for passing to the imethodcall() function."""
456
+
457
+ # ResultClass and Role parameters that are strings should be
458
+ # mapped to CIMClassName objects.
459
+
460
+ if (params.has_key?(:ResultClass) and params[:ResultClass].is_a?(String))
461
+ params[:ResultClass] = CIMClassName.new(params[:ResultClass])
462
+ end
463
+ if (params.has_key?("AssocClass") and params["AssocClass"].is_a?(String))
464
+ params[:AssocClass] = CIMClassName.new(params[:AssocClass])
465
+ end
466
+ return params
467
+ end
468
+
469
+ def Associators(object_name, params = {})
470
+ #"""Enumerate CIM classes or instances that are associated to a
471
+ #particular source CIM Object. Pass a keyword parameter of
472
+ #'ClassName' to return associators for a CIM class, pass
473
+ #'InstanceName' to return the associators for a CIM instance."""
474
+
475
+ params = self._map_association_params(params)
476
+ params = self._add_objectname_param(params, object_name)
477
+
478
+ result = self.imethodcall("Associators",
479
+ params)
480
+
481
+ return [] if result.nil?
482
+ return result[2].collect { |x| x[2]}
483
+ end
484
+
485
+ def AssociatorNames(object_name, params = {})
486
+ #"""Enumerate the names of CIM classes or instances that are
487
+ #associated to a particular source CIM Object. Pass a keyword
488
+ #parameter of 'ClassName' to return associators for a CIM
489
+ #class, pass 'InstanceName' to return the associators for a CIM
490
+ #instance. Returns a list of CIMInstanceName objects with the
491
+ #host and namespace attributes set."""
492
+
493
+ params = self._map_association_params(params)
494
+ params = self._add_objectname_param(params, object_name)
495
+
496
+ result = self.imethodcall("AssociatorNames",
497
+ params)
498
+ return [] if result.nil?
499
+ return result[2].collect { |x| x[2]}
500
+ end
501
+
502
+ def References(object_name, params = {})
503
+ #"""Enumerate the association objects that refer to a
504
+ #particular target CIM class or instance. Pass a keyword
505
+ #parameter of 'ClassName' to return associators for a CIM
506
+ #class, pass 'InstanceName' to return the associators for a CIM
507
+ #instance."""
508
+
509
+ params = self._map_association_params(params)
510
+ params = self._add_objectname_param(params, object_name)
511
+
512
+ result = self.imethodcall("References",
513
+ params)
514
+ return [] if result.nil?
515
+ return result[2].collect { |x| x[2]}
516
+ end
517
+
518
+ def ReferenceNames(object_name, params = {})
519
+ #"""Enumerate the name of association objects that refer to a
520
+ #particular target CIM class or instance. Pass a keyword
521
+ #parameter of 'ClassName' to return associators for a CIM
522
+ #class, pass 'InstanceName' to return the associators for a CIM
523
+ #instance."""
524
+
525
+ params = self._map_association_params(params)
526
+ params = self._add_objectname_param(params, object_name)
527
+
528
+ result = self.imethodcall("ReferenceNames",
529
+ params)
530
+ return [] if result.nil?
531
+ return result[2].collect { |x| x[2]}
532
+ end
533
+
534
+ #
535
+ # Method provider API
536
+ #
537
+
538
+ def InvokeMethod(methodname, objectname, params = {})
539
+
540
+ obj = objectname.clone
541
+
542
+ if (obj.is_a?(String))
543
+ obj = CIMLocalClassPath.new(self.default_namespace, obj)
544
+ end
545
+
546
+ if obj.is_a?(CIMInstanceName) and obj.namespace.nil?
547
+ obj.namespace = DEFAULT_NAMESPACE
548
+ end
549
+
550
+ result = self.methodcall(methodname, obj, params)
551
+
552
+ # Convert the RETURNVALUE into a Ruby object
553
+ if (!result.empty? and result[0][0] == "RETURNVALUE")
554
+ returnvalue = tocimobj(result[0][1]["PARAMTYPE"],
555
+ result[0][2])
556
+
557
+ # Convert output parameters into a dictionary of Python
558
+ # objects.
559
+
560
+ output_params = {}
561
+
562
+ result[1..-1].each do |p|
563
+ output_params[p[0]] = tocimobj(p[1], p[2])
564
+ end
565
+ return returnvalue, output_params
566
+ else
567
+ return nil, {}
568
+ end
569
+ end
570
+ end
571
+ end