obmcutil: check for transition states on commands

When obmcutil was originally written, the design was that users know
what they are doing so it would allow them to shoot themselves in the
foot if they wanted. It was a lab/debug tool. Proper external interfaces
like the Web UI enforce when power on and off operations can be issued.

As time has gone on, the use of OpenBMC has increased significantly and
so has the userbase of obmcutil. Within IBM we have dozens of different
groups interacting with OpenBMC servers and hundreds of users of
obmcutil. The figuratively shooting of ones foot has become a problem.

We see multiple defects per month where the end result is that someone
was using obmcutil in a way they should not. A lot of times the bad
behavior is buried in some automated script that the person opening the
defect doesn't even know about. It results in countless hours of
developer debug to determine root cause.

This commit will not fix all of the issues we've seen but it will fix a
lot of them by ensuring a user does not issue a obmcutil power on/off
command when one is already in process.

Tested:
- Set Chassis state to TransitioningToOff and confirmed a power on/off
  was not allowed
- Set Host state to .TransitioningToOff and confirmed a power on/off was
  not allowed
- Did above tests with TransitioningToOn/TransitioningToRunning
- Verified good path poweron
- Verified --force allows a user to override the new check

Change-Id: Ie7123f37dd98ffd388f79ff7b31662c30a730bcf
Signed-off-by: Andrew Geissler <geissonator@yahoo.com>
diff --git a/obmcutil b/obmcutil
index 1aa75c9..ecaed6a 100755
--- a/obmcutil
+++ b/obmcutil
@@ -20,8 +20,7 @@
 HOST_TIMEOUT_TARGET=obmc-host-timeout@0.target
 HOST_CRASH_TARGET=obmc-host-crash@0.target
 
-## NOTE: The following global variables are used only in the run_timeout cmd.
-## By declaring these globally instead of passing them through the
+## NOTE: By declaring these globally instead of passing them through the
 ## intermediary functions, which may not be "best practice", the readability
 ## and cleanliness of the code should at least be increased.
 
@@ -40,6 +39,8 @@
 G_VERBOSE=
 # Instance id, default 0
 G_INSTANCE_ID="0"
+# Force a command even if system state is not correct
+G_FORCE=
 
 function print_help()
 {
@@ -86,6 +87,7 @@
     echo "  -w, --wait          block until state transition succeeds or fails"
     echo "  -v, --verbose       print the journal to stdout if --wait is supplied"
     echo "  -i, -id             instance id, default 0"
+    echo "  -f, --force         force issuing the command ignoring preconditions (use with caution)"
     exit 0
 }
 
@@ -280,6 +282,36 @@
     done
 }
 
+# check if system is in transitioning state for chassis or host and
+# reject request if it is (if force option not set)
+function check_chassis_host_states()
+{
+    # If user has --force enabled, no check
+    if [ -n "$G_FORCE" ]; then
+        return 0
+    fi
+
+    OBJECT=$STATE_OBJECT/chassis$G_INSTANCE_ID
+    SERVICE=$(mapper get-service "$OBJECT")
+    INTERFACE=$STATE_INTERFACE.Chassis
+    PROPERTY=CurrentPowerState
+    state=$(get_property "$SERVICE" "$OBJECT" "$INTERFACE $PROPERTY" | cut -d '"' -f2)
+    if [[ ${state} =~ "xyz.openbmc_project.State.Chassis.PowerState.Transitioning"* ]]; then
+        echo "Chassis is $state, request rejected, use --force to override"
+        exit 1
+    fi
+
+    OBJECT=$STATE_OBJECT/host$G_INSTANCE_ID
+    SERVICE=$(mapper get-service "$OBJECT")
+    INTERFACE=$STATE_INTERFACE.Host
+    PROPERTY=CurrentHostState
+    state=$(get_property "$SERVICE" "$OBJECT" "$INTERFACE $PROPERTY" | cut -d '"' -f2)
+    if [[ ${state} =~ "xyz.openbmc_project.State.Host.HostState.Transitioning"* ]]; then
+        echo "Host is $state, request rejected, use --force to override"
+        exit 1
+    fi
+}
+
 # helper function to check for boot block errors and notify user
 function check_and_warn_boot_block()
 {
@@ -338,6 +370,7 @@
 {
     case "$1" in
         chassisoff)
+            check_chassis_host_states
             OBJECT=$STATE_OBJECT/chassis$G_INSTANCE_ID
             SERVICE=$(mapper get-service "$OBJECT")
             INTERFACE=$STATE_INTERFACE.Chassis
@@ -348,6 +381,7 @@
             set_property "$SERVICE" "$OBJECT" $INTERFACE $PROPERTY "s" $VALUE
             ;;
         chassison)
+            check_chassis_host_states
             check_and_warn_boot_block
             OBJECT=$STATE_OBJECT/chassis$G_INSTANCE_ID
             SERVICE=$(mapper get-service "$OBJECT")
@@ -359,6 +393,7 @@
             set_property "$SERVICE" "$OBJECT" $INTERFACE $PROPERTY "s" $VALUE
             ;;
         poweroff)
+            check_chassis_host_states
             OBJECT=$STATE_OBJECT/host$G_INSTANCE_ID
             SERVICE=$(mapper get-service "$OBJECT")
             INTERFACE=$STATE_INTERFACE.Host
@@ -369,6 +404,7 @@
             set_property "$SERVICE" "$OBJECT" $INTERFACE $PROPERTY "s" $VALUE
             ;;
         poweron)
+            check_chassis_host_states
             check_and_warn_boot_block
             OBJECT=$STATE_OBJECT/host$G_INSTANCE_ID
             SERVICE=$(mapper get-service "$OBJECT")
@@ -570,6 +606,10 @@
             G_INSTANCE_ID="${arg#*=}"
             shiftcnt=$((shiftcnt+1))
             ;;
+        -f|--force)
+            G_FORCE=y
+            shiftcnt=$((shiftcnt+1))
+            ;;
         -*)
             print_usage_err "Unknown option: $arg"
             ;;