Как переопределить содержимое пакета во время выполнения?


[EDIT: я запускаю Python 2.7.3]

Я сетевой инженер по профессии, и я взламывал ncclient (версия на веб-сайте старая, и эта была версией, над которой я работал), чтобы заставить его работать с реализацией Brocade NETCONF. Есть некоторые хитрости, которые я должен был сделать, чтобы заставить его работать с нашим парчовым оборудованием, но я должен был раскошелиться на пакет и сделать хитрости к самому источнику. Это не казалось мне "чистым", поэтому я решил, что я хочу попробовать сделать это "правильным способом" и переопределить пару вещей, которые существуют в пакете*; три вещи конкретно:

  1. "статический метод", называемый build (), который принадлежит классу HelloHandler, который сам является подклассом SessionListener
  2. The ".Атрибут _id " класса RPC (оригинальная реализация использовала uuid, и парчовым коробкам это не очень понравилось, поэтому в моих оригинальных настройках я просто изменил это на статическое значение, которое никогда не было измененный).
  3. небольшая настройка функции util, которая создает атрибуты фильтра XML

Пока у меня есть этот код в файле 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 я вижу, что мои определения подкласса (а именно тот, который изменяет XML для hello exchange-staticmethod build) игнорируются, и парчовая коробка не знает, как интерпретировать XML, что исходный метод ncclient HelloHandler.build() генерирует**. Я также могу видеть в сгенерированном файле журнала, что другие вещи, которые я пытаюсь переопределить, также игнорируются, как идентификатор сообщения (статическое значение 1), а также фильтры XML.

Итак, я здесь в некотором замешательстве. Я действительно нашел этот пост в блоге / модуль из моего исследования, и он, похоже, делает именно то, что я хочу, но я действительно хотел бы понять, что я делаю неправильно, делая это вручную, а не используя модуль, который кто-то уже написал в качестве оправдания, чтобы не пришлось разбираться в этом самостоятельно.

Может ли кто-нибудь объяснить мне, если это "обезьянье латание" и на самом деле плохо? Я видел в своих исследованиях, что обезьянье латание нежелательно, но этот ответ и этот ответ меня немного смущают. Для меня, мое желание, чтобы переопределить эти биты удержали бы меня от того, чтобы поддерживать всю вилку моего собственного ncclient.

* * чтобы дать немного больше контекста, этот XML, который ncclient.transport.session.HelloHandler.build() генерирует по умолчанию, парчовой коробке, похоже, не нравится:

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

Цель моего переопределенного метода build() состоит в том, чтобы превратить вышеупомянутый XML в это (что парча действительно любит:

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

1 ответ:

Таким образом, оказывается, что "мета-информация" не должна была быть так поспешно удалена, потому что, опять же, трудно найти ответы на то, что я ищу, когда я не полностью понимаю, что я хочу спросить. То, что я действительно хотел сделать, было переопределить вещи в пакете во время выполнения.

Вот что я изменил 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, потому что я не понял или не понимаю, как изменить атрибуты объекта, прежде чем __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

Заслуга принадлежитэтому рецепту на ActiveState за то, что он наконец-то дал мне понять, что я действительно хотел сделать. Код, который я первоначально опубликовал, я не думаю, что был технически неправильным - если то, что я хотел сделать, было раскошелиться мой собственный ncclient и вносить в него изменения и/или поддерживать его, что было совсем не то, что я хотел сделать, по крайней мере, не сейчас.

Я отредактирую название своего вопроса, чтобы лучше отразить то, что я изначально хотел - если у других людей есть лучшие или более чистые идеи, я полностью открыт.