#! /bin/sh
# Copyright (C) 2009, 2010, 2011, 2012 Nicira, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

case $0 in
    */*) dir0=`echo "$0" | sed 's,/[^/]*$,,'` ;;
    *) dir0=./ ;;
esac
. "$dir0/ovs-lib" || exit 1

for dir in "$sbindir" "$bindir" /sbin /bin /usr/sbin /usr/bin; do
    case :$PATH: in
        *:$dir:*) ;;
        *) PATH=$PATH:$dir ;;
    esac
done

## ----- ##
## start ##
## ----- ##

insert_openvswitch_mod_if_required () {
    # If openvswitch is already loaded then we're done.
    test -e /sys/module/openvswitch -o -e /sys/module/openvswitch_mod && \
     return 0

    # Load openvswitch.  If that's successful then we're done.
    action "Inserting openvswitch module" modprobe openvswitch && return 0

    # If the bridge module is loaded, then that might be blocking
    # openvswitch.  Try to unload it, if there are no bridges.
    test -e /sys/module/bridge || return 1
    bridges=`echo /sys/class/net/*/bridge | sed 's,/sys/class/net/,,g;s,/bridge,,g'`
    if test "$bridges" != "*"; then
        log_warning_msg "not removing bridge module because bridges exist ($bridges)"
        return 1
    fi
    action "removing bridge module" rmmod bridge || return 1

    # Try loading openvswitch again.
    action "Inserting openvswitch module" modprobe openvswitch
}

insert_brcompat_mod_if_required () {
    if test -e /sys/module/bridge; then
        log_warning_msg "bridge module is loaded, not loading brcompat"
        return 1
    fi
    test -e /sys/module/brcompat -o -e /sys/module/brcompat_mod && return 0
    action "Inserting brcompat module" modprobe brcompat
}

insert_mod_if_required () {
    insert_openvswitch_mod_if_required || return 1
    if test X"$BRCOMPAT" = Xyes; then
        if insert_brcompat_mod_if_required; then
            :
        else
            log_warning_msg "could not load brcompat module, disabling bridge compatibility"
            BRCOMPAT=no
        fi
    fi
}

ovs_vsctl () {
    ovs-vsctl --no-wait --timeout=5 "$@"
}

ovsdb_tool () {
    ovsdb-tool -vANY:console:off "$@"
}

create_db () {
    action "Creating empty database $DB_FILE" ovsdb_tool create "$DB_FILE" "$DB_SCHEMA"
}

upgrade_db () {
    schemaver=`ovsdb_tool schema-version "$DB_SCHEMA"`
    if test ! -e "$DB_FILE"; then
        log_warning_msg "$DB_FILE does not exist"
        install -d -m 755 -o root -g root `dirname $DB_FILE`
        create_db
    elif test X"`ovsdb_tool needs-conversion "$DB_FILE" "$DB_SCHEMA"`" != Xno; then
        # Back up the old version.
        version=`ovsdb_tool db-version "$DB_FILE"`
        cksum=`ovsdb_tool db-cksum "$DB_FILE" | awk '{print $1}'`
        backup=$DB_FILE.backup$version-$cksum
        action "Backing up database to $backup" cp "$DB_FILE" "$backup" || return 1

        # Compact database.  This is important if the old schema did not enable
        # garbage collection (i.e. if it did not have any tables with "isRoot":
        # true) but the new schema does.  In that situation the old database
        # may contain a transaction that creates a record followed by a
        # transaction that creates the first use of the record.  Replaying that
        # series of transactions against the new database schema (as "convert"
        # does) would cause the record to be dropped by the first transaction,
        # then the second transaction would cause a referential integrity
        # failure (for a strong reference).
        #
        # Errors might occur on an Open vSwitch downgrade if ovsdb-tool doesn't
        # understand some feature of the schema used in the OVSDB version that
        # we're downgrading from, so we don't give up on error.
        action "Compacting database" ovsdb_tool compact "$DB_FILE"

        # Upgrade or downgrade schema.
        if action "Converting database schema" ovsdb_tool convert "$DB_FILE" "$DB_SCHEMA"; then
            :
        else
            log_warning_msg "Schema conversion failed, using empty database instead"
            rm -f "$DB_FILE"
            create_db
        fi
    fi
}

set_system_ids () {
    set ovs_vsctl set Open_vSwitch .

    OVS_VERSION=`ovs-vswitchd --version | sed 's/.*) //;1q'`
    set "$@" ovs-version="$OVS_VERSION"

    case $SYSTEM_ID in
        random)
            id_file=$etcdir/system-id.conf
            uuid_file=$etcdir/install_uuid.conf
            if test -e "$id_file"; then
                SYSTEM_ID=`cat "$id_file"`
            elif test -e "$uuid_file"; then
                # Migrate from old file name.
                . "$uuid_file"
                SYSTEM_ID=$INSTALLATION_UUID
                echo "$SYSTEM_ID" > "$id_file"
            elif SYSTEM_ID=`uuidgen`; then
                echo "$SYSTEM_ID" > "$id_file"
            else
                log_failure_msg "missing uuidgen, could not generate system ID"
            fi
            ;;

        '')
            log_failure_msg "system ID not configured, please use --system-id"
            ;;

        *)
            ;;
    esac
    set "$@" external-ids:system-id="\"$SYSTEM_ID\""

    if test X"$SYSTEM_TYPE" != X; then
        set "$@" system-type="\"$SYSTEM_TYPE\""
    else
        log_failure_msg "no default system type, please use --system-type"
    fi

    if test X"$SYSTEM_VERSION" != X; then
        set "$@" system-version="\"$SYSTEM_VERSION\""
    else
        log_failure_msg "no default system version, please use --system-version"
    fi

    action "Configuring Open vSwitch system IDs" "$@" $extra_ids
}

start () {
    if test X"$FORCE_COREFILES" = Xyes; then
        ulimit -Sc 67108864
    fi

    insert_mod_if_required || return 1

    if daemon_is_running ovsdb-server; then
	log_success_msg "ovsdb-server is already running"
    else
	# Create initial database or upgrade database schema.
	upgrade_db || return 1

	# Start ovsdb-server.
	set ovsdb-server "$DB_FILE"
	set "$@" -vANY:CONSOLE:EMER -vANY:SYSLOG:ERR -vANY:FILE:INFO
	set "$@" --remote=punix:"$DB_SOCK"
	set "$@" --remote=db:Open_vSwitch,manager_options
	set "$@" --private-key=db:SSL,private_key
	set "$@" --certificate=db:SSL,certificate
	set "$@" --bootstrap-ca-cert=db:SSL,ca_cert
	start_daemon "$OVSDB_SERVER_PRIORITY" "$@" || return 1

	# Initialize database settings.
	ovs_vsctl -- init -- set Open_vSwitch . db-version="$schemaver" \
	    || return 1
	set_system_ids || return 1
	if test X"$DELETE_BRIDGES" = Xyes; then
            for bridge in `ovs_vsctl list-br`; do
		ovs_vsctl del-br $bridge
            done
	fi
    fi

    if daemon_is_running ovs-vswitchd; then
	log_success_msg "ovs-vswitchd is already running"
    else
	# Increase the limit on the number of open file descriptors.
	# ovs-vswitchd needs 16 per datapath, plus a few extra, so this
	# should allow for 256 (or more) bridges.
	ulimit -n 5000

	# Start ovs-vswitchd.
	set ovs-vswitchd unix:"$DB_SOCK"
	set "$@" -vANY:CONSOLE:EMER -vANY:SYSLOG:ERR -vANY:FILE:INFO
	if test X"$MLOCKALL" != Xno; then
	    set "$@" --mlockall
	fi
	start_daemon "$OVS_VSWITCHD_PRIORITY" "$@"
    fi

    if daemon_is_running ovs-brcompatd; then
	log_success_msg "ovs-brcompatd is already running"
    elif test X"$BRCOMPAT" = Xyes; then
        set ovs-brcompatd
	set "$@" -vANY:CONSOLE:EMER -vANY:SYSLOG:ERR -vANY:FILE:INFO
	start_daemon "$OVS_BRCOMPATD_PRIORITY" "$@"
    fi
}

## ---- ##
## stop ##
## ---- ##

stop () {
    stop_daemon ovs-brcompatd
    stop_daemon ovs-vswitchd
    stop_daemon ovsdb-server
}

## ----------------- ##
## force-reload-kmod ##
## ----------------- ##

internal_interfaces () {
    # Outputs a list of internal interfaces:
    #
    #   - There is an internal interface for every bridge, whether it
    #     has an Interface record or not and whether the Interface
    #     record's 'type' is properly set or not.
    #
    #   - There is an internal interface for each Interface record whose
    #     'type' is 'internal'.
    #
    # But ignore interfaces that don't really exist.
    for d in `(ovs_vsctl --bare \
                -- --columns=name find Interface type=internal \
		-- list-br) | sort -u`
    do
        if test -e "/sys/class/net/$d"; then
	    printf "%s " "$d"
	fi
    done
}

save_interfaces () {
    "$datadir/scripts/ovs-save" $ifaces > "$script"
}

force_reload_kmod () {
    ifaces=`internal_interfaces`
    action "Detected internal interfaces: $ifaces" true

    stop

    script=`mktemp`
    trap 'rm -f "$script"' 0 1 2 13 15
    if action "Saving interface configuration" save_interfaces; then
        :
    else
        log_warning_msg "Failed to save configuration, not replacing kernel module"
        start
        exit 1
    fi
    chmod +x "$script"

    for dp in `ovs-dpctl dump-dps`; do
        action "Removing datapath: $dp" ovs-dpctl del-dp "$dp"
    done

    # try both old and new names in case this is post upgrade
    if test -e /sys/module/brcompat_mod; then
        action "Removing brcompat module" rmmod brcompat_mod
    elif test -e /sys/module/brcompat; then
        action "Removing brcompat module" rmmod brcompat
    fi
    if test -e /sys/module/openvswitch_mod; then
        action "Removing openvswitch module" rmmod openvswitch_mod
    elif test -e /sys/module/openvswitch; then
        action "Removing openvswitch module" rmmod openvswitch
    fi

    start

    action "Restoring interface configuration" "$script"
    rc=$?
    if test $rc = 0; then
        level=debug
    else
        level=err
    fi
    log="logger -p daemon.$level -t ovs-save"
    $log "force-reload-kmod interface restore script exited with status $rc:"
    $log -f "$script"

    "$datadir/scripts/ovs-check-dead-ifs"
}

## --------------- ##
## enable-protocol ##
## --------------- ##

enable_protocol () {
    # Translate the protocol name to a number, because "iptables -n -L" prints
    # some protocols by name (despite the -n) and therefore we need to look for
    # both forms.
    #
    # (iptables -S output is more uniform but old iptables doesn't have it.)
    protonum=`grep "^$PROTOCOL[ 	]" /etc/protocols | awk '{print $2}'`
    if expr X"$protonum" : X'[0-9]\{1,\}$' > /dev/null; then :; else
        log_failure_msg "unknown protocol $PROTOCOL"
        return 1
    fi

    name=$PROTOCOL
    match="(\$2 == \"$PROTOCOL\" || \$2 == $protonum)"
    insert="iptables -I INPUT -p $PROTOCOL"
    if test X"$DPORT" != X; then
        name="$name to port $DPORT"
        match="$match && /dpt:$DPORT/"
        insert="$insert --dport $DPORT"
    fi
    if test X"$SPORT" != X; then
        name="$name from port $SPORT"
        match="$match && /spt:$SPORT/"
        insert="$insert --sport $SPORT"
    fi
    insert="$insert -j ACCEPT"

    if (iptables -n -L INPUT) >/dev/null 2>&1; then
        if iptables -n -L INPUT | awk "$match { n++ } END { exit n == 0 }"
        then
            # There's already a rule for this protocol.  Don't override it.
            log_success_msg "iptables already has a rule for $name, not explicitly enabling"
        else
            action "Enabling $name with iptables" $insert
        fi
    elif (iptables --version) >/dev/null 2>&1; then
        action "cannot list iptables rules, not adding a rule for $name"
    else
        action "iptables binary not installed, not adding a rule for $name"
    fi
}

## ---- ##
## main ##
## ---- ##

set_defaults () {
    SYSTEM_ID=

    DELETE_BRIDGES=no
    BRCOMPAT=no

    DAEMON_CWD=/
    FORCE_COREFILES=yes
    MLOCKALL=yes
    OVSDB_SERVER_PRIORITY=-10
    OVS_VSWITCHD_PRIORITY=-10
    OVS_BRCOMPATD_PRIORITY=-10

    DB_FILE=$etcdir/conf.db
    DB_SOCK=$rundir/db.sock
    DB_SCHEMA=$datadir/vswitch.ovsschema

    PROTOCOL=gre
    DPORT=
    SPORT=

    type_file=$etcdir/system-type.conf
    version_file=$etcdir/system-version.conf

    if test -e "$type_file" ; then
        SYSTEM_TYPE=`cat $type_file`
        SYSTEM_VERSION=`cat $version_file`
    elif (lsb_release --id) >/dev/null 2>&1; then
        SYSTEM_TYPE=`lsb_release --id -s`
        system_release=`lsb_release --release -s`
        system_codename=`lsb_release --codename -s`
        SYSTEM_VERSION="${system_release}-${system_codename}"
    else
        SYSTEM_TYPE=unknown
        SYSTEM_VERSION=unknown
    fi
}

usage () {
    set_defaults
    cat <<EOF
$0: controls Open vSwitch daemons
usage: $0 [OPTIONS] COMMAND

This program is intended to be invoked internally by Open vSwitch startup
scripts.  System administrators should not normally invoke it directly.

Commands:
  start              start Open vSwitch daemons
  stop               stop Open vSwitch daemons
  status             check whether Open vSwitch daemons are running
  version            print versions of Open vSwitch daemons
  load-kmod          insert modules if not already present
  force-reload-kmod  save OVS network device state, stop OVS, unload kernel
                     module, reload kernel module, start OVS, restore state
  enable-protocol    enable protocol specified in options with iptables
  help               display this help message

One of the following options is required for "start" and "force-reload-kmod":
  --system-id=UUID   set specific ID to uniquely identify this system
  --system-id=random  use a random but persistent UUID to identify this system

Other important options for "start" and "force-reload-kmod":
  --system-type=TYPE  set system type (e.g. "XenServer")
  --system-version=VERSION  set system version (e.g. "5.6.100-39265p")
  --external-id="key=value"
                     add given key-value pair to Open_vSwitch external-ids
  --delete-bridges   delete all bridges just before starting ovs-vswitchd

Less important options for "start" and "force-reload-kmod":
  --daemon-cwd=DIR               set working dir for OVS daemons (default: $DAEMON_CWD)
  --no-force-corefiles           do not force on core dumps for OVS daemons
  --no-mlockall                  do not lock all of ovs-vswitchd into memory
  --ovsdb-server-priority=NICE   set ovsdb-server's niceness (default: $OVSDB_SERVER_PRIORITY)
  --ovs-vswitchd-priority=NICE   set ovs-vswitchd's niceness (default: $OVS_VSWITCHD_PRIORITY)
  --ovs-brcompatd-priority=NICE  set ovs-brcompatd's niceness (default: $OVS_BRCOMPATD_PRIORITY)

Options for "start", "force-reload-kmod", "load-kmod", "status", and "version":
  --brcompat         enable Linux bridge compatibility module and daemon

File location options:
  --db-file=FILE     database file name (default: $DB_FILE)
  --db-sock=SOCKET   JSON-RPC socket name (default: $DB_SOCK)
  --db-schema=FILE   database schema file name (default: $DB_SCHEMA)

Options for "enable-protocol":
  --protocol=PROTOCOL  protocol to enable with iptables (default: gre)
  --sport=PORT       source port to match (for tcp or udp protocol)
  --dport=PORT       ddestination port to match (for tcp or udp protocol)

Other options:
  -h, --help                  display this help message
  -V, --version               display version information

Default directories with "configure" option and environment variable override:
  logs: /usr/local/var/log/openvswitch (--log-dir, OVS_LOGDIR)
  pidfiles and sockets: /usr/local/var/run/openvswitch (--run-dir, OVS_RUNDIR)
  system configuration: /usr/local/etc (--sysconfdir, OVS_SYSCONFDIR)
  data files: /usr/local/share/openvswitch (--pkgdatadir, OVS_PKGDATADIR)
  user binaries: /usr/local/bin (--bindir, OVS_BINDIR)
  system binaries: /usr/local/sbin (--sbindir, OVS_SBINDIR)

Please report bugs to bugs@openvswitch.org (see REPORTING-BUGS for details).
EOF

    exit 0
}

set_option () {
    var=`echo "$option" | tr abcdefghijklmnopqrstuvwxyz- ABCDEFGHIJKLMNOPQRSTUVWXYZ_`
    eval set=\${$var+yes}
    eval old_value=\$$var
    if test X$set = X || \
        (test $type = bool && \
        test X"$old_value" != Xno && test X"$old_value" != Xyes); then
        echo >&2 "$0: unknown option \"$arg\" (use --help for help)"
        return
    fi
    eval $var=\$value
}

daemons () {
    echo ovsdb-server ovs-vswitchd
    if test X"$BRCOMPAT" = Xyes; then
        echo ovs-brcompatd
    fi
}

set_defaults
extra_ids=
command=
for arg
do
    case $arg in
        -h | --help)
            usage
            ;;
        -V | --version)
            echo "$0 (Open vSwitch) $VERSION"
            exit 0
            ;;
        --external-id=*)
            value=`expr X"$arg" : 'X[^=]*=\(.*\)'`
            case $value in
                *=*)
                    extra_ids="$extra_ids external-ids:$value"
                    ;;
                *)
                    echo >&2 "$0: --external-id argument not in the form \"key=value\""
                    exit 1
                    ;;
            esac
            ;;
        --[a-z]*=*)
            option=`expr X"$arg" : 'X--\([^=]*\)'`
            value=`expr X"$arg" : 'X[^=]*=\(.*\)'`
            type=string
            set_option
            ;;
        --no-[a-z]*)
            option=`expr X"$arg" : 'X--no-\(.*\)'`
            value=no
            type=bool
            set_option
            ;;
        --[a-z]*)
            option=`expr X"$arg" : 'X--\(.*\)'`
            value=yes
            type=bool
            set_option
            ;;
        -*)
            echo >&2 "$0: unknown option \"$arg\" (use --help for help)"
            exit 1
            ;;
        *)
            if test X"$command" = X; then
                command=$arg
            else
                echo >&2 "$0: exactly one non-option argument required (use --help for help)"
                exit 1
            fi
            ;;
    esac
done
case $command in
    start)
        start
        ;;
    stop)
        stop
        ;;
    status)
        rc=0
        for daemon in `daemons`; do
            daemon_status $daemon || rc=$?
        done
        exit $rc
        ;;
    version)
        for daemon in `daemons`; do
            $daemon --version
        done
        ;;
    force-reload-kmod)
	force_reload_kmod
        ;;
    load-kmod)
        insert_mod_if_required
        ;;
    enable-protocol)
        enable_protocol
        ;;
    help)
        usage
        ;;
    '')
        echo >&2 "$0: missing command name (use --help for help)"
        exit 1
        ;;
    *)
        echo >&2 "$0: unknown command \"$command\" (use --help for help)"
        exit 1
        ;;
esac

