OpenStack 云平台流量监控插件tap-as-a-service(Taas)代码解析(二):

在上一篇文章中,以create_tap_service为例,讲解了OpenStack中云端流量捕获插件Tap-as-a-service的Plugin的代码流程

https://blog.csdn.net/m0_37313888/article/details/82693789

先回顾一下Taas的结构,这篇文章我将继续讲解Taas Agent与Taas Driver的工作流程

neutron对应的OpenStack版本:Queuens,关于如何在生产环境中的openstack上安装tap-as-a-service插件请看我的另一篇文章https://blog.csdn.net/m0_37313888/article/details/83450245

1.Taas插件加载与初始化

众所周知,OpenStack中Plugin只负责数据库以及消息队列的维护,工作具体是在agent节点上执行的。taas作为在这里是作为OpenvSwitch的一个extension集成到neutron-openvswitch-agent中,并且在neutron-openvswitch-agent启动时进行加载与初始化。

首先看一看taas 在agent上的初始化过程

在neutron-openvswitch-agent加载时,顺便把taas插件加载的过程:

1)插件加载与实例化

在neutron-openvswitch-agent初始化代码中(代码位置:neutron/plugins/ml2/driver/openvswitch/agent/ovs_neutron_agent.py)

,第2290行附近,有这么一段代码,这段代码将实例从配置文件中读出并且添加到ext_mgr作为ext_mgr的一个成员对象。

2288    ext_manager.register_opts(cfg.CONF)
2289
2290    ext_mgr = ext_manager.L2AgentExtensionsManager(cfg.CONF)

对应到neutron/agent/l2/l2_agent_extensions_manager.py中的下面这段代码

21    L2_AGENT_EXT_MANAGER_NAMESPACE = 'neutron.agent.l2.extensions'
22
23
24    def register_opts(conf):
25        agent_ext_mgr_config.register_agent_ext_manager_opts(conf)

neutron/conf/agent/agent_extensions_manager.py

17    AGENT_EXT_MANAGER_OPTS = [
18        cfg.ListOpt('extensions',
19                    default=[],
20                    help=_('Extensions list to use')),
21    ]
22
23
24    def register_agent_ext_manager_opts(cfg=cfg.CONF):
25        cfg.register_opts(AGENT_EXT_MANAGER_OPTS, 'agent')

代码是直接读了配置文件中agent部分的extensions选项。具体的配置过程请阅读https://blog.csdn.net/m0_37313888/article/details/83450245

加载插件的具体位置如下,加载下面这个插件就是创建了下面这个类的一个实例。

[neutron.agent.l2.extensions]
taas = neutron_taas.services.taas.agents.extensions.taas:TaasAgentExtension

2)插件的初始化

插件的加载只是在内存中生成了一个实例。但是这个插件实例还没有具体发挥它的作用,这个插件实例在OVSNeutronAgent的运行过程中才真正发挥它的作用。在neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py中插件管理器作为了OVSNeutronAgent的成员参与到了OVSNeutronAgent的工作中.(下图中的ext_mgr就是包含了taas的插件管理器)

neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py

2297    try:
2298        agent = OVSNeutronAgent(bridge_classes, ext_mgr, cfg.CONF)
2299        capabilities.notify_init_event(n_const.AGENT_TYPE_OVS, agent)
2300    except (RuntimeError, ValueError) as e:
2301        LOG.error("%s Agent terminated!", e)
2302        sys.exit(1)
2303    agent.daemon_loop()

插件管理器被传递到OVSNeutronAgent的成员变量中(下图)

140    self.ext_manager = ext_manager

之后,还是在这个代码中,进行下面的操作:1.绑定agent_api操作2.初始化所有的插件

219        agent_api = ovs_ext_api.OVSAgentExtensionAPI(self.int_br, self.tun_br)    
220        self.ext_manager.initialize(
221            self.connection, constants.EXTENSION_DRIVER_TYPE, agent_api)

查看具体的 代码实现如下:

class AgentExtensionsManager(stevedore.named.NamedExtensionManager):
    """Manage agent extensions."""

    def __init__(self, conf, namespace):

        super(AgentExtensionsManager, self).__init__(
            namespace, conf.agent.extensions,
            invoke_on_load=True, name_order=True)
        LOG.info("Loaded agent extensions: %s", self.names())

    def initialize(self, connection, driver_type, agent_api=None):

        # Initialize each agent extension in the list.
        for extension in self:
            LOG.info("Initializing agent extension '%s'", extension.name)
            # If the agent has provided an agent_api object, this object will
            # be passed to all interested extensions.  This object must be
            # consumed by each such extension before the extension's
            # initialize() method is called, as the initialization step
            # relies on the agent_api already being available.

            extension.obj.consume_api(agent_api)
            extension.obj.initialize(connection, driver_type)

taas-agent-extension在这段代码中被加载,我们来看看taas-agent-extension做了啥

class TaasAgentExtension(l2_extension.L2AgentExtension):

    def initialize(self, connection, driver_type):
        """Initialize agent extension."""
        self.taas_agent = taas_ovs_agent.TaasOvsAgentRpcCallback(
            cfg.CONF, driver_type)
        self.taas_agent.consume_api(self.agent_api)
        self.taas_agent.initialize()

    def consume_api(self, agent_api):
        """Receive neutron agent API object

        Allows an extension to gain access to resources internal to the
        neutron agent and otherwise unavailable to the extension.
        """
        self.agent_api = agent_api

    def handle_port(self, context, port):
        pass

    def delete_port(self, context, port):
        pass

三件事:

1. 加载本地rpc回调函数类: TaasOvsAgentRpcCallback

2. 加载agent_api: ovs_ext_api.OVSAgentExtensionAPI(self.int_br, self.tun_br) 

3. 初始化本地rpc回调函数类 

本地的rpc回调函数类如下:

class TaasOvsAgentRpcCallback(api.TaasAgentRpcCallbackMixin):

    def __init__(self, conf, driver_type):
        LOG.debug("TaaS OVS Agent initialize called")

        self.conf = conf
        self.driver_type = driver_type

        super(TaasOvsAgentRpcCallback, self).__init__()

    def initialize(self):

        self.taas_driver = manager.NeutronManager.load_class_for_provider(
            'neutron_taas.taas.agent_drivers', self.driver_type)()
        self.taas_driver.consume_api(self.agent_api)
        self.taas_driver.initialize()

        self._taas_rpc_setup()
        TaasOvsAgentService(self).start()

    def consume_api(self, agent_api):
        self.agent_api = agent_api

    def _invoke_driver_for_plugin_api(self, context, args, func_name):
        ........
        try:
            self.taas_driver.__getattribute__(func_name)(args)
        except Exception:
            LOG.debug("Failed to invoke the driver")

        return

    def create_tap_service(self, context, tap_service, host):
        ........

    def create_tap_flow(self, context, tap_flow_msg, host):
        ........

    def delete_tap_service(self, context, tap_service, host):
        ........

    def delete_tap_flow(self, context, tap_flow_msg, host):
        ........

    def _taas_rpc_setup(self):
        # setup RPC to msg taas plugin
        self.taas_plugin_rpc = TaasOvsPluginApi(
            topics.TAAS_PLUGIN, self.conf.host)

        endpoints = [self]
        conn = n_rpc.Connection()
        conn.create_consumer(topics.TAAS_AGENT, endpoints, fanout=False)
        conn.consume_in_threads()

    def periodic_tasks(self):
        ........

我们来重点看一看上面函数中的initialize()部分的代码,这个是agent上的taas-extension的rpc代码的初始化的核心代码

1. 

self.taas_driver = manager.NeutronManager.load_class_for_provider('neutron_taas.taas.agent_drivers', self.driver_type)()

看一看NeutronManager.load_class_for_provider

    @staticmethod
    def load_class_for_provider(namespace, plugin_provider):
        """Loads plugin using alias or class name

        :param namespace: namespace where alias is defined
        :param plugin_provider: plugin alias or class name
        :returns: plugin that is loaded
        :raises ImportError: if fails to load plugin
        """

        try:
            return runtime.load_class_by_alias_or_classname(namespace,
                                                            plugin_provider)
        except ImportError:
            with excutils.save_and_reraise_exception():
                LOG.error("Plugin '%s' not found.", plugin_provider)

runtime.load_class_by_alias_or_classname(namespace,  plugin_provider)如下:

def load_class_by_alias_or_classname(namespace, name):

    if not name:
        LOG.error("Alias or class name is not set")
        raise ImportError(_("Class not found."))
    try:
   
        mgr = driver.DriverManager(
            namespace, name, warn_on_missing_entrypoint=False)
        class_to_load = mgr.driver
    except RuntimeError:
        e1_info = sys.exc_info()
        # Fallback to class name
        try:
            class_to_load = importutils.import_class(name)
        except (ImportError, ValueError):
            LOG.error("Error loading class by alias",
                      exc_info=e1_info)
            LOG.error("Error loading class by class name",
                      exc_info=True)
            raise ImportError(_("Class not found."))
    return class_to_load

通过stevedore.driver.DriverManager类动态加载模块。这是一个与动态加载类有关的模块。关于这个模块的使用,可以看看这篇文章:http://www.360doc.com/content/14/0429/19/9482_373285413.shtml

然后self.taas_driver通过给出的namespace:

neutron_taas.taas.agent_drivers

与 driver_type:

ovs  被加载。具体被加载的类写在了setup.cfg的entry_points里面

[entry_points]
neutron.agent.l2.extensions =
    taas = neutron_taas.services.taas.agents.extensions.taas:TaasAgentExtension
neutron_taas.taas.agent_drivers =
    ovs = neutron_taas.services.taas.drivers.linux.ovs_taas:OvsTaasDriver
neutron.service_plugins =
    taas = neutron_taas.services.taas.taas_plugin:TaasPlugin
neutron.db.alembic_migrations =
    tap-as-a-service = neutron_taas.db.migration:alembic_migration
tempest.test_plugins =
    tap-as-a-service = neutron_taas.tests.tempest_plugin.plugin:NeutronTaaSPlugin
neutronclient.extension =
    tap_service = neutron_taas.taas_client.tapservice
    tap_flow = neutron_taas.taas_client.tapflow

即,self.taas_driver = 

neutron_taas.services.taas.drivers.linux.ovs_taas:OvsTaasDriver

2.

self.taas_driver.consume_api(self.agent_api)

在self.taas_driver, 即neutron_taas.services.taas.drivers.linux.ovs_taas:OvsTaasDriver中,consume_api()方法如下:

    def consume_api(self, agent_api):
        self.agent_api = agent_api

简单的增加了一个self.agent_api成员

3.

self.taas_driver.initialize()

还是看一下self.taas_driver,

    def initialize(self):
        self.int_br = self.agent_api.request_int_br()
        self.tun_br = self.agent_api.request_tun_br()
        self.tap_br = OVSBridge_tap_extension('br-tap', self.root_helper)

        # Prepare OVS bridges for TaaS
        self.setup_ovs_bridges()

        # Setup key-value manager for ingress BCMC flows
        self.bcmc_kvm = taas_ovs_utils.key_value_mgr(4096)

这个时候agent_api就派上用场了!这个对象,顾名思义,就是一个agent所映射产生的一个api对象,提供许多访问本地资源的接口,在ovs中,是本地所建立的网桥与相关网络设备。我们看到,先是获取了int-br网桥与tun_br 网桥,来看一看获取网桥的基本操作,这里agent_api是ovs_ext_api.OVSAgentExtensionAPI(self.int_br, self.tun_br) 

如下

class OVSAgentExtensionAPI(object):
    '''Implements the Agent API for Open vSwitch agent.

    Extensions can gain access to this API by overriding the consume_api
    method which has been added to the AgentExtension class.
    '''

    def __init__(self, int_br, tun_br):
        super(OVSAgentExtensionAPI, self).__init__()
        self.br_int = int_br
        self.br_tun = tun_br

    def request_int_br(self):
        """Allows extensions to request an integration bridge to use for
        extension specific flows.
        """
        return OVSCookieBridge(self.br_int)

    def request_tun_br(self):
        """Allows extensions to request a tunnel bridge to use for
        extension specific flows.

        If tunneling is not enabled, this method will return None.
        """
        if not self.br_tun:
            return None

        return OVSCookieBridge(self.br_tun)

这里OVSCookieBridge让我们想到了web开发中的cookie,但是这个其实就是把本地的网桥信息浅拷贝一份然后返回给程序进一步使用

class OVSCookieBridge(object):

    def __new__(cls, bridge):
        cookie_bridge = bridge.clone()
        cookie_bridge.set_agent_uuid_stamp(bridge.request_cookie())

        return cookie_bridge

    def __init__(self, bridge):
        pass
    def clone(self):
        '''Used by OVSCookieBridge, can be overridden by subclasses if a
        behavior different from copy.copy is needed.
        '''
        return copy.copy(self)

接着,然后建立了一个tap网桥。这一步是在agent上实现流量拷贝、迁移、以及重定向的重头戏。

self.setup_ovs_bridges()

具体代码在neutron_taas/services/taas/drivers/linux/ovs_taas.py

这里为了直观的展示这个流程,把流程分为4个部分

1.创建一个tap网桥

刚开始我看这一段的时候也很是迷惑,因为ovs有专门的流量镜像功能啊,为何不直接管理ovs做一个流量镜像呢?原来,tap-as--a-service的主要设计理念是为不同节点上的虚拟机提供统一的流量捕获服务,这样的话方案中流量必须能够方便的进行跨主机迁移。如果采用vxlan隧道的话就很容易建立新的流量隧道实现流量的跨节点迁移。换句话说,流量既能够定向到相同宿主机上的另一台虚拟机,也能重定向到另一台宿主机上的另一台虚拟机!

        self.tap_br.create()

        # Connect br-tap to br-int and br-tun
        self.int_br.add_patch_port('patch-int-tap', 'patch-tap-int')
        self.tap_br.add_patch_port('patch-tap-int', 'patch-int-tap')
        self.tun_br.add_patch_port('patch-tun-tap', 'patch-tap-tun')
        self.tap_br.add_patch_port('patch-tap-tun', 'patch-tun-tap')

        # Get patch port IDs
        patch_tap_int_id = self.tap_br.get_port_ofport('patch-tap-int')
        patch_tap_tun_id = self.tap_br.get_port_ofport('patch-tap-tun')
        patch_tun_tap_id = self.tun_br.get_port_ofport('patch-tun-tap')

2.删除预先定义的与tap-as-a-service相关的所有规则

        self.tap_br.delete_flows(table=0)
        self.tap_br.delete_flows(table=taas_ovs_consts.TAAS_RECV_LOC)
        self.tap_br.delete_flows(table=taas_ovs_consts.TAAS_RECV_REM)

        self.tun_br.delete_flows(table=0,
                                 in_port=patch_tun_tap_id)
        self.tun_br.delete_flows(table=taas_ovs_consts.TAAS_SEND_UCAST)
        self.tun_br.delete_flows(table=taas_ovs_consts.TAAS_SEND_FLOOD)
        self.tun_br.delete_flows(table=taas_ovs_consts.TAAS_CLASSIFY)
        self.tun_br.delete_flows(table=taas_ovs_consts.TAAS_DST_CHECK)
        self.tun_br.delete_flows(table=taas_ovs_consts.TAAS_SRC_CHECK)
        self.tun_br.delete_flows(table=taas_ovs_consts.TAAS_DST_RESPOND)
        self.tun_br.delete_flows(table=taas_ovs_consts.TAAS_SRC_RESPOND)

3.初始化tap网桥的OpenFlow流表

        self.tap_br.add_flow(table=0,
                             priority=1,
                             in_port=patch_tap_int_id,
                             actions="resubmit(,%s)" %
                             taas_ovs_consts.TAAS_RECV_LOC)

        self.tap_br.add_flow(table=0,
                             priority=1,
                             in_port=patch_tap_tun_id,
                             actions="resubmit(,%s)" %
                             taas_ovs_consts.TAAS_RECV_REM)

        self.tap_br.add_flow(table=0,
                             priority=0,
                             actions="drop")

        self.tap_br.add_flow(table=taas_ovs_consts.TAAS_RECV_LOC,
                             priority=0,
                             actions="output:%s" % str(patch_tap_tun_id))

        self.tap_br.add_flow(table=taas_ovs_consts.TAAS_RECV_REM,
                             priority=0,
                             actions="drop")

这段代码可能看起来比较吃力,我在下面做出一些解释:

里面出现了 taas_ovs_consts.TAAS_RECV_LOC 与 taas_ovs_consts.TAAS_RECV_REM两张表。结合后面的代码仔细一看,TAAS_RECV_LOC负责处理从br-int过来的流量(流出vm的流量),默认为转发到br-tun;TAAS_RECV_REM负责处理从br-tun过来的流量,默认为不让任何来自于br-tun的流量通过。来自其他网桥的流量也默认不通过。

4.初始化br-tun网桥的OpenFlow流表

        self.tun_br.add_flow(table=0,
                             priority=1,
                             in_port=patch_tun_tap_id,
                             actions="resubmit(,%s)" %
                             taas_ovs_consts.TAAS_SEND_UCAST)

        self.tun_br.add_flow(table=taas_ovs_consts.TAAS_SEND_UCAST,
                             priority=0,
                             actions="resubmit(,%s)" %
                             taas_ovs_consts.TAAS_SEND_FLOOD)

        flow_action = self._create_tunnel_flood_flow_action()
        if flow_action != "":
            self.tun_br.add_flow(table=taas_ovs_consts.TAAS_SEND_FLOOD,
                                 priority=0,
                                 actions=flow_action)

        self.tun_br.add_flow(table=taas_ovs_consts.TAAS_CLASSIFY,
                             priority=2,
                             reg0=0,
                             actions="resubmit(,%s)" %
                             taas_ovs_consts.TAAS_DST_CHECK)

        self.tun_br.add_flow(table=taas_ovs_consts.TAAS_CLASSIFY,
                             priority=1,
                             reg0=1,
                             actions="resubmit(,%s)" %
                             taas_ovs_consts.TAAS_DST_CHECK)

        self.tun_br.add_flow(table=taas_ovs_consts.TAAS_CLASSIFY,
                             priority=1,
                             reg0=2,
                             actions="resubmit(,%s)" %
                             taas_ovs_consts.TAAS_SRC_CHECK)

        self.tun_br.add_flow(table=taas_ovs_consts.TAAS_DST_CHECK,
                             priority=0,
                             actions="drop")

        self.tun_br.add_flow(table=taas_ovs_consts.TAAS_SRC_CHECK,
                             priority=0,
                             actions="drop")

        self.tun_br.add_flow(table=taas_ovs_consts.TAAS_DST_RESPOND,
                             priority=2,
                             reg0=0,
                             actions="output:%s" % str(patch_tun_tap_id))

        self.tun_br.add_flow(table=taas_ovs_consts.TAAS_DST_RESPOND,
                             priority=1,
                             reg0=1,
                             actions=(
                                 "output:%s,"
                                 "move:NXM_OF_VLAN_TCI[0..11]->NXM_NX_TUN_ID"
                                 "[0..11],mod_vlan_vid:2,output:in_port" %
                                 str(patch_tun_tap_id)))

        self.tun_br.add_flow(table=taas_ovs_consts.TAAS_SRC_RESPOND,
                             priority=1,
                             actions=(
                                 "learn(table=%s,hard_timeout=60,"
                                 "priority=1,NXM_OF_VLAN_TCI[0..11],"
                                 "load:NXM_OF_VLAN_TCI[0..11]->NXM_NX_TUN_ID"
                                 "[0..11],load:0->NXM_OF_VLAN_TCI[0..11],"
                                 "output:NXM_OF_IN_PORT[])" %
                                 taas_ovs_consts.TAAS_SEND_UCAST))

        return

以上就是给计算节点开机后,taas插件的初始化过程。关于在实际使用时流表的配置,以及为什么要这么配置,我后期会专门写一篇文章来讲解。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值