aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Braun <martin.braun@ettus.com>2018-01-24 16:26:03 -0800
committerBrent Stapleton <bstapleton@g.hmc.edu>2018-10-09 17:11:08 -0700
commite5782693cad41b851b37f5f2535221a2f81c6872 (patch)
treeecce225b5594d9a7244a743e2fd6c14546ded33e
parent6466ae1d3eedec66c8eb1014ceb3ca48d25e72d5 (diff)
downloaduhd-e5782693cad41b851b37f5f2535221a2f81c6872.tar.gz
uhd-e5782693cad41b851b37f5f2535221a2f81c6872.tar.bz2
uhd-e5782693cad41b851b37f5f2535221a2f81c6872.zip
mpm: Add usrp_update_fs
This provides a new utility for MPM devices (usrp_update_fs.py), which goes through all the necessary steps to update a filesystem. Will trigger a mender update, but the tool is not specific to Mender and can be changed to use other methods in the future.
-rw-r--r--mpm/python/CMakeLists.txt1
-rwxr-xr-xmpm/python/usrp_update_fs194
2 files changed, 195 insertions, 0 deletions
diff --git a/mpm/python/CMakeLists.txt b/mpm/python/CMakeLists.txt
index 4b8904600..8218ce915 100644
--- a/mpm/python/CMakeLists.txt
+++ b/mpm/python/CMakeLists.txt
@@ -50,6 +50,7 @@ EXECUTE_PROCESS(COMMAND ${PYTHON_EXECUTABLE} -c
INSTALL(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/build/lib/usrp_mpm DESTINATION ${CMAKE_INSTALL_PREFIX}/${USRP_MPM_PYTHON_DIR})
INSTALL(PROGRAMS
aurora_bist_test.py
+ usrp_update_fs
usrp_hwd.py
DESTINATION ${RUNTIME_DIR}
)
diff --git a/mpm/python/usrp_update_fs b/mpm/python/usrp_update_fs
new file mode 100755
index 000000000..0d44a88d2
--- /dev/null
+++ b/mpm/python/usrp_update_fs
@@ -0,0 +1,194 @@
+#!/usr/bin/env python3
+#
+# Copyright 2018 Ettus Research, a National Instruments Company
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+"""
+Embedded USRP filesystem update utility
+"""
+
+from __future__ import print_function
+import os
+import time
+import subprocess
+import argparse
+import tempfile
+from builtins import input
+import requests
+
+MPM_DEVICE = None # This is the default value
+try:
+ from usrp_mpm import __mpm_device__ as MPM_DEVICE
+except ImportError:
+ print("ERROR: Could not import MPM.")
+
+DEFAULT_IMAGES_INSTALL_LOCATION = '/usr/share/uhd/images/'
+DEFAULT_REMOTE_MANIFEST_URL =\
+ 'https://raw.githubusercontent.com/EttusResearch/uhd/{tag}/images/manifest.txt'
+DEFAULT_FS_IMAGE = {'n3xx': 'usrp_n3xx_fs.mender', 'e320': 'usrp_e320_fs.mender'}
+DEFAULT_MENDER_TARGET = {
+ 'n3xx': 'n3xx_...._mender_default',
+ 'e320': 'e320_...._mender_default',
+}
+
+def parse_args():
+ """ Parse args """
+ parser = argparse.ArgumentParser(
+ description="""USRP filesystem update
+
+ If run without any options, it will reset the filesystem to the state
+ it was originally in, but with the same version ("factory reset").
+
+ By using -m or -t, the precise version of the new filesystem can be
+ selected. -t master will update to the very latest filesystem image.
+
+ Most options require access to the internet to download the latest
+ manifest and/or filesystem image.
+ """,
+ )
+ parser.add_argument(
+ '--image',
+ help="Binary image of the filesystem update. If this is given, all "
+ "other options are ignored. Can be a file or a URL",
+ )
+ parser.add_argument(
+ '-y', '--yes', action="store_true",
+ help="Answer questions with yes",
+ )
+ parser.add_argument(
+ '-m', '--manifest',
+ help="Manifest source. Can be a file or a URL. Overwrites -t",
+ )
+ parser.add_argument(
+ '-d', '--device-type', default=MPM_DEVICE, required=MPM_DEVICE is None,
+ help="Specify/overwrite device type " \
+ "(DANGER! May brick device if used incorrectly!)",
+ )
+ parser.add_argument(
+ '-t', '--git-tag',
+ help="Will try and locate the given git tag or branch and get the "
+ "corresponding manifest. Ignored when -m is given. Using this "
+ "requires internet access.",
+ )
+ return parser.parse_args()
+
+def download_image(device_type, manifest_path=None):
+ """
+ Run uhd_images_downloader to fetch the mender image
+ """
+ downloader_command = [
+ 'python3',
+ '/usr/bin/uhd_images_downloader',
+ '-t', DEFAULT_MENDER_TARGET[device_type],
+ '-i', DEFAULT_IMAGES_INSTALL_LOCATION,
+ ]
+ if manifest_path is not None:
+ downloader_command = downloader_command + ['-m', manifest_path]
+ subprocess.check_call(downloader_command)
+
+def prepare_manifest(manifest):
+ """Create a valid local path for a manifest"""
+ manifest = manifest.strip()
+ if os.path.isfile(manifest):
+ return manifest
+ elif manifest.strip().startswith('http'):
+ manifest = manifest.strip()
+ temp_dir = tempfile.mkdtemp()
+ print("Downloading manifest file from {}...".format(
+ manifest.strip()))
+ req_obj = requests.get(manifest)
+ manifest_path = os.path.join(temp_dir, 'manifest.txt')
+ open(manifest_path, 'wb').write(req_obj.content)
+ # This will leave a stray file in /tmp but that's OK... we're blowing
+ # away the FS anyway
+ return manifest_path
+
+def get_manifest_from_tag(git_tag):
+ """Turn a git tag or branch into a URL to a valid manifest"""
+ manifest_url = DEFAULT_REMOTE_MANIFEST_URL.format(git_tag)
+ return prepare_manifest(manifest_url)
+
+def prepare_image(device_type, args):
+ """Figure out which mender image to use, and make sure it's available.
+
+ Returns the path to a valid Mender image.
+ """
+ if args.image is not None:
+ print("Using image for upgrade: {}".format(args.image))
+ # This is the only case where we don't invoke the images downloader
+ return args.image
+ manifest_path = None
+ if args.manifest is not None:
+ print("Using manifest to download image: {}".format(args.manifest))
+ manifest_path = prepare_manifest(args.manifest)
+ elif args.git_tag:
+ manifest_path = get_manifest_from_tag(args.git_tag)
+ else:
+ print("Using default manifest.")
+ # Note: The image downloader may choose to do nothing if the requested
+ # image is already downloaded. It's safe to call anyway.
+ download_image(device_type, manifest_path)
+ return os.path.join(
+ DEFAULT_IMAGES_INSTALL_LOCATION,
+ DEFAULT_FS_IMAGE[device_type]
+ )
+
+def apply_image(mender_image):
+ """Actually run mender to inject the mender_image"""
+ subprocess.check_call(['mender', '-rootfs', mender_image])
+
+def reboot():
+ """
+ Reboot
+ """
+ print("Will reboot now. Hit Ctrl-C before the countdown expires to cancel.")
+ print("Rebooting in 3...")
+ time.sleep(1)
+ print("2...")
+ time.sleep(1)
+ print("1...")
+ time.sleep(1)
+ os.system('reboot')
+
+def run():
+ """Main execution"""
+ args = parse_args()
+ mender_image = prepare_image(args.device_type, args)
+ if mender_image is None:
+ print("Error: Unable to identify filesystem image!")
+ return False
+ apply_image(mender_image)
+ print("Applied image. After reboot, check if everything works, and then "
+ "run the command '$ mender -commit' to confirm (otherwise, this "
+ "update will be undone.")
+ print("Note: Any data stored in this partition will be not accessible "
+ "after reboot.")
+ if args.yes:
+ reboot_now = 'y'
+ else:
+ reboot_now = input("Reboot now? [Yn] ")
+ if reboot_now == "" or reboot_now.lower() == 'y':
+ try:
+ reboot() # This will implicitly terminate the program, but it looks
+ # neater if we have the return statement here
+ return True
+ except KeyboardInterrupt:
+ pass
+ print("Skipping reboot. Your device will load the updated file system "
+ "on the next boot.")
+ return True
+
+def main():
+ """ Go, go, go! """
+ try:
+ return run()
+ except SystemExit: # for argparse's --help
+ return True
+ except BaseException as ex:
+ print("Error: Unexpected exception caught!")
+ print(str(ex))
+ return False
+
+if __name__ == '__main__':
+ exit(not main())