Skip to content

Commit bd2d4c3

Browse files
authored
[nvidia-bluefield] add sonic-byo python script (#19774)
- Why I did it To add a possibility to disable SONiC containers and run a user-provided data-plane application. - How I did it Added Nvidia platform-specific script sonic-byo.py - How to verify it Manual test --------- Signed-off-by: Yakiv Huryk <[email protected]>
1 parent dbd6f5e commit bd2d4c3

File tree

2 files changed

+194
-0
lines changed

2 files changed

+194
-0
lines changed

files/build_templates/sonic_debian_extension.j2

+2
Original file line numberDiff line numberDiff line change
@@ -1122,6 +1122,8 @@ for fw_file_name in ${!FW_FILE_MAP[@]}; do
11221122
sudo ln -s /host/image-$SONIC_IMAGE_VERSION/$PLATFORM_DIR/fw/dpu/${FW_FILE_MAP[$fw_file_name]} $FILESYSTEM_ROOT/etc/bluefield/${FW_FILE_MAP[$fw_file_name]}
11231123
done
11241124

1125+
sudo install -m 755 platform/nvidia-bluefield/byo/sonic-byo.py $FILESYSTEM_ROOT/usr/bin/sonic-byo.py
1126+
11251127
SONIC_PLATFORM_PY3_WHEEL_NAME=$(basename {{platform_api_py3_wheel_path}})
11261128
sudo cp {{platform_api_py3_wheel_path}} $FILESYSTEM_ROOT/$SONIC_PLATFORM_PY3_WHEEL_NAME
11271129
sudo https_proxy=$https_proxy LANG=C chroot $FILESYSTEM_ROOT pip3 install $SONIC_PLATFORM_PY3_WHEEL_NAME
+192
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
#!/usr/bin/python
2+
#
3+
# Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES.
4+
# Apache-2.0
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#
18+
19+
import argparse
20+
import docker
21+
import subprocess
22+
import os
23+
import sys
24+
25+
DOCKER_TIMEOUT = 600
26+
CONTAINER_NAME = 'byo-app-container'
27+
28+
client = docker.DockerClient(timeout=DOCKER_TIMEOUT)
29+
api_client = docker.APIClient(timeout=DOCKER_TIMEOUT)
30+
31+
32+
def get_args():
33+
parser = argparse.ArgumentParser(description='Utility for SONiC BYO configuration')
34+
mode = parser.add_subparsers(dest='mode')
35+
mode.required = True
36+
37+
mode.add_parser('disable', help='Stop BYO application, restart SONiC services')
38+
39+
enable_parser = mode.add_parser('enable', help='Stop SONiC services and run BYO application')
40+
group = enable_parser.add_mutually_exclusive_group(required=True)
41+
group.add_argument('--pull', help='Docker image URL to pull')
42+
group.add_argument('--file', help='Docker image gz file to load')
43+
group.add_argument('--image', help='Docker image name to run')
44+
45+
return parser.parse_args()
46+
47+
48+
def run_cmd(cmd, verbose=False):
49+
if verbose:
50+
print(' '.join(cmd))
51+
subprocess.run(cmd)
52+
53+
54+
def sonic_services_ctl(start, verbose):
55+
services = [
56+
'featured',
57+
'swss',
58+
'syncd',
59+
'pmon',
60+
'snmp',
61+
'lldp',
62+
'gnmi',
63+
'bgp',
64+
'eventd'
65+
]
66+
67+
systemctlops_start = ['unmask', 'enable', 'start']
68+
systemctlops_stop = ['stop', 'disable', 'mask']
69+
70+
# Start featured last
71+
if start:
72+
services = services[1:] + services[:1]
73+
74+
print('#', 'Starting' if start else 'Stopping', ', '.join(services))
75+
76+
ops = systemctlops_start if start else systemctlops_stop
77+
for op in ops:
78+
run_cmd(["systemctl", op] + services, verbose=verbose)
79+
80+
81+
def prepare_sonic(verbose=False):
82+
print('# Preparing sonic..')
83+
84+
sonic_services_ctl(start=False, verbose=verbose)
85+
86+
print('# Loading mlx5_core driver')
87+
run_cmd(["modprobe", "mlx5_core"], verbose=verbose)
88+
89+
90+
def restore_sonic(verbose=False):
91+
print('# Restoring sonic..')
92+
sonic_services_ctl(start=True, verbose=verbose)
93+
94+
95+
def pull_image(name):
96+
try:
97+
print(f'# Pulling image {name}')
98+
for line in api_client.pull(name, stream=True, decode=True):
99+
status = line.get('status', '')
100+
progress = line.get('progress', '')
101+
if progress:
102+
sys.stdout.write(f'\r{status}: {progress}')
103+
else:
104+
sys.stdout.write(f'\r{status}')
105+
sys.stdout.flush()
106+
print()
107+
return api_client.inspect_image(name)['Id']
108+
except docker.errors.APIError as e:
109+
print(f'Error pulling image: {e}')
110+
return None
111+
112+
113+
def load_gz(file):
114+
def chunked_file(f):
115+
loaded = 0
116+
total = os.path.getsize(file)
117+
chunk_size = max(8192, int(total / 1000))
118+
with open(file, 'rb') as f:
119+
while True:
120+
chunk = f.read(chunk_size)
121+
if not chunk:
122+
print()
123+
break
124+
loaded += len(chunk)
125+
progress = loaded / total * 100
126+
sys.stdout.write(f'\rLoading.. {progress:.2f}%')
127+
sys.stdout.flush()
128+
yield chunk
129+
130+
try:
131+
print(f'# Loading image {file}')
132+
return client.images.load(chunked_file(file))[0].id
133+
134+
except Exception as e:
135+
print(f'Failed to load: {e}')
136+
return None
137+
138+
139+
def run_container(image):
140+
print(f'# Running image {image}')
141+
config = {
142+
'image': image,
143+
'name': CONTAINER_NAME,
144+
'detach': True,
145+
'tty': True,
146+
'privileged': True,
147+
'network_mode': 'host',
148+
'auto_remove': True
149+
}
150+
151+
container = client.containers.run(**config) # nosemgrep: python.docker.security.audit.docker-arbitrary-container-run.docker-arbitrary-container-run
152+
print(f'Container name: {container.name}')
153+
154+
155+
def stop_container():
156+
try:
157+
container = client.containers.get(CONTAINER_NAME)
158+
container.stop()
159+
print(f'Container {CONTAINER_NAME} stopped and removed successfully')
160+
161+
except docker.errors.NotFound:
162+
print(f'Container {CONTAINER_NAME} not found')
163+
except Exception as e:
164+
print(f'Docker error occurred: {str(e)}')
165+
166+
167+
def byo_enable(args):
168+
prepare_sonic(verbose=True)
169+
if args.pull:
170+
image_name = pull_image(args.pull)
171+
elif args.file:
172+
image_name = load_gz(args.file)
173+
else:
174+
image_name = args.image
175+
run_container(image_name)
176+
177+
178+
def byo_disable():
179+
stop_container()
180+
restore_sonic(verbose=True)
181+
182+
183+
def main():
184+
args = get_args()
185+
if args.mode == 'enable':
186+
byo_enable(args)
187+
if args.mode == 'disable':
188+
byo_disable()
189+
190+
191+
if __name__ == "__main__":
192+
main()

0 commit comments

Comments
 (0)