Skip to content

Commit 606a119

Browse files
Distro CLI: Add DeviceUpdater skeleton with validation logic
- Add DeviceUpdater class with component validation - Add COMPONENT_SERVICES mapping for updatable components - Add test manifest and unit tests for validation logic - Stub implementations for artifact acquisition, package creation, and transfer This is the first step in implementing device update functionality.
1 parent 8493ac2 commit 606a119

File tree

5 files changed

+393
-46
lines changed

5 files changed

+393
-46
lines changed
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# Copyright (c) 2004-present, Facebook, Inc.
2+
# All rights reserved.
3+
#
4+
# This source code is licensed under the BSD-style license found in the
5+
# LICENSE file in the root directory of this source tree. An additional grant
6+
# of patent rights can be found in the PATENTS file in the same directory.
7+
8+
"""Device update logic for updating FBOSS services on devices."""
9+
10+
import logging
11+
from pathlib import Path
12+
13+
from distro_cli.lib.exceptions import DistroInfraError
14+
from distro_cli.lib.manifest import ImageManifest
15+
16+
logger = logging.getLogger(__name__)
17+
18+
# Component to systemd services mapping.
19+
COMPONENT_SERVICES: dict[str, list[str]] = {
20+
"fboss-forwarding-stack": ["wedge_agent", "fsdb", "qsfp_service"],
21+
"fboss-platform-stack": [
22+
"platform_manager",
23+
"sensor_service",
24+
"fan_service",
25+
"data_corral_service",
26+
],
27+
}
28+
29+
30+
class DeviceUpdateError(DistroInfraError):
31+
"""Error during device update."""
32+
33+
34+
class DeviceUpdater:
35+
"""Handles updating FBOSS services on a device.
36+
37+
Workflow:
38+
1. Validate component is supported for update
39+
2. Acquire artifacts (build OR download)
40+
3. Create update package (artifacts + service_update.sh) to scp to the device
41+
4. Get device IP
42+
5. SCP update package to device
43+
6. SSH: extract and run service_update.sh
44+
"""
45+
46+
def __init__(
47+
self,
48+
mac: str,
49+
manifest: ImageManifest,
50+
component: str,
51+
device_ip: str | None = None,
52+
):
53+
"""Initialize the DeviceUpdater.
54+
55+
Args:
56+
mac: Device MAC address
57+
manifest: Parsed image manifest
58+
component: Component name to update
59+
device_ip: Optional device IP (if already known)
60+
"""
61+
self.mac = mac
62+
self.manifest = manifest
63+
self.component = component
64+
self.device_ip = device_ip
65+
66+
def _get_services(self) -> list[str]:
67+
"""Get systemd services for the component."""
68+
return COMPONENT_SERVICES.get(self.component, [])
69+
70+
def validate(self) -> None:
71+
"""Validate the update request.
72+
73+
Raises:
74+
DeviceUpdateError: If validation fails
75+
"""
76+
if self.component not in COMPONENT_SERVICES:
77+
raise DeviceUpdateError(
78+
f"Component '{self.component}' is not updatable. "
79+
f"Updatable components: {', '.join(COMPONENT_SERVICES.keys())}"
80+
)
81+
82+
if not self.manifest.has_component(self.component):
83+
raise DeviceUpdateError(
84+
f"Component '{self.component}' not found in manifest"
85+
)
86+
87+
services = self._get_services()
88+
if not services:
89+
raise DeviceUpdateError(
90+
f"Component '{self.component}' has no services defined in COMPONENT_SERVICES"
91+
)
92+
93+
component_data = self.manifest.get_component(self.component)
94+
has_download = "download" in component_data
95+
has_execute = "execute" in component_data
96+
97+
if not has_download and not has_execute:
98+
raise DeviceUpdateError(
99+
f"Component '{self.component}' has neither 'download' nor 'execute'"
100+
)
101+
102+
def _acquire_artifacts(self) -> Path:
103+
"""Acquire component artifacts via build or download.
104+
105+
Returns:
106+
Path to the component artifact (tarball)
107+
108+
Raises:
109+
DeviceUpdateError: If artifact acquisition fails
110+
"""
111+
raise NotImplementedError("Stub")
112+
113+
def _create_update_package(self, artifact_path: Path) -> Path:
114+
"""Create update package with artifacts and update_service.sh script.
115+
116+
Args:
117+
artifact_path: Path to the component artifact tarball
118+
119+
Returns:
120+
Path to the created update package
121+
122+
Raises:
123+
DeviceUpdateError: If package creation fails
124+
"""
125+
raise NotImplementedError("Stub")
126+
127+
def _transfer_and_execute(self, package_path: Path, services: list[str]) -> None:
128+
"""Transfer update package to device and execute update_service.sh.
129+
130+
Args:
131+
package_path: Path to the update package tarball
132+
services: List of services to update
133+
134+
Raises:
135+
DeviceUpdateError: If transfer or execution fails
136+
"""
137+
raise NotImplementedError("Stub")
138+
139+
def update(self) -> bool:
140+
"""Execute the update workflow.
141+
142+
Returns:
143+
True if update succeeded
144+
145+
Raises:
146+
DeviceUpdateError: If update fails
147+
"""
148+
raise NotImplementedError("Stub")

fboss-image/distro_cli/ruff.toml

Lines changed: 0 additions & 37 deletions
This file was deleted.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"distribution_formats": {
3+
"onie": "fboss-onie.bin"
4+
},
5+
"kernel": {
6+
"download": "https://example.com/kernel.tar"
7+
},
8+
"fboss-forwarding-stack": {
9+
"download": "file:///opt/fboss/fboss-forwarding-stack.tar"
10+
},
11+
"fboss-platform-stack": {
12+
"execute": "/opt/fboss/build-platform-stack.sh"
13+
}
14+
}

0 commit comments

Comments
 (0)