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

2013-06-17 by Daisuke Kotani

前回はmain loopの流れを一通り眺めましたが、長い関数(bridge, ofproto, ofproto-dpif, netdev)は後回しにしていました。今回はbridge_run関数、bridge_run_fast関数、bridge_wait関数を少し詳しく見ていきます。

bridgeとは

Open vSwitch内では、いくつもの仮想スイッチのようなもの(それぞれ別々のポートが割り当てられていて、別々のOpenFlow Controllerとのコネクションを持つことができます)が作られています。それぞれの仮想スイッチをbridgeと呼びます。

      OpenFlow Controller          OpenFlow Controller    
               |                            |             
OVS------------|----------------------------|------------+
 |  +----------------------+   +----------------------+  |
 |  |    Virtual Switch    |   |    Virtual Switch    |  |
 |  |      (bridge 1)      |   |      (bridge 2)      |  |
 |  +----------------------+   +----------------------+  |
 +------|-----|----|----|---------|----|-----|------|----+   
      Port  Port  Port Port      Port  Port  Port  Port

bridge_run

bridge_run関数はvswitchd/bridge.cにあります。ちょっと長いです。

void
bridge_run(void)
{
    static struct ovsrec_open_vswitch null_cfg;
    const struct ovsrec_open_vswitch *cfg;
    struct ovsdb_idl_txn *reconf_txn = NULL;
    struct sset types;
    const char *type;

    bool vlan_splinters_changed;
    struct bridge *br;

    ovsrec_open_vswitch_init(&null_cfg);

    /* (Re)configure if necessary. */
    if (!reconfiguring) {
        ovsdb_idl_run(idl);

        if (ovsdb_idl_is_lock_contended(idl)) {
            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
            struct bridge *br, *next_br;

            VLOG_ERR_RL(&rl, "another ovs-vswitchd process is running, "
                        "disabling this process until it goes away");

            HMAP_FOR_EACH_SAFE (br, next_br, node, &all_bridges) {
                bridge_destroy(br);
            }
            /* Since we will not be running system_stats_run() in this process
             * with the current situation of multiple ovs-vswitchd daemons,
             * disable system stats collection. */
            system_stats_enable(false);
            return;
        } else if (!ovsdb_idl_has_lock(idl)) {
            return;
        }
    }

ここまでは、他のovs-vswitchdが動いていないかチェックしているところ。

    cfg = ovsrec_open_vswitch_first(idl);

    /* Initialize the ofproto library.  This only needs to run once, but
     * it must be done after the configuration is set.  If the
     * initialization has already occurred, bridge_init_ofproto()
     * returns immediately. */
    bridge_init_ofproto(cfg);

ofproto libraryの初期化をしています。bridge_init_ofprotoは前々回読みました。

    /* Let each datapath type do the work that it needs to do. */
    sset_init(&types);
    ofproto_enumerate_types(&types);
    SSET_FOR_EACH (type, &types) {
        ofproto_type_run(type);
    }
    sset_destroy(&types);

datapath typeを抽出し、それぞれのtypeを引数にしてofproto_type_runを呼び出しています。ofproto_type_runはofproto/ofproto-dpif.cに書かれています。

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;
}

引数で指定されたtypeのtype_runの関数を実行しているだけのようです。これより深いところはofprotoやofproto-dpifのところで読みます。 main loopに戻ります。

    /* Let each bridge do the work that it needs to do. */
    HMAP_FOR_EACH (br, node, &all_bridges) {
        ofproto_run(br->ofproto);
    }

ofproto_run関数を実行しているようです。この関数の中はofprotoのところで読みます。

    /* Re-configure SSL.  We do this on every trip through the main loop,
     * instead of just when the database changes, because the contents of the
     * key and certificate files can change without the database changing.
     *
     * We do this before bridge_reconfigure() because that function might
     * initiate SSL connections and thus requires SSL to be configured. */
    if (cfg && cfg->ssl) {
        const struct ovsrec_ssl *ssl = cfg->ssl;

        stream_ssl_set_key_and_cert(ssl->private_key, ssl->certificate);
        stream_ssl_set_ca_cert_file(ssl->ca_cert, ssl->bootstrap_ca_cert);
    }

SSL関連の処理ですが、今回は無視します。

    if (!reconfiguring) {
        /* If VLAN splinters are in use, then we need to reconfigure if VLAN
         * usage has changed. */
        vlan_splinters_changed = false;
        if (vlan_splinters_enabled_anywhere) {
            HMAP_FOR_EACH (br, node, &all_bridges) {
                if (ofproto_has_vlan_usage_changed(br->ofproto)) {
                    vlan_splinters_changed = true;
                    break;
                }
            }
        }

        if (ovsdb_idl_get_seqno(idl) != idl_seqno || vlan_splinters_changed) {
            idl_seqno = ovsdb_idl_get_seqno(idl);
            if (cfg) {
                reconf_txn = ovsdb_idl_txn_create(idl);
                bridge_reconfigure(cfg);
            } else {
                /* We still need to reconfigure to avoid dangling pointers to
                 * now-destroyed ovsrec structures inside bridge data. */
                bridge_reconfigure(&null_cfg);
            }
        }
    }

reconfiguringがfalseのときに、VLAN splinterの再設定をします。

VLAN splinterはVLANをサポートしていない古いLinuxのために用意されているもので、仮想的にtag VLANを実現しています(manの記述。manを読む限りでは、tagを使いたいポートに対して、そのポートに出すVLAN一つ一つに対応する仮想インターフェイスを作っているようです。VLANの設定が変更になったらその仮想ポートを変更しないといけないので、このような処理が入っているのではないかと思います。それに従ってbridgeの設定も変更しなければならない、と。

bridge_reconfigure関数はvswitchd/bridge.cにあります。

static void
bridge_reconfigure(const struct ovsrec_open_vswitch *ovs_cfg)
{
    unsigned long int *splinter_vlans;
    struct bridge *br;

    COVERAGE_INC(bridge_reconfigure);

    ovs_assert(!reconfiguring);
    reconfiguring = true;

    /* Destroy "struct bridge"s, "struct port"s, and "struct iface"s according
     * to 'ovs_cfg' while update the "if_cfg_queue", with only very minimal
     * configuration otherwise.
     *
     * This is mostly an update to bridge data structures. Nothing is pushed
     * down to ofproto or lower layers. */
    add_del_bridges(ovs_cfg);
    splinter_vlans = collect_splinter_vlans(ovs_cfg);
    HMAP_FOR_EACH (br, node, &all_bridges) {
        bridge_add_del_ports(br, splinter_vlans);
    }
    free(splinter_vlans);

add_del_bridges関数は、bridgeのデータを新しいものに更新する関数、collect_splinter_vlansはVLAN splinter機能で使われているVLANを抽出する関数、bridge_add_del_portsはそれぞれのbridgeに所属するポートのデータを更新する関数。

    /* Delete datapaths that are no longer configured, and create ones which
     * don't exist but should. */
    bridge_update_ofprotos();

    /* Make sure each "struct iface" has a correct ofp_port in its ofproto. */
    HMAP_FOR_EACH (br, node, &all_bridges) {
        bridge_refresh_ofp_port(br);
    }

    /* Clear database records for "if_cfg"s which haven't been instantiated. */
    HMAP_FOR_EACH (br, node, &all_bridges) {
        struct if_cfg *if_cfg;

        HMAP_FOR_EACH (if_cfg, hmap_node, &br->if_cfg_todo) {
            iface_clear_db_record(if_cfg->cfg);
        }
    }

    reconfigure_system_stats(ovs_cfg);
}

ここら辺はbridge関連のデータを再設定しているところでしょうか。コメントを読むとそういう気がします。

    if (reconfiguring) {
        if (!reconf_txn) {
            reconf_txn = ovsdb_idl_txn_create(idl);
        }

        if (bridge_reconfigure_continue(cfg ? cfg : &null_cfg)) {
            reconfiguring = false;

            if (cfg) {
                ovsrec_open_vswitch_set_cur_cfg(cfg, cfg->next_cfg);
            }

            /* If we are completing our initial configuration for this run
             * of ovs-vswitchd, then keep the transaction around to monitor
             * it for completion. */
            if (!initial_config_done) {
                initial_config_done = true;
                daemonize_txn = reconf_txn;
                reconf_txn = NULL;
            }
        }
    }

    if (reconf_txn) {
        ovsdb_idl_txn_commit(reconf_txn);
        ovsdb_idl_txn_destroy(reconf_txn);
        reconf_txn = NULL;
    }

    if (daemonize_txn) {
        enum ovsdb_idl_txn_status status = ovsdb_idl_txn_commit(daemonize_txn);
        if (status != TXN_INCOMPLETE) {
            ovsdb_idl_txn_destroy(daemonize_txn);
            daemonize_txn = NULL;

            /* ovs-vswitchd has completed initialization, so allow the
             * process that forked us to exit successfully. */
            daemonize_complete();

            VLOG_INFO_ONCE("%s (Open vSwitch) %s", program_name, VERSION);
        }
    }

ここまではOpen vSwitchのデータベースを更新している部分(たぶん)。 あまり興味がないので適当です(^^;

    /* Refresh interface and mirror stats if necessary. */
    if (time_msec() >= iface_stats_timer) {
        if (cfg) {
            struct ovsdb_idl_txn *txn;

            txn = ovsdb_idl_txn_create(idl);
            HMAP_FOR_EACH (br, node, &all_bridges) {
                struct port *port;
                struct mirror *m;

                HMAP_FOR_EACH (port, hmap_node, &br->ports) {
                    struct iface *iface;

                    LIST_FOR_EACH (iface, port_elem, &port->ifaces) {
                        iface_refresh_stats(iface);
                        iface_refresh_status(iface);
                    }
                }

                HMAP_FOR_EACH (m, hmap_node, &br->mirrors) {
                    mirror_refresh_stats(m);
                }

            }
            refresh_controller_status();
            ovsdb_idl_txn_commit(txn);
            ovsdb_idl_txn_destroy(txn); /* XXX */
        }

        iface_stats_timer = time_msec() + IFACE_STATS_INTERVAL;
    }

これは interface status を更新している部分。iface_refresh_stats, iface_refresh_status, mirror_refresh_stats が値を更新している関数のようです。どれもvswitchd/bridge.cの中にあります。

    run_system_stats();
    instant_stats_run();
}

run_system_stats はシステムの状態を収集してデータベースをアップデートする関数です。内部ではsystem_stats_run を経由してLoad Averageやメモリ使用量などを収集しています。

instant_stats_run は、ソースコードのコメントによると、「状態が変更されたらすぐにデータベースに反映しなければならないものを更新するもの」だそうです。実際に instant_stats_run 関数の中を読んでみると、STP, LACP, インターフェイスのup/downなどが更新されています。vswitchd/bridge.c にあります。

bridge_run_fast

bridge_run_fast関数はvswitchd/bridge.cにあります。

/* Performs periodic activity required by bridges 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 ofprotos
 * that use the ofproto-dpif implementation. */
void
bridge_run_fast(void)
{
    struct sset types;
    const char *type;
    struct bridge *br;

    sset_init(&types);
    ofproto_enumerate_types(&types);
    SSET_FOR_EACH (type, &types) {
        ofproto_type_run_fast(type);
    }
    sset_destroy(&types);

    HMAP_FOR_EACH (br, node, &all_bridges) {
        ofproto_run_fast(br->ofproto);
    }
}

ofproto_enumerate_typesで何かの種類を列挙して、それぞれに対してofproto_type_run_fast関数を呼び出し、最後にそれぞれのbridgeのofprotoに対してofproto_run_fastを実行しているようです。次回以降、それぞれの関数をもう少し詳しく見てみます。

bridge_wait

bridge_wait 関数は vswitchd/bridge.c にあります。

void
bridge_wait(void)
{
    struct sset types;
    const char *type;

    ovsdb_idl_wait(idl);
    if (daemonize_txn) {
        ovsdb_idl_txn_wait(daemonize_txn);
    }

    if (reconfiguring) {
        poll_immediate_wake();
    }

    sset_init(&types);
    ofproto_enumerate_types(&types);
    SSET_FOR_EACH (type, &types) {
        ofproto_type_wait(type);
    }
    sset_destroy(&types);

    if (!hmap_is_empty(&all_bridges)) {
        struct bridge *br;

        HMAP_FOR_EACH (br, node, &all_bridges) {
            ofproto_wait(br->ofproto);
        }
        poll_timer_wait_until(iface_stats_timer);
    }

    system_stats_wait();
    instant_stats_wait();
}

何か処理することがあればpollのtimeoutを0にして次のループで処理されるように、そうでなければ待ち、という処理をしているようです。ofproto_type_waitやofproto_waitはofprotoの時に読みます。



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