How to override material in a package at runtime?

[EDIT: I am running Python 2.7.3]

I trade in the network, and I hacked into ncclient (the version on the website is outdated, and this was the version I was working on) so that it works with the Brocade NETCONF implementation. There are some settings that I had to make to make it work with our Brocade equipment, but I had to refuse the package and make changes to the source code. This did not seem “clean” to me, so I decided to try “correctly” and redefine a couple of things that exist in the package *; three things:

  • A "static method" called build (), which belongs to the HelloHandler class, which itself is a subclass of SessionListener
  • Attribute "._id" of RPC class (the original implementation used in uuid and Brocade blocks didn’t really like, so in my initial tricks I just changed it to a static value that never changed).
  • A small tweak to the util function, which builds the attributes of an XML filter

So far, I have this code in the brcd_ncclient.py file:

 #!/usr/bin/env python # hack on XML element creation and create a subclass to override HelloHandler's # build() method to format the XML in a way that the brocades actually like from ncclient.xml_ import * from ncclient.transport.session import HelloHandler from ncclient.operations.rpc import RPC, RaiseMode from ncclient.operations import util # register brocade namespace and create functions to create proper xml for # hello/capabilities exchange BROCADE_1_0 = "http://brocade.com/ns/netconf/config/netiron-config/" register_namespace('brcd', BROCADE_1_0) brocade_new_ele = lambda tag, ns, attrs={}, **extra: ET.Element(qualify(tag, ns), attrs, **extra) brocade_sub_ele = lambda parent, tag, ns, attrs={}, **extra: ET.SubElement(parent, qualify(tag, ns), attrs, **extra) # subclass RPC to override self._id to change uuid-generated message-id's; # Brocades seem to not be able to handle the really long id's class BrcdRPC(RPC): def __init__(self, session, async=False, timeout=30, raise_mode=RaiseMode.NONE): self._id = "1" return super(BrcdRPC, self).self._id class BrcdHelloHandler(HelloHandler): def __init__(self): return super(BrcdHelloHandler, self).__init__() @staticmethod def build(capabilities): hello = brocade_new_ele("hello", None, {'xmlns':"urn:ietf:params:xml:ns:netconf:base:1.0"}) caps = brocade_sub_ele(hello, "capabilities", None) def fun(uri): brocade_sub_ele(caps, "capability", None).text = uri map(fun, capabilities) return to_xml(hello) #return super(BrcdHelloHandler, self).build() ??? # since there no classes I'm assuming I can just override the function itself # in ncclient.operations.util? def build_filter(spec, capcheck=None): type = None if isinstance(spec, tuple): type, criteria = spec # brocades want the netconf prefix on subtree filter attribute rep = new_ele("filter", {'nc:type':type}) if type == "xpath": rep.attrib["select"] = criteria elif type == "subtree": rep.append(to_ele(criteria)) else: raise OperationError("Invalid filter type") else: rep = validated_element(spec, ("filter", qualify("filter")), attrs=("type",)) # TODO set type var here, check if select attr present in case of xpath.. if type == "xpath" and capcheck is not None: capcheck(":xpath") return rep 

And then in my netconftest.py file I have:

 #!/usr/bin/env python from ncclient import manager from brcd_ncclient import * manager.logging.basicConfig(filename='ncclient.log', level=manager.logging.DEBUG) # brocade server capabilities advertising as 1.1 compliant when they're really not # this will stop ncclient from attempting 1.1 chunked netconf message transactions manager.CAPABILITIES = ['urn:ietf:params:netconf:capability:writeable-running:1.0', 'urn:ietf:params:netconf:base:1.0'] # BROCADE_1_0 is the namespace defined for netiron configs in brcd_ncclient # this maps to the 'brcd' prefix used in xml elements, ie subtree filter criteria with manager.connect(host='hostname_or_ip', username='username', password='password') as m: # 'get' request with no filter - for brocades just shows 'show version' data c = m.get() print c # 'get-config' request with 'mpls-config' filter - if no filter is # supplied with 'get-config', brocade returns nothing netironcfg = brocade_new_ele('netiron-config', BROCADE_1_0) mplsconfig = brocade_sub_ele(netironcfg, 'mpls-config', BROCADE_1_0) filterstr = to_xml(netironcfg) c2 = m.get_config(source='running', filter=('subtree', filterstr)) print c2 # so far it only looks like the supported filters for 'get-config' # operations are: 'interface-config', 'vlan-config' and 'mpls-config' 

Whenever I run my netconftest.py file, I get timeout errors because in the ncclient.log log ncclient.log I see that my subclass definitions (namely the one that modifies XML for greetings - staticmethod build )) are ignored, and the Brocade field does not know how to interpret the XML that the source ncclient HelloHandler.build() generates. I also see in the generated log file that other things I'm trying to override are also ignored, for example, the message identifier (static value 1), as well as XML filters.

So, I'm a little confused. I found this blog post / module from my research, and it seems to be doing what I want, but I would really like to be able to understand what I'm doing wrong by doing it manually, instead of using the module, which someone has already written as an excuse not to come up with this on their own.

* Can someone explain to me if this "beheading of the monkeys" is really bad? I have seen in my research that correcting monkeys is undesirable, but this answer and this answer confuse me quite a bit. For me, my desire to override these bits would not prevent me from supporting the whole fork of my own ncclient.

** To give a little more context, this XML, which ncclient.transport.session.HelloHandler.build() generates by default, seems like the Brocade box doesn't like:

 <?xml version='1.0' encoding='UTF-8'?> <nc:hello xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0"> <nc:capabilities> <nc:capability>urn:ietf:params:netconf:base:1.0</nc:capability> <nc:capability>urn:ietf:params:netconf:capability:writeable-running:1.0</nc:capability> </nc:capabilities> </nc:hello> 

The purpose of my overridden build() method is to turn the above XML into this (which Brocade does:

 <?xml version="1.0" encoding="UTF-8"?> <hello xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <capabilities> <capability>urn:ietf:params:netconf:base:1.0</capability> <capability>urn:ietf:params:netconf:capability:writeable-running:1.0</capability> </capabilities> </hello> 
+6
source share
1 answer

So, it turns out that the "meta-information" should not have been removed so hastily, because again it is difficult to find answers to what I need, when I do not quite understand what I want to ask. What I really wanted to do was redefine the material in the package at runtime .

Here I changed brcd_ncclient.py to (comments are removed for brevity):

 #!/usr/bin/env python from ncclient import manager from ncclient.xml_ import * brcd_new_ele = lambda tag, ns, attrs={}, **extra: ET.Element(qualify(tag, ns), attrs, **extra) brcd_sub_ele = lambda parent, tag, ns, attrs={}, **extra: ET.SubElement(parent, qualify(tag, ns), attrs, **extra) BROCADE_1_0 = "http://brocade.com/ns/netconf/config/netiron-config/" register_namespace('brcd', BROCADE_1_0) @staticmethod def brcd_build(capabilities): hello = brcd_new_ele("hello", None, {'xmlns':"urn:ietf:params:xml:ns:netconf:base:1.0"}) caps = brcd_sub_ele(hello, "capabilities", None) def fun(uri): brcd_sub_ele(caps, "capability", None).text = uri map(fun, capabilities) return to_xml(hello) def brcd_build_filter(spec, capcheck=None): type = None if isinstance(spec, tuple): type, criteria = spec # brocades want the netconf prefix on subtree filter attribute rep = new_ele("filter", {'nc:type':type}) if type == "xpath": rep.attrib["select"] = criteria elif type == "subtree": rep.append(to_ele(criteria)) else: raise OperationError("Invalid filter type") else: rep = validated_element(spec, ("filter", qualify("filter")), attrs=("type",)) if type == "xpath" and capcheck is not None: capcheck(":xpath") return rep manager.transport.session.HelloHandler.build = brcd_build manager.operations.util.build_filter = brcd_build_filter 

And then in netconftest.py :

 #!/usr/bin/env python from brcd_ncclient import * manager.logging.basicConfig(filename='ncclient.log', level=manager.logging.DEBUG) manager.CAPABILITIES = ['urn:ietf:params:netconf:capability:writeable-running:1.0', 'urn:ietf:params:netconf:base:1.0'] with manager.connect(host='host', username='user', password='password') as m: netironcfg = brcd_new_ele('netiron-config', BROCADE_1_0) mplsconfig = brcd_sub_ele(netironcfg, 'mpls-config', BROCADE_1_0) filterstr = to_xml(netironcfg) c2 = m.get_config(source='running', filter=('subtree', filterstr)) print c2 

It will take me almost to where I want to be. I still need to edit the source code to change the identifier of the message generated with uuid1().urn , because I did not understand or did not understand how to change the attributes of the object before __init__ happens at runtime (chicken / egg problem?) ; here's the abusive code in ncclient/operations/rpc.py :

 class RPC(object): DEPENDS = [] REPLY_CLS = RPCReply def __init__(self, session, async=False, timeout=30, raise_mode=RaiseMode.NONE): self._session = session try: for cap in self.DEPENDS: self._assert(cap) except AttributeError: pass self._async = async self._timeout = timeout self._raise_mode = raise_mode self._id = uuid1().urn # Keeps things simple instead of having a class attr with running ID that has to be locked 

The loan goes to this recipe on ActiveState in order to finally attract me to what I really wanted to do. The code that I originally posted, I don’t think it was technically incorrect - if I wanted to do this, I would bounce off my own ncclient and make changes to it and / or save it, which I didn’t want to do at all, at least for now.

I will edit the title of the question to better reflect what I originally wanted - if other people have better or cleaner ideas, I am completely open.

+2
source

All Articles