
Hi Sharad, Actually, I did modify the script in a way that it is still compatible with cimtest (based on tests using ComputerSystemIndication 01_created_indication). But if you think it makes sense to fork it to a standalone script (and rename it), that is fine too. The usage of the script as I intend is interactive from the command line. I can add some documentation with example commands. Is there a standard place this should go, or should I add a README? Thanks, Dave
-----Original Message----- From: libvirt-cim-bounces@redhat.com [mailto:libvirt-cim- bounces@redhat.com] On Behalf Of Sharad Mishra Sent: Monday, March 19, 2012 5:40 PM To: List for discussion and development of libvirt CIM Subject: Re: [Libvirt-cim] cimtest - add SSL support to indication_tester.py
Dave,
I assume that this patch is not going to be applied to the existing indication_tester.py in cimtest. In that case, we can add this modified indication_tester.py as a new file at the same location as current indication_tester.py. We do need to give this script a new name. This script is currently not used/invoked by any existing cimtests. Do you want to write tests to use this script?
Regards Sharad Mishra
In the XenKvmLib directory in cimtest, there is a "indication_tester.py" module that is both a command-line program and a component of cimtest. Here is a proposed patch that adds new functionality to the cmdline interface while preserving compatibility with cimtest.
The most significant new feature is support for SSL indications. There is also a new --trigger mode to generate a test indication using the SFCB Test_Indication provider. This allows the script to act as a completely stand-alone test tool (for SFCB) without the user having to rely on the "xmltest" files and wbemcat.
Below is a complete list of the new features. I am looking for feedback and potentially, the correct process to check into "cimtest".
Best regards, Dave Heller
New features:
- Supports https indications. Adds new options --certFile and --keyFile to specify server certificate and private key for the SSL enabled indication listener. - Supports verification of indication sender (i.e. CIMOM) SSL certificate. New option --trustStore is used to specify the CA certificate file; alternately the --noverify option may be used to disable peer certificate checking. - Adds new option --dest to specify handler destination. This same URL is used to register the subscription (at the CIMOM) and start the indication listener (locally). Via --dest one can specify scheme (http or https), IP or hostname, listener port, and optionally userid:password, all in a single parameter. - New option --interop can be used to override the default interop namespace (i.e root/interop vs. root/PG_InterOp) allowing easy support for both openpegasus and sfcb. - Modifies the default value of existing --ns option (indication namespace); if not specified the indication namespace will default to the same value as the interop namespace. - New option --verbose can be used to display debugging info, such as details (subject, issuer) of the indication sender cert (when peer verification enabled). - Improved error handling (i.e. catch exceptions) for the following operations: starting listener locally, receiving incoming indications, POST operations (i.e. subscribe, unsubscribe) to CIMOM. - New option --trigger can be used to send a request to CIMOM to trigger an indication via the SFCB Test_Indication provider.
Limitations:
- Not tested on IPv6. - The --trigger option currently only supports the sfcb Test_Indication provider. (It could be made more general by allowing the XML send to support an arbitrary namespace, classname and method call. Open to suggestions here.)
--- indication_tester.orig.py 2012-03-05 23:12:08.000000000 -0500 +++ indication_tester.py 2012-03-19 13:12:55.000000000 -0400 @@ -1,18 +1,25 @@ #!/usr/bin/python -# Copyright IBM Corp. 2007 +# Copyright IBM Corp. 2007-2012 # # indication_tester.py - Tool for testing indication subscription and # delivery against a CIMOM # Author: Dan Smith <danms@us.ibm.com> +# SSL support added by Dave Heller <hellerda@us.ibm.com>
import sys from optparse import OptionParser -import BaseHTTPServer +from urlparse import urlparse, urlunparse +from xml.dom.minidom import parse, parseString import httplib import base64 -from xml.dom.minidom import parse, parseString +import errno, os, re +import socket +from SocketServer import BaseServer +from SimpleHTTPServer import SimpleHTTPRequestHandler +from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer +from OpenSSL import SSL
-def filter_xml(name, type, ns, sysname): +def filter_xml(name, type, ns, sysname, interopNS): return """ <?xml version="1.0" encoding="utf-8"?> <CIM CIMVERSION="2.0" DTDVERSION="2.0"> @@ -20,8 +27,8 @@ <SIMPLEREQ> <IMETHODCALL NAME="CreateInstance"> <LOCALNAMESPACEPATH> - <NAMESPACE NAME="root"/> - <NAMESPACE NAME="PG_InterOp"/> + <NAMESPACE NAME="%s"/> + <NAMESPACE NAME="%s"/> </LOCALNAMESPACEPATH> <IPARAMVALUE NAME="NewInstance"> <INSTANCE CLASSNAME="CIM_IndicationFilter"> @@ -53,9 +60,9 @@ </SIMPLEREQ> </MESSAGE> </CIM> - """ % (sysname, name, type, ns) + """ % (interopNS[0], interopNS[1], sysname, name, type, ns)
-def handler_xml(name, port, sysname): +def handler_xml(name, destUrl, sysname, interopNS): return """ <?xml version="1.0" encoding="utf-8"?> <CIM CIMVERSION="2.0" DTDVERSION="2.0"> @@ -63,8 +70,8 @@ <SIMPLEREQ> <IMETHODCALL NAME="CreateInstance"> <LOCALNAMESPACEPATH> - <NAMESPACE NAME="root"/> - <NAMESPACE NAME="PG_InterOp"/> + <NAMESPACE NAME="%s"/> + <NAMESPACE NAME="%s"/> </LOCALNAMESPACEPATH> <IPARAMVALUE NAME="NewInstance"> <INSTANCE CLASSNAME="CIM_IndicationHandlerCIMXML"> @@ -81,7 +88,7 @@ <VALUE>%sHandler</VALUE> </PROPERTY> <PROPERTY NAME="Destination" TYPE="string"> - <VALUE>http://localhost:%i</VALUE> + <VALUE>%s</VALUE> </PROPERTY> </INSTANCE> </IPARAMVALUE> @@ -89,9 +96,9 @@ </SIMPLEREQ> </MESSAGE> </CIM> - """ % (sysname, name, port) + """ % (interopNS[0], interopNS[1], sysname, name, destUrl)
-def subscription_xml(name, sysname): +def subscription_xml(name, sysname, interopNS): return """ <?xml version="1.0" encoding="utf-8"?> <CIM CIMVERSION="2.0" DTDVERSION="2.0"> @@ -99,8 +106,8 @@ <SIMPLEREQ> <IMETHODCALL NAME="CreateInstance"> <LOCALNAMESPACEPATH> - <NAMESPACE NAME="root"/> - <NAMESPACE NAME="PG_InterOp"/> + <NAMESPACE NAME="%s"/> + <NAMESPACE NAME="%s"/> </LOCALNAMESPACEPATH> <IPARAMVALUE NAME="NewInstance"> <INSTANCE CLASSNAME="CIM_IndicationSubscription"> @@ -167,9 +174,9 @@ </SIMPLEREQ> </MESSAGE> </CIM> - """ % (sysname, name, sysname, name) + """ % (interopNS[0], interopNS[1], sysname, name, sysname, name)
-def delete_inst_xml(name, type, sysname, inst_name): +def delete_inst_xml(name, type, sysname, inst_name, interopNS): return """ <?xml version="1.0" encoding="utf-8"?> <CIM CIMVERSION="2.0" DTDVERSION="2.0"> @@ -177,8 +184,8 @@ <SIMPLEREQ> <IMETHODCALL NAME="DeleteInstance"> <LOCALNAMESPACEPATH> - <NAMESPACE NAME="root"/> - <NAMESPACE NAME="PG_InterOp"/> + <NAMESPACE NAME="%s"/> + <NAMESPACE NAME="%s"/> </LOCALNAMESPACEPATH> <IPARAMVALUE NAME="InstanceName"> <INSTANCENAME CLASSNAME="CIM_Indication%s"> @@ -200,9 +207,9 @@ </SIMPLEREQ> </MESSAGE> </CIM>; - """ % (type, sysname, type, inst_name); + """ % (interopNS[0], interopNS[1], type, sysname, type, inst_name);
-def delete_sub_xml(name, sysname): +def delete_sub_xml(name, sysname, interopNS): return """ <?xml version="1.0" encoding="utf-8"?> <CIM CIMVERSION="2.0" DTDVERSION="2.0"> @@ -210,8 +217,8 @@ <SIMPLEREQ> <IMETHODCALL NAME="DeleteInstance"> <LOCALNAMESPACEPATH> - <NAMESPACE NAME="root"/> - <NAMESPACE NAME="PG_InterOp"/> + <NAMESPACE NAME="%s"/> + <NAMESPACE NAME="%s"/> </LOCALNAMESPACEPATH> <IPARAMVALUE NAME="InstanceName"> <INSTANCENAME CLASSNAME="CIM_IndicationSubscription"> @@ -273,7 +280,45 @@ </SIMPLEREQ> </MESSAGE> </CIM>; - """ % (sysname, name, sysname, name) + """ % (interopNS[0], interopNS[1], sysname, name, sysname, name) + +def trigger_xml(type, interopNS): + return """ + <?xml version="1.0" encoding="utf-8"?> + <CIM CIMVERSION="2.0" DTDVERSION="2.0"> + <MESSAGE ID="4711" PROTOCOLVERSION="1.0"> + <SIMPLEREQ> + <METHODCALL NAME="SendTestIndication"> + <LOCALCLASSPATH> + <LOCALNAMESPACEPATH> + <NAMESPACE NAME="%s"/> + <NAMESPACE NAME="%s"/> + </LOCALNAMESPACEPATH> + <CLASSNAME NAME="%s"/> + </LOCALCLASSPATH> + </METHODCALL> + </SIMPLEREQ> + </MESSAGE> + </CIM> + """ % (interopNS[0], interopNS[1], type) + # FIXME: this should really use indication NS, not interop NS. + +def update_url_port(parsedUrl, port): + # Must manually reconstruct netloc to update the port value. + if isinstance(parsedUrl.username, basestring): + if isinstance(parsedUrl.password, basestring): + netloc = "%s:%s@%s:%s" % (parsedUrl.username,
+ parsedUrl.hostname, port) + else: + netloc = "%s@%s:%s" % (parsedUrl.username, + parsedUrl.hostname, port) + else: + netloc = "%s:%s" % (parsedUrl.hostname, port) + + # Reassemble url with the updated netloc. return a string. + return urlunparse((parsedUrl.scheme, netloc, + parsedUrl.path, parsedUrl.params, + parsedUrl.query, parsedUrl.fragment))
class CIMIndication: def __init__(self, xmldata): @@ -286,50 +331,135 @@ def __str__(self): return self.name
-class CIMSocketHandler(BaseHTTPServer.BaseHTTPRequestHandler): +def socket_handler_wrapper(*parms): + try: + CIMSocketHandler(*parms) + except Exception as e: + print "SSL error: %s" % str(e) + +class CIMSocketHandler(SimpleHTTPRequestHandler): + def setup(self): + self.connection = self.request + self.rfile = socket._fileobject(self.request, "rb", self.rbufsize) + self.wfile = socket._fileobject(self.request, "wb", self.wbufsize) + def do_POST(self): length = self.headers.getheader('content-length') data = self.rfile.read(int(length))
indication = CIMIndication(data) - print "Got indication: %s" % indication + print "Got indication: %s from %s" % (indication, self.client_address) if self.server.print_ind: - print "%s\n\n" % data + print "%s\n" % data self.server.indications.append(indication) + # Silence the unwanted log output from send_response() + realStderr = sys.stderr + sys.stderr = open(os.devnull,'a') + self.send_response(200) + sys.stderr = realStderr + +class SecureHTTPServer(HTTPServer): + def __init__(self, server_address, HandlerClass): + BaseServer.__init__(self, server_address, HandlerClass) + + def verify_cb(conn, cert, errnum, depth, ok): + if options.verbose: + print('Verify peer certificate chain: level %d:' % depth) + print('subject=%s' % cert.get_subject()) + print('issuer =%s' % cert.get_issuer()) + return ok + + ctx = SSL.Context(SSL.SSLv23_METHOD) + #ctx.use_certificate_file(options.certFile) + ctx.use_certificate_chain_file(options.certFile) + ctx.use_privatekey_file(options.keyFile) + + if options.noverify: + ctx.set_verify(SSL.VERIFY_NONE, verify_cb) + else: + #ctx.set_verify(SSL.VERIFY_PEER, verify_cb) + ctx.set_verify(SSL.VERIFY_PEER|SSL.VERIFY_FAIL_IF_NO_PEER_CERT, + verify_cb) + ctx.load_verify_locations(options.trustStore) + + self.socket = SSL.Connection(ctx, socket.socket(self.address_family, + self.socket_type)) + self.server_bind() + self.server_activate()
+# The param defaults allow new options from main() w/o losing compat w/ cimtest. class CIMIndicationSubscription: - def __init__(self, name, typ, ns, print_ind, sysname, port=0): + def __init__(self, name, typ, ns, print_ind, sysname, port=0, + interopNS=('root','PG_InterOp'), destUrl="http://localhost:8000", + triggermode=False): self.name = name self.type = typ self.ns = ns self.sysname = sysname + self.interopNS = interopNS + self.print_ind = print_ind + + # We do not want to open a listener socket in trigger mode. + if triggermode: + self.trigger_xml = trigger_xml(typ, interopNS) + return + + parsedUrl = urlparse(destUrl) + + # Increment the listener port by the offset value. + if isinstance(parsedUrl.port, int): + listenerPort = parsedUrl.port + port + else: + listenerPort = 8000 + port + + destUrl = update_url_port(parsedUrl, listenerPort) + + try: + if parsedUrl.scheme == "http": + self.server = HTTPServer((parsedUrl.hostname, + listenerPort), + CIMSocketHandler) + elif parsedUrl.scheme == "https": + self.server = SecureHTTPServer((parsedUrl.hostname, + listenerPort), + socket_handler_wrapper) + except IOError as e: + print "Error creating listener socket: %s" % str(e) + exit(e.errno)
- self.port = 8000 + port - self.server = BaseHTTPServer.HTTPServer(('', self.port), - CIMSocketHandler) self.server.print_ind = print_ind self.server.indications = []
- self.filter_xml = filter_xml(name, typ, ns, sysname) - self.handler_xml = handler_xml(name, self.port, sysname) - self.subscription_xml = subscription_xml(name, sysname) + self.filter_xml = filter_xml(name, typ, ns, sysname, interopNS) + self.handler_xml = handler_xml(name, destUrl, sysname, interopNS) + self.subscription_xml = subscription_xml(name, sysname, interopNS)
def __do_cimpost(self, conn, body, method, auth_hdr=None): headers = {"CIMOperation" : "MethodCall", "CIMMethod" : method, - "CIMObject" : "root/PG_Interop", + #"CIMObject" : "root/PG_Interop", + "CIMObject" : "%s/%s" % self.interopNS, "Content-Type" : 'application/xml; charset="utf-8"'}
if auth_hdr: headers["Authorization"] = "Basic %s" % auth_hdr
- conn.request("POST", "/cimom", body, headers) - resp = conn.getresponse() - if not resp.getheader("content-length"): - raise Exception("Request Failed: %d %s" % - (resp.status, resp.reason)) - - resp.read() + try: + conn.request("POST", "/cimom", body, headers) + resp = conn.getresponse() + if not resp.getheader("content-length"): + raise Exception("Request Failed: %d %s" % + (resp.status, resp.reason)) + except IOError as e: + print "Error connecting to CIMOM: %s" % str(e) + exit(e.errno) + + if self.print_ind: + print "Reply from CIMOM:" + #print resp.msg + print resp.read() + else: + resp.read()
def subscribe(self, url, cred=None): self.conn = httplib.HTTPConnection(url) @@ -353,26 +483,39 @@ else: auth_hdr = None
- xml = delete_sub_xml(self.name, self.sysname) + xml = delete_sub_xml(self.name, self.sysname, self.interopNS) self.__do_cimpost(self.conn, xml, "DeleteInstance", auth_hdr) xml = delete_inst_xml(self.name, "HandlerCIMXML", self.sysname, - "%sHandler" % self.name) + "%sHandler" % self.name, self.interopNS) self.__do_cimpost(self.conn, xml, "DeleteInstance", auth_hdr) xml = delete_inst_xml(self.name, "Filter", self.sysname, - "%sFilter" % self.name) + "%sFilter" % self.name, self.interopNS) self.__do_cimpost(self.conn, xml, "DeleteInstance", auth_hdr)
-def dump_xml(name, typ, ns, sysname): - filter_str = filter_xml(name, typ, ns, sysname) - handler_str = handler_xml(name, 8000, sysname) - subscript_str = subscription_xml(name, sysname) - del_filter_str = delete_inst_xml(name, "Filter", sysname, "%sFilter" % name) + def trigger(self, url, cred=None): + self.conn = httplib.HTTPConnection(url) + if cred: + (u, p) = cred + auth_hdr = base64.b64encode("%s:%s" % (u, p)) + else: + auth_hdr = None + + self.__do_cimpost(self.conn, self.trigger_xml, + "SendTestIndication", auth_hdr) + +def dump_xml(name, typ, ns, sysname, interopNS, destUrl): + filter_str = filter_xml(name, typ, ns, sysname, interopNS) + handler_str = handler_xml(name, destUrl, sysname, interopNS) + subscript_str = subscription_xml(name, sysname, interopNS) + del_filter_str = delete_inst_xml(name, "Filter", sysname, "%sFilter" % name, + interopNS) del_handler_str = delete_inst_xml(name, "HandlerCIMXML", sysname, - "%sHandler" % name) - del_subscript_str = delete_sub_xml(name, sysname) + "%sHandler" % name, interopNS) + del_subscript_str = delete_sub_xml(name, sysname, interopNS) + trigger_str = trigger_xml(typ, interopNS)
print "CreateFilter:\n%s\n" % filter_str print "DeleteFilter:\n%s\n" % del_filter_str @@ -380,15 +523,20 @@ print "DeleteHandler:\n%s\n" % del_handler_str print "CreateSubscription:\n%s\n" % subscript_str print "DeleteSubscription:\n%s\n" % del_subscript_str + print "Indication trigger:\n%s\n" % trigger_str
def main(): usage = "usage: %prog [options] provider\nex: %prog CIM_InstModification" parser = OptionParser(usage)
+ # FIXME: SecureHTTPServer still relies on this, need a better way. + global options + parser.add_option("-u", "--url", dest="url", default="localhost:5988", help="URL of CIMOM to connect to (host:port)") - parser.add_option("-N", "--ns", dest="ns", default="root/virt", - help="Namespace (default is root/virt)") + parser.add_option("-N", "--ns", dest="ns", + help="Namespace in which the register the indication \ + (default is the same value as the interop namespace)") parser.add_option("-n", "--name", dest="name", default="Test", help="Name for filter, handler, subscription \ (default: Test)") @@ -398,16 +546,41 @@ parser.add_option("-p", "--print-ind", dest="print_ind", default=False, action="store_true", help="Print received indications to stdout.") + parser.add_option("-v", "--verbose", dest="verbose", default=False, + action="store_true", + help="Print addtional debug info.") parser.add_option("-U", "--user", dest="username", default=None, - help="HTTP Auth username") + help="HTTP Auth username (CIMOM)") parser.add_option("-P", "--pass", dest="password", default=None, - help="HTTP Auth password") + help="HTTP Auth password (CIMOM)") parser.add_option("--port", dest="port", default=0, type=int, help="Port increment value (server default: 8000)") + parser.add_option("--dest", dest="destUrl", default="localhost:8000", + help="URL of destination handler \ + (default: http://localhost:8000)") + parser.add_option("--certFile", dest="certFile", default=None, + help="File containing the local certificate to use") + parser.add_option("--keyFile", dest="keyFile", default=None, + help="File containing private key for local cert \ + (if none provided, assume key is in the certFile)") + parser.add_option("--trustStore", dest="trustStore", default=None, + help="File containing trusted certificates for \ + remote endpoint verification") + parser.add_option("--noverify", dest="noverify", default=False, + action="store_true", + help="Skip verification of remote endpoint certificate \ + for incoming https indications") + parser.add_option("-i", "--interop", dest="interop", + default="root/interop", + help="Interop namespace name (default: root/interop)") + parser.add_option("-t", "--trigger", dest="trigger", default=False, + action="store_true", + help="Trigger mode: send a request to CIMOM to trigger \ + an indication via a method call ")
(options, args) = parser.parse_args()
- if len(args) == 0: + if not options.trigger and len(args)==0: print "Fatal: no indication type provided." sys.exit(1)
@@ -421,20 +594,75 @@ else: sysname = options.url
+ if "/" in options.interop: + options.interopNS = tuple(options.interop.split("/")) + else: + options.interopNS = ("root", options.interop) + + # If no value provided for indication NS, default is same as interopNS. + if not options.ns: + options.ns = "%s/%s" % options.interopNS + + if options.verbose: + print "Interop namespace = %s/%s" % options.interopNS + print "Indication namespace = %s" % options.ns + + # If url does not begin with http or https, assume http. + parsedUrl = urlparse(options.destUrl) + if not re.search(parsedUrl.scheme, "https"): + destUrl = "http://" + options.destUrl + else: + destUrl = options.destUrl + + if parsedUrl.scheme == "https": + if not options.trustStore and not options.noverify: + print "Error: must provide --trustStore or --noverify with https." + sys.exit(1) + elif options.trustStore and options.noverify: + print "Error: options --trustStore and --noverify are exclusive." + sys.exit(1) + if not options.certFile: + print "Error: no certificate file provided." + sys.exit(1) + elif not options.keyFile: + print "No keyFile provided; assuming private key \ + contained in certFile." + options.keyFile = options.certFile + if options.dump: - dump_xml(options.name, args[0], options.ns, sysname) + if isinstance(parsedUrl.port, int): + listenerPort = parsedUrl.port + options.port + else: + listenerPort = 8000 + options.port + + destUrl = update_url_port(parsedUrl, listenerPort) + dump_xml(options.name, args[0], options.ns, sysname, options.interopNS, + destUrl) + sys.exit(0) + + # Trigger mode: currently only supports SFCB Test_Indication
On Mon, 2012-03-19 at 13:21 -0400, Dave Heller wrote: parsedUrl.password, provider.
+ if options.trigger: + classname = "Test_Indication" + sub = CIMIndicationSubscription(options.name, classname, options.ns, + options.print_ind, sysname, options.port, + options.interopNS, destUrl, True) + print "Triggering indication for %s" % classname + sub.trigger(options.url, auth) sys.exit(0)
sub = CIMIndicationSubscription(options.name, args[0], options.ns, - options.print_ind, sysname, options.port) + options.print_ind, sysname, options.port, + options.interopNS, destUrl) + + print "Creating subscription for %s" % args[0] sub.subscribe(options.url, auth) print "Watching for %s" % args[0]
try: sub.server.serve_forever() - except KeyboardInterrupt,e: - sub.unsubscribe(auth) + except KeyboardInterrupt as e: print "Cancelling subscription for %s" % args[0] + sub.unsubscribe(auth)
if __name__=="__main__": sys.exit(main())
_______________________________________________ Libvirt-cim mailing list Libvirt-cim@redhat.com https://www.redhat.com/mailman/listinfo/libvirt-cim
_______________________________________________ Libvirt-cim mailing list Libvirt-cim@redhat.com https://www.redhat.com/mailman/listinfo/libvirt-cim