
On Mon, 2012-03-19 at 19:41 -0400, Dave Heller wrote:
Hi Sharad,
Actually, I did modify the script in a way that it is still compatible with cimtest (based on tests using ComputerSystemIndication
In that case, I am going to apply your patch and test as update to existing indication script.
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?
You can add it to the script header itself. Do you want to send another patch with usage added and "orig" removed from the file name? Regards, Sharad Mishra
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
_______________________________________________ Libvirt-cim mailing list Libvirt-cim@redhat.com https://www.redhat.com/mailman/listinfo/libvirt-cim