Skip to content

Commit bcf36eb

Browse files
Fix issues for sonic_installer upgrade-docker and sonic_installer rollback-docker (#2278)
#### What I did Fix issues: 1. upgrade-docker shall not check STATE DB for those container which do not support warm mode 2. rollback-docker sometimes cannot really rollback the container because it cannot get the correct docker image tag #### How I did it 1. for containers do not support warm mode, ignore STATE_DB check 2. use another command to get docker image tag #### How to verify it Manual test. UT is not created for this change because there are many docker command running for these two CLIs, thus: 1. Mock so many docker command causes the test case very tricky. The case would be even more complicated than the CLIs. 2. UT cannot really test the command in case of so many docker command Instead, a new sonic-mgmt test case is on the way to cover these two CLI. Will create PR for the sonic-mgmt new test case later.
1 parent ca14133 commit bcf36eb

File tree

2 files changed

+142
-25
lines changed

2 files changed

+142
-25
lines changed

sonic_installer/main.py

+15-25
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ def get_docker_tag_name(image):
134134

135135

136136
def echo_and_log(msg, priority=LOG_NOTICE, fg=None):
137-
if priority >= LOG_ERR:
137+
if priority == LOG_ERR:
138138
# Print to stderr if priority is error
139139
click.secho(msg, fg=fg, err=True)
140140
else:
@@ -647,7 +647,7 @@ def set_fips(image, enable_fips):
647647
bootloader = get_bootloader()
648648
if not image:
649649
image = bootloader.get_next_image()
650-
if image not in bootloader.get_installed_images():
650+
if image not in bootloader.get_installed_images():
651651
echo_and_log('Error: Image does not exist', LOG_ERR)
652652
sys.exit(1)
653653
bootloader.set_fips(image, enable=enable_fips)
@@ -743,7 +743,8 @@ def cleanup():
743743
"swss",
744744
"syncd",
745745
"teamd",
746-
"telemetry"
746+
"telemetry",
747+
"mgmt-framework"
747748
]
748749

749750
# Upgrade docker image
@@ -786,16 +787,8 @@ def upgrade_docker(container_name, url, cleanup_image, skip_check, tag, warm):
786787
echo_and_log("Image file '{}' does not exist or is not a regular file. Aborting...".format(image_path), LOG_ERR)
787788
raise click.Abort()
788789

789-
warm_configured = False
790790
# warm restart enable/disable config is put in stateDB, not persistent across cold reboot, not saved to config_DB.json file
791-
state_db = SonicV2Connector(host='127.0.0.1')
792-
state_db.connect(state_db.STATE_DB, False)
793-
TABLE_NAME_SEPARATOR = '|'
794-
prefix = 'WARM_RESTART_ENABLE_TABLE' + TABLE_NAME_SEPARATOR
795-
_hash = '{}{}'.format(prefix, container_name)
796-
if state_db.get(state_db.STATE_DB, _hash, "enable") == "true":
797-
warm_configured = True
798-
state_db.close(state_db.STATE_DB)
791+
warm_configured = hget_warm_restart_table('STATE_DB', 'WARM_RESTART_ENABLE_TABLE', container_name, 'enable') == "true"
799792

800793
if container_name == "swss" or container_name == "bgp" or container_name == "teamd":
801794
if warm_configured is False and warm:
@@ -866,23 +859,19 @@ def upgrade_docker(container_name, url, cleanup_image, skip_check, tag, warm):
866859
run_command("docker tag %s:latest %s:%s" % (image_name, image_name, tag))
867860
run_command("systemctl restart %s" % container_name)
868861

869-
# All images id under the image name
870-
image_id_all = get_container_image_id_all(image_name)
871-
872-
# this is image_id for image with "latest" tag
873-
image_id_latest = get_container_image_id(image_latest)
874-
875-
for id in image_id_all:
876-
if id != image_id_latest:
877-
# Unless requested, the previoud docker image will be preserved
878-
if not cleanup_image and id == image_id_previous:
879-
continue
880-
run_command("docker rmi -f %s" % id)
862+
if cleanup_image:
863+
# All images id under the image name
864+
image_id_all = get_container_image_id_all(image_name)
865+
# Unless requested, the previoud docker image will be preserved
866+
for id in image_id_all:
867+
if id == image_id_previous:
868+
run_command("docker rmi -f %s" % id)
869+
break
881870

882871
exp_state = "reconciled"
883872
state = ""
884873
# post warm restart specific procssing for swss, bgp and teamd dockers, wait for reconciliation state.
885-
if warm_configured is True or warm:
874+
if warm_app_names and (warm_configured is True or warm):
886875
count = 0
887876
for warm_app_name in warm_app_names:
888877
state = ""
@@ -939,6 +928,7 @@ def rollback_docker(container_name):
939928
for id in image_id_all:
940929
if id != image_id_previous:
941930
version_tag = get_docker_tag_name(id)
931+
break
942932

943933
# make previous image as latest
944934
run_command("docker tag %s:%s %s:latest" % (image_name, version_tag, image_name))

tests/installer_docker_test.py

+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import pytest
2+
import sonic_installer.main as sonic_installer
3+
4+
from click.testing import CliRunner
5+
from unittest.mock import patch, MagicMock
6+
7+
SUCCESS = 0
8+
9+
10+
@patch('sonic_installer.main.get_container_image_name', MagicMock(return_value='docker-fpm-frr'))
11+
@patch('sonic_installer.main.get_container_image_id_all', MagicMock(return_value=['1', '2']))
12+
@patch('sonic_installer.main.get_container_image_id', MagicMock(return_value=['1']))
13+
@patch('sonic_installer.main.get_docker_tag_name', MagicMock(return_value='some_tag'))
14+
@patch('sonic_installer.main.echo_and_log', MagicMock())
15+
@patch('sonic_installer.main.run_command')
16+
def test_rollback_docker_basic(mock_run_cmd):
17+
runner = CliRunner()
18+
result = runner.invoke(
19+
sonic_installer.sonic_installer.commands['rollback-docker'], ['-y', 'bgp']
20+
)
21+
22+
assert result.exit_code == SUCCESS
23+
expect_docker_tag_command = 'docker tag docker-fpm-frr:some_tag docker-fpm-frr:latest'
24+
mock_run_cmd.assert_called_with(expect_docker_tag_command)
25+
26+
mock_run_cmd.reset()
27+
result = runner.invoke(
28+
sonic_installer.sonic_installer.commands['rollback-docker'], ['-y', 'snmp']
29+
)
30+
31+
assert result.exit_code == SUCCESS
32+
mock_run_cmd.assert_any_call('systemctl restart snmp')
33+
34+
35+
@patch('sonic_installer.main.get_container_image_name', MagicMock(return_value='docker-fpm-frr'))
36+
@patch('sonic_installer.main.get_container_image_id_all', MagicMock(return_value=['1']))
37+
def test_rollback_docker_no_extra_image():
38+
runner = CliRunner()
39+
result = runner.invoke(
40+
sonic_installer.sonic_installer.commands['rollback-docker'], ['-y', 'bgp']
41+
)
42+
assert result.exit_code != SUCCESS
43+
44+
45+
@pytest.mark.parametrize("container", ['bgp', 'swss', 'teamd', 'pmon'])
46+
@patch('sonic_installer.main.get_container_image_name', MagicMock(return_value='docker-fpm-frr'))
47+
@patch('sonic_installer.main.get_container_image_id', MagicMock(return_value='1'))
48+
@patch('sonic_installer.main.get_container_image_id_all', MagicMock(return_value=['1', '2']))
49+
@patch('sonic_installer.main.validate_url_or_abort', MagicMock())
50+
@patch('sonic_installer.main.urlretrieve', MagicMock())
51+
@patch('os.path.isfile', MagicMock(return_value=True))
52+
@patch('sonic_installer.main.get_docker_tag_name', MagicMock(return_value='some_tag'))
53+
@patch('sonic_installer.main.run_command', MagicMock())
54+
@patch("sonic_installer.main.subprocess.Popen")
55+
@patch('sonic_installer.main.hget_warm_restart_table')
56+
def test_upgrade_docker_basic(mock_hget, mock_popen, container):
57+
def mock_hget_impl(db_name, table_name, warm_app_name, key):
58+
if table_name == 'WARM_RESTART_ENABLE_TABLE':
59+
return "false"
60+
elif table_name == 'WARM_RESTART_TABLE':
61+
return 'reconciled'
62+
63+
mock_hget.side_effect = mock_hget_impl
64+
mock_proc = MagicMock()
65+
mock_proc.communicate = MagicMock(return_value=(None, None))
66+
mock_proc.returncode = 0
67+
mock_popen.return_value = mock_proc
68+
69+
runner = CliRunner()
70+
result = runner.invoke(
71+
sonic_installer.sonic_installer.commands['upgrade-docker'],
72+
['-y', '--cleanup_image', '--warm', container, 'http://']
73+
)
74+
75+
print(result.output)
76+
assert result.exit_code == SUCCESS
77+
78+
79+
@patch('sonic_installer.main.get_container_image_name', MagicMock(return_value='docker-fpm-frr'))
80+
@patch('sonic_installer.main.get_container_image_id', MagicMock(return_value=['1']))
81+
@patch('sonic_installer.main.validate_url_or_abort', MagicMock())
82+
@patch('sonic_installer.main.urlretrieve', MagicMock(side_effect=Exception('download failed')))
83+
def test_upgrade_docker_download_fail():
84+
runner = CliRunner()
85+
result = runner.invoke(
86+
sonic_installer.sonic_installer.commands['upgrade-docker'],
87+
['-y', '--cleanup_image', '--warm', 'bgp', 'http://']
88+
)
89+
assert 'download failed' in result.output
90+
assert result.exit_code != SUCCESS
91+
92+
93+
@patch('sonic_installer.main.get_container_image_name', MagicMock(return_value='docker-fpm-frr'))
94+
@patch('sonic_installer.main.get_container_image_id', MagicMock(return_value=['1']))
95+
@patch('sonic_installer.main.validate_url_or_abort', MagicMock())
96+
@patch('sonic_installer.main.urlretrieve', MagicMock(side_effect=Exception('download failed')))
97+
def test_upgrade_docker_image_not_exist():
98+
runner = CliRunner()
99+
result = runner.invoke(
100+
sonic_installer.sonic_installer.commands['upgrade-docker'],
101+
['-y', '--cleanup_image', '--warm', 'bgp', 'invalid_url']
102+
)
103+
assert 'does not exist' in result.output
104+
assert result.exit_code != SUCCESS
105+
106+
107+
@patch('sonic_installer.main.get_container_image_name', MagicMock(return_value='docker-fpm-frr'))
108+
@patch('sonic_installer.main.get_container_image_id', MagicMock(return_value=['1']))
109+
@patch('sonic_installer.main.validate_url_or_abort', MagicMock())
110+
@patch('sonic_installer.main.urlretrieve', MagicMock())
111+
@patch('os.path.isfile', MagicMock(return_value=True))
112+
@patch('sonic_installer.main.get_docker_tag_name', MagicMock(return_value='some_tag'))
113+
@patch('sonic_installer.main.run_command', MagicMock())
114+
@patch('sonic_installer.main.hget_warm_restart_table', MagicMock(return_value='false'))
115+
@patch("sonic_installer.main.subprocess.Popen")
116+
def test_upgrade_docker_image_swss_check_failed(mock_popen):
117+
mock_proc = MagicMock()
118+
mock_proc.communicate = MagicMock(return_value=(None, None))
119+
mock_proc.returncode = 1
120+
mock_popen.return_value = mock_proc
121+
runner = CliRunner()
122+
result = runner.invoke(
123+
sonic_installer.sonic_installer.commands['upgrade-docker'],
124+
['-y', '--cleanup_image', '--warm', 'swss', 'http://']
125+
)
126+
assert 'RESTARTCHECK failed' in result.output
127+
assert result.exit_code != SUCCESS

0 commit comments

Comments
 (0)