Add upload_and_update.py tool

A tool to upload tarball to TFTP server and update BMC with it, using
REST APIs.

Signed-off-by: Lei YU <mine260309@gmail.com>
diff --git a/leiyu/obmc-utils/LICENSE b/leiyu/obmc-utils/LICENSE
new file mode 100644
index 0000000..78e3d2c
--- /dev/null
+++ b/leiyu/obmc-utils/LICENSE
@@ -0,0 +1,14 @@
+Copyright 2017 IBM Corp.
+
+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.
+
diff --git a/leiyu/obmc-utils/README.md b/leiyu/obmc-utils/README.md
new file mode 100644
index 0000000..5b1e643
--- /dev/null
+++ b/leiyu/obmc-utils/README.md
@@ -0,0 +1,5 @@
+# openbmc-utils
+
+* `upload_and_update.py`
+A tool to upload a tarball to TFTP server and update BMC with it.
+**Note**: It uses legacy methods to update BMC so it may break in future when the code update is refactored.
diff --git a/leiyu/obmc-utils/upload_and_update.py b/leiyu/obmc-utils/upload_and_update.py
new file mode 100755
index 0000000..c99f325
--- /dev/null
+++ b/leiyu/obmc-utils/upload_and_update.py
@@ -0,0 +1,222 @@
+#!/usr/bin/env python
+"""
+Usage: upload_and_update.py <--file tarball>
+                            <--tftp user@tftp-ip:/path/to/tftproot>
+                            [--password SSH-PASSWORD-TO-TFTP]
+                            --bmc <bmc-ip>
+                            [-v]
+
+This scripts copies OpenBMC tarball to TFTP server,
+and uses REST APIs to update the tarball to BMC.
+
+Note on tftp server the tarball will be renamed to tarball_<user>
+"""
+
+import argparse
+import json
+import os
+import subprocess
+from subprocess import check_call, CalledProcessError
+from time import sleep
+
+
+def get_tftp_ip(tftp):
+    if '@' in tftp:
+        ip = tftp.split('@')[1].split(':')[0]
+    else:
+        ip = tftp.split(':')[0]
+    return ip
+
+
+def get_filename(tarball):
+    return os.path.basename(tarball)
+
+def get_server_filename(tftp, tarball):
+    if '@' in tftp:
+        user = tftp.split('@')[0]
+    else:
+        import getpass
+        user = getpass.getuser()
+    return get_filename(tarball) + "_" + user
+
+
+def checkBmcAlive(bmc):
+    cmds = ['ping', '-c', '1', bmc]
+    try:
+        check_call(cmds, stdout=FNULL, stderr=FNULL)
+    except CalledProcessError:
+        return False
+    else:
+        return True
+
+
+def login(bmc):
+    url = 'https://%s/login' % bmc
+    cmds = ['curl', '-s', '-c', 'cjar', '-k', '-X', 'POST', '-H',
+            'Content-Type: application/json', '-d',
+            '{"data": [ "root", "0penBmc"]}', url]
+    try:
+        check_call(cmds, stdout=FNULL, stderr=FNULL)
+    except CalledProcessError:
+        return False
+    else:
+        return True
+
+
+def prepare(bmc):
+    url = 'https://%s/org/openbmc/control/flash/bmc/action/prepareForUpdate' % bmc
+    cmds = ['curl', '-s', '-b', 'cjar', '-k', '-X', 'POST', '-H',
+            'Content-Type: application/json', '-d',
+            '{"data": []}', url]
+    check_call(cmds, stdout=FNULL, stderr=FNULL)
+
+
+def preserveNetwork(bmc):
+    url = 'https://%s/org/openbmc/control/flash/bmc/attr/preserve_network_settings' % bmc
+    cmds = ['curl', '-s', '-b', 'cjar', '-k', '-X', 'PUT', '-H',
+            'Content-Type: application/json', '-d',
+            '{"data": 1}', url]
+    check_call(cmds, stdout=FNULL, stderr=FNULL)
+
+
+def updateViaTFTP(tftp, tarball, bmc):
+    tftp_ip = get_tftp_ip(tftp)
+    serverfile = get_server_filename(tftp, tarball)
+    url = 'https://%s/org/openbmc/control/flash/bmc/action/updateViaTftp' % bmc
+    cmds = ['curl', '-s', '-b', 'cjar', '-k', '-X', 'POST', '-H',
+            'Content-Type: application/json', '-d',
+            '{"data": ["%s", "%s"]}' % (tftp_ip, serverfile), url]
+    check_call(cmds, stdout=FNULL, stderr=FNULL)
+
+
+def applyUpdate(bmc):
+    url = 'https://%s/org/openbmc/control/flash/bmc/action/Apply' % bmc
+    cmds = ['curl', '-s', '-b', 'cjar', '-k', '-X', 'POST', '-H',
+            'Content-Type: application/json', '-d',
+            '{"data": []}', url]
+    check_call(cmds, stdout=FNULL, stderr=FNULL)
+
+
+def getProgress(bmc):
+    url = 'https://%s/org/openbmc/control/flash/bmc/action/GetUpdateProgress' % bmc
+    cmds = ['curl', '-s', '-b', 'cjar', '-k', '-X', 'POST', '-H',
+            'Content-Type: application/json', '-d',
+            '{"data": []}', url]
+    try:
+        output = subprocess.check_output(cmds, stderr=FNULL)
+    except CalledProcessError as e:
+        # Sometimes curl fails with timeout error, let's ignore it
+        return ''
+    if FNULL is None:  # Do not print log when FNULL is devnull
+        print output
+    return json.loads(output)['data']
+
+
+def reboot(bmc):
+    url = 'https://%s/org/openbmc/control/bmc0/action/warmReset' % bmc
+    cmds = ['curl', '-s', '-b', 'cjar', '-k', '-X', 'POST', '-H',
+            'Content-Type: application/json', '-d',
+            '{"data": []}', url]
+    check_call(cmds, stdout=FNULL, stderr=FNULL)
+
+
+def waitForState(state, bmc):
+    status = getProgress(bmc)
+    while state not in status:
+        if 'Error' in status:
+            raise Exception(status)
+        print 'Still waiting for status: \'%s\', current: \'%s\'' % (state, status.split('\n', 1)[0])
+        sleep(5)
+        status = getProgress(bmc)
+
+
+def upload(tftp, password, tarball):
+    target = "%s/%s" % (tftp, get_server_filename(tftp, tarball))
+    print 'Uploading \'%s\' to \'%s\' ...' % (tarball, target)
+    if password is None:
+        cmds = ['scp', tarball, target]
+    else:
+        cmds = ['sshpass', '-p', password, 'scp', tarball, target]
+    # print cmds
+    check_call(cmds, stdout=FNULL, stderr=FNULL)
+
+
+def update(tftp, tarball, bmc):
+    print 'Update...'
+
+    login(bmc)
+    print 'Prepare BMC to update'
+    prepare(bmc)
+
+    # After prepare, BMC will reboot, let's wait for it
+    print 'Waiting BMC to reboot...'
+    sleep(30)
+    while not checkBmcAlive(bmc):
+        sleep(5)
+    print 'BMC is back'
+
+    login(bmc)
+    print 'Logged in'
+
+    print 'Preserve network...'
+    preserveNetwork(bmc)
+
+    print 'Update via TFTP...'
+    updateViaTFTP(tftp, tarball, bmc)
+
+    print 'Waiting for downloading...'
+    sleep(10)
+    waitForState('Image ready to apply', bmc)
+
+    print 'Apply image...'
+    applyUpdate(bmc)
+    sleep(10)
+    waitForState('Apply Complete', bmc)
+
+    print 'Reboot BMC...'
+    reboot(bmc)
+    sleep(30)
+    while not checkBmcAlive(bmc):
+        sleep(5)
+    pass
+
+
+def main():
+    parser = argparse.ArgumentParser(
+        description='Upload tarball to remote TFTP server and update it on BMC')
+    parser.add_argument('-f', '--file', required=True, dest='tarball',
+                        help='The tarball to upload and update')
+    parser.add_argument('-t', '--tftp', required=True, dest='tftp',
+                        help='The TFTP address including username and full path')
+    parser.add_argument('-p', '--password', dest='password',
+                        help='The password of TFTP server')
+    parser.add_argument('-b', '--bmc', required=True, dest='bmc',
+                        help='The BMC IP address')
+    parser.add_argument('-v', '--verbose', action='store_true',
+                        help='Print verbose log')
+
+    args = parser.parse_args()
+    args = vars(args)
+
+    if args['tftp'] is None or args['tarball'] is None or args['bmc'] is None:
+        parser.print_help()
+        exit(1)
+    global FNULL
+    if args['verbose']:
+        FNULL = None  # Print log to stdout/stderr, for debug purpose
+    else:
+        FNULL = open(os.devnull, 'w')  # Redirect stdout/stderr to devnull
+
+    if checkBmcAlive(args['bmc']):
+        print 'BMC is alive'
+    else:
+        print 'BMC is down, check it first'
+        exit(1)
+
+    upload(args['tftp'], args['password'], args['tarball'])
+    update(args['tftp'], args['tarball'], args['bmc'])
+
+    print 'Completed!'
+
+if __name__ == "__main__":
+    main()