Open vSwitchのソースコードを読む(7) ovs-vswitchd の ofproto

2013-06-18 by Daisuke Kotani

前回はmain loopの中のbridgeに関する処理を見ました。今回はofprotoに関する関数、ofproto_run, ofproto_type_run_fast, ofproto_run_fast, ofproto_enumerate_types, ofproto_type_wait, ofproto_wait などを読んでいきます。

ofproto とは

第1回で、Open vSwitchのアーキテクチャを引用しました。再掲します。

            _
           |   +-------------------+
           |   |    ovs-vswitchd   |<-->ovsdb-server
           |   +-------------------+
           |   |      ofproto      |<-->OpenFlow controllers
           |   +--------+-+--------+  _
           |   | netdev | |ofproto-|   |
 userspace |   +--------+ |  dpif  |   |
           |   | netdev | +--------+   |
           |   |provider| |  dpif  |   |
           |   +---||---+ +--------+   |
           |       ||     |  dpif  |   | implementation of
           |       ||     |provider|   | ofproto provider
           |_      ||     +---||---+   |
                   ||         ||       |
            _  +---||-----+---||---+   |
           |   |          |datapath|   |
    kernel |   |          +--------+  _|
           |   |                   |
           |_  +--------||---------+
                        ||
                     physical
                       NIC

ofproto は、いろんなインターフェイスを抽象化して、OpenFlowコントローラやovs-vswitchdに対するインターフェイスを提供している層です。

下位の層(netdevやofproto-dpif)は、ofproto_class 構造体(ofproto/ofproto-provider.h)に、下位層の初期化や1ループで行う処理など下位層で処理を行う関数へのポインタをセットして、ofprotoにある配列ofproto_classesに登録(ofproto_class_register関数)します。例えば、ofproto-dpif に関する ofproto_class 構造体はofproto_dpif_class(ofproto/ofproto-dpif.c)に定義されていて、これをofproto_init関数(ofproto/ofproto.c)内でofproto_classesに登録しています。

ofproto の生成

ofprotoがOpenFlowスイッチとして振る舞うのに必要なデータは、主にofproto構造体で定義されています。これはofproto/ofproto-provider.hにあります。

/* An OpenFlow switch.
 *
 * With few exceptions, ofproto implementations may look at these fields but
 * should not modify them. */
struct ofproto {
    struct hmap_node hmap_node; /* In global 'all_ofprotos' hmap. */
    const struct ofproto_class *ofproto_class;
    char *type;                 /* Datapath type. */
    char *name;                 /* Datapath name. */

    /* Settings. */
    uint64_t fallback_dpid;     /* Datapath ID if no better choice found. */
    uint64_t datapath_id;       /* Datapath ID. */
    unsigned flow_eviction_threshold; /* Threshold at which to begin flow
                                       * table eviction. Only affects the
                                       * ofproto-dpif implementation */
    bool forward_bpdu;          /* Option to allow forwarding of BPDU frames
                                 * when NORMAL action is invoked. */
    char *mfr_desc;             /* Manufacturer (NULL for default)b. */
    char *hw_desc;              /* Hardware (NULL for default). */
    char *sw_desc;              /* Software version (NULL for default). */
    char *serial_desc;          /* Serial number (NULL for default). */
    char *dp_desc;              /* Datapath description (NULL for default). */
    enum ofp_config_flags frag_handling; /* One of OFPC_*.  */

    /* Datapath. */
    struct hmap ports;          /* Contains "struct ofport"s. */
    struct shash port_by_name;
    unsigned long *ofp_port_ids;/* Bitmap of used OpenFlow port numbers. */
    struct simap ofp_requests;  /* OpenFlow port number requests. */
    uint16_t alloc_port_no;     /* Last allocated OpenFlow port number. */
    uint16_t max_ports;         /* Max possible OpenFlow port num, plus one. */

    /* Flow tables. */
    struct oftable *tables;
    int n_tables;

    /* Optimisation for flow expiry.
     * These flows should all be present in tables. */
    struct list expirable;      /* Expirable 'struct rule"s in all tables. */

    /* OpenFlow connections. */
    struct connmgr *connmgr;

    /* Flow table operation tracking. */
    int state;                  /* Internal state. */
    struct list pending;        /* List of "struct ofopgroup"s. */
    unsigned int n_pending;     /* list_size(&pending). */
    struct hmap deletions;      /* All OFOPERATION_DELETE "ofoperation"s. */

    /* Flow table operation logging. */
    int n_add, n_delete, n_modify; /* Number of unreported ops of each kind. */
    long long int first_op, last_op; /* Range of times for unreported ops. */
    long long int next_op_report;    /* Time to report ops, or LLONG_MAX. */
    long long int op_backoff;        /* Earliest time to report ops again. */

    /* Linux VLAN device support (e.g. "eth0.10" for VLAN 10.)
     *
     * This is deprecated.  It is only for compatibility with broken device
     * drivers in old versions of Linux that do not properly support VLANs when
     * VLAN devices are not used.  When broken device drivers are no longer in
     * widespread use, we will delete these interfaces. */
    unsigned long int *vlan_bitmap; /* 4096-bit bitmap of in-use VLANs. */
    bool vlans_changed;             /* True if new VLANs are in use. */
    int min_mtu;                    /* Current MTU of non-internal ports. */
};

datapath id や description, table, controller への connection (connmgr)などのデータを入れることができます。1つのOpenFlowスイッチで扱えるdatapath の type は1つです。

ofprotoの生成は、bridge_update_ofprotos関数(vswitchd/bridge.c)の中で、ofproto_create関数(ofproto/ofproto.c)を呼び出すことで行われています。ofproto_create関数ではofproto構造体やそれに付随するもの(Tableなど)の初期化が行われています。

ofproto_run

ofproto_run 関数はmain loopの中のofprotoに関する処理のメインになるところです。ofproto/ofproto.cにあります。

int
ofproto_run(struct ofproto *p)
{
    struct sset changed_netdevs;
    const char *changed_netdev;
    struct ofport *ofport;
    int error;

    error = p->ofproto_class->run(p);

登録されたofproto_class(ofproto-dpifなど)の処理を実行しています。

    if (error && error != EAGAIN) {
        VLOG_ERR_RL(&rl, "%s: run failed (%s)", p->name, strerror(error));
    }

    if (p->ofproto_class->port_poll) {
        char *devname;

        while ((error = p->ofproto_class->port_poll(p, &devname)) != EAGAIN) {
            process_port_change(p, error, devname);
        }
    }

    /* Update OpenFlow port status for any port whose netdev has changed.
     *
     * Refreshing a given 'ofport' can cause an arbitrary ofport to be
     * destroyed, so it's not safe to update ports directly from the
     * HMAP_FOR_EACH loop, or even to use HMAP_FOR_EACH_SAFE.  Instead, we
     * need this two-phase approach. */
    sset_init(&changed_netdevs);
    HMAP_FOR_EACH (ofport, hmap_node, &p->ports) {
        unsigned int change_seq = netdev_change_seq(ofport->netdev);
        if (ofport->change_seq != change_seq) {
            ofport->change_seq = change_seq;
            sset_add(&changed_netdevs, netdev_get_name(ofport->netdev));
        }
    }
    SSET_FOR_EACH (changed_netdev, &changed_netdevs) {
        update_port(p, changed_netdev);
    }
    sset_destroy(&changed_netdevs);

ここら辺は、port や port status に変更があったときのための処理です。たぶん。

    switch (p->state) {
    case S_OPENFLOW:
        connmgr_run(p->connmgr, handle_openflow);
        break;

    case S_EVICT:
        connmgr_run(p->connmgr, NULL);
        ofproto_evict(p);
        if (list_is_empty(&p->pending) && hmap_is_empty(&p->deletions)) {
            p->state = S_OPENFLOW;
        }
        break;

    case S_FLUSH:
        connmgr_run(p->connmgr, NULL);
        ofproto_flush__(p);
        if (list_is_empty(&p->pending) && hmap_is_empty(&p->deletions)) {
            connmgr_flushed(p->connmgr);
            p->state = S_OPENFLOW;
        }
        break;

    default:
        NOT_REACHED();
    }

ここは、OpenFlow スイッチの状態によって処理を分けるところです。stateの値の意味はenum ofproto_stateに書かれています。

enum ofproto_state {
    S_OPENFLOW,                 /* Processing OpenFlow commands. */
    S_EVICT,                    /* Evicting flows from over-limit tables. */
    S_FLUSH,                    /* Deleting all flow table rules. */
};

S_OPENFLOWは通常のOpenFlowの処理を行うモード、S_EVICTはフローテーブルを小さくする処理も実行するモード、S_FLUSHはフローテーブルのエントリを全て削除する処理も行うモード、だそうです。 フロー管理は次回以降で詳しく読むことにします。

    if (time_msec() >= p->next_op_report) {
        long long int ago = (time_msec() - p->first_op) / 1000;
        long long int interval = (p->last_op - p->first_op) / 1000;
        struct ds s;

        ds_init(&s);
        ds_put_format(&s, "%d flow_mods ",
                      p->n_add + p->n_delete + p->n_modify);
        if (interval == ago) {
            ds_put_format(&s, "in the last %lld s", ago);
        } else if (interval) {
            ds_put_format(&s, "in the %lld s starting %lld s ago",
                          interval, ago);
        } else {
            ds_put_format(&s, "%lld s ago", ago);
        }

        ds_put_cstr(&s, " (");
        if (p->n_add) {
            ds_put_format(&s, "%d adds, ", p->n_add);
        }
        if (p->n_delete) {
            ds_put_format(&s, "%d deletes, ", p->n_delete);
        }
        if (p->n_modify) {
            ds_put_format(&s, "%d modifications, ", p->n_modify);
        }
        s.length -= 2;
        ds_put_char(&s, ')');

        VLOG_INFO("%s: %s", p->name, ds_cstr(&s));
        ds_destroy(&s);

        p->n_add = p->n_delete = p->n_modify = 0;
        p->next_op_report = LLONG_MAX;
    }

ここら辺は、定期的にOpenFlowスイッチに関する統計情報(フローエントリの追加、削除、編集の回数)をログに吐くところだと思います。

    return error;
}

終わり。

ofproto_run_fast

ofproto_run_fast は、ofprotoに関する処理のうち、ofproto_runより短い間隔で実行したいものです。1ループ当たり4回実行されます。

/* Performs periodic activity required by 'ofproto' that needs to be done
 * with the least possible latency.
 *
 * It makes sense to call this function a couple of times per poll loop, to
 * provide a significant performance boost on some benchmarks with the
 * ofproto-dpif implementation. */
int
ofproto_run_fast(struct ofproto *p)
{
    int error;

    error = p->ofproto_class->run_fast ? p->ofproto_class->run_fast(p) : 0;
    if (error && error != EAGAIN) {
        VLOG_ERR_RL(&rl, "%s: fastpath run failed (%s)",
                    p->name, strerror(error));
    }
    return error;
}

下位層のrun_fastを実行しているだけでした。

ofproto_enumerate_types

登録されている下位層の種類をリストアップする関数です。 内部では、下位層の対応する関数を呼び出しています(関数は関数ポインタとしてofproto_class構造体で指定されています)。

/* Clears 'types' and enumerates all registered ofproto types into it.  The
 * caller must first initialize the sset. */
void
ofproto_enumerate_types(struct sset *types)
{
    size_t i;

    for (i = 0; i < n_ofproto_classes; i++) {
        ofproto_classes[i]->enumerate_types(types);
    }
}

ofproto_type_run, ofproto_type_run_fast

ofproto_type_run, ofproto_type_run_fast は、それぞれ、下位層(datapath)のtype_run, type_run_fast の処理を行うものです。

ofproto_class 構造体のコメントによると、 type_run, type_run_fast は下位層に特有の処理を行うもの、run, run_fast は OpenFlow スイッチとして動くのに必要な処理を行うためのもの、という役割分担があるそうです。ofproto_run, ofproto_run_fast 関数の引数はofproto構造体のポインタだったのに対し、ofproto_type_run, ofproto_type_run_fast 関数の引数は下位層(datapath) の種類の名前になっているという違いもそのような理由だからなのかなぁ、と思ったりしています。

int
ofproto_type_run(const char *datapath_type)
{
    const struct ofproto_class *class;
    int error;

    datapath_type = ofproto_normalize_type(datapath_type);
    class = ofproto_class_find__(datapath_type);

    error = class->type_run ? class->type_run(datapath_type) : 0;
    if (error && error != EAGAIN) {
        VLOG_ERR_RL(&rl, "%s: type_run failed (%s)",
                    datapath_type, strerror(error));
    }
    return error;
}

int
ofproto_type_run_fast(const char *datapath_type)
{
    const struct ofproto_class *class;
    int error;

    datapath_type = ofproto_normalize_type(datapath_type);
    class = ofproto_class_find__(datapath_type);

    error = class->type_run_fast ? class->type_run_fast(datapath_type) : 0;
    if (error && error != EAGAIN) {
        VLOG_ERR_RL(&rl, "%s: type_run_fast failed (%s)",
                    datapath_type, strerror(error));
    }
    return error;
}

ofproto_wait, ofproto_type_wait

ofproto_wait, ofproto_type_wait 関数のどちらも、ofproto_run, ofproto_type_run を実行する必要があるときにpoll loopから抜けられるようにする関数です。

今回はあまり関係がない(&疲れてきた^^;)ので省略。



このエントリーをはてなブックマークに追加