Skip to content

Commit

Permalink
chore: Update configuration logic (#146)
Browse files Browse the repository at this point in the history
Co-authored-by: Ghislain Bourgeois <[email protected]>
  • Loading branch information
Mark Beierl and ghislainbourgeois authored Apr 5, 2024
1 parent a2cde23 commit f077ddb
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 87 deletions.
133 changes: 84 additions & 49 deletions src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@
DPDK_ACCESS_INTERFACE_RESOURCE_NAME = "intel.com/intel_sriov_vfio_access"
DPDK_CORE_INTERFACE_RESOURCE_NAME = "intel.com/intel_sriov_vfio_core"
CONFIG_FILE_NAME = "upf.json"
BESSCTL_CONFIGURE_EXECUTED_FILE_NAME = "bessctl_configure_executed"
BESSD_PORT = 10514
PROMETHEUS_PORT = 8080
PFCP_PORT = 8805
Expand Down Expand Up @@ -532,6 +531,16 @@ def _on_collect_unit_status(self, event: CollectStatusEvent): # noqa C901
event.add_status(WaitingStatus("Waiting for bessd service to run"))
logger.info("Waiting for bessd service to run")
return
if not self._is_bessd_grpc_service_ready():
event.add_status(
WaitingStatus("Waiting for bessd service to accept configuration messages")
)
logger.info("Waiting for bessd service to accept configuration messages")
return
if not self._is_bessd_configured():
event.add_status(WaitingStatus("Waiting for bessd configuration to complete"))
logger.info("Waiting for bessd configuration to complete")
return
if not service_is_running_on_container(self._bessd_container, self._routectl_service_name):
event.add_status(WaitingStatus("Waiting for routectl service to run"))
logger.info("Waiting for routectl service to run")
Expand Down Expand Up @@ -640,65 +649,95 @@ def _configure_bessd_workload(self) -> None:
logger.info("Service `routectl` restarted")
self._bessd_container.restart(self._bessd_service_name)
logger.info("Service `bessd` restarted")
self._wait_for_bessd_grpc_service_to_be_ready(timeout=60)
self._run_bess_configuration()

def _run_bess_configuration(self) -> None:
"""Runs bessd configuration in workload."""
if self._is_bessd_configured():
return

logger.info("Starting configuration of the `bessd` service")
command = "/opt/bess/bessctl/bessctl run /opt/bess/bessctl/conf/up4"
try:
(stdout, stderr) = self._exec_command_in_bessd_workload(
command=command,
environment=self._bessd_environment_variables,
timeout=30,
)

logger.info("Service `bessd` configuration script complete")
for line in stdout.splitlines():
logger.debug("`up4.bess`: %s", line)
if stderr:
for line in stderr.split():
logger.error("`up4.bess`: %s", line)
return
except ExecError as e:
logger.info("Failed running configuration for bess: %s", e.stderr)
except ChangeError:
logger.info("Timout executing: %s", command)

def _wait_for_bessd_grpc_service_to_be_ready(self, timeout: float = 60):
initial_time = time.time()
timeout = 300
while time.time() - initial_time <= timeout:
try:
if not self._is_bessctl_executed():
logger.info("Starting configuration of the `bessd` service")
self._exec_command_in_bessd_workload(
command="/opt/bess/bessctl/bessctl run /opt/bess/bessctl/conf/up4",
environment=self._bessd_environment_variables,
)
message = "Service `bessd` configured"
logger.info(message)
self._create_bessctl_executed_validation_file(message)
return
return
except (ChangeError, ExecError):
logger.info("Failed running configuration for bess")
time.sleep(2)
raise TimeoutError("Timed out trying to run configuration for bess")

def _configure_bessd_for_dpdk(self) -> None:
"""Configures bessd container for DPDK."""
dpdk = DPDK(
statefulset_name=self.model.app.name,
namespace=self._namespace,
dpdk_access_interface_resource_name=DPDK_ACCESS_INTERFACE_RESOURCE_NAME,
dpdk_core_interface_resource_name=DPDK_CORE_INTERFACE_RESOURCE_NAME,
)
if not dpdk.is_configured(container_name=self._bessd_container_name):
dpdk.configure(container_name=self._bessd_container_name)
while not self._is_bessd_grpc_service_ready():
if time.time() - initial_time > timeout:
raise TimeoutError("Timed out waiting for bessd gRPC server to become ready")
time.sleep(2)

def _is_bessctl_executed(self) -> bool:
"""Check if BESSD_CONFIG_CHECK_FILE_NAME exists.
def _is_bessd_grpc_service_ready(self) -> bool:
"""Checks if bessd grpc service is ready.
If bessctl configure is executed once this file exists.
Examines the output from bessctl to see if it is able to communicate
with bessd. This indicates the service is ready to accept configuration
commands.
Returns:
bool: True/False
"""
return path_exists(
container=self._bessd_container, path=f"/{BESSCTL_CONFIGURE_EXECUTED_FILE_NAME}"
)
command = "/opt/bess/bessctl/bessctl show version"
try:
self._exec_command_in_bessd_workload(
command=command,
timeout=10,
)
return True
except ExecError as e:
logger.info("gRPC Check: %s", e)
return False

def _is_bessd_configured(self) -> bool:
"""Checks if bessd has been configured.
def _create_bessctl_executed_validation_file(self, content) -> None:
"""Create BESSCTL_CONFIGURE_EXECUTED_FILE_NAME.
Examines the output from bessctl to show worker. If there is no
active worker, bessd is assumed not to be configured.
This must be created outside of the persistent storage volume so that
on container restart, bessd configuration will run again.
Returns:
bool: True/False
"""
push_file(
container=self._bessd_container,
path=f"/{BESSCTL_CONFIGURE_EXECUTED_FILE_NAME}",
source=content,
command = "/opt/bess/bessctl/bessctl show worker"
try:
(stdout, _) = self._exec_command_in_bessd_workload(
command=command,
timeout=10,
)
logger.debug("bessd configured workers: %s", stdout)
return True
except ExecError as e:
logger.info("Configuration check: %s", e)
return False

def _configure_bessd_for_dpdk(self) -> None:
"""Configures bessd container for DPDK."""
dpdk = DPDK(
statefulset_name=self.model.app.name,
namespace=self._namespace,
dpdk_access_interface_resource_name=DPDK_ACCESS_INTERFACE_RESOURCE_NAME,
dpdk_core_interface_resource_name=DPDK_CORE_INTERFACE_RESOURCE_NAME,
)
logger.info("Pushed %s configuration check file", BESSCTL_CONFIGURE_EXECUTED_FILE_NAME)
if not dpdk.is_configured(container_name=self._bessd_container_name):
dpdk.configure(container_name=self._bessd_container_name)

def _create_default_route(self) -> None:
"""Creates ip route towards core network."""
Expand Down Expand Up @@ -737,7 +776,7 @@ def _create_ip_tables_rule(self) -> None:

def _exec_command_in_bessd_workload(
self, command: str, timeout: Optional[int] = 30, environment: Optional[dict] = None
) -> tuple:
) -> tuple[str, str]:
"""Executes command in bessd container.
Args:
Expand All @@ -750,10 +789,6 @@ def _exec_command_in_bessd_workload(
timeout=timeout,
environment=environment,
)
for line in process.stdout:
logger.info(line)
for line in process.stderr:
logger.error(line)
return process.wait_output()

def _configure_pfcp_agent_workload(self) -> None:
Expand Down
51 changes: 13 additions & 38 deletions tests/unit/test_charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,8 @@ def test_given_can_connect_to_bessd_when_bessd_pebble_ready_then_bessctl_configu
timeout = 0
environment = {}

grpc_check_cmd = "/opt/bess/bessctl/bessctl show version".split()
config_check_cmd = "/opt/bess/bessctl/bessctl show worker".split()
bessctl_cmd = ["/opt/bess/bessctl/bessctl", "run", "/opt/bess/bessctl/conf/up4"]

def bessctl_handler(args: testing.ExecArgs) -> testing.ExecResult:
Expand All @@ -693,6 +695,8 @@ def bessctl_handler(args: testing.ExecArgs) -> testing.ExecResult:

self.harness.handle_exec("bessd", ["ip"], result=0)
self.harness.handle_exec("bessd", ["iptables-legacy"], result=0)
self.harness.handle_exec("bessd", grpc_check_cmd, result=0)
self.harness.handle_exec("bessd", config_check_cmd, result=1)
self.harness.handle_exec("bessd", bessctl_cmd, handler=bessctl_handler)
patch_is_ready.return_value = True

Expand All @@ -706,15 +710,16 @@ def bessctl_handler(args: testing.ExecArgs) -> testing.ExecResult:

@patch("ops.model.Container.get_service")
@patch(f"{MULTUS_LIBRARY_PATH}.KubernetesMultusCharmLib.is_ready")
def test_given_connects_and_bessctl_executed_file_exists_then_bessctl_configure_not_executed(
def test_given_connects_and_bess_configured_then_bessctl_configure_not_executed(
self, patch_is_ready, _
):
self.harness.set_can_connect("bessd", True)
self.harness.set_can_connect("pfcp-agent", True)
(self.root / "bessctl_configure_executed").write_text("")

bessctl_called = False

grpc_check_cmd = "/opt/bess/bessctl/bessctl show version".split()
config_check_cmd = "/opt/bess/bessctl/bessctl show worker".split()
bessctl_cmd = ["/opt/bess/bessctl/bessctl", "run", "/opt/bess/bessctl/conf/up4"]

def bessctl_handler(_: testing.ExecArgs) -> testing.ExecResult:
Expand All @@ -724,49 +729,15 @@ def bessctl_handler(_: testing.ExecArgs) -> testing.ExecResult:

self.harness.handle_exec("bessd", ["ip"], result=0)
self.harness.handle_exec("bessd", ["iptables-legacy"], result=0)
self.harness.handle_exec("bessd", grpc_check_cmd, result=0)
self.harness.handle_exec("bessd", config_check_cmd, result=0)
self.harness.handle_exec("bessd", bessctl_cmd, handler=bessctl_handler)
patch_is_ready.return_value = True

self.harness.container_pebble_ready(container_name="bessd")

self.assertFalse(bessctl_called)

@patch("ops.model.Container.get_service")
@patch(f"{MULTUS_LIBRARY_PATH}.KubernetesMultusCharmLib.is_ready")
def test_given_connects_and_bessctl_executed_file_dont_exist_then_bessctl_configure_executed(
self, patch_is_ready, _
):
self.harness.set_can_connect("bessd", True)
self.harness.set_can_connect("pfcp-agent", True)
bessctl_called = False
timeout = 0
environment = {}

bessctl_cmd = ["/opt/bess/bessctl/bessctl", "run", "/opt/bess/bessctl/conf/up4"]

def bessctl_handler(args: testing.ExecArgs) -> testing.ExecResult:
nonlocal bessctl_called
nonlocal timeout
nonlocal environment
bessctl_called = True
timeout = args.timeout
environment = args.environment
return testing.ExecResult(exit_code=0)

self.harness.handle_exec("bessd", ["ip"], result=0)
self.harness.handle_exec("bessd", ["iptables-legacy"], result=0)
self.harness.handle_exec("bessd", bessctl_cmd, handler=bessctl_handler)

patch_is_ready.return_value = True

self.harness.container_pebble_ready(container_name="bessd")

self.assertTrue(bessctl_called)
self.assertEqual(timeout, 30)
self.assertEqual(
environment, {"CONF_FILE": "/etc/bess/conf/upf.json", "PYTHONPATH": "/opt/bess"}
)

@patch(f"{MULTUS_LIBRARY_PATH}.KubernetesMultusCharmLib.is_ready")
def test_given_storage_not_attached_when_bessd_pebble_ready_then_status_is_waiting(
self,
Expand Down Expand Up @@ -830,6 +801,10 @@ def test_given_bessd_service_is_running_when_pfcp_agent_pebble_ready_then_pebble
patch_get_service.return_value = service_info_mock
patch_multus_is_ready.return_value = True
self.harness.set_can_connect(container="bessd", val=True)
grpc_check_cmd = "/opt/bess/bessctl/bessctl show version".split()
config_check_cmd = "/opt/bess/bessctl/bessctl show worker".split()
self.harness.handle_exec("bessd", grpc_check_cmd, result=0)
self.harness.handle_exec("bessd", config_check_cmd, result=0)

self.harness.container_pebble_ready(container_name="pfcp-agent")
self.harness.evaluate_status()
Expand Down

0 comments on commit f077ddb

Please sign in to comment.