diff --git a/src/charm.py b/src/charm.py index eb8e8e9b..682627f1 100755 --- a/src/charm.py +++ b/src/charm.py @@ -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 @@ -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") @@ -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.""" @@ -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: @@ -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: diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py index c062f1b6..a1b0ded2 100644 --- a/tests/unit/test_charm.py +++ b/tests/unit/test_charm.py @@ -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: @@ -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 @@ -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: @@ -724,6 +729,8 @@ 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 @@ -731,42 +738,6 @@ def bessctl_handler(_: testing.ExecArgs) -> testing.ExecResult: 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, @@ -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()