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
On Mon, 2012-03-19 at 13:21 -0400, Dave Heller wrote:
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(a)us.ibm.com>
+# SSL support added by Dave Heller <hellerda(a)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.password,
+ 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 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(a)redhat.com
https://www.redhat.com/mailman/listinfo/libvirt-cim