如何在运行时覆盖包中的东西?

时间:2013-01-05 22:54:45

标签: python python-2.7 oop python-module ietf-netconf

[编辑:我正在运行Python 2.7.3]

我是一名网络工程师,我一直在攻击ncclient(网站上的版本已经过时了,this是我一直在努力的版本)使其与Brocade的NETCONF实现一起使用。为了让它与我们的Brocade设备一起使用,我必须进行一些调整,但是我不得不拆掉包并对源本身进行调整。这对我来说并不“干净”,所以我决定尝试以“正确的方式”做到并覆盖包装中存在的一些东西*;具体三件事:

  1. 一个名为build()的“静态方法”,它属于HelloHandler类,它本身是SessionListener的子类
  2. RPC类的“._id”属性(原始实现使用uuid,而Brocade盒子不太喜欢这个,所以在我原来的调整中我只是将它改为一个永不改变的静态值。) / LI>
  3. 对构建XML过滤器属性的util函数的一个小调整
  4. 到目前为止,我将此代码放在文件brcd_ncclient.py中:

    #!/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's 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
    

    然后在我的文件netconftest.py中我有:

    #!/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'
    

    每当我运行netconftest.py文件时,我都会收到超时错误,因为在日志文件ncclient.log中我可以看到我的子类定义(即更改用于hello交换的XML的那个 - staticmethod { {1}})被忽略,Brocade框不知道如何解释原始ncclient build方法生成的XML **。我还可以在生成的日志文件中看到我正在尝试覆盖的其他内容也被忽略,例如message-id(静态值为1)以及XML过滤器。

    所以,我在这里有点不知所措。我确实从我的研究中找到了this blog post/module,它似乎完全符合我的要求,但我真的希望能够通过手工操作来理解我做错了什么,而不是使用有人已经写过的模块作为借口,不必自己解决这个问题。

    *有人可以向我解释这是“猴子补丁”,实际上是不是很糟糕?我在研究中看到猴子修补是不可取的,但this answerthis answer让我感到困惑。对我来说,我想要覆盖这些位将阻止我不得不维护我自己的ncclient的整个分支。

    **为了提供更多的上下文,这个XML({1}}默认生成,Brocade框似乎不喜欢:

    HelloHandler.build()

    我重写的ncclient.transport.session.HelloHandler.build()方法的目的是将上面的XML转换为这个(Brocade喜欢这样做:

    <?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>
    

1 个答案:

答案 0 :(得分:2)

事实证明,“元信息”不应该被如此匆忙地删除,因为再次,当我不完全理解我想要问的内容时,很难找到我所追求的答案。我真正想做的是在运行 的 包中覆盖内容。

以下是我已将brcd_ncclient.py更改为(为简洁起见而删除的评论):

#!/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

然后在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

这让我几乎到了我想去的地方。我仍然需要编辑原始源代码以更改使用uuid1().urn生成的消息ID,因为我还没有弄清楚或不了解如何在__init__发生之前更改对象的属性运行时(鸡/蛋问题?);这是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

归功于this recipe on ActiveState,最终让我知道我真正想做的事情。我最初发布的代码我不认为在技术上是不正确的 - 如果我想要做的是分离我自己的ncclient并对其进行更改和/或维护它,这根本不是我想要做的,至少现在不行。

我会编辑我的问题标题以更好地反映我原本想要的东西 - 如果其他人有更好或更清洁的想法,我就完全开放了。