Skip to content

Commit

Permalink
Merge branch 'main' into additional_comp
Browse files Browse the repository at this point in the history
  • Loading branch information
fred-labs committed Sep 2, 2024
2 parents bb338ab + 6f05d87 commit 52c2d9a
Show file tree
Hide file tree
Showing 67 changed files with 3,021 additions and 3,942 deletions.
3 changes: 3 additions & 0 deletions docs/development.rst
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ Implement an Action
- Make use of ``kwargs['logger']``, available in ``setup()``
- If you want to draw markers for RViz, use ``kwargs['marker_handler']``, available in ``setup()`` (with ROS backend)
- Use arguments from ``__init__()`` for a longer running initialization in ``setup()`` and the arguments from ``execute()`` to set values just before executing the action.
- ``__init__()`` and ``setup()`` are called once, ``execute()`` might be called multiple times.
- osc2 arguments can only be consumed once, either in ``__init__()`` or ``execute()``. Exception: If an ``associated_actor`` exists, it's an argument of both methods.
- Arguments that need late resolving (e.g. referring to variables or external methods) need to be consumed in ``execute()``.
- ``setup()`` provides several arguments that might be useful:
- ``input_dir``: Directory containing the scenario file
- ``output_dir``: If given on command-line, contains the directory to save output to
Expand Down
8 changes: 6 additions & 2 deletions docs/libraries.rst
Original file line number Diff line number Diff line change
Expand Up @@ -437,10 +437,14 @@ Patch an existing Kubernetes network policy.
- ``string``
-
- The target network policy to patch
* - ``network_enabled``
* - ``ingress_enabled``
- ``bool``
-
- Should the network be enabled
- Should ingress (i.e., incoming) network traffic be enabled
* - ``egress_enabled``
- ``bool``
-
- Should egress (i.e., outgoing) network traffic be enabled
* - ``match_label``
- ``key_value``
-
Expand Down
8 changes: 4 additions & 4 deletions docs/tutorials.rst
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ Then, we can write the implementation of action plugin in Python.
class CustomAction(BaseAction):
def __init__(self, data: str):
def __init__(self):
super().__init__()
def execute(self, data: str):
Expand All @@ -105,9 +105,9 @@ Then, we can write the implementation of action plugin in Python.
In the example, we created a custom action plugin to print a message on the
screen. The first step is to create an action implementation, based on the class ``BaseAction``.
There are two methods that can be overloaded in order to receive the action arguments as defined in the osc file.
The first is the ``__init__()`` function which gets the argument values as they get initialized during parsing the scenario file.
The second is the ``execute()`` function which gets the argument values as they are currently defined at the time the action gets executed.
This allows to initialize the action and then set the latest values just before the action gets triggered.

1. ``__init__()`` function which is called once for each action instance. It can consume any of the arguments defined within the scenario file. If there are long-running initialization tasks, it is good practice to execute those within ``setup()``, which is also only called once.
2. ``execute()`` function is called when the action gets active. It receives all remaining arguments, that are not consumed within ``__init__()``. It is good practice to consume as many arguments as possible here, to allow late-resolving (e.g. receiving the latest value from variables or external methods).

The action plugin ``custom_action`` only defines one parameter ``data``, so the behavior only has to accept ``data`` as an
argument. Then, override the ``update()`` function to define how the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

class CustomAction(BaseAction):

def __init__(self, data: str): # get action arguments, at the time of initialization
def __init__(self): # get action arguments, at the time of initialization
super().__init__()

def execute(self, data: str): # get action arguments, at the time of execution (may got updated during scenario execution)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from enum import Enum

import py_trees
from scenario_execution.actions.base_action import BaseAction
from scenario_execution.actions.base_action import BaseAction, ActionError

import docker
import tempfile
Expand Down Expand Up @@ -46,20 +46,20 @@ def setup(self, **kwargs):
# self.output_dir = tempfile.mkdtemp() # for testing: does not remove directory afterwards

if "input_dir" not in kwargs:
raise ValueError("input_dir not defined.")
raise ActionError("input_dir not defined.", action=self)
input_dir = kwargs["input_dir"]
# check docker image
self.client = docker.from_env()
image_name = 'floorplan:latest'
filterred_images = self.client.images.list(filters={'reference': image_name})
if len(filterred_images) == 0:
raise ValueError(f"Required docker image '{image_name}' does not exist.")
raise ActionError(f"Required docker image '{image_name}' does not exist.", action=self)

# check files
if not os.path.isabs(self.file_path):
self.file_path = os.path.join(input_dir, self.file_path)
if not os.path.isfile(self.file_path):
raise ValueError(f"Floorplan file {self.file_path} not found.")
raise ActionError(f"Floorplan file {self.file_path} not found.", action=self)
self.floorplan_name = os.path.splitext(os.path.basename(self.file_path))[0]

def update(self) -> py_trees.common.Status:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,32 +17,32 @@
import os
import py_trees
from scenario_execution_gazebo.actions.utils import SpawnUtils
from scenario_execution.actions.base_action import BaseAction
from scenario_execution.actions.base_action import BaseAction, ActionError
from shutil import which
import tempfile


class GenerateGazeboWorld(BaseAction):

def __init__(self, associated_actor, sdf_template: str, arguments: list):
def __init__(self, associated_actor, sdf_template: str):
super().__init__()
self.sdf_template = sdf_template
self.spawn_utils = SpawnUtils(self.logger)

def setup(self, **kwargs):
if which("xacro") is None:
raise ValueError("'xacro' not found.")
raise ActionError("'xacro' not found.", action=self)
if "input_dir" not in kwargs:
raise ValueError("input_dir not defined.")
raise ActionError("input_dir not defined.", action=self)
input_dir = kwargs["input_dir"]

if not os.path.isabs(self.sdf_template):
self.sdf_template = os.path.join(input_dir, self.sdf_template)
if not os.path.isfile(self.sdf_template):
raise ValueError(f"SDF Template {self.sdf_template} not found.")
raise ActionError(f"SDF Template {self.sdf_template} not found.", action=self)
self.tmp_file = tempfile.NamedTemporaryFile(suffix=".sdf") # for testing, do not delete temp file: delete=False

def execute(self, associated_actor, sdf_template: str, arguments: list):
def execute(self, associated_actor, arguments: list):
self.arguments_string = ""
for elem in arguments:
self.arguments_string += f'{elem["key"]}:={elem["value"]}'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ class GazeboActorExists(RunProcess):
"""

def __init__(self, entity_name: str, world_name: str):
def __init__(self):
super().__init__()
self.entity_name = None
self.current_state = ActorExistsActionState.IDLE

def execute(self, entity_name: str, world_name: str): # pylint: disable=arguments-differ
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ class GazeboDeleteActor(RunProcess):
"""

def __init__(self, associated_actor, entity_name: str, world_name: str):
def __init__(self, associated_actor):
super().__init__()
self.entity_name = None
self.current_state = DeleteActionState.IDLE

def execute(self, associated_actor, entity_name: str, world_name: str): # pylint: disable=arguments-differ
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from tf2_ros.transform_listener import TransformListener
from tf2_geometry_msgs import PoseStamped
from .gazebo_spawn_actor import GazeboSpawnActor
from scenario_execution.actions.base_action import ActionError


class GazeboRelativeSpawnActor(GazeboSpawnActor):
Expand All @@ -29,20 +30,17 @@ class GazeboRelativeSpawnActor(GazeboSpawnActor):
"""

def __init__(self, associated_actor,
frame_id: str, parent_frame_id: str,
distance: float, world_name: str, xacro_arguments: list,
model: str):
super().__init__(associated_actor, None, world_name, xacro_arguments, model)
def __init__(self, associated_actor, xacro_arguments: list, model: str):
super().__init__(associated_actor, xacro_arguments, model)
self._pose = '{}'
self.model = model
self.world_name = None
self.xacro_arguments = xacro_arguments
self.tf_buffer = Buffer()
self.tf_listener = None

def execute(self, associated_actor, # pylint: disable=arguments-differ
frame_id: str, parent_frame_id: str,
distance: float, world_name: str, xacro_arguments: list,
model: str):
super().execute(associated_actor, None, world_name, xacro_arguments, model)
def execute(self, associated_actor, frame_id: str, parent_frame_id: str, distance: float, world_name: str): # pylint: disable=arguments-differ
super().execute(associated_actor, None, world_name)
self.frame_id = frame_id
self.parent_frame_id = parent_frame_id
self.distance = distance
Expand Down Expand Up @@ -97,4 +95,4 @@ def calculate_new_pose(self):
f' w: {new_pose.pose.orientation.w} x: {new_pose.pose.orientation.x} y: {new_pose.pose.orientation.y} z: {new_pose.pose.orientation.z}' \
' } }'
except TransformException as e:
raise ValueError(f"No transform available ({self.parent_frame_id}->{self.frame_id})") from e
raise ActionError(f"No transform available ({self.parent_frame_id}->{self.frame_id})", action=self) from e
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from rclpy.qos import QoSProfile, QoSDurabilityPolicy, QoSHistoryPolicy, QoSReliabilityPolicy
from rclpy.node import Node
import py_trees
from scenario_execution.actions.base_action import ActionError
from scenario_execution.actions.run_process import RunProcess
from .utils import SpawnUtils

Expand All @@ -44,7 +45,7 @@ class GazeboSpawnActor(RunProcess):
"""

def __init__(self, associated_actor, spawn_pose: list, world_name: str, xacro_arguments: list, model: str):
def __init__(self, associated_actor, xacro_arguments: list, model: str):
"""
init
"""
Expand All @@ -68,7 +69,7 @@ def setup(self, **kwargs):
except KeyError as e:
error_message = "didn't find 'node' in setup's kwargs [{}][{}]".format(
self.name, self.__class__.__name__)
raise KeyError(error_message) from e
raise ActionError(error_message, action=self) from e

self.utils = SpawnUtils(logger=self.logger)

Expand All @@ -88,12 +89,10 @@ def setup(self, **kwargs):
self.entity_model, self.entity_name, self.xacro_arguments)

if not self.sdf:
raise ValueError(f'Invalid model specified ({self.entity_model})')
raise ActionError(f'Invalid model specified ({self.entity_model})', action=self)
self.current_state = SpawnActionState.MODEL_AVAILABLE

def execute(self, associated_actor, spawn_pose: list, world_name: str, xacro_arguments: list, model: str): # pylint: disable=arguments-differ
if self.entity_model != model or set(self.xacro_arguments) != set(xacro_arguments):
raise ValueError("Runtime change of model not supported.")
def execute(self, associated_actor, spawn_pose: list, world_name: str): # pylint: disable=arguments-differ
self.spawn_pose = spawn_pose
self.world_name = world_name

Expand Down Expand Up @@ -175,7 +174,7 @@ def get_spawn_pose(self):
f' w: {quaternion[0]} x: {quaternion[1]} y: {quaternion[2]} z: {quaternion[3]}' \
' } }'
except KeyError as e:
raise ValueError("Could not get values") from e
raise ActionError("Could not get values", action=self) from e
return pose

def set_command(self, command):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class GazeboWaitForSim(RunProcess):
Class to wait for the simulation to become active
"""

def __init__(self, world_name: str, timeout: int):
def __init__(self):
super().__init__()
self.current_state = WaitForSimulationActionState.IDLE

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ class KubernetesBaseActionState(Enum):

class KubernetesBaseAction(BaseAction):

def __init__(self, namespace: str, within_cluster: bool):
def __init__(self, within_cluster: bool):
super().__init__()
self.namespace = namespace
self.namespace = None
self.within_cluster = within_cluster
self.client = None
self.current_state = KubernetesBaseActionState.IDLE
Expand All @@ -44,10 +44,8 @@ def setup(self, **kwargs):
config.load_kube_config()
self.client = client.CoreV1Api()

def execute(self, namespace: str, within_cluster: bool):
def execute(self, namespace: str):
self.namespace = namespace
if within_cluster != self.within_cluster:
raise ValueError("parameter 'within_cluster' is not allowed to change since initialization.")

def update(self) -> py_trees.common.Status: # pylint: disable=too-many-return-statements
if self.current_state == KubernetesBaseActionState.IDLE:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,12 @@ class KubernetesPatchNetworkPolicyState(Enum):

class KubernetesPatchNetworkPolicy(BaseAction):

def __init__(self, namespace: str, target: str, network_enabled: bool, match_label: tuple, within_cluster: bool):
def __init__(self, namespace: str, target: str, ingress_enabled: bool, egress_enabled: bool, match_label: tuple, within_cluster: bool):
super().__init__()
self.namespace = namespace
self.target = target
self.network_enabled = network_enabled
self.ingress_enabled = ingress_enabled
self.egress_enabled = egress_enabled
self.within_cluster = within_cluster
if not isinstance(match_label, dict) or not "key" in match_label or not "value" in match_label:
raise ValueError("match_label expected to be key-value pair.")
Expand All @@ -53,7 +54,7 @@ def setup(self, **kwargs):
def update(self) -> py_trees.common.Status: # pylint: disable=too-many-return-statements
if self.current_state == KubernetesPatchNetworkPolicyState.IDLE:
self.current_request = self.network_client.patch_namespaced_network_policy(self.target, body=self.get_network_policy(
policy_name=self.target, enable=self.network_enabled, match_label=self.match_label), namespace=self.namespace, async_req=True)
policy_name=self.target, enable_ingress=self.ingress_enabled, enable_egress=self.egress_enabled, match_label=self.match_label), namespace=self.namespace, async_req=True)
self.current_state = KubernetesPatchNetworkPolicyState.REQUEST_SENT
self.feedback_message = f"Requested patching '{self.target}' in namespace '{self.namespace}'" # pylint: disable= attribute-defined-outside-init
return py_trees.common.Status.RUNNING
Expand All @@ -76,14 +77,16 @@ def update(self) -> py_trees.common.Status: # pylint: disable=too-many-return-s
return py_trees.common.Status.FAILURE
return py_trees.common.Status.FAILURE

def get_network_policy(self, policy_name, match_label, enable):
def get_network_policy(self, policy_name, match_label, enable_ingress, enable_egress):
body = client.V1NetworkPolicy()
body.metadata = client.V1ObjectMeta(name=f"{policy_name}")
body.spec = client.V1NetworkPolicySpec(pod_selector=client.V1LabelSelector(match_labels={match_label["key"]: match_label["value"]}))
if enable:
body.spec.egress = [client.V1NetworkPolicyEgressRule()]
if enable_ingress:
body.spec.ingress = [client.V1NetworkPolicyIngressRule()]
else:
body.spec.egress = []
body.spec.ingress = []
if enable_egress:
body.spec.egress = [client.V1NetworkPolicyEgressRule()]
else:
body.spec.egress = []
return body
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@

class KubernetesPatchPod(KubernetesBaseAction):

def __init__(self, namespace: str, target: str, body: str, within_cluster: bool):
super().__init__(namespace, within_cluster)
self.target = target
def __init__(self, within_cluster: bool):
super().__init__(within_cluster)
self.namespace = None
self.target = None
self.body = None

def execute(self, namespace: str, target: str, body: str, within_cluster: bool): # pylint: disable=arguments-differ
super().execute(namespace, within_cluster)
def execute(self, namespace: str, target: str, body: str): # pylint: disable=arguments-differ
super().execute(namespace)
self.target = target
trimmed_data = body.encode('utf-8').decode('unicode_escape')
try:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@ class KubernetesPodExecState(Enum):

class KubernetesPodExec(BaseAction):

def __init__(self, target: str, command: list, regex: bool, namespace: str, within_cluster: bool):
def __init__(self, within_cluster: bool):
super().__init__()
self.target = target
self.namespace = namespace
self.regex = regex
self.command = command
self.target = None
self.namespace = None
self.regex = None
self.command = None
self.within_cluster = within_cluster
self.client = None
self.reponse_queue = queue.Queue()
Expand All @@ -56,9 +56,7 @@ def setup(self, **kwargs):

self.exec_thread = threading.Thread(target=self.pod_exec, daemon=True)

def execute(self, target: str, command: list, regex: bool, namespace: str, within_cluster: bool):
if within_cluster != self.within_cluster:
raise ValueError("parameter 'within_cluster' is not allowed to change since initialization.")
def execute(self, target: str, command: list, regex: bool, namespace: str):
self.target = target
self.namespace = namespace
self.command = command
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def setup(self, **kwargs):
self.k8s_client = client.api_client.ApiClient()
self.network_client = client.NetworkingV1Api(self.k8s_client)

def execute(self, target: str, status: tuple, namespace: str, within_cluster: bool):
def execute(self):
self.monitoring_thread = threading.Thread(target=self.watch_network, daemon=True)
self.monitoring_thread.start()

Expand Down
Loading

0 comments on commit 52c2d9a

Please sign in to comment.