| name | fabric-recipes |
| description | Deploy common applications on FABRIC including web servers, LLMs, scientific workflows, DHCP, BGP peering, and network monitoring |
| allowed-tools | ["Read","Grep","Glob","Write","Edit","Bash"] |
Instructions
When invoked, generate code for deploying common applications and services on FABRIC. Available recipes:
- Apache Web Server — Multi-tier web service with firewall
- Ollama LLM — Local large language model inference on GPU nodes
- Pegasus/HTCondor — Scientific workflow engine across multiple sites
- DHCP Server — Dynamic IP allocation on L2 networks
- BGP Peering — PEERING client for BGP route advertisement
- pmacctd — NetFlow/sFlow traffic accounting
- Sub-Interfaces — VLAN-tagged virtual interfaces
Help the user choose the right recipe and generate complete code.
Patterns
Apache Web Server
from fabrictestbed_extensions.fablib.fablib import FablibManager
from ipaddress import IPv4Network
fablib = FablibManager()
site = fablib.get_random_site()
slice = fablib.new_slice(name="apache-web-server")
subnet = IPv4Network("192.168.1.0/24")
net = slice.add_l2network(name="net1", subnet=subnet)
server = slice.add_node(name="server", site=site, image="default_ubuntu_20")
server_iface = server.add_component(model="NIC_Basic", name="nic1").get_interfaces()[0]
server_iface.set_mode("auto")
net.add_interface(server_iface)
client = slice.add_node(name="client", site=site, image="default_ubuntu_20")
client_iface = client.add_component(model="NIC_Basic", name="nic1").get_interfaces()[0]
client_iface.set_mode("auto")
net.add_interface(client_iface)
slice.submit()
server = slice.get_node(name="server")
server.execute("sudo apt-get update && sudo apt-get install -y apache2")
server.execute("sudo ufw allow 'Apache' && sudo ufw allow ssh && echo 'y' | sudo ufw enable")
apache_root = "/home/ubuntu/apache_contents"
server.execute(f"mkdir -p {apache_root}")
server.execute(
f"sudo sed -i 's,/var/www/html,{apache_root},g' "
"/etc/apache2/sites-available/000-default.conf"
)
server.execute("sudo service apache2 restart")
server.execute(f"echo '<h1>Hello FABRIC</h1>' > {apache_root}/index.html")
server_addr = server.get_interface(network_name="net1").get_ip_addr()
client = slice.get_node(name="client")
stdout, _ = client.execute(f"curl http://{server_addr}")
print(stdout)
Ollama LLM Server (GPU Node)
site = fablib.get_random_site(
filter_function=lambda x: x['rtx6000_available'] > 0
)
slice = fablib.new_slice(name="ollama-llm")
node = slice.add_node(
name="llm-node",
site=site,
cores=16,
ram=64,
disk=200,
image="default_ubuntu_22",
)
node.add_component(model="GPU_RTX6000", name="gpu1")
node.add_fabnet()
slice.submit()
node = slice.get_node(name="llm-node")
node.execute("curl -fsSL https://ollama.com/install.sh | sh")
override_config = """[Service]
Environment="OLLAMA_HOST=0.0.0.0:11434"
"""
node.execute("sudo mkdir -p /etc/systemd/system/ollama.service.d")
node.execute(
f"echo '{override_config}' | "
"sudo tee /etc/systemd/system/ollama.service.d/override.conf"
)
node.execute("sudo systemctl daemon-reload && sudo systemctl restart ollama")
node.execute("ollama pull deepseek-r1:7b")
stdout, _ = node.execute('ollama run deepseek-r1:7b "Tell me a joke"')
print(stdout)
ollama_ip = node.get_interface(
network_name=f"FABNET_IPv4_{node.get_site()}"
).get_ip_addr()
print(f"Ollama API: http://{ollama_ip}:11434")
Pegasus/HTCondor Scientific Workflow
sites = fablib.get_random_sites(count=2)
slice = fablib.new_slice(name="pegasus-experiment")
submit = slice.add_node(
name="pegasus-submit",
site=sites[0],
cores=16,
ram=32,
disk=500,
)
submit.add_fabnet()
for i, site in enumerate(sites):
worker = slice.add_node(
name=f"worker-{i}",
site=site,
cores=24,
ram=48,
disk=500,
)
worker.add_fabnet()
slice.submit()
nodes = slice.get_nodes()
host_to_ip = {}
for n in nodes:
for iface in n.get_interfaces():
ip = iface.get_ip_addr()
if ip:
host_to_ip[n.get_name()] = str(ip)
break
hosts_block = "\n".join(f"{ip} {host}" for host, ip in host_to_ip.items())
for n in nodes:
n.execute(f"sudo sh -c 'echo \"{hosts_block}\" >> /etc/hosts'")
for n in nodes:
n.execute('ssh-keygen -t rsa -N "" -f ~/.ssh/id_rsa', quiet=True)
n.execute('sudo ssh-keygen -t rsa -N "" -f /root/.ssh/id_rsa', quiet=True)
DHCP Server on L2 Network
from ipaddress import IPv4Network, IPv4Address
subnet = IPv4Network("192.168.1.0/24")
slice = fablib.new_slice(name="dhcp-experiment")
site = fablib.get_random_site()
net = slice.add_l2network(name="dhcp-net", subnet=subnet)
server = slice.add_node(name="dhcp-server", site=site)
server_iface = server.add_component(model="NIC_Basic", name="nic1").get_interfaces()[0]
server_iface.set_mode("config")
net.add_interface(server_iface)
for i in range(3):
client = slice.add_node(name=f"client-{i}", site=site)
iface = client.add_component(model="NIC_Basic", name="nic1").get_interfaces()[0]
iface.set_mode("manual")
net.add_interface(iface)
slice.submit()
server = slice.get_node(name="dhcp-server")
server_iface = server.get_interface(network_name="dhcp-net")
available_ips = list(subnet.hosts())
server_iface.ip_addr_add(addr=available_ips[0], subnet=subnet)
server.execute(f"sudo ifconfig {server_iface.get_os_interface()} up")
server.execute("sudo apt-get update && sudo apt-get install -y isc-dhcp-server")
dhcpd_config = """
subnet 192.168.1.0 netmask 255.255.255.0 {
range 192.168.1.100 192.168.1.200;
option subnet-mask 255.255.255.0;
}
"""
server.execute(f"sudo bash -c 'echo \"{dhcpd_config}\" >> /etc/dhcp/dhcpd.conf'")
server.execute("sudo systemctl restart isc-dhcp-server")
for i in range(3):
client = slice.get_node(name=f"client-{i}")
os_iface = client.get_interface(network_name="dhcp-net").get_os_interface()
client.execute(f"sudo dhclient {os_iface}")
stdout, _ = client.execute(f"ip addr show {os_iface}")
print(f"client-{i}: {stdout}")
BGP Peering Client
site = fablib.get_random_site()
slice = fablib.new_slice(name="peering-client")
node = slice.add_node(name="peering-node", site=site, cores=4, ram=16, disk=50)
fab6 = slice.add_l3network(name="fab6", type="IPv6")
iface = node.add_component(model="NIC_Basic", name="nic1").get_interfaces()[0]
iface.set_mode("auto")
fab6.add_interface(iface)
slice.submit()
node = slice.get_node(name="peering-node")
fab6net = slice.get_network(name="fab6")
fab6iface = node.get_interface(network_name="fab6")
node.execute(f"sudo ip link set dev {fab6iface.get_os_interface()} up")
node.ip_route_add(subnet=fablib.FABNETV6_SUBNET, gateway=fab6net.get_gateway())
node.execute(
"git clone --branch fabric https://github.com/PEERINGTestbed/client.git"
)
node.upload_file("client.crt", "/home/ubuntu/client/certs/client.crt")
node.upload_file("prefixes.txt", "/home/ubuntu/client/prefixes.txt")
pmacctd Network Monitoring
from ipaddress import IPv6Address, IPv6Network
site = fablib.get_random_site()
slice = fablib.new_slice(name="pmacctd-experiment")
net = slice.add_l2network(name="monitor-net")
node1 = slice.add_node(name="sender", site=site)
iface1 = node1.add_component(model="NIC_Basic", name="nic1").get_interfaces()[0]
net.add_interface(iface1)
node2 = slice.add_node(name="collector", site=site)
iface2 = node2.add_component(model="NIC_Basic", name="nic1").get_interfaces()[0]
net.add_interface(iface2)
slice.submit()
node1 = slice.get_node(name="sender")
iface1 = node1.get_interface(network_name="monitor-net")
iface1.ip_addr_add(addr=IPv6Address("fd3f:f209:c712::1"), subnet=IPv6Network("fd3f:f209:c712::/48"))
node1.execute(f"sudo ifconfig {iface1.get_os_interface()} up")
node1.execute(
"sudo apt-get update && "
"sudo apt-get install -y libpcap-dev pkg-config libtool autoconf automake libjansson-dev"
)
node1.execute(
"wget https://github.com/pmacct/pmacct/archive/refs/tags/v1.7.8.tar.gz && "
"tar xzf v1.7.8.tar.gz && cd pmacct-1.7.8 && "
"./autogen.sh && ./configure --enable-jansson && make && sudo make install"
)
pmacctd_config = """
daemonize: false
pcap_interface: {iface_name}
aggregate: src_host, dst_host, src_port, dst_port, proto
plugins: print[csv]
print_output[csv]: csv
""".format(iface_name=iface1.get_os_interface())
node1.execute(f"echo '{pmacctd_config}' > pcap_csv.conf")
node1.execute("sudo pmacctd -f pcap_csv.conf &")
Sub-Interfaces (VLAN Tagging)
site = fablib.get_random_site(
filter_function=lambda x: x['nic_connectx_6_available'] > 0
)
slice = fablib.new_slice(name="vlan-experiment")
net1 = slice.add_l3network(name="vlan100", type="IPv4")
net2 = slice.add_l3network(name="vlan200", type="IPv4")
node = slice.add_node(name="multi-vlan", site=site, cores=4, ram=16, disk=50)
nic = node.add_component(model="NIC_ConnectX_6", name="nic1").get_interfaces()[0]
sub1 = nic.add_sub_interface("child1", vlan="100")
sub1.set_mode("auto")
net1.add_interface(sub1)
sub2 = nic.add_sub_interface("child2", vlan="200")
sub2.set_mode("auto")
net2.add_interface(sub2)
node.add_route(subnet=fablib.FABNETV4_SUBNET, next_hop=net1.get_gateway())
slice.submit()
node = slice.get_node(name="multi-vlan")
stdout, _ = node.execute("ip addr show")
print(stdout)
for net_name in ["vlan100", "vlan200"]:
iface = node.get_interface(network_name=net_name)
print(f"{net_name}: {iface.get_ip_addr()}")
Important Notes
- Apache: Use
ufw for firewall; change document root via sites-available config
- Ollama: Requires GPU node; set
OLLAMA_HOST=0.0.0.0 for remote access
- Pegasus: Needs SSH key exchange and
/etc/hosts across all nodes
- DHCP: Server interface uses
'config' mode; clients use 'manual' mode
- BGP Peering: Requires PEERING testbed credentials and FABNet IPv6
- pmacctd: Supports CSV, IPFIX, and JSON output formats
- Sub-interfaces: Require dedicated NICs (ConnectX-5/6/7), not NIC_Basic