Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tegra: Add support for commit service and verifying root and boot slot alignment #1

Open
wants to merge 5 commits into
base: dunfell
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions meta-mender-tegra/recipes-mender/mender-client/files/mender
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/bin/sh

echo "Starting mender wrapper"

# Did the user specify standalone install mode?
echo " $@" | grep ' install \| -install ' > /dev/null
if [ $? -eq 0 ]; then
echo "Detected install arg for standalone install mode"
install_arg=true
# Exit with failure and error message if we don't have alignment
# between boot slot and rootfs. It's not safe to update in this case
mender-tegra-verify-boot-rootfs-slot-alignment || exit 1
if [ -e /var/volatile/mender-tegra-boot-slot-mismatch-install-disabled ]; then

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest using /run instead of /var/volatile as the location for this sentinel file.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion! @jajoosiddhant can you please make this change before you test on Monday?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified

echo "Disabling mender -install since a boot slot mismatch was detected."
echo "please reboot to realign boot slots"
exit 1
fi
fi

# Call the base mender executable, renamed
mender_override $@

rc=$?
if [ $rc -eq 0 ]; then
if [ "$install_arg" = true ]; then
echo "Adding marker file to show standalone install was successful"
# Add a marker to show update was successful, for auto-commit script
touch /var/lib/mender/mender-install-success
fi
fi
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[Unit]
Description=Automatic Mender Commit Service
After=mender-client.service

[Service]
ExecStart=/usr/bin/mender-client-commit.sh
Type=oneshot
StandardOutput=journal+console

[Install]
WantedBy=multi-user.target
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/sh

mender-tegra-verify-boot-rootfs-slot-alignment || exit 0

if [ -f /var/lib/mender/mender-install-success ]; then
/usr/bin/mender -commit
rc=$?
if [ $rc -eq 0 ]; then
rm /var/lib/mender/mender-install-success
fi
fi
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/sh
nvbootctrl_slot=`nvbootctrl get-current-slot`
echo "Current boot slot is ${nvbootctrl_slot}"
echo "Boot slot misalignment is not possible on cboot based tegra platforms"
exit 0
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/sh
echo "This tegra platform does not support redundant bootloader"
exit 0
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/bin/sh

echo "Verify if nvbootctrl slot matches rootfs partition"

# This variable will be populated with the partition number to boot from
mender_boot_partition=`fw_printenv -n mender_boot_part`
devnam=`grep -h "RootfsPart" /etc/mender/mender.conf /var/lib/mender/mender.conf | grep ${mender_boot_partition} | tr -d '" ' | sed "s/RootfsPart//" | cut -f1 -d":"`

# Fetching corresponding boot slot
if [ $devnam = A ]; then
mender_boot_slot=0
elif [ $devnam = B ]; then
mender_boot_slot=1
fi

# Have default value here in case priorities are equal
nvbootctrl_slot=`nvbootctrl get-current-slot`
priority_slot0=`nvbootctrl dump-slots-info | grep 'priority' | awk 'FNR == 1 {print $4}' | cut -d ',' -f 1`
priority_slot1=`nvbootctrl dump-slots-info | grep 'priority' | awk 'FNR == 2 {print $4}' | cut -d ',' -f 1`

# Determining the boot slot that would be used after reboot
# Whichever boot slot would have the higher priority, that would be used in the next boot.
if [ $priority_slot1 -gt $priority_slot0 ]; then
nvbootctrl_slot=1
elif [ $priority_slot0 -gt $priority_slot1 ]; then
nvbootctrl_slot=0
fi

# Checking if the mender and nvbootctrl align to the same boot slots
# if not set nvbootctrl active boot slot sane as mender_boot_slot
# Dumping Warning prints to stderr since mender state scripts logs only read stderr.
if [ $mender_boot_slot -ne $nvbootctrl_slot ]; then
echo "********************* WARNING!!!!! *********************"
echo "Partition mismatch for boot and rootfs on reboot"
echo "mender boot slot: ${mender_boot_slot}"
echo "nvbootctrl slot: ${nvbootctrl_slot}"
echo "Setting the current nvbootctrl slot manually to the value of mender boot slot"
nvbootctrl set-active-boot-slot ${mender_boot_slot}

echo "********************* WARNING!!!!! *********************"
echo "Dumping partition information"
echo `nvbootctrl get-current-slot`
echo `nvbootctrl dump-slots-info`
echo `fw_printenv -n mender_boot_part`

echo "********************* WARNING!!!!! *********************"
echo "Disabling mender-client systemd service to prevent any more updates"
systemctl stop mender-client

# Create a marker file to prevent mender-install from running
touch /var/volatile/mender-tegra-boot-slot-mismatch-install-disabled
echo "Reboot the device to set the active slot before proceeding for a mender -install or mender -commit"
exit 1
else
echo "Verified"
fi

exit 0
Original file line number Diff line number Diff line change
@@ -1 +1,66 @@
RDEPENDS_${PN}_append_tegra = "${@' tegra-bup-payload libubootenv-fake' if d.getVar('PREFERRED_PROVIDER_virtual/bootloader').startswith('cboot') else ''}"

FILESEXTRAPATHS_prepend := "${THISDIR}/files:"
SRC_URI_append = " \
file://mender \
file://mender-client-commit.service \
file://mender-client-commit.sh \
file://mender-tegra-verify-boot-rootfs-slot-alignment-uboot \
file://mender-tegra-verify-boot-rootfs-slot-alignment-cboot \
file://mender-tegra-verify-boot-rootfs-slot-alignment-no-redundant-bootloader \
"

FILES_${PN} += " \
${systemd_system_unitdir}/mender-client-commit.service \
${bindir}/mender-client-commit.sh \
${bindir}/mender_base \
${bindir}/mender-tegra-verify-boot-rootfs-slot-alignment \
"

SYSTEMD_SERVICE_${PN} += "mender-client-commit.service"

install_commit_service() {
# Rename the mender binary as mender_override. We'll override with a script that records how it's used
mv ${D}${bindir}/mender ${D}${bindir}/mender_override
install -m 0755 ${WORKDIR}/mender ${D}${bindir}/mender
install -d ${D}${sysconfdir}/mender/scripts
install -m 0755 ${WORKDIR}/mender-client-commit.sh ${D}${bindir}/mender-client-commit.sh
install -m 0644 ${WORKDIR}/mender-client-commit.service ${D}${systemd_system_unitdir}/mender-client-commit.service
}

# By default, install the cboot version of the slot alignment verify script
install_slot_align_verify() {
install -m 0755 ${WORKDIR}/mender-tegra-verify-boot-rootfs-slot-alignment-cboot ${D}${bindir}/mender-tegra-verify-boot-rootfs-slot-alignment
}


# When mender-uboot override is defined, install the u-boot version. This one is important for ensuring slots are aligned
# between uboot and the rootfs before taking actions like update
install_slot_align_verify_mender-uboot() {
install -m 0755 ${WORKDIR}/mender-tegra-verify-boot-rootfs-slot-alignment-uboot ${D}${bindir}/mender-tegra-verify-boot-rootfs-slot-alignment
}

# This verify script is a no-op, since there's nothing to do if redundant bootloader is not supported
install_slot_align_verify_no_redundant_bootloader() {
install -m 0755 ${WORKDIR}/mender-tegra-verify-boot-rootfs-slot-alignment-no-redundant-bootloader ${D}${bindir}/mender-tegra-verify-boot-rootfs-slot-alignment
}

# Tegra 210 (nano) platforms don't support redundant bootloader today
# Use the version of the slot alignment verify script which doesn't use redundant bootloader
do_install_append_tegra210() {
install_commit_service
install_slot_align_verify_no_redundant_bootloader
}

do_install_append_tegra194() {
install_commit_service
install_slot_align_verify
}

do_install_append_tegra186() {
install_commit_service
# If building the uboot build, the slot alignment verify script will be installed by override here
install_slot_align_verify
}

PACKAGE_ARCH = "${MACHINE_ARCH}"
156 changes: 156 additions & 0 deletions scripts/mender_tegra_tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import fabric2
from fabric2 import Connection
import argparse
import time
import traceback
import sys
import re
import subprocess
import platform

class MenderStandaloneTests:
DEFAULT_USER = 'root'
args = None
connection = None
argparser = None

def get_parser(self):
if self.argparser is None:
'''
Argument parsing for new deployment and provisioning process
'''
argparser = argparse.ArgumentParser(prog='mender_tegra_tests.py',
usage='%(prog)s [options]',
description='Provisions Boulder AI devices')
argparser.add_argument('-d',
'--device',
help='The IP address or name of the device')

argparser.add_argument('-u',
'--user',
help="The SSH username (default is {})".format(self.DEFAULT_USER))

argparser.add_argument('-p',
'--password',
help='The SSH password (default is no password)')

argparser.add_argument('-k',
'--key',
help='The SSH key file (used instead of password if specified)')

argparser.add_argument('-i',
'--install',
help='The mender install argument to use with standalone install' +
' (http://mylocalserver:8000/path/to/mender/file')
self.argparser = argparser
return self.argparser


def get_args(self):
if self.args is None:
self.args = self.get_parser().parse_args()

if self.args.user is None:
print("No user specified, using {}".format(self.DEFAULT_USER))
self.args.user=self.DEFAULT_USER
return self.args

def get_connection(self):
args = self.get_args()
if self.connection is None:
if args.key is not None:
self.connection = Connection(
host='{}@{}'.format(args.user, args.device),
connect_kwargs={
"key_filename": args.key,
"password": args.password
})
elif args.password is not None:
self.connection = Connection(
host='{}@{}'.format(args.user, args.device),
connect_kwargs={
"password": args.password
})
else:
self.connection = Connection(
host='{}@{}'.format(args.user, args.device),
connect_kwargs={
"password": "",
"look_for_keys": False
})
return self.connection

def wait_for_device(self):
conn = self.get_connection()
print('Trying to connect to {}....'.format(self.get_args().device))
success = False
while not success:
try:
conn.open()
success = True
except Exception as e:
print('Exception connecting, retrying in 3 seconds..')
print(e)
traceback.print_exc(file=sys.stdout)
time.sleep(3)

def ping(self):
args=self.get_args()
"""
Returns True if host (str) responds to a ping request.
Remember that a host may not respond to a ping (ICMP) request even if the host name is valid.
See https://stackoverflow.com/a/32684938/1446624
"""

# Option for the number of packets as a function of
param = '-n' if platform.system().lower() == 'windows' else '-c'

# Building the command. Ex: "ping -c 1 google.com"
command = ['ping', param, '1', args.device]

return subprocess.call(command) == 0

def wait_for_device_removal(self):
while self.ping():
pass

def mender_install(self):
args = self.get_args()
conn = self.get_connection()
if args.install is None:
self.get_parser().print_help()
print("Missing argument install")
raise RuntimeError("Missing argument install")
result = conn.run("mender -install {}".format(args.install))
# Mender doesn't return error states, so we can't check return codes here
match = re.search(r' level=error ', result.stderr, re.MULTILINE)
if match is not None:
raise RuntimeError("Mender install failed with error messages in the logs")

def mender_commit(self):
self.get_connection().run("mender -commit")

def reboot(self):
conn = self.get_connection()
print("Rebooting device")
result = conn.run("reboot", warn=True)
self.wait_for_device_removal()
self.wait_for_device()

def do_single_mender_update(self):
self.wait_for_device()
self.mender_install()
self.reboot()
self.mender_commit()


def do_test(self):
self.do_single_mender_update()



if __name__ == '__main__':
test = MenderStandaloneTests()
test.do_test()


1 change: 1 addition & 0 deletions scripts/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fabric2>=2.5.0