onos设备子系统之Core

onos基本概念

onos作为一个极具规模的网络操作系统,其各种功能的背后是日益庞大的组件库。onos将相互关联且协作完成同一功能的组件集合称为Service(服务)或Subsystem(子系统),比如设备子系统、链路子系统等。考虑到OSGi中也有服务概念,下文仅使用子系统术语。

上图将onos的组件分为了3层:App,Core(主要由Manager和Store组成)和Provider,任何onos组件都必然位于其中一层。各层之间通过相应的接口(AdminService、Service等)进行交互。这里简单介绍一下各层承担的任务:

  • Provider:使用特定的协议库与网络环境进行交互,并将网络数据提供给Core。某些Provider还需要接收来自Core的控制命令,将其根据特定协议下发到网络环境中去。
  • Core:主要包括Manager和Store。
    • Manager:从Provider接收信息并将其提供给应用程序或其他服务。
    • Store:主要负责与其他onos实例进行东西向通信,保证状态一致性。该组件一般用于onos集群中。
  • Application:通过AdminService和Service接口获取网络信息,并给予相应反馈。

这里不得不赞叹onos的架构,各类子系统结合构建了onos的生态环境,并且每个子系统都可以分为App,Core和Provider三层。这里需要注意的是,一般来讲子系统的Core层是最稳定的,不依赖于具体的硬件设备和上层app。当用户需要让onos适配其他设备时,只需要扩展Provider层,使用特定的Provider实现即可。
接下来讲解设备子系统的Core层内容,主要包括Manager和Store。源码版本为2.3.0。

DeviceManager

DeviceManager位于Core层,负责提供南向接口(Provider层)和北向接口(App层)的实现。

public class DeviceManager
    extends AbstractListenerProviderRegistry<DeviceEvent, DeviceListener, DeviceProvider, DeviceProviderService>
    implements DeviceService, DeviceAdminService, DeviceProviderRegistry, PortConfigOperatorRegistry {...}

可以看到DeviceManager的继承(实现)关系较复杂,这里只阅读其直接父类或接口的源码。

AbstractListenerProviderRegistry抽象类

public abstract class AbstractListenerProviderRegistry<E extends Event, L extends EventListener<E>,
                                                   P extends Provider, S extends ProviderService<P>>
    extends AbstractProviderRegistry<P, S> implements ListenerService<E, L>

继承了AbstractProviderRegistry,并实现了ListenerService接口。

  • AbstractProviderRegistry:可以把它理解成注册表,注册对应的Provider,具体的注册行为依赖于ProviderId。对于每一种Provider,允许存在多种scheme(如of),但是每种scheme只能注册一个主Provider(辅助Provider不进行注册)。这也能理解,对于Openflow(of)只允许存在一种Provider,若是有多个,那么选择哪个Provider进行处理就会发生混乱。至于ProviderService的创建过程,则由实现类控制。

    ProviderId具有两个特殊属性scheme和ancillary。
    scheme用来判断该Provider可否用于特定的设备,ancillary(boolean变量)用来表示该Provider是辅助的还是主要的。

  • ListenerService:对于Event,可增加或删除Listener。

AbstractListenerProviderRegistry中的代码也比较简单,主要是用一个Set集合来存储所有的Listener。另外,该类是抽象类,并没有实现创建ProviderService的方法。

DeviceService接口

该接口继承了ListenerService<DeviceEvent, DeviceListener>。由于其面向的是App层,所以其提供的接口方法包括:获得相应设备,获得设备端口信息等。

DeviceAdminService接口

该接口继承于DeviceService,多了两个方法:移除设备和更改设备端口状态。该接口相较于DeviceService的层级更高,因为DeviceService只允许进行设备相关的查询,而DeviceAdminService则可对设备进行更新操作。

DeviceProviderRegistry接口

跟AbstractListenerProviderRegistry有点重复了,所以没什么好讲的。

PortConfigOperatorRegistry接口

设备端口操作的注册表,说明了某种设备端口操作支持的端口类型。

DeviceManager具体实现

接下来是DeviceManager的精华部分。

创建ProviderService

之前说了AbstractListenerProviderRegistry未实现创建ProviderService的方法。

protected DeviceProviderService createProviderService(
        DeviceProvider provider) {
    return new InternalDeviceProviderService(provider);
}

可以看到是创建了InternalDeviceProviderService(是内部类)对象,其对于DeviceProviderService声明的接口方法的具体实现如下:

  • deviceConnected:当有新设备连接时,先在DeviceManager内存储设备状态,并读取该设备基本配置、端口描述配置和注释配置以生成完整的设备描述对象deviceDescription,并将这些信息存入Store(之后也会对端口信息想进行存储)获得相应的DeviceEvent。然后获取当前onos对于新Device的身份(主/备),并向Device发送RoleRequest。最后将DeviceEvent派发出去。
  • deviceDisconnected:设备断开连接时,同样要更新store内信息,并派发相应事件。

其他处理就不仔细讲了,有兴趣可以直接阅读源码。

查询Device

之前说过,这部分方法由DeviceService声明,其具体实现其实就是调用stroe进行查询。(由此可见,store在onos中可以看作一个黑盒子,大部分信息都会存入store,大部分查询也通过访问store获得)

提供AdminService

实现DeviceAdminServie,主要是更新store中信息,并生成信息派发。

active方法

@Component(immediate = true,
           service = {DeviceService.class, DeviceAdminService.class,
                      DeviceProviderRegistry.class, PortConfigOperatorRegistry.class })
public class DeviceManager

由于DeviceManager由@Component注解,所以其会在OSGi框架中自动注册为DeviceService、DeviceAdminService、DeviceProviderRegistry和PortConfigOperatorRegistry的服务对象,并且会直接执行active()。

@Activate
public void activate() {
    portAnnotationOp = new PortAnnotationOperator(networkConfigService);
    deviceAnnotationOp = new DeviceAnnotationOperator(networkConfigService);
    portOpsIndex.put(PortAnnotationConfig.class, portAnnotationOp);

    backgroundService = newSingleThreadScheduledExecutor(
            groupedThreads("onos/device", "manager-background", log));
    localNodeId = clusterService.getLocalNode().id();

    store.setDelegate(delegate);
    eventDispatcher.addSink(DeviceEvent.class, listenerRegistry);
    mastershipService.addListener(mastershipListener);
    networkConfigService.addListener(networkConfigListener);

    backgroundService.scheduleWithFixedDelay(() -> {
        try {
            mastershipCheck();
        } catch (Exception e) {
            log.error("Exception thrown during integrity check", e);
        }
    }, 1, 1, TimeUnit.MINUTES);

    portReqeustExecutor = newSingleThreadExecutor();

    communicationService.<InternalPortUpDownEvent>addSubscriber(
            PORT_UPDOWN_SUBJECT,
            SERIALIZER::decode,
            this::handlePortRequest,
            portReqeustExecutor);

    log.info("Started");
}

初始化工作包括:

  1. 根据networkConfigService生成相应的portAnnotationOp和deviceAnnotationOp,并进行注册。
  2. 初始化后台线程池。
  3. 为Store设置代理(代理主要负责自动分发Store生成的Event)。
  4. 设置eventDispatcher服务的监听者为内部的listenerRegistry。另外对mastershipService和networkConfigService添加监听者。
  5. 后台线程池开始工作,每隔一定时间,就检测所有可达的设备是否都有有效的主控制器。
  6. 最后在communicationService中注册端口开关消息的处理方法。

GossipDeviceStore

是DeviceStore的一种实现,也是默认使用的实现。其位于Core层,承担Core层Store的角色。由于不同onos实例间的状态同步要求,这里采用的是Gossip协议。

Gossip 算法又被称为反熵(Anti-Entropy),熵是物理学上的一个概念,代表杂乱无章,而反熵就是在杂乱无章中寻求一致,这充分说明了 Gossip 的特点:在一个有界网络中,每个节点都随机地与其他节点通信,经过一番杂乱无章的通信,最终所有节点的状态都会达成一致。每个节点可能知道所有其他节点,也可能仅知道几个邻居节点,只要这些节可以通过网络连通,最终他们的状态都是一致的,当然这也是疫情传播的特点。
简单的描述下这个协议,首先要传播谣言就要有种子节点。种子节点每秒都会随机向其他节点发送自己所拥有的节点列表,以及需要传播的消息。任何新加入的节点,就在这种传播方式下很快地被全网所知道。这个协议的神奇就在于它从设计开始就没想到信息一定要传递给所有的节点,但是随着时间的增长,在最终的某一时刻,全网会得到相同的信息。当然这个时刻可能仅仅存在于理论,永远不可达。
以上引用自https://www.jianshu.com/p/133560ef28df

GossipDeviceStore的继承关系如下图所示:

Event接口

Event之前就提到过,是onos扩展监听的基础。

public interface Event<T extends Enum, S> {
    long time();    // 事件发生时的时间戳
    T type();        // 事件类型,比如DeviceEvent、FlowRuleEvent等
    S subject();    // 该事件类型下的进一步分类,比如DeviceEvent下的DEVICE_UPDATE
}

StoreDelegate接口

与某个Store绑定,并且能够在Store内部接收其生成的Event。每个Store内部都有一个StoreDelegate对象。

Store接口

Store能够存储信息并在集群中分发信息。

GossipDeviceStore具体实现

接下来是GossipDeviceStore的精华部分。

active方法

  1. 初始化线程池,executor和backgroundExecutor。
  2. 往clusterCommunicator中注册不同消息的处理方法。
  3. 在后台线程池中运行反熵任务,主要做的是每隔一段时间(这里是5s)选择集群中的其他节点发送Advertisement消息(含有当前的Device信息)。
  4. 创建端口统计信息的分布式映射
  5. 然后再初始化两个最终一致性映射表
    其实4、5步暂时不太明白,等之后再去着重看看。

createOrUpdateDevice方法

Store的很多使用都是改变设备的存储信息并生成事件。之前有提到过当设备连接时,DeviceManager会调用Store的createOrUpdateDevice方法,这里就以此来进行说明。

  1. 先判断当前onos实例是否是该Device的主控制器。
  2. 然后生成相应的创造Device的时间戳,并生成该Device的<ProviderId, DeviceDescriptions>映射表。
  3. 在该Device的映射表中插入(ProviderId,DeviceDescriptions),并生成相应的DeviceEvent。
  4. 这个DeviceEvent(其分类为DEVICE_DEVICE)先传给其他onos实例(这里仅仅是通知了该事件,而同步信息还是交由反熵任务来完成),再通知代理该事件(之前在DeviceManager中的代理实现接收到消息后,直接交给eventDispatcher)。

Core层总结

再其感叹onos的架构,一开始可能不太理解,但是稍许深入就会觉得整个代码结构特别清晰。Manager算是所有子系统的核心中的核心,负责向App层提供服务和接收Provider层的信息。而Store可以形容为Manager的数据库,所有的数据都交给Store存储来同步。Store不仅要反馈信息给Manager,而且需要和其他onos实例的Store进行交互以完成信息同步。
另外可以看到,任何子系统都不能独立存在,它们都需要一组公共服务的支持。比如设备子系统就需要ClusterService(获取集群中各个实例信息)、ClusterCommunicationService(帮助onos实例间进行沟通)等。