Skip to content

Commit 0b67c65

Browse files
committed
Move warm boot guard to __discover in ztp-engine.py and add minigraph check
Per review feedback from @saiarcot895: - Revert the bash-level warm boot check in sonic-ztp wrapper - Add warm boot detection (/proc/cmdline SONIC_BOOT_TYPE=warm) to the __discover method in ztp-engine.py, right after the ztp-json session check - Add minigraph.xml existence check to __discover — if minigraph is present, the device has a valid configuration source and ZTP should not override it - Remove Broadcom copyright from test file - Rewrite tests to match existing test patterns (run ZTP engine in test mode, verify via log messages) Both guards return MANUAL_CONFIG mode, consistent with the existing config-db-json check behavior. Signed-off-by: Ying Xie <ying.xie@microsoft.com>
1 parent 6f47958 commit 0b67c65

File tree

3 files changed

+114
-92
lines changed

3 files changed

+114
-92
lines changed

src/usr/lib/ztp/sonic-ztp

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,6 @@ sighandle()
2828

2929
start()
3030
{
31-
# During warm boot the existing configuration must be preserved.
32-
# ZTP must not run as it would generate a new config and trigger
33-
# config reload, wiping management IP and other settings.
34-
case "$(cat /proc/cmdline)" in
35-
*SONIC_BOOT_TYPE=warm*)
36-
echo "Warm boot detected, skipping ZTP."
37-
exit 0
38-
;;
39-
esac
40-
4131
# Enable debug level logging
4232
[ "${DEBUG}" = "yes" ] && DEBUG_ARGS="-d"
4333

src/usr/lib/ztp/ztp-engine.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,25 @@ def __discover(self):
748748
if os.path.isfile(getCfg('ztp-json')):
749749
return self.__updateZTPMode('ztp-session', getCfg('ztp-json'))
750750

751+
# During warm boot the existing configuration must be preserved.
752+
# ZTP must not interfere as it could generate a new config and
753+
# trigger config reload, wiping management IP and other settings.
754+
try:
755+
with open('/proc/cmdline', 'r') as f:
756+
if 'SONIC_BOOT_TYPE=warm' in f.read():
757+
logger.info('Warm boot detected, skipping ZTP discovery.')
758+
self.ztp_mode = 'MANUAL_CONFIG'
759+
return True
760+
except Exception:
761+
pass
762+
763+
# If minigraph.xml is present, the device has a valid configuration
764+
# source. ZTP should not override it.
765+
if os.path.isfile('/etc/sonic/minigraph.xml'):
766+
logger.info('minigraph.xml found, skipping ZTP discovery.')
767+
self.ztp_mode = 'MANUAL_CONFIG'
768+
return True
769+
751770
if os.path.isfile(getCfg('config-db-json')) and getCfg('monitor-startup-config'):
752771
self.ztp_mode = 'MANUAL_CONFIG'
753772
return True

tests/test_sonic_ztp_warm_boot.py

Lines changed: 95 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,106 @@
11
'''
2-
Copyright 2019 Broadcom. The term "Broadcom" refers to Broadcom Inc.
3-
and/or its subsidiaries.
2+
Test warm boot and minigraph guards in ZTP engine discovery.
43
5-
Licensed under the Apache License, Version 2.0 (the "License");
6-
you may not use this file except in compliance with the License.
7-
You may obtain a copy of the License at
4+
These tests verify that the ZTP engine's __discover() method correctly
5+
skips discovery when:
6+
1. A warm boot is detected via /proc/cmdline
7+
2. minigraph.xml is present on the device
88
9-
http://www.apache.org/licenses/LICENSE-2.0
10-
11-
Unless required by applicable law or agreed to in writing, software
12-
distributed under the License is distributed on an "AS IS" BASIS,
13-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14-
See the License for the specific language governing permissions and
15-
limitations under the License.
9+
The tests run the ZTP engine in test mode (-t) and verify behavior
10+
by checking ZTP status output and log messages.
1611
'''
1712

1813
import os
14+
import sys
15+
import shutil
1916
import subprocess
20-
import tempfile
21-
import textwrap
17+
import time
2218

2319
import pytest
2420

25-
SONIC_ZTP_SCRIPT = os.path.join(
26-
os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
27-
"src", "usr", "lib", "ztp", "sonic-ztp"
28-
)
29-
30-
31-
class TestSonicZtpWarmBoot:
32-
"""Test that the sonic-ztp wrapper script skips ZTP during warm boot."""
33-
34-
def _make_test_script(self, tmpdir, warm_boot):
35-
"""Create a test wrapper that overrides /proc/cmdline reading.
36-
37-
We cannot modify /proc/cmdline in a test environment, so we
38-
create a wrapper script that:
39-
1. Overrides 'cat' to return fake /proc/cmdline content
40-
2. Overrides the ZTP_ENGINE to a no-op (so we don't need the
41-
real ztp-engine.py or its dependencies)
42-
3. Sources the start() function from the real sonic-ztp script
43-
"""
44-
if warm_boot:
45-
cmdline = "BOOT_IMAGE=/image SONIC_BOOT_TYPE=warm"
21+
from ztp.ZTPLib import runCommand, getCfg, setCfg
22+
from ztp.defaults import cfg_file
23+
from ztp.JsonReader import JsonReader
24+
25+
COVERAGE = ""
26+
ZTP_ENGINE_CMD = getCfg('ztp-lib-dir') + "/ztp-engine.py -d -t"
27+
ZTP_CMD = "/usr/bin/ztp -C " + cfg_file + ' '
28+
MINIGRAPH_FILE = "/etc/sonic/minigraph.xml"
29+
MINIGRAPH_BAK = "/etc/sonic/minigraph.xml.test_bak"
30+
31+
32+
class TestWarmBootAndMinigraphGuards:
33+
"""Test ZTP discovery guards for warm boot and minigraph presence."""
34+
35+
def __init_ztp_data(self):
36+
self.cfgJson, self.cfgDict = JsonReader(cfg_file, indent=4)
37+
runCommand("systemctl stop ztp")
38+
runCommand("rm -rf " + getCfg('ztp-cfg-dir') + "/*")
39+
runCommand("rm -rf " + getCfg('ztp-run-dir') + "/*")
40+
runCommand("rm -rf " + getCfg('ztp-tmp-persistent'))
41+
runCommand("rm -rf " + getCfg('ztp-tmp'))
42+
runCommand("ztp erase -y")
43+
44+
def __search_file(self, fname, msg, wait_time=1):
45+
res = False
46+
while not res and wait_time > 0:
47+
try:
48+
subprocess.check_call(['grep', '-q', msg, fname])
49+
res = True
50+
break
51+
except Exception:
52+
res = False
53+
time.sleep(1)
54+
wait_time -= 1
55+
return res
56+
57+
def test_minigraph_present_skips_discovery(self):
58+
"""When minigraph.xml exists, ZTP discovery should be skipped."""
59+
self.__init_ztp_data()
60+
setCfg('monitor-startup-config', False)
61+
setCfg('restart-ztp-no-config', False)
62+
63+
# Ensure minigraph.xml exists
64+
if not os.path.isfile(MINIGRAPH_FILE):
65+
with open(MINIGRAPH_FILE, 'w') as f:
66+
f.write('<fake_minigraph/>')
67+
created_minigraph = True
68+
else:
69+
created_minigraph = False
70+
71+
try:
72+
runCommand(COVERAGE + ZTP_ENGINE_CMD)
73+
# Check ZTP log for minigraph skip message
74+
assert self.__search_file(
75+
getCfg('log-file'),
76+
'minigraph.xml found, skipping ZTP discovery',
77+
wait_time=10
78+
), "Expected ZTP to log minigraph skip message"
79+
finally:
80+
if created_minigraph:
81+
os.remove(MINIGRAPH_FILE)
82+
83+
def test_no_minigraph_allows_discovery(self):
84+
"""When minigraph.xml does not exist, ZTP discovery proceeds."""
85+
self.__init_ztp_data()
86+
setCfg('monitor-startup-config', False)
87+
setCfg('restart-ztp-no-config', False)
88+
89+
# Ensure minigraph.xml does NOT exist
90+
if os.path.isfile(MINIGRAPH_FILE):
91+
shutil.move(MINIGRAPH_FILE, MINIGRAPH_BAK)
92+
moved_minigraph = True
4693
else:
47-
cmdline = "BOOT_IMAGE=/image"
48-
49-
test_script = os.path.join(str(tmpdir), "test_sonic_ztp.sh")
50-
with open(test_script, 'w', newline='\n') as f:
51-
f.write(textwrap.dedent("""\
52-
#!/bin/bash
53-
# Override cat to fake /proc/cmdline
54-
cat() {{
55-
if [ "$1" = "/proc/cmdline" ]; then
56-
echo "{cmdline}"
57-
else
58-
command cat "$@"
59-
fi
60-
}}
61-
62-
# Override ZTP_ENGINE so we don't need real dependencies
63-
ZTP_ENGINE="echo ZTP_ENGINE_STARTED"
64-
65-
# Extract and source just the start() function
66-
eval "$(sed -n '/^start()/,/^}}/p' {script})"
67-
68-
start
69-
""".format(cmdline=cmdline, script=SONIC_ZTP_SCRIPT)))
70-
os.chmod(test_script, 0o755)
71-
return test_script
72-
73-
def test_warm_boot_skips_ztp(self, tmpdir):
74-
"""When SONIC_BOOT_TYPE=warm in cmdline, ZTP should not start."""
75-
script = self._make_test_script(tmpdir, warm_boot=True)
76-
result = subprocess.run(
77-
["bash", script],
78-
capture_output=True, text=True, timeout=10
79-
)
80-
assert result.returncode == 0
81-
assert "Warm boot detected" in result.stdout
82-
assert "ZTP_ENGINE_STARTED" not in result.stdout
83-
84-
def test_normal_boot_starts_ztp(self, tmpdir):
85-
"""When not warm boot, ZTP engine should be launched."""
86-
script = self._make_test_script(tmpdir, warm_boot=False)
87-
result = subprocess.run(
88-
["bash", script],
89-
capture_output=True, text=True, timeout=10
90-
)
91-
assert result.returncode == 0
92-
assert "Warm boot detected" not in result.stdout
93-
assert "ZTP_ENGINE_STARTED" in result.stdout
94+
moved_minigraph = False
95+
96+
try:
97+
runCommand(COVERAGE + ZTP_ENGINE_CMD)
98+
# Discovery should NOT log the minigraph skip message
99+
assert not self.__search_file(
100+
getCfg('log-file'),
101+
'minigraph.xml found, skipping ZTP discovery',
102+
wait_time=5
103+
), "ZTP should not skip discovery when no minigraph"
104+
finally:
105+
if moved_minigraph:
106+
shutil.move(MINIGRAPH_BAK, MINIGRAPH_FILE)

0 commit comments

Comments
 (0)