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()