#! /bin/sh
#
# Copyright (c) 2013, 2015, 2016 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.

set -e

run() {
    echo "$@"
    (cd "$sandbox" && "$@") || exit 1
}

run_xterm() {
    title=$1;
    shift
    run xterm -T "$title" -e "$@"  &
}

rungdb() {
    under_gdb=$1
    gdb_run=$2
    shift
    shift

    # Remove the --detach and to put the process under gdb control.
    # Also remove --vconsole:off to allow error message to show up
    # on the console.
    # Use "DISPLAY" variable to determine out if X is supported
    if $under_gdb && [ "$DISPLAY" ]; then
        args=`echo $@ |sed s/--detach//g | sed s/--vconsole:off//g`
        xterm_title=$1

        gdb_cmd=""
        if $gdb_run; then
            gdb_cmd="-ex run"
        fi

        run_xterm $xterm_title gdb $gdb_cmd --args $args
    else
        run $@
    fi
}

gdb_vswitchd=false
gdb_ovsdb=false
gdb_vswitchd_ex=false
gdb_ovsdb_ex=false
gdb_ovn_northd=false
gdb_ovn_northd_ex=false
gdb_ovn_ic=false
gdb_ovn_ic_ex=false
gdb_ovn_controller=false
gdb_ovn_controller_ex=false
gdb_ovn_controller_vtep=false
gdb_ovn_controller_vtep_ex=false
builddir=
ovsbuilddir=
srcdir=
ovssrcdir=
schema=
installed=false
built=false
ovn=true
ovnsb_schema=
ovnnb_schema=
ic_sb_schema=
ic_nb_schema=
ovn_rbac=true
n_northds=1
n_ics=1
n_controllers=1
nbdb_model=standalone
nbdb_servers=3
sbdb_model=backup
sbdb_servers=3
ic_nb_model=clustered
ic_nb_servers=3
ic_sb_model=clustered
ic_sb_servers=3
dummy=override

for option; do
    # This option-parsing mechanism borrowed from a Autoconf-generated
    # configure script under the following license:

    # Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001,
    # 2002, 2003, 2004, 2005, 2006, 2009, 2013 Free Software Foundation, Inc.
    # This configure script is free software; the Free Software Foundation
    # gives unlimited permission to copy, distribute and modify it.

    # If the previous option needs an argument, assign it.
    if test -n "$prev"; then
        eval $prev=\$option
        prev=
        continue
    fi
    case $option in
        *=*) optarg=`expr "X$option" : '[^=]*=\(.*\)'` ;;
        *) optarg=yes ;;
    esac

    case $dashdash$option in
        --)
            dashdash=yes ;;
        -h|--help)
            cat <<EOF
ovs-sandbox, for starting a sandboxed dummy Open vSwitch environment
usage: $0 [OPTION...]

If you run ovs-sandbox from an OVS build directory, it uses the OVS that
you built.  Otherwise, if you have an installed Open vSwitch, it uses
the installed version.

These options force ovs-sandbox to use a particular OVN and OVS build:
  -b, --builddir=DIR   specify OVN build directory
  -s, --srcdir=DIR     specify OVN source directory
  --ovs-build, --ovsbuilddir=DIR specify Open vSwitch build directory
  --ovs-src, --ovssrcdir=DIR specify Open vSwitch source directory
These options force ovs-sandbox to use an installed Open vSwitch:
  -i, --installed      use installed Open vSwitch

General options:
  -g, --gdb-vswitchd   run ovs-vswitchd under gdb
  -d, --gdb-ovsdb      run ovsdb-server under gdb
  --gdb-ovn-northd     run ovn-northd under gdb
  --gdb-ovn-ic         run ovn-ic under gdb
  --gdb-ovn-controller run ovn-controller under gdb
  --gdb-ovn-controller-vtep run ovn-controller-vtep under gdb
  --dummy=ARG          pass --enable-dummy=ARG to vswitchd (default: override)
  -R, --gdb-run        automatically start running the daemon in gdb
                       for any daemon set to run under gdb
  -S, --schema=FILE    use FILE as vswitch.ovsschema

OVN options:
  --no-ovn-rbac        disable role-based access control for OVN
  --n-northds=NUMBER   run NUMBER copies of northd (default: 1)
  --n-ics=NUMBER       run NUMBER copies of ic (default: 1)
  --nbdb-model=standalone|backup|clustered    northbound database model
  --nbdb-servers=N     number of servers in nbdb cluster (default: 3)
  --sbdb-model=standalone|backup|clustered    southbound database model
  --sbdb-servers=N     number of servers in sbdb cluster (default: 3)
  --ic-nb-model=standalone|backup|clustered   ic-northbound database model
  --ic-nb-servers=N     number of servers in IC NB cluster (default: 3)
  --ic-sb-model=standalone|backup|clustered   ic-southbound database model
  --ic-sb-servers=N     number of servers in IC SB cluster (default: 3)

Other options:
  -h, --help           Print this usage message.
EOF
            exit 0
            ;;

        --b*=*)
            builddir=$optarg
            built=:
            ;;
        -b|--b*)
            prev=builddir
            built=:
            ;;
        --ovs-build*=*)
            ovsbuilddir=$optarg
            ;;
        --ovs-build*)
            prev=ovsbuilddir
            ;;
        --ovs-src*=*)
            ovssrcdir=$optarg
            ;;
        --ovs-src*)
            prev=ovssrcdir
            ;;
        --sr*=*)
            srcdir=$optarg
            built=false
            ;;
        --dummy)
            prev=dummy
            ;;
        --dummy=*)
            dummy=$optarg
            ;;
        -s|--sr*)
            prev=srcdir
            built=false
            ;;
        -i|--installed)
            installed=:
            ;;
        --sc*=*)
            schema=$optarg
            installed=:
            ;;
        -S|--sc*)
            prev=schema
            installed=:
            ;;
        -g|--gdb-v*)
            gdb_vswitchd=true
            gdb_vswitchd_ex=false
            ;;
        -e|--gdb-ex-v*)
            gdb_vswitchd=true
            gdb_vswitchd_ex=true
            ;;
        -d|--gdb-ovsdb)
            gdb_ovsdb=true
            gdb_ovsdb_ex=false
            ;;
        -r|--gdb-ex-o*)
            gdb_ovsdb=true
            gdb_ovsdb_ex=true
            ;;
        --gdb-ovn-northd)
            gdb_ovn_northd=true
            ;;
        --gdb-ovn-ic)
            gdb_ovn_ic=true
            ;;
        --gdb-ovn-controller)
            gdb_ovn_controller=true
            ;;
        --gdb-ovn-controller-vtep)
            gdb_ovn_controller_vtep=true
            ;;
        --no-ovn-rbac)
            ovn_rbac=false
            ;;
        --n-northd*=*)
            n_northds=$optarg
            ;;
        --n-northd*)
            prev=n_northds
            ;;
        --n-ic*=*)
            n_ics=$optarg
            ;;
        --n-ic*)
            prev=n_ics_
            ;;
        --n-controller*=*)
            n_controllers=$optarg
            ;;
        --n-controller*)
            prev=n_controllers
            ;;
        --nbdb-s*=*)
            nbdb_servers=$optarg
            nbdb_model=clustered
            ;;
        --nbdb-s*)
            prev=nbdb_servers
            nbdb_model=clustered
            ;;
        --nbdb-m*=*)
            nbdb_model=$optarg
            ;;
        --nbdb-m*)
            prev=nbdb_model
            ;;
        --sbdb-s*=*)
            sbdb_servers=$optarg
            sbdb_model=clustered
            ;;
        --sbdb-s*)
            prev=sbdb_servers
            sbdb_model=clustered
            ;;
        --sbdb-m*=*)
            sbdb_model=$optarg
            ;;
        --sbdb-m*)
            prev=sbdb_model
            ;;
        --ic-nb-s*=*)
            ic_nb_servers=$optarg
            ic_nb_model=clustered
            ;;
        --ic-nb-s*)
            prev=ic_nb_servers
            ic_nb_model=clustered
            ;;
        --ic-nb-m*=*)
            ic_nb_model=$optarg
            ;;
        --ic-nb-m*)
            prev=ic_nb_model
            ;;
        --ic-sb-s*=*)
            ic_sb_servers=$optarg
            ic_sb_model=clustered
            ;;
        --ic-sb-s*)
            prev=ic_sb_servers
            ic_sb_model=clustered
            ;;
        --ic-sb-m*=*)
            ic_sb_model=$optarg
            ;;
        --ic-sb-m*)
            prev=ic_sb_model
            ;;
        -R|--gdb-run)
            gdb_vswitchd_ex=true
            gdb_ovsdb_ex=true
            gdb_ovn_northd_ex=true
            gdb_ovn_ic_ex=true
            gdb_ovn_controller_ex=true
            gdb_ovn_controller_vtep_ex=true
            ;;
        -*)
            echo "unrecognized option $option (use --help for help)" >&2
            exit 1
            ;;
        *)
            echo "$option: non-option arguments not supported (use --help for help)" >&2
            exit 1
            ;;
    esac
    shift
done

if $installed && $built; then
    echo "sorry, conflicting options (use --help for help)" >&2
    exit 1
elif $installed || $built; then
    :
elif test -e ovs/vswitchd/ovs-vswitchd; then
    built=:
    builddir=.
elif (ovs-vswitchd --version) >/dev/null 2>&1; then
    installed=:
else
    echo "can't find an OVS build or install (use --help for help)" >&2
    exit 1
fi

if $built; then
    if test ! -e "$builddir"/controller/ovn-controller; then
        echo "$builddir does not appear to be an OVN build directory" >&2
        exit 1
    fi
    builddir=`cd $builddir && pwd`

    # Find srcdir.
    case $srcdir in
        '')
            srcdir=$builddir
            if test ! -e "$srcdir"/README.rst; then
                srcdir=`cd $builddir/.. && pwd`
            fi
            ;;
        /*) ;;
        *) srcdir=`pwd`/$srcdir ;;
    esac
    schema=$ovssrcdir/vswitchd/vswitch.ovsschema
    if test ! -e "$schema"; then
        echo >&2 'source directory not found, please use --srcdir'
        exit 1
    fi
    if $ovn; then
        ovnsb_schema=$srcdir/ovn-sb.ovsschema
        if test ! -e "$ovnsb_schema"; then
            echo >&2 'source directory not found, please use --srcdir'
            exit 1
        fi
        ovnnb_schema=$srcdir/ovn-nb.ovsschema
        if test ! -e "$ovnnb_schema"; then
            echo >&2 'source directory not found, please use --srcdir'
            exit 1
        fi
        ic_sb_schema=$srcdir/ovn-ic-sb.ovsschema
        if test ! -e "$ic_sb_schema"; then
            echo >&2 'source directory not found, please use --srcdir'
            exit 1
        fi
        ic_nb_schema=$srcdir/ovn-ic-nb.ovsschema
        if test ! -e "$ic_nb_schema"; then
            echo >&2 'source directory not found, please use --srcdir'
            exit 1
        fi
        vtep_schema=$ovssrcdir/vtep/vtep.ovsschema
        if test ! -e "$vtep_schema"; then
            echo >&2 'source directory not found, please use --srcdir'
            exit 1
        fi
    fi

    # Put built tools early in $PATH.
    if test ! -e $ovsbuilddir/vswitchd/ovs-vswitchd; then
        echo >&2 'build not found, please change set $builddir or change directory'
        exit 1
    fi
    PATH=$ovsbuilddir/ovsdb:$ovsbuilddir/vswitchd:$ovsbuilddir/utilities:$ovsbuilddir/vtep:$PATH
    PATH=$builddir/controller:$builddir/controller-vtep:$builddir/northd:$builddir/ic:$builddir/utilities:$PATH
    export PATH
else
    case $schema in
        '')
            for schema in \
                /usr/local/share/openvswitch/vswitch.ovsschema \
                /usr/share/openvswitch/vswitch.ovsschema \
                none; do
                if test -r $schema; then
                    break
                fi
            done
            ;;
        /*) ;;
        *) schema=`pwd`/$schema ;;
    esac
    if test ! -r "$schema"; then
        echo "can't find vswitch.ovsschema, please specify --schema" >&2
        exit 1
    fi
    echo "running with ovn is only supported from the build dir." >&2
    exit 1
fi

# Create sandbox.
rm -rf sandbox
mkdir sandbox
sandbox=`cd sandbox && pwd`

# Set up environment for OVS programs to sandbox themselves.
OVS_RUNDIR=$sandbox; export OVS_RUNDIR
OVN_RUNDIR=$sandbox; export OVN_RUNDIR
OVS_LOGDIR=$sandbox; export OVS_LOGDIR
OVS_DBDIR=$sandbox; export OVS_DBDIR
OVS_SYSCONFDIR=$sandbox; export OVS_SYSCONFDIR
PYTHONPATH=$ovssrcdir/python:$PYTHONPATH; export PYTHONPATH

if $built; then
    # Easy access to OVS manpages.
    (cd "$builddir" && ${MAKE-make} install-man install-man-rst mandir="$sandbox"/man)
    MANPATH=$sandbox/man:; export MANPATH
fi

# Ensure cleanup.
trap 'kill `cat "$sandbox"/*.pid`' 0 1 2 3 13 14 15

# Create database and start ovsdb-server.
touch "$sandbox"/.conf.db.~lock~
run ovsdb-tool create conf.db "$schema"
ovsdb_server_args=

run ovsdb-tool create vtep.db "$vtep_schema"
ovsdb_server_args="vtep.db conf.db"

if [ "$HAVE_OPENSSL" = yes ]; then
    OVS_PKI="run ovs-pki --dir=$sandbox/pki --log=$sandbox/ovs-pki.log"
    $OVS_PKI init
    $OVS_PKI req+sign ovnsb switch
    $OVS_PKI req+sign ovnnb switch
    for i in $(seq $n_controllers); do
        $OVS_PKI -u req+sign chassis-$i switch
    done
fi

rungdb $gdb_ovsdb $gdb_ovsdb_ex ovsdb-server --detach --no-chdir --pidfile -vconsole:off --log-file -vsyslog:off \
       --remote=punix:"$sandbox"/db.sock \
       --remote=db:Open_vSwitch,Open_vSwitch,manager_options \
       $ovsdb_server_args

ovn_start_db() {
    local db=$1 model=$2 servers=$3 schema=$4
    local DB=$(echo $db | tr a-z A-Z)
    local schema_name=$(ovsdb-tool schema-name $schema)

    case $model in
        standalone | backup) ;;
        clustered)
            case $servers in
                [1-9] | [1-9][0-9]) ;;
                *) echo "${db}db servers must be between 1 and 99" >&2
                    exit 1
                    ;;
            esac
            ;;
        *)
            echo "unknown ${db}db model \"$model\"" >&2
            exit 1
            ;;
    esac

    ovn_start_ovsdb_server() {
        local i=$1; shift
        rungdb $gdb_ovsdb $gdb_ovsdb_ex ovsdb-server --detach --no-chdir \
                --pidfile=$db$i.pid -vconsole:off --log-file=$db$i.log \
                -vsyslog:off \
                --remote=db:$schema_name,${DB}_Global,connections \
                --private-key=db:$schema_name,SSL,private_key \
                --certificate=db:$schema_name,SSL,certificate \
                --ca-cert=db:$schema_name,SSL,ca_cert \
                --ssl-protocols=db:$schema_name,SSL,ssl_protocols \
                --ssl-ciphers=db:$schema_name,SSL,ssl_ciphers \
                --unixctl=${db}$i --remote=punix:$db$i.ovsdb ${db}$i.db "$@"
    }

    case $model in
        standalone)
            run ovsdb-tool create ${db}1.db "$schema"
            ovn_start_ovsdb_server 1
            remote=unix:${db}1.ovsdb
            ;;
        backup)
            for i in 1 2; do
                run ovsdb-tool create $db$i.db "$schema"
            done
            ovn_start_ovsdb_server 1
            ovn_start_ovsdb_server 2 --sync-from=unix:${db}1.ovsdb
            remote=unix:${db}1.ovsdb
            backup_note="$backup_note
The backup server of OVN $DB can be accessed by:
* ovn-${db}ctl --db=unix:`pwd`/sandbox/${db}2.ovsdb
* ovs-appctl -t `pwd`/sandbox/${db}2
The backup database file is sandbox/${db}2.db
"
            ;;
        clustered)
            for i in $(seq $servers); do
                if test $i = 1; then
                    run ovsdb-tool create-cluster ${db}1.db "$schema" unix:${db}1.raft;
                else
                    run ovsdb-tool join-cluster $db$i.db $schema_name unix:$db$i.raft unix:${db}1.raft
                fi
                ovn_start_ovsdb_server $i
            done
            remote=unix:${db}1.ovsdb
            for i in `seq 2 $servers`; do
                remote=$remote,unix:$db$i.ovsdb
            done
            for i in $(seq $servers); do
                run ovsdb-client wait unix:$db$i.ovsdb $schema_name connected
            done
            ;;
    esac
    eval OVN_${DB}_DB=\$remote
    eval export OVN_${DB}_DB
}

backup_note=
ovn_start_db nb "$nbdb_model" "$nbdb_servers" "$ovnnb_schema"
ovn_start_db sb "$sbdb_model" "$sbdb_servers" "$ovnsb_schema"
ovn_start_db ic_nb "$ic_nb_model" "$ic_nb_servers" "$ic_nb_schema"
ovn_start_db ic_sb "$ic_sb_model" "$ic_sb_servers" "$ic_sb_schema"

#Add a small delay to allow ovsdb-server to launch.
sleep 0.1

#Wait for ovsdb-server to finish launching.
if test ! -e "$sandbox"/db.sock; then
    printf "Waiting for ovsdb-server to start..."
    while test ! -e "$sandbox"/db.sock; do
        sleep 1;
    done
    echo "  Done"
fi

# Initialize database.
run ovs-vsctl --no-wait -- init

# Start ovs-vswitchd.
rungdb $gdb_vswitchd $gdb_vswitchd_ex ovs-vswitchd --detach --no-chdir --pidfile -vconsole:off --log-file -vsyslog:off \
    --enable-dummy=$dummy -vvconn -vnetdev_dummy

ovn-nbctl init
ovn-sbctl init
ovn-ic-nbctl init
ovn-ic-sbctl init
ovn-nbctl set NB_Global . name=az-1

ovs-vsctl set open . external-ids:system-id=chassis-1
ovs-vsctl set open . external-ids:hostname=sandbox
ovs-vsctl set open . external-ids:ovn-encap-type=geneve
ovs-vsctl set open . external-ids:ovn-encap-ip=127.0.0.1

if [ "$HAVE_OPENSSL" = yes ]; then
    ovn-nbctl set-ssl $sandbox/ovnnb-privkey.pem  $sandbox/ovnnb-cert.pem $sandbox/pki/switchca/cacert.pem
    ovn-nbctl set-connection pssl:6641
    ovn-sbctl set-ssl $sandbox/ovnsb-privkey.pem  $sandbox/ovnsb-cert.pem $sandbox/pki/switchca/cacert.pem
    if $ovn_rbac; then
        ovn-sbctl set-connection role=ovn-controller pssl:6642
    else
        ovn-sbctl set-connection pssl:6642
    fi
    ovs-vsctl set open . external-ids:ovn-remote=ssl:127.0.0.1:6642
    OVN_CTRLR_PKI="-p $sandbox/chassis-1-privkey.pem -c $sandbox/chassis-1-cert.pem -C $sandbox/pki/switchca/cacert.pem"
else
    ovs-vsctl set open . external-ids:ovn-remote=$OVN_SB_DB
    OVN_CTRLR_PKI=""
fi
for i in $(seq $n_ics); do
    if [ $i -eq 1 ]; then inst=""; else inst=$i; fi
    rungdb $gdb_ovn_ic $gdb_ovn_ic_ex ovn-ic --detach \
            --no-chdir --pidfile=ovn-ic${inst}.pid -vconsole:off \
            --log-file=ovn-ic${inst}.log -vsyslog:off \
            --ovnsb-db="$OVN_SB_DB" --ovnnb-db="$OVN_NB_DB" \
            --ic-sb-db="$OVN_IC_SB_DB" --ic-nb-db="$OVN_IC_NB_DB"
done
for i in $(seq $n_northds); do
    if [ $i -eq 1 ]; then inst=""; else inst=$i; fi
    rungdb $gdb_ovn_northd $gdb_ovn_northd_ex ovn-northd --detach \
            --no-chdir --pidfile=ovn-northd${inst}.pid -vconsole:off \
            --log-file=ovn-northd${inst}.log -vsyslog:off \
            --ovnsb-db="$OVN_SB_DB" --ovnnb-db="$OVN_NB_DB"
done
for i in $(seq $n_controllers); do
    if [ $i -eq 1 ]; then inst=""; else inst=$i; fi
    rungdb $gdb_ovn_controller $gdb_ovn_controller_ex ovn-controller \
            $OVN_CTRLR_PKI --detach --no-chdir -vsyslog:off \
            --log-file=ovn-controller${inst}.log \
            --pidfile=ovn-controller${inst}.pid -vconsole:off
done
rungdb $gdb_ovn_controller_vtep $gdb_ovn_controller_vtep_ex \
    ovn-controller-vtep --detach --no-chdir --pidfile -vconsole:off \
    $OVN_CTRLR_PKI --log-file -vsyslog:off \
    --ovnsb-db=unix:"$sandbox"/ovnsb_db.sock

cat <<EOF



----------------------------------------------------------------------
You are running in a dummy Open vSwitch environment.  You can use
ovs-vsctl, ovs-ofctl, ovs-appctl, and other tools to work with the
dummy switch.

This environment also has the OVN daemons and databases enabled.
You can use ovn-nbctl and ovn-sbctl to interact with the OVN databases.
$backup_note
EOF
cat <<EOF
Log files, pidfiles, and the configuration database are in the
"sandbox" subdirectory.

Exit the shell to kill the running daemons.
EOF

status=0; $SHELL || status=$?

cat <<EOF
----------------------------------------------------------------------



EOF

exit $status
