Skip to content
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
85 changes: 85 additions & 0 deletions .github/workflows/fboss-sim.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
name: Build & Test fboss-sim
run-name: Build & Test fboss-sim
on:
pull_request:
paths:
- 'fboss-sim/**'
- 'fboss/cli/**'
- '.github/workflows/fboss-sim.yml'
# Allow manually triggering the workflow
workflow_dispatch:

# Cancel superseded runs on the same ref.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
fboss-sim:
if: github.repository_owner == 'facebook'
runs-on: 32-core-ubuntu
steps:
- name: Check out repository code
uses: actions/checkout@v6

# Build the fake-SAI agents and the fboss2 CLI / integration-test targets
# into <workspace>/.build_dir (the getdeps scratch dir that
# fboss-sim-docker-package.py reads). This also produces the CentOS
# fboss_image:latest used for the collect step below. --target may be
# repeated to build several targets sequentially into one scratch dir.
- name: Build fake-SAI agent + fboss2 targets
run: >
sudo
./fboss/oss/scripts/docker-build.py
--scratch-path ${{ github.workspace }}/.build_dir
--target fboss_fake_agent_targets
--target fboss2_targets
--no-docker-output
--local
--env-var BUILD_SAI_FAKE
--num-jobs 18

- name: Restore workspace ownership
run: sudo chown -R runner:runner ${{ github.workspace }}

# Phase 1: collect binaries + libs inside the CentOS fboss_image, so ldd
# resolves the same system libraries the runtime image base (CentOS
# Stream 9) provides. Writes <workspace>/tmp_build_dir.
- name: Collect fboss-sim artifacts (inside CentOS build image)
run: >
sudo
docker run --rm
-v ${{ github.workspace }}:/var/FBOSS/fboss:z
--cap-add=CAP_AUDIT_WRITE
fboss_image:latest
python3 /var/FBOSS/fboss/fboss-sim/scripts/fboss-sim-docker-package.py --collect-only

- name: Restore workspace ownership
run: sudo chown -R runner:runner ${{ github.workspace }}

# Phase 2: build the runtime image on the host (where the Docker daemon is).
- name: Build fboss-sim runtime image
run: python3 fboss-sim/scripts/fboss-sim-docker-package.py --build-only

- name: Start fboss-sim runtime container
run: python3 fboss-sim/scripts/fboss-sim-docker-run.py

- name: Wait for agent services to be ready
run: |
for i in $(seq 1 30); do
if docker exec fboss_sim_runtime_${USER} /opt/fboss/bin/fboss2 show interface >/dev/null 2>&1; then
echo "✓ Agent ready after $(( (i - 1) * 2 ))s"
exit 0
fi
echo " [$(( (i - 1) * 2 ))s] not ready yet, retrying..."
sleep 2
done
echo "❌ Agent not ready"
docker exec fboss_sim_runtime_${USER} tail -n 50 /var/facebook/logs/fboss/wedge_agent.log || true
exit 1

- name: Run fboss2 integration test
run: docker exec fboss_sim_runtime_${USER} /opt/fboss/bin/fboss2_integration_test
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1341,8 +1341,8 @@ add_dependencies(fboss_fake_agent_targets
fboss_agent_thrift_libs
fboss_sw_agent
sai_replayer-fake
wedge_agent-fake
fboss_hw_agent-fake
wedge_agent-sai_impl
fboss_hw_agent-sai_impl
)

add_custom_target(fboss_fake_agent_test_targets)
Expand Down
9 changes: 5 additions & 4 deletions cmake/AgentPlatformsSai.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -206,10 +206,11 @@ find_library(SAI_IMPL sai_impl)
message(STATUS "SAI_IMPL: ${SAI_IMPL}")

if(BUILD_SAI_FAKE)
BUILD_SAI_WEDGE_AGENT("fake" fake_sai)
endif()

if(SAI_IMPL)
# Name fake-SAI agent binaries with the -sai_impl suffix so they sit at
# the same /opt/fboss/bin/ paths the systemd unit files reference and
# package.py FORWARDING_BINARIES matches without modification.
BUILD_SAI_WEDGE_AGENT("sai_impl" fake_sai)
elseif(SAI_IMPL)
BUILD_SAI_WEDGE_AGENT("sai_impl" ${SAI_IMPL})
install(
TARGETS
Expand Down
130 changes: 130 additions & 0 deletions fboss-sim/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# fboss-sim - FBOSS Simulator Runtime

Minimal Docker runtime (~1.2 GB) for FBOSS with fake SAI — no hardware required.

## Quick Start

```bash
# 1. Build FBOSS with fake SAI (binaries land in .build_dir/build/fboss/).
# Use the standard getdeps OSS build with the fake-SAI target.
sudo ./build/fbcode_builder/getdeps.py build --allow-system-packages fboss

# 2. Build the runtime image.
# The packager has two phases:
# --collect-only : resolve binaries+libs into tmp_build_dir/ (ldd must run
# in an OS matching the image base — CentOS Stream 9)
# --build-only : docker build the image from tmp_build_dir/
# If you built on a CentOS Stream 9 host, run both at once with no flag:
python3 fboss-sim/scripts/fboss-sim-docker-package.py

# 3. Start the container
python3 fboss-sim/scripts/fboss-sim-docker-run.py

# 4. Run integration test
docker exec fboss_sim_runtime_${USER} /opt/fboss/bin/fboss2_integration_test
```

### Split build/host environments

When FBOSS is built inside a CentOS build container on a different-distro host
(e.g. Ubuntu), `ldd` must resolve libraries inside the container, but
`docker build` needs the host's Docker daemon. Run the two phases separately:

```bash
# collect inside the build container (CentOS libs resolve correctly)
docker exec <build-container> python3 \
fboss-sim/scripts/fboss-sim-docker-package.py --collect-only

# build the image on the host
python3 fboss-sim/scripts/fboss-sim-docker-package.py --build-only
```

## Agent Modes

**Split mode** (default): `fboss_sw_agent` + `fboss_hw_agent@0` run as separate systemd services.
The hw_agent connects to sw_agent over IPv6 (`::1`). This requires the container to have a
non-loopback IPv6 address — `fboss-sim-docker-run.py` handles this automatically by creating
a user-defined IPv6-enabled Docker network (`fboss_sim_net_${USER}`, subnet `fd00:fb05:5::/64`).

**Monolithic mode**: single `wedge_agent` process.
```bash
docker exec fboss_sim_runtime_${USER} switch-agent-mode.sh mono
```

## Useful Commands

```bash
# Check agent status
docker exec fboss_sim_runtime_${USER} systemctl status fboss_sw_agent
docker exec fboss_sim_runtime_${USER} systemctl status fboss_hw_agent@0

# View agent logs
docker exec fboss_sim_runtime_${USER} tail -f /var/facebook/logs/fboss/wedge_agent.log

# Run FBOSS CLI
docker exec fboss_sim_runtime_${USER} /opt/fboss/bin/fboss2 show interface

# Open a shell
docker exec -it fboss_sim_runtime_${USER} bash
```

## Architecture

```
fboss-sim/
├── docker/
│ ├── Dockerfile.runtime # CentOS Stream 9 minimal runtime image
│ └── runtime/
│ ├── mono.conf # Monolithic agent config
│ ├── fruid.json # Platform identification (virtual env)
│ ├── setup-container.sh # Runs at image build time: symlinks, jemalloc, services
│ └── switch-agent-mode.sh # Toggle between split and monolithic mode
└── scripts/
├── fboss-sim-docker-package.py # Collect binaries+libs (via ldd), build runtime image
└── fboss-sim-docker-run.py # Create IPv6 network, start runtime container
```

`Dockerfile.runtime` copies the FBOSS `centos-09.0` rootfs overlay
(`fboss-image/image_builder/templates/centos-09.0/root_files/`) for the systemd
layout — the agent units the simulator runs (`fboss_sw_agent`,
`fboss_hw_agent@`, the hw-agents target), the login-shell env hook, and the
eth0 IPv6 sysctl. That rootfs is part of the FBOSS tree, so this directory
has no external build dependencies.

### How `fboss-sim-docker-package.py` works

1. Verifies required binaries exist in `.build_dir/build/fboss/`
2. **Collect phase** (`--collect-only`, or the first half of a flagless run):
resolves each binary's shared libraries via `ldd` and copies binaries,
libraries, and config files into `tmp_build_dir/`. This must run where the
resolved libs match the image base OS (CentOS Stream 9).
3. **Build phase** (`--build-only`, or the second half of a flagless run):
builds the Docker image from `Dockerfile.runtime` using `tmp_build_dir/` as
context, then cleans up `tmp_build_dir/`.

### Why IPv6 matters for split mode

`folly::SocketAddress::setFromLocalPort()` calls `getaddrinfo(nullptr, port, AI_ADDRCONFIG)`.
`AI_ADDRCONFIG` only returns IPv6 results if the host has at least one non-loopback IPv6 address.
Docker's default bridge sets `net.ipv6.conf.eth0.disable_ipv6=1` per-interface, so containers
get no non-loopback IPv6 → Thrift servers bind to `0.0.0.0` only → hw_agent's connection to
`::1` is refused. The user-defined network with `--ipv6` prevents this.

## Binaries Included

| Binary | Purpose |
|--------|---------|
| `wedge_agent-sai_impl` | Monolithic agent (fake SAI) |
| `fboss_sw_agent` | Split mode: SW/control plane |
| `fboss_hw_agent-sai_impl` | Split mode: HW/forwarding plane (fake SAI) |
| `fboss2` | FBOSS CLI |
| `fboss2-dev` | FBOSS dev CLI |
| `fboss2_integration_test` | CLI integration test suite |
| `setup_fboss_env` | Environment setup helper |

## Container Capabilities

- `CAP_NET_ADMIN`: TUN interface creation (TunManager)
- `CAP_SYS_ADMIN`: systemd cgroup management
- `/dev/net/tun`: virtual network interfaces
- `--shm-size=512m`: shared memory for FBOSS IPC
92 changes: 92 additions & 0 deletions fboss-sim/docker/Dockerfile.runtime
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Minimal FBOSS Simulator Runtime Image
# This image contains only the essential runtime components (~1.2 GB vs 34 GB)
# Build artifacts are copied from tmp_build_dir (extracted by fboss-sim-docker-package.py)

FROM quay.io/centos/centos:stream9

# Install all runtime dependencies in a single layer
# - epel-release: provides EPEL repository for jemalloc
# - systemd: provides systemctl for service management
# - iproute: provides /sbin/ip for network interface management (used by wedge_agent)
# - procps-ng: provides pkill for process management (used by AgentPreStartExec)
# - jemalloc: alternative malloc implementation that's more robust against corruption
# - jq: JSON processor for config manipulation
# - vim, git: development/debugging tools
# - sudo: privilege escalation for runtime tooling/scripts
RUN dnf install -y \
epel-release \
&& dnf install -y \
systemd \
vim \
git \
iproute \
procps-ng \
jemalloc \
jq \
sudo \
&& dnf clean all && rm -rf /var/cache/dnf

# Create FBOSS directories
RUN mkdir -p \
/opt/fboss/bin \
/opt/fboss/lib \
/opt/fboss/share \
/etc/coop \
/root/config \
/var/facebook/fboss \
/var/facebook/logs/fboss \
/dev/shm/fboss

# Copy FBOSS production rootfs overlay (systemd services, helper scripts, configs)
COPY fboss-image/image_builder/templates/centos-09.0/root_files/ /

# Copy mono agent configuration (base config)
COPY fboss-sim/docker/runtime/mono.conf /root/config/mono.conf

# Generate split.conf from mono.conf by setting multi_switch to true
# Using jq for robust JSON manipulation (handles formatting, validates syntax)
RUN jq '.defaultCommandLineArgs.multi_switch = "true"' \
/root/config/mono.conf > /root/config/split.conf

# Copy fruid.json (platform identification for virtual environment)
COPY fboss-sim/docker/runtime/fruid.json /var/facebook/fboss/fruid.json

# Copy agent mode switching script
COPY fboss-sim/docker/runtime/switch-agent-mode.sh /usr/local/bin/switch-agent-mode.sh

# Copy container setup script
COPY fboss-sim/docker/runtime/setup-container.sh /tmp/setup-container.sh

# Copy FBOSS runtime binaries and libraries from tmp_build_dir
# The fboss-sim-docker-package.py script extracts fboss_bins.tar.zst to tmp_build_dir
# Optimized layer ordering: rarely-changing content first, frequently-changing last

# Layer 1: Shared libraries (rarely change)
COPY tmp_build_dir/lib/ /opt/fboss/lib/

# Register /opt/fboss/lib with the dynamic linker so binaries resolve their deps
# without needing LD_LIBRARY_PATH to be set in the calling shell.
RUN echo "/opt/fboss/lib" > /etc/ld.so.conf.d/fboss.conf && ldconfig

# Layer 2: Share configs and platform data (rarely change)
COPY tmp_build_dir/share/ /opt/fboss/share/

# Layer 3: All FBOSS binaries (7 required binaries pre-filtered by fboss-sim-docker-package.py)
# Includes: wedge_agent-sai_impl, fboss_sw_agent, fboss_hw_agent-sai_impl, fboss2, fboss2-dev, fboss2_integration_test, setup_fboss_env
COPY tmp_build_dir/bin/ /opt/fboss/bin/

# Run container setup script
# This configures: symlinks, jemalloc, multi-switch mode, service masking, etc.
RUN chmod +x /tmp/setup-container.sh && \
/tmp/setup-container.sh && \
rm /tmp/setup-container.sh

ENV PATH="/opt/fboss/bin:/usr/local/bin:${PATH}" \
LD_LIBRARY_PATH="/opt/fboss/lib" \
FBOSS2_DEV_PATH="/opt/fboss/bin/fboss2-dev" \
MALLOC_ARENA_MAX=1 \
MALLOC_MMAP_THRESHOLD_=0 \
MALLOC_CHECK_=0

STOPSIGNAL SIGRTMIN+3
CMD ["/sbin/init"]
28 changes: 28 additions & 0 deletions fboss-sim/docker/runtime/fruid.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"Actions": [],
"Resources": [],
"Information": {
"Product Sub-Version": "1",
"Facebook PCBA Part Number": "",
"Product Version": "1",
"Product Part Number": "VIRTUAL-001",
"Extended MAC Address Size": "139",
"Facebook PCB Part Number": "",
"Product Name": "MONTBLANC",
"Local MAC": "02:00:00:00:00:01",
"PCB Manufacturer": "Virtual",
"CRC8": "0x0",
"System Assembly Part Number": "VIRT001",
"ODM PCBA Serial Number": "",
"Product Serial Number": "VIRTUAL-MONTBLANC-001",
"ODM PCBA Part Number": "",
"System Manufacturing Date": "2026031300",
"Version": "0",
"Location on Fabric": "Virtual",
"Assembled At": "Container",
"Product Production State": "0",
"Product Asset Tag": "fboss_base_virtual",
"Extended MAC Base": "02:00:00:00:00:00",
"System Manufacturer": "FBOSS Virtual Environment"
}
}
Loading
Loading