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
16 changes: 16 additions & 0 deletions netsim/ansible/tasks/linux/vbmc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
- name: Add VirtualBMC nodes
command: >
vbmc add {{ item.name }}
--port {{ item.ipmi_port }}
--address {{ item.ipmi_address|default('::') }}
--username {{ item.ipmi_user|default('admin') }}
--password {{ item.ipmi_password|default('admin') }}
tags: [ print_action, always ]
loop: "{{ vbmc_nodes|default([]) }}"
when: vbmc.server|default(False)

- name: Start VirtualBMC nodes
command: "vbmc start {{ item.name }}"
tags: [ print_action, always ]
loop: "{{ vbmc_nodes|default([]) }}"
when: vbmc.server|default(False)

Check failure on line 16 in netsim/ansible/tasks/linux/vbmc.yml

View workflow job for this annotation

GitHub Actions / tests

16:35 [new-line-at-end-of-file] no new line character at the end of file
Empty file.
36 changes: 36 additions & 0 deletions netsim/daemons/vbmc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
description: VirtualBMC - IPMI emulation for libvirt
daemon_config:
node:
module: [ vbmc ]
clab:
group_vars:
docker_shell: bash -il
ansible_connection: docker
ansible_user: root
image: netlab/virtualbmc:latest
build: 'https://netlab.tools/netlab/clab/#netlab-clab-build'
features:
initial:
roles: [ host ]
libvirt:
image:
features:
vbmc:
client: true
server: false
virtualbox:
image:
features:
vbmc:
server: true
client: false
initial:
roles: [ host ]
vbmc:
server: true
module: [ vbmc ]
group_vars:
docker_shell: bash -il
ansible_connection: docker
ansible_user: root
19 changes: 19 additions & 0 deletions netsim/daemons/vbmc/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
FROM ubuntu:24.04
ENV DEBIAN_FRONTEND=noninteractive

LABEL maintainer="Netlab project <netlab.tools>"
LABEL description="VirtualBMC - IPMI emulation for libvirt"

RUN apt-get update && apt-get install -y \
python3 python3-pip python3-libvirt \
&& rm -rf /var/lib/apt/lists/*

RUN python3 -m pip install virtualbmc --break-system-packages

# Create VirtualBMC configuration directory
RUN mkdir -p /root/.vbmc

WORKDIR /root

# VirtualBMC daemon needs to run in foreground mode for containers
CMD [ "vbmcd", "--foreground" ]
16 changes: 16 additions & 0 deletions netsim/daemons/vbmc/Dockerfile.ipmitool
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
FROM ubuntu:24.04
ENV DEBIAN_FRONTEND=noninteractive

LABEL maintainer="Netlab project <netlab.tools>"
LABEL description="ipmitool - IPMI client for testing"

RUN apt-get update && apt-get install -y \
ipmitool \
iputils-ping \
net-tools \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /root

# Keep container alive so we can run docker exec against it
CMD [ "tail", "-f", "/dev/null" ]
2 changes: 2 additions & 0 deletions netsim/devices/linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ libvirt:
server: true
relay:
ipv4: true
vbmc:
client: true
virtualbox:
image: bento/ubuntu-24.04
group_vars:
Expand Down
92 changes: 92 additions & 0 deletions netsim/modules/vbmc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#
# VirtualBMC module
#
import typing
from box import Box

from . import _Module
from ..utils import log
from .. import data
from ..augment import devices

'''
Check VirtualBMC server node compatibility
'''
def valid_vbmc_server(node: Box, topology: Box) -> bool:
if not node.get('vbmc.server', False):
return False

features = devices.get_device_features(node,topology.defaults)

if node.get('vbmc.server',False) and not features.vbmc.server:
log.error(
f'Node {node.name} cannot be a VirtualBMC server',
category=log.IncorrectValue,
module='vbmc')
return False
return True

'''
Build the list of VirtualBMC client nodes for the server to manage.
This function creates the vbmc_nodes list that will be used by Ansible tasks.
'''
def build_vbmc_node_list(topology: Box) -> None:
vbmc_nodes: list[dict[str, typing.Any]] = []

# Find all nodes that are VirtualBMC clients
for node_name, node in topology.get('nodes', {}).items():
if not node.get('vbmc.client', False):
continue

# Build and append IPMI configuration for this client node
vbmc_node = dict({
'name': node.get('domain', node_name),
'ipmi_port': node.get('vbmc.ipmi_port', 6230 + len(vbmc_nodes)),
'ipmi_address': node.get('vbmc.ipmi_address', '::'),
'ipmi_user': node.get('vbmc.ipmi_user', 'admin'),
'ipmi_password': node.get('vbmc.ipmi_password', 'admin'),
})

vbmc_nodes.append(vbmc_node)

# Store the list in topology for use by server nodes
topology.vbmc.nodes = vbmc_nodes

'''
Set the vbmc_nodes list in the VirtualBMC server node data
'''
def transform_vbmc_server_config(topology: Box) -> None:

if not topology.get('vbmc.nodes', False):
build_vbmc_node_list(topology)

# Find all VirtualBMC server nodes
for node_name, node in topology.get('nodes', {}).items():
if node.get('vbmc.server', False):
# Copy topology VirtualBMC nodes into server node data
node.vbmc_nodes = topology.vbmc.nodes

class VBMC(_Module):

"""
VirtualBMC module transformation:

* Check server node validity
* Build list of client nodes for servers to manage
"""
def module_post_transform(self, topology: Box) -> None:
for node in topology.nodes.values():
if not valid_vbmc_server(node, topology):
continue
else:
# Add necessary binds for qemu:///system access
# VirtualBMC uses this to abstract IPMI actions to libvirt domains
binds = [
'/var/run/libvirt/libvirt-sock:/var/run/libvirt/libvirt-sock',
'/var/run/libvirt/libvirt-sock-ro:/var/run/libvirt/libvirt-sock-ro'
]
for bind in binds:
data.append_to_list(node.clab, 'binds', bind)

# Build the client list for all servers
transform_vbmc_server_config(topology)
15 changes: 15 additions & 0 deletions netsim/modules/vbmc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# VirtualBMC default settings and attributes
#
---
transform_after: [ vlan, vrf, ospf, eigrp, isis, bgp ]
config_after: [ vlan, vrf, vxlan ]
attributes:
node:
client: bool
server: bool
ipmi_user: string
ipmi_password: string
ipmi_port: int
features:
client: Emulated Bare Metal
server: VirtualBMC Server
3 changes: 3 additions & 0 deletions netsim/providers/libvirt.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,9 @@ def create_vagrant_batches(topology: Box) -> None:

class Libvirt(_Provider):

def augment_node_data(self, node: Box, topology: Box) -> None:
node.domain = self.get_node_name(node.name,topology)

"""
pre_transform hook: mark multi-provider links as LAN links
"""
Expand Down
1 change: 1 addition & 0 deletions netsim/validate/linux.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import typing
import re
from netsim.data import global_vars
from netsim.validate.vbmc import *

def exec_ping(
host: str,
Expand Down
30 changes: 30 additions & 0 deletions netsim/validate/vbmc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""
VBMC validation routines
"""

from box import Box
import typing
import re
from netsim.data import global_vars
from netsim import providers

def exec_poweroff_test(id: str, data: Box, topology: Box) -> str:
ipmi_user: str = data.get('vbmc.ipmi_user', 'admin')
ipmi_password: str = data.get('vbmc.ipmi_password', 'admin')
ipmi_port: int = data.get('vbmc.ipmi_port', 6230)
return f'ipmitool -I lanplus -H {id} -U {ipmi_user} -P {ipmi_password} -p {ipmi_port} power off'

def valid_poweroff_test(id: str, data: Box, topology: Box) -> bool:
node_name = data.get('name', None)
_result = global_vars.get_result_dict('_result')
if 'Chassis Power Control: Down/Off' not in _result.stdout:
raise Exception(f'Node ({data.name}) did not power down on request.')

p_module = providers.get_provider_module(topology, 'libvirt')
state = p_module.call('get_lab_status').get(node_name, None)
if not state.status:
raise Exception(f'libvirt node state unreadable for ({data.name})')
elif 'shutoff' not in state.status:
raise Exception(f'libvirt node state is not \'shutoff\' for ({data.name})')
return True

2 changes: 1 addition & 1 deletion tests/errors/invalid-module.log
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
IncorrectValue in topology: attribute nodes.r2.module has invalid value(s): whatever
... valid values are: bfd, bgp, dhcp, eigrp, evpn, gateway, isis, lag, mpls, ospf, ripv2, routing, sr, srv6, stp, vlan, vrf, vxlan
... valid values are: bfd, bgp, dhcp, eigrp, evpn, gateway, isis, lag, mpls, ospf, ripv2, routing, sr, srv6, stp, vbmc, vlan, vrf, vxlan
IncorrectValue in topology: attribute module has invalid value(s): provider
Fatal error in netlab: Cannot proceed beyond this point due to errors, exiting
2 changes: 1 addition & 1 deletion tests/errors/module-missing-prerequisite.log
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
IncorrectValue in topology: attribute nodes.r1.module has invalid value(s): mody
... valid values are: bfd, bgp, dhcp, eigrp, evpn, gateway, isis, lag, modx, mpls, ospf, ripv2, routing, sr, srv6, stp, vlan, vrf, vxlan
... valid values are: bfd, bgp, dhcp, eigrp, evpn, gateway, isis, lag, modx, mpls, ospf, ripv2, routing, sr, srv6, stp, vbmc, vlan, vrf, vxlan
Fatal error in netlab: Cannot proceed beyond this point due to errors, exiting
2 changes: 1 addition & 1 deletion tests/errors/validate-list.log
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
IncorrectValue in groups: attribute groups.g1.module has invalid value(s): a
... valid values are: bfd, bgp, dhcp, eigrp, evpn, gateway, isis, lag, mpls, ospf, ripv2, routing, sr, srv6, stp, vlan, vrf, vxlan
... valid values are: bfd, bgp, dhcp, eigrp, evpn, gateway, isis, lag, mpls, ospf, ripv2, routing, sr, srv6, stp, vbmc, vlan, vrf, vxlan
IncorrectType in groups: attribute 'groups.g2.module' must be a scalar or a list, found dictionary
Fatal error in netlab: Cannot proceed beyond this point due to errors, exiting
62 changes: 62 additions & 0 deletions tests/integration/vbmc/01-vbmc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
---

Check failure on line 1 in tests/integration/vbmc/01-vbmc.yml

View workflow job for this annotation

GitHub Actions / tests

1:4 [new-lines] wrong new line character: expected \n
message: |
This lab validates the basic function of the vbmc daemon. A single libvirt
node is created and probed with the ipmitool Dockerfile provided alongside
the vbmc daemon.

groups:
baremetal:
members: [ h1, h2, bmc ]
module: [ vbmc ]

nodes:
h1:
device: linux
provider: libvirt
vbmc.client: true
h2:
device: linux
provider: libvirt
vbmc.client: true
vbmc.ipmi_user: operator
vbmc.ipmi_password: rotarepo
vbmc.ipmi_port: 62323
bmc:
device: vbmc
provider: clab
vbmc.server: true
ipmitool:
device: linux
provider: clab
image: netlab/vbmc.ipmitool:latest

validate:
poweroff:
description: Validate VBMC Function
nodes: [ ipmitool ]
devices: [ linux ]
pass: VBMC successfully controlled the power state of the libvirt domain
fail: VBMC was unable to control the power state of the libvirt domain
plugin: poweroff_test(nodes.bmc.mgmt.ipv4, nodes.h1, topology)

#poweroff:
# description: Validate VBMC Function
# nodes: [ ipmitool ]
# devices: [ linux ]
# pass: VBMC successfully controlled the power state of the libvirt domain
# fail: VBMC was unable to control the power state of the libvirt domain
# exec: |
# ipmitool -I lanplus -H bmc -U admin -P admin -p 6230 power off &&
# ipmitool -I lanplus -H bmc -U admin -P admin -p 6230 power status
# valid: |
# 'Chassis Power is off' in stdout
#customize:
# description: Validate VBMC Function
# nodes: [ ipmitool ]
# devices: [ linux ]
# pass: Alternate values for user/password/port applied successfully
# fail: There was an issue using alternate values for user/password/port
# exec: |
# ipmitool -I lanplus -H bmc -U operator -P rotarepo -p 62323 power status
# valid: |
# 'Chassis Power is on' in stdout

Check failure on line 62 in tests/integration/vbmc/01-vbmc.yml

View workflow job for this annotation

GitHub Actions / tests

62:39 [new-line-at-end-of-file] no new line character at the end of file
5 changes: 5 additions & 0 deletions tests/topology/expected/6pe.yml
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ nodes:
router_id: 10.0.0.4
box: cisco/iosv
device: iosv
domain: input_ce1
id: 4
interfaces:
- ifindex: 1
Expand Down Expand Up @@ -222,6 +223,7 @@ nodes:
router_id: 10.0.0.5
box: cisco/iosv
device: iosv
domain: input_ce2
id: 5
interfaces:
- ifindex: 1
Expand Down Expand Up @@ -259,6 +261,7 @@ nodes:
ipv6: true
box: cisco/iosv
device: iosv
domain: input_cr
id: 3
interfaces:
- ifindex: 1
Expand Down Expand Up @@ -372,6 +375,7 @@ nodes:
router_id: 10.0.0.1
box: cisco/iosv
device: iosv
domain: input_pe1
id: 1
interfaces:
- ifindex: 1
Expand Down Expand Up @@ -487,6 +491,7 @@ nodes:
router_id: 10.0.0.2
box: cisco/iosv
device: iosv
domain: input_pe2
id: 2
interfaces:
- ifindex: 1
Expand Down
Loading
Loading