diff options
-rw-r--r-- | doc/overview.org | 20 | ||||
-rw-r--r-- | files/common/etc/fsm/update/common.sh | 36 | ||||
-rw-r--r-- | files/common/etc/fsm/update/initial_state | 1 | ||||
-rwxr-xr-x | files/common/etc/fsm/update/trans/default.enter | 5 | ||||
-rwxr-xr-x | files/common/etc/fsm/update/trans/scheduled-applying.trans | 6 | ||||
-rwxr-xr-x | files/common/etc/fsm/update/trans/scheduled.enter | 9 | ||||
-rwxr-xr-x | files/common/etc/fsm/update/trans/scheduled.leave | 9 | ||||
-rwxr-xr-x | files/common/etc/fsm/update/watch/default | 72 |
8 files changed, 148 insertions, 10 deletions
diff --git a/doc/overview.org b/doc/overview.org index 019638d..78bf230 100644 --- a/doc/overview.org +++ b/doc/overview.org @@ -24,7 +24,7 @@ * State machines (FSMs) State machines are implemented using the /sbin/fsm script (see below). -** Network +** inetable Controls the different network states that result of the local availability of internet connection and the state of the cloud. @@ -65,13 +65,13 @@ digraph dsd { - all internet traffic is redirected to a local httpd, yelling the network status and explaining FFJ -** Update +** update Implements all-or-nothing update of nodes (e.g. if the network protocol changes incompatibly). Synchronized via p2ptable firmware-versions with the fields - machine_id - - current firmware (sha256) - - target firmware (sha256); empty if no update shall be performed + - current firmware (some human readable version string) + - SHA256 of target firmware; empty if no update shall be performed - time target: set by admin to time when update shall happen - acknowledge time: set by device to time target once ready for an upgrade @@ -81,9 +81,9 @@ digraph dsd { #+begin_dot FSM_Update.png -Tpng digraph { - Idle -> Ready; - Ready ->{ Scheduled; Idle }; - Scheduled ->{Applying; Idle }; + Idle -> Ready; + Ready ->{Idle; Scheduled} + Scheduled ->{Idle; Scheduled; Applying} } #+end_dot *** Idle @@ -98,8 +98,8 @@ digraph { conditions hold: 1. target firmware, update time target and acknowledge update time are empty - 2. current time > time target == acknowledge time; And target - firmware points to a new version that is locally stored an + 2. time target == acknowledge time; And target + firmware points to a new version that is locally stored and verified Once this state is reached the update is performed. @@ -120,7 +120,7 @@ digraph { responds, we are online - returns connectivity status - TODO: ping multiple hosts in parallel -** Finite state machine +** Finite state machines FSMs are implemented using - /sbin/fsm :: a script to monitor and change the state: - fsm watch <name> :: check whether a state change shall occur diff --git a/files/common/etc/fsm/update/common.sh b/files/common/etc/fsm/update/common.sh new file mode 100644 index 0000000..8218dd5 --- /dev/null +++ b/files/common/etc/fsm/update/common.sh @@ -0,0 +1,36 @@ +Tbl=/tmp/p2ptbl/update +TblIf=br-mesh +FwDir=/tmp/firmware + +# updates global state using current internal state; implicit +# definition of the table format +GS_update () { + p2ptbl update $Tbl "$NodeId" "$CurFw\t$TargetFw\t$TargetTime\t$AckTime" $TblIf +} + +# assemble internal state +CurState=$1 +CurTime=$(date +%s) +NodeId=$(cat /proc/sys/kernel/random/boot_id) # TODO: replace with stable machine id +CurFw="$(cat /etc/firmware)" +[ -n "$NodeId" -a -n "$CurFw" ] + +# get current global state from p2ptable +p2ptbl init $Tbl +GS=$(p2ptbl get $Tbl $NodeId) +if [ -n "$GS" ]; then + GSCurFw=$( echo "$GS" | cut -f1) + TargetFw=$( echo "$GS" | cut -f2) + TargetTime=$(echo "$GS" | cut -f3) + AckTime=$( echo "$GS" | cut -f4) + # update stale firmware entries .. should only happen after manual + # edit of /etc/firmware + [ "$CurFw" == "$GSCurFw" ] || GS_update +else + # no entry exists -> create one + TargetFw="" + TargetTime="" + AckTime="" + GS_update +fi +FwDst=$FwDir/$TargetFw diff --git a/files/common/etc/fsm/update/initial_state b/files/common/etc/fsm/update/initial_state new file mode 100644 index 0000000..5a5e41c --- /dev/null +++ b/files/common/etc/fsm/update/initial_state @@ -0,0 +1 @@ +idle diff --git a/files/common/etc/fsm/update/trans/default.enter b/files/common/etc/fsm/update/trans/default.enter new file mode 100755 index 0000000..ffdb026 --- /dev/null +++ b/files/common/etc/fsm/update/trans/default.enter @@ -0,0 +1,5 @@ +#!/bin/sh -e +. ../common.sh + +CurState=$2 +GS_update diff --git a/files/common/etc/fsm/update/trans/scheduled-applying.trans b/files/common/etc/fsm/update/trans/scheduled-applying.trans new file mode 100755 index 0000000..928d9eb --- /dev/null +++ b/files/common/etc/fsm/update/trans/scheduled-applying.trans @@ -0,0 +1,6 @@ +#!/bin/sh -e +. ../common.sh + +(echo "I would have called: sysupgrade $FwDst" + date + echo) > /tmp/updatophilie
\ No newline at end of file diff --git a/files/common/etc/fsm/update/trans/scheduled.enter b/files/common/etc/fsm/update/trans/scheduled.enter new file mode 100755 index 0000000..7488570 --- /dev/null +++ b/files/common/etc/fsm/update/trans/scheduled.enter @@ -0,0 +1,9 @@ +#!/bin/sh -e +. ../common.sh + +AckTime=$TargetTime +CurState=$2 +GS_update + +# TODO: may be increase gossip frequency to allow faster updates +# despite packet loss and queue overflows in hbbpd
\ No newline at end of file diff --git a/files/common/etc/fsm/update/trans/scheduled.leave b/files/common/etc/fsm/update/trans/scheduled.leave new file mode 100755 index 0000000..69826fe --- /dev/null +++ b/files/common/etc/fsm/update/trans/scheduled.leave @@ -0,0 +1,9 @@ +#!/bin/sh -e +. ../common.sh + +AckTime="" + +# disable gossip during update - the new state is broadcasted during +# *.enter immediately +TblIf= +GS_update diff --git a/files/common/etc/fsm/update/watch/default b/files/common/etc/fsm/update/watch/default new file mode 100755 index 0000000..a6858af --- /dev/null +++ b/files/common/etc/fsm/update/watch/default @@ -0,0 +1,72 @@ +#!/bin/sh -e +set -x + +. ../common.sh + +# announce a transition and terminate +trans () { + echo $1 + exit +} + +# is the firmware file available? +checkFw () { + [ -n "$TargetFw" -a -f "$FwDst" ] || trans idle +} + +checkFwHash () { + [ "$(sha256sum <$FwDst 2>/dev/null | cut -f1 -d' ')" == "$TargetFw" ] || trans idle +} + +# is target time passed but not set +checkTime () { + [ $CurTime -lt "$TargetTime" -o "$AckTime" -eq "$TargetTime" ] 2>/dev/null \ + || trans ready +} + +# check all-or-nothing-invariant for whole update state table +checkPeerState() { + p2ptbl show $Tbl \ + | ( + IFS=$'\t' + while read OId OCurFw OTargetFw OTargetTime OAckTime; do + [ -z "$OTargetFw" -a -z "$OTargetTime" -a -z "$OAckTime" ] && continue + [ -n "$OTargetFw" -a "$OTargetTime" -eq "$OAckTime" ] && continue + exit 1 + done ) || trans ready +} + + +case $CurState in + idle) + checkFw + checkFwHash + # ^^ check hash of firmware file once if the hash is correct; + # do not optimize for the rare case of a wrong hash or + # continuously failing updates + trans ready + ;; + ready) + checkFw + checkTime + [ "$TargetTime" -gt $CurTime ] 2>/dev/null || trans ready + trans scheduled + ;; + scheduled) + checkFw + checkTime + [ $CurTime -gt "$TargetTime" ] || trans scheduled + checkPeerState + trans applying + ;; + *) + # anything else is illegal; especially the state "applying" + # must not be reached: during transition toward it the + # firmware is flashed and the router rebooted + echo "illegal state $CurState" >&2 + exit 1 + ;; +esac + +# default and if any case falls through +trans idle
\ No newline at end of file |