diff --git a/docs/dictionary.txt b/docs/dictionary.txt
index f46eb76c..4a822588 100644
--- a/docs/dictionary.txt
+++ b/docs/dictionary.txt
@@ -33,4 +33,8 @@ png
svg
Kubernetes
yaml
-absolutized
\ No newline at end of file
+absolutized
+moveit
+replan
+effector
+mnt
diff --git a/docs/libraries.rst b/docs/libraries.rst
index 84c77a0d..b875dec7 100644
--- a/docs/libraries.rst
+++ b/docs/libraries.rst
@@ -10,6 +10,8 @@ Beside ``osc.standard`` provided by OpenSCENARIO 2 (which we divide into ``osc.s
* - Name
- Description
+ * - ``osc.docker``
+ - Docker Library (provided with :repo_link:`libs/scenario_execution_docker`)
* - ``osc.gazebo``
- Gazebo Library (provided with :repo_link:`libs/scenario_execution_gazebo`)
* - ``osc.helpers``
@@ -32,6 +34,163 @@ Beside ``osc.standard`` provided by OpenSCENARIO 2 (which we divide into ``osc.s
Additional features can be implemented by defining your own library.
+Docker
+------
+
+The library contains actions to interact with `Docker `_. Import it with ``import osc.docker``. It's provided by the package :repo_link:`libs/scenario_execution_docker`.
+
+``docker_run()``
+^^^^^^^^^^^^^^^^
+
+Runs a Docker container
+
+.. list-table::
+ :widths: 15 15 5 65
+ :header-rows: 1
+ :class: tight-table
+
+ * - Parameter
+ - Type
+ - Default
+ - Description
+ * - ``image``
+ - ``string``
+ -
+ - The image to run
+ * - ``command``
+ - ``string``
+ -
+ - The command to run in the container
+ * - ``container_name``
+ - ``string``
+ -
+ - The name for this container
+ * - ``detach``
+ - ``bool``
+ - false
+ - Whether to run container in the background
+ * - ``environment``
+ - ``list of string``
+ -
+ - Environment variables to set inside the container, i.e., a list of strings in the format ["SOMEVARIABLE=xxx"].
+ * - ``network``
+ - ``string``
+ -
+ - Name of the network this container will be connected to at creation time
+ * - ``privileged``
+ - ``bool``
+ - false
+ - Give extended privileges to this container
+ * - ``remove``
+ - ``bool``
+ - true
+ - Remove the container when it as finished running
+ * - ``stream``
+ - ``bool``
+ - true
+ - If true and detach is false, return a log generator instead of a string. Ignored if detach is true.
+ * - ``volumes``
+ - ``list of string``
+ -
+ - A list of strings which each one of its elements specifies a mount volume: ['/home/user1/:/mount/vol2','/home/user2/:/mount/vol1']
+
+``docker_exec()``
+^^^^^^^^^^^^^^^^^
+
+Runs a command inside a given Docker container
+
+.. list-table::
+ :widths: 15 15 5 65
+ :header-rows: 1
+ :class: tight-table
+
+ * - Parameter
+ - Type
+ - Default
+ - Description
+ * - ``container``
+ - ``string``
+ -
+ - The name or id of the container to run the command in
+ * - ``container``
+ - ``string``
+ -
+ - The name or id of the container to run the command in
+ * - ``command``
+ - ``string``
+ -
+ - The command to run inside the container
+ * - ``environment``
+ - ``list of string``
+ -
+ - Environment variables to set inside the container, i.e., a list of strings in the format ["SOMEVARIABLE=xxx"].
+ * - ``privileged``
+ - ``bool``
+ - false
+ - Give extended privileges to this container
+ * - ``user``
+ - ``string``
+ - root
+ - User to execute command as
+ * - ``workdir``
+ - ``string``
+ -
+ - Path to working directory for this exec session
+
+``docker_copy()``
+^^^^^^^^^^^^^^^^^
+
+Copy a file or folder from the container.
+Note that this actions potentially blocks other action calls if the copied content is large.
+In case large files or folders need to be copied, consider mounting a volume to the container instead of this action.
+
+.. list-table::
+ :widths: 15 15 5 65
+ :header-rows: 1
+ :class: tight-table
+
+ * - Parameter
+ - Type
+ - Default
+ - Description
+ * - ``container``
+ - ``string``
+ -
+ - The name or id of the container to run the command in
+ * - ``file_path``
+ - ``string``
+ -
+ - Path to the file or folder inside the container to retrieve
+
+``docker_put()``
+^^^^^^^^^^^^^^^^^
+
+Copy a file or folder from the local system into a running container.
+Note that this actions potentially blocks other action calls if the copied content is large.
+In case large files or folders need to be copied, consider mounting a volume to the container instead of this action.
+
+.. list-table::
+ :widths: 15 15 5 65
+ :header-rows: 1
+ :class: tight-table
+
+ * - Parameter
+ - Type
+ - Default
+ - Description
+ * - ``container``
+ - ``string``
+ -
+ - The name or id of the container to put the file or folder into
+ * - ``source_path``
+ - ``string``
+ -
+ - Path to the file or folder in the local system to copy
+ * - ``target_path``
+ - ``string``
+ -
+ - Target path inside the container to put the file or folder
+
Gazebo
------
@@ -1604,4 +1763,4 @@ Capture the screen content within a video.
* - ``frame_rate``
- ``float``
- ``25.0``
- - Frame-rate of the resulting video
\ No newline at end of file
+ - Frame-rate of the resulting video
diff --git a/libs/scenario_execution_docker/MANIFEST.in b/libs/scenario_execution_docker/MANIFEST.in
new file mode 100644
index 00000000..15f85d4a
--- /dev/null
+++ b/libs/scenario_execution_docker/MANIFEST.in
@@ -0,0 +1 @@
+include scenario_execution_docker/lib_osc/*.osc
diff --git a/libs/scenario_execution_docker/README.md b/libs/scenario_execution_docker/README.md
new file mode 100644
index 00000000..432b872d
--- /dev/null
+++ b/libs/scenario_execution_docker/README.md
@@ -0,0 +1,8 @@
+# Scenario Execution Library for Docker interaction
+
+The `scenario_execution_docker` package provides actions to interact with docker.
+
+It provides the following scenario execution library:
+
+- `docker.osc`: Actions to interact with docker
+
diff --git a/libs/scenario_execution_docker/package.xml b/libs/scenario_execution_docker/package.xml
new file mode 100644
index 00000000..92bafcc9
--- /dev/null
+++ b/libs/scenario_execution_docker/package.xml
@@ -0,0 +1,23 @@
+
+
+
+ scenario_execution_docker
+ 1.2.0
+ Scenario Execution library for docker interactions
+ Intel Labs
+ Intel Labs
+ Apache-2.0
+
+ scenario_execution
+
+ python3-docker
+
+ ament_copyright
+ ament_flake8
+ ament_pep257
+ python3-pytest
+
+
+ ament_python
+
+
diff --git a/libs/scenario_execution_docker/resource/scenario_execution_docker b/libs/scenario_execution_docker/resource/scenario_execution_docker
new file mode 100644
index 00000000..e69de29b
diff --git a/libs/scenario_execution_docker/scenario_execution_docker/__init__.py b/libs/scenario_execution_docker/scenario_execution_docker/__init__.py
new file mode 100644
index 00000000..3ba13780
--- /dev/null
+++ b/libs/scenario_execution_docker/scenario_execution_docker/__init__.py
@@ -0,0 +1,15 @@
+# Copyright (C) 2024 Intel Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
diff --git a/libs/scenario_execution_docker/scenario_execution_docker/actions/__init__.py b/libs/scenario_execution_docker/scenario_execution_docker/actions/__init__.py
new file mode 100644
index 00000000..3ba13780
--- /dev/null
+++ b/libs/scenario_execution_docker/scenario_execution_docker/actions/__init__.py
@@ -0,0 +1,15 @@
+# Copyright (C) 2024 Intel Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
diff --git a/libs/scenario_execution_docker/scenario_execution_docker/actions/docker_copy.py b/libs/scenario_execution_docker/scenario_execution_docker/actions/docker_copy.py
new file mode 100644
index 00000000..11b2c4d5
--- /dev/null
+++ b/libs/scenario_execution_docker/scenario_execution_docker/actions/docker_copy.py
@@ -0,0 +1,99 @@
+# Copyright (C) 2024 Intel Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+
+from enum import Enum
+
+import os
+import docker
+import tempfile
+import tarfile
+import py_trees
+from scenario_execution.actions.base_action import BaseAction, ActionError
+
+
+class CopyStatus(Enum):
+ IDLE = 1
+ FOUND_CONTAINER = 2
+ COPYING = 3
+ DONE = 4
+
+
+class DockerCopy(BaseAction):
+ """
+ Copy a file or folder from a running container
+ """
+
+ def __init__(self, container: str, file_path: str):
+ super().__init__()
+ self.container = container
+ self.file_path = file_path
+
+ self.container_object = None
+ self.current_state = CopyStatus.IDLE
+ self.output_dir = None
+ self.client = None
+ self.result_data = None
+
+ def setup(self, **kwargs):
+ # create docker client
+ self.client = docker.from_env()
+
+ # check output_dir
+ if "output_dir" not in kwargs:
+ raise ActionError("output_dir not defined.", action=self)
+
+ if kwargs['output_dir']:
+ if not os.path.exists(kwargs['output_dir']):
+ raise ActionError(f"Specified destination dir '{kwargs['output_dir']}' does not exist", action=self)
+ self.output_dir = kwargs['output_dir']
+
+ def update(self) -> py_trees.common.Status:
+ if self.current_state == CopyStatus.IDLE:
+ try:
+ self.container_object = self.client.containers.get(self.container)
+ self.current_state = CopyStatus.FOUND_CONTAINER
+ except docker.errors.APIError as e:
+ self.feedback_message = f"Docker container {self.container} not yet running {e}" # pylint: disable= attribute-defined-outside-init
+ return py_trees.common.Status.RUNNING
+
+ if self.current_state == CopyStatus.FOUND_CONTAINER:
+ try:
+ self.result_data, _ = self.container_object.get_archive(
+ path=self.file_path)
+ self.current_state = CopyStatus.COPYING
+ self.feedback_message = f"Copying data from path {self.file_path} in container {self.container} to {self.output_dir}" # pylint: disable= attribute-defined-outside-init
+ except docker.errors.APIError as e:
+ self.feedback_message = f"Copying of data from path {self.file_path} failed: {e}" # pylint: disable= attribute-defined-outside-init
+ return py_trees.common.Status.FAILURE
+
+ if self.current_state == CopyStatus.COPYING:
+ output_tar = tempfile.NamedTemporaryFile(suffix=".tar")
+ try:
+ with open(output_tar.name, 'wb') as f:
+ for chunk in self.result_data:
+ f.write(chunk)
+ with tarfile.open(output_tar.name, 'r') as tar:
+ tar.extractall(self.output_dir)
+ self.current_state = CopyStatus.DONE
+ except tarfile.ReadError as e:
+ self.feedback_message = f"Copying of data from path {self.file_path} failed: {e}" # pylint: disable= attribute-defined-outside-init
+ return py_trees.common.Status.FAILURE
+
+ if self.current_state == CopyStatus.DONE:
+ self.feedback_message = f"Finished copying of data from path {self.file_path} to {self.output_dir}" # pylint: disable= attribute-defined-outside-init
+ return py_trees.common.Status.SUCCESS
+
+ return py_trees.common.Status.RUNNING
diff --git a/libs/scenario_execution_docker/scenario_execution_docker/actions/docker_exec.py b/libs/scenario_execution_docker/scenario_execution_docker/actions/docker_exec.py
new file mode 100644
index 00000000..9d9e257c
--- /dev/null
+++ b/libs/scenario_execution_docker/scenario_execution_docker/actions/docker_exec.py
@@ -0,0 +1,105 @@
+# Copyright (C) 2024 Intel Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+
+from enum import Enum
+
+import docker
+import py_trees
+from scenario_execution.actions.base_action import BaseAction
+
+
+class ExecutionStatus(Enum):
+ IDLE = 1
+ FOUND_CONTAINER = 2
+ EXECUTING = 3
+ DONE = 4
+
+
+class DockerExec(BaseAction):
+ """
+ Run a command inside a container
+ """
+
+ def __init__(self, container: str, command: str,
+ environment: list, privileged: bool,
+ user: str, workdir: str):
+ super().__init__()
+ self.container = container
+ self.command = command
+ self.environment = environment
+ self.privileged = privileged
+ self.user = user
+ self.workdir = workdir
+
+ self.client = None
+ self.container_object = None
+ self.execution_instance = None
+ self.execution_output = None
+ self.current_state = ExecutionStatus.IDLE
+
+ def setup(self, **kwargs):
+ # create docker client
+ self.client = docker.from_env()
+
+ def update(self) -> py_trees.common.Status:
+ if self.current_state == ExecutionStatus.IDLE:
+ try:
+ self.container_object = self.client.containers.get(self.container)
+ self.current_state = ExecutionStatus.FOUND_CONTAINER
+ except docker.errors.APIError as e:
+ self.feedback_message = f"Docker container {self.container} not yet running {e}" # pylint: disable= attribute-defined-outside-init
+ return py_trees.common.Status.RUNNING
+
+ if self.current_state == ExecutionStatus.FOUND_CONTAINER:
+ try:
+ self.execution_instance = self.client.api.exec_create(
+ self.container_object.id,
+ self.command,
+ environment=self.environment,
+ privileged=self.privileged,
+ user=self.user,
+ workdir=self.workdir)
+
+ self.execution_output = self.client.api.exec_start(
+ self.execution_instance['Id'],
+ tty=False,
+ stream=True
+ )
+ self.current_state = ExecutionStatus.EXECUTING
+ self.feedback_message = f"Executing '{self.command}' in container {self.container}" # pylint: disable= attribute-defined-outside-init
+ except docker.errors.APIError as e:
+ self.feedback_message = f"Docker exec of command '{self.command}' failed: {e}" # pylint: disable= attribute-defined-outside-init
+ return py_trees.common.Status.FAILURE
+
+ if self.current_state == ExecutionStatus.EXECUTING:
+ try:
+ log = next(self.execution_output)
+ self.feedback_message = f"Executing '{self.command}' in container {self.container} with output: {log.decode()}" # pylint: disable= attribute-defined-outside-init
+ except StopIteration:
+ self.current_state = ExecutionStatus.DONE
+
+ if self.current_state == ExecutionStatus.DONE:
+ exit_metadata = self.client.api.exec_inspect(self.execution_instance['Id'])
+ assert not exit_metadata['Running']
+ exit_code = exit_metadata['ExitCode']
+ if exit_code == 0:
+ self.feedback_message = f"Finished execution of '{self.command}' in container {self.container}" # pylint: disable= attribute-defined-outside-init
+ return py_trees.common.Status.SUCCESS
+ else:
+ self.feedback_message = f"Execution of '{self.command}' in container {self.container} failed" # pylint: disable= attribute-defined-outside-init
+ return py_trees.common.Status.FAILURE
+
+ return py_trees.common.Status.RUNNING
diff --git a/libs/scenario_execution_docker/scenario_execution_docker/actions/docker_put.py b/libs/scenario_execution_docker/scenario_execution_docker/actions/docker_put.py
new file mode 100644
index 00000000..8ea96dd9
--- /dev/null
+++ b/libs/scenario_execution_docker/scenario_execution_docker/actions/docker_put.py
@@ -0,0 +1,95 @@
+# Copyright (C) 2024 Intel Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+
+from enum import Enum
+
+import os
+import docker
+import tempfile
+import tarfile
+import py_trees
+from scenario_execution.actions.base_action import BaseAction, ActionError
+
+
+class CopyStatus(Enum):
+ IDLE = 1
+ FOUND_CONTAINER = 2
+ COPYING = 3
+ DONE = 4
+
+
+class DockerPut(BaseAction):
+ """
+ Copy a file or folder from the local filesystem into a running container
+ """
+
+ def __init__(self, container: str, source_path: str, target_path: str):
+ super().__init__()
+ self.container = container
+ self.source_path = source_path
+ self.target_path = target_path
+
+ self.container_object = None
+ self.current_state = CopyStatus.IDLE
+ self.client = None
+ self.tar = None
+
+ def setup(self, **kwargs):
+ # create docker client
+ self.client = docker.from_env()
+
+ # check if source path exists
+ if not os.path.exists(self.source_path):
+ raise ActionError(f"The given source path {self.source_path} does not exist", action=self)
+
+ def update(self) -> py_trees.common.Status:
+ if self.current_state == CopyStatus.IDLE:
+ try:
+ self.container_object = self.client.containers.get(self.container)
+ self.current_state = CopyStatus.FOUND_CONTAINER
+ except docker.errors.APIError as e:
+ self.feedback_message = f"Docker container {self.container} not yet running {e}" # pylint: disable= attribute-defined-outside-init
+ return py_trees.common.Status.RUNNING
+
+ if self.current_state == CopyStatus.FOUND_CONTAINER:
+ self.tar = tempfile.NamedTemporaryFile(suffix=".tar")
+ try:
+ with tarfile.open(self.tar.name, 'w:') as tar:
+ tar.add(
+ self.source_path,
+ arcname=os.path.basename(self.source_path))
+ self.current_state = CopyStatus.COPYING
+ except tarfile.ReadError as e:
+ self.feedback_message = f"Compressing data to a tar file from path {self.source_path} failed: {e}" # pylint: disable= attribute-defined-outside-init
+ return py_trees.common.Status.FAILURE
+
+ if self.current_state == CopyStatus.COPYING:
+ success = self.container_object.put_archive(
+ path=self.target_path,
+ data=self.tar
+ )
+ if success:
+ self.current_state = CopyStatus.DONE
+ self.feedback_message = f"Copying data from path {self.source_path} to {self.target_path} inside container {self.container}" # pylint: disable= attribute-defined-outside-init
+ else:
+ self.feedback_message = f"Copying data from path {self.source_path} to {self.target_path} inside container {self.container} failed: {e}" # pylint: disable= attribute-defined-outside-init
+ return py_trees.common.Status.FAILURE
+
+ if self.current_state == CopyStatus.DONE:
+ self.feedback_message = f"Finished copying data from path {self.source_path} to {self.target_path} inside container {self.container}" # pylint: disable= attribute-defined-outside-init
+ return py_trees.common.Status.SUCCESS
+
+ return py_trees.common.Status.RUNNING
diff --git a/libs/scenario_execution_docker/scenario_execution_docker/actions/docker_run.py b/libs/scenario_execution_docker/scenario_execution_docker/actions/docker_run.py
new file mode 100644
index 00000000..9350c780
--- /dev/null
+++ b/libs/scenario_execution_docker/scenario_execution_docker/actions/docker_run.py
@@ -0,0 +1,115 @@
+# Copyright (C) 2024 Intel Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+
+import os
+from enum import Enum
+
+import docker
+import py_trees
+from scenario_execution.actions.base_action import BaseAction, ActionError
+
+
+class ContainerStatus(Enum):
+ IDLE = 1
+ RUNNING = 2
+ DONE = 3
+
+
+class DockerRun(BaseAction):
+ """
+ Run a container
+ """
+
+ def __init__(self, image: str, command: str, container_name: str,
+ detach: bool, environment: list, network: str,
+ privileged: bool, remove: bool, stream: bool, volumes: list):
+ super().__init__()
+ self.image = image
+ self.command = command
+ self.container_name = container_name
+ self.detach = detach
+ self.environment = environment
+ self.network = network
+ self.privileged = privileged
+ self.remove = remove
+ self.stream = stream
+ self.volumes = volumes
+
+ self.client = None
+ self.container = None
+ self.current_state = ContainerStatus.IDLE
+
+ def setup(self, **kwargs):
+ # create docker client
+ self.client = docker.from_env()
+ # check docker image
+ filterred_images = self.client.images.list(filters={'reference': self.image})
+ if len(filterred_images) == 0:
+ raise ActionError(f"Required docker image '{self.image}' does not exist.", action=self)
+
+ def update(self) -> py_trees.common.Status:
+ if self.current_state == ContainerStatus.IDLE:
+ try:
+ self.container = self.client.containers.run(
+ self.image,
+ command=self.command,
+ detach=self.detach,
+ environment=self.environment,
+ name=self.container_name,
+ network=self.network,
+ privileged=self.privileged,
+ stream=self.stream,
+ remove=self.remove,
+ user=os.getuid(),
+ group_add=[os.getgid()],
+ volumes=self.volumes)
+ except docker.errors.APIError as e:
+ self.feedback_message = f"Docker run failed: {e}" # pylint: disable= attribute-defined-outside-init
+ return py_trees.common.Status.FAILURE
+ self.current_state = ContainerStatus.RUNNING
+ self.feedback_message = f"Running docker container {self.image}" # pylint: disable= attribute-defined-outside-init
+ return py_trees.common.Status.RUNNING
+
+ if self.current_state == ContainerStatus.RUNNING:
+ if self.stream and not self.detach:
+ try:
+ log = next(self.container)
+ self.feedback_message = f"Running container {self.image} with output: {log.decode()}" # pylint: disable= attribute-defined-outside-init
+ except StopIteration:
+ self.current_state = ContainerStatus.DONE
+ self.feedback_message = f"Docker container {self.image} finished cleanly" # pylint: disable= attribute-defined-outside-init
+ return py_trees.common.Status.SUCCESS
+ elif self.detach:
+ self.container.reload()
+ if self.container:
+ res = self.container.status
+ self.feedback_message = f"Running container {self.image} with status {res}" # pylint: disable= attribute-defined-outside-init
+ if res in ["removing", "exited"]:
+ self.current_state = ContainerStatus.DONE
+ self.feedback_message = f"Docker container {self.image} finished cleanly" # pylint: disable= attribute-defined-outside-init
+ return py_trees.common.Status.SUCCESS
+
+ if self.current_state == ContainerStatus.DONE:
+ self.feedback_message = f"Docker container {self.image} finished cleanly" # pylint: disable= attribute-defined-outside-init
+ return py_trees.common.Status.SUCCESS
+
+ return py_trees.common.Status.RUNNING
+
+ def shutdown(self):
+ if self.container is None:
+ return
+
+ self.container.stop(timeout=0)
diff --git a/libs/scenario_execution_docker/scenario_execution_docker/get_osc_library.py b/libs/scenario_execution_docker/scenario_execution_docker/get_osc_library.py
new file mode 100644
index 00000000..959aa95e
--- /dev/null
+++ b/libs/scenario_execution_docker/scenario_execution_docker/get_osc_library.py
@@ -0,0 +1,19 @@
+# Copyright (C) 2024 Intel Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+
+
+def get_osc_library():
+ return 'scenario_execution_docker', 'docker.osc'
diff --git a/libs/scenario_execution_docker/scenario_execution_docker/lib_osc/docker.osc b/libs/scenario_execution_docker/scenario_execution_docker/lib_osc/docker.osc
new file mode 100644
index 00000000..85ac1007
--- /dev/null
+++ b/libs/scenario_execution_docker/scenario_execution_docker/lib_osc/docker.osc
@@ -0,0 +1,32 @@
+action docker_run:
+ # run a docker container
+ image: string # the image to run
+ command: string # the command to run in the container
+ container_name: string # the name for this container
+ detach: bool = false # Run container in the background
+ environment: list of string # Environment variables to set inside the container, i.e., a list of strings in the format ["SOMEVARIABLE=xxx"].
+ network: string # Name of the network this container will be connected to at creation time
+ privileged: bool = false # Give extended privileges to this container
+ remove: bool = true # Remove the container when it as finished running
+ stream: bool = true # If true and detach is false, return a log generator instead of a string. Ignored if detach is true.
+ volumes: list of string # A list of strings which each one of its elements specifies a mount volume: ['/home/user1/:/mnt/vol2','/home/user2:/mnt/vol1']
+
+action docker_exec:
+ # Run a command inside a given container
+ container: string # the name or id of the container to run the command in
+ command: string # the command to run inside the container
+ environment: list of string # Environment variables to set inside the container, i.e., a list of strings in the format ["SOMEVARIABLE=xxx"].
+ privileged: bool = false # Give extended privileges to this container
+ user: string = 'root' # User to execute command as. Default: root
+ workdir: string # Path to working directory for this exec session
+
+action docker_copy:
+ # Copy a file or folder from the container
+ container: string # the name or id of the container to get the file or folder from
+ file_path: string # Path to the file or folder to retrieve
+
+action docker_put:
+ # Copy a file or folder from the local system into a running container
+ container: string # the name or id of the container to put the file or folder into
+ source_path: string # Path to the file or folder in the local system to copy
+ target_path: string # Target path inside the container to put the file or folder
diff --git a/libs/scenario_execution_docker/scenarios/test_docker_copy.osc b/libs/scenario_execution_docker/scenarios/test_docker_copy.osc
new file mode 100644
index 00000000..3fb0d7ff
--- /dev/null
+++ b/libs/scenario_execution_docker/scenarios/test_docker_copy.osc
@@ -0,0 +1,13 @@
+import osc.docker
+import osc.helpers
+
+scenario test_docker_copy:
+ timeout(25s)
+ do parallel:
+ docker_run(image: 'ubuntu', command: 'sleep 20', detach: true, container_name: 'sleeping_beauty', remove: true)
+ serial:
+ docker_exec(container: 'sleeping_beauty', command: 'mkdir -p /tmp/test_dir/')
+ docker_exec(container: 'sleeping_beauty', command: 'touch /tmp/test_dir/test.txt')
+ docker_exec(container: 'sleeping_beauty', command: 'touch /tmp/test_dir/test_1.txt')
+ docker_copy(container: 'sleeping_beauty', file_path: '/tmp/test_dir/')
+ emit end
\ No newline at end of file
diff --git a/libs/scenario_execution_docker/scenarios/test_docker_exec.osc b/libs/scenario_execution_docker/scenarios/test_docker_exec.osc
new file mode 100644
index 00000000..2386eecb
--- /dev/null
+++ b/libs/scenario_execution_docker/scenarios/test_docker_exec.osc
@@ -0,0 +1,10 @@
+import osc.docker
+import osc.helpers
+
+scenario test_docker_exec:
+ timeout(15s)
+ do parallel:
+ docker_run(image: 'ubuntu', command: 'sleep 10', detach: true, container_name: 'sleeping_beauty', remove: true)
+ serial:
+ docker_exec(container: 'sleeping_beauty', command: 'echo hello world')
+ emit end
\ No newline at end of file
diff --git a/libs/scenario_execution_docker/scenarios/test_docker_put.osc b/libs/scenario_execution_docker/scenarios/test_docker_put.osc
new file mode 100644
index 00000000..a4bc50ce
--- /dev/null
+++ b/libs/scenario_execution_docker/scenarios/test_docker_put.osc
@@ -0,0 +1,12 @@
+import osc.docker
+import osc.helpers
+
+# for this scenario to succeed, create a folder 'test_dir' in your /tmp folder with some dummy files in it
+scenario test_docker_put:
+ timeout(10s)
+ do parallel:
+ docker_run(image: 'ubuntu', command: 'sleep 5', detach: true, container_name: 'sleeping_beauty', remove: true)
+ serial:
+ docker_put(container: 'sleeping_beauty', source_path: '/tmp/test_dir/', target_path: '/tmp/')
+ docker_exec(container: 'sleeping_beauty', command: 'ls /tmp/ | grep test_dir')
+ emit end
\ No newline at end of file
diff --git a/libs/scenario_execution_docker/scenarios/test_docker_run.osc b/libs/scenario_execution_docker/scenarios/test_docker_run.osc
new file mode 100644
index 00000000..1f10ac2e
--- /dev/null
+++ b/libs/scenario_execution_docker/scenarios/test_docker_run.osc
@@ -0,0 +1,9 @@
+import osc.docker
+import osc.helpers
+
+scenario test_docker_run:
+ timeout(15s)
+ do serial:
+ docker_run(image: 'ubuntu', command: 'sleep 5', detach: true, remove: true, container_name: 'sleeping_beauty')
+ docker_run(image: 'ubuntu', command: 'echo hello world')
+ emit end
\ No newline at end of file
diff --git a/libs/scenario_execution_docker/setup.cfg b/libs/scenario_execution_docker/setup.cfg
new file mode 100644
index 00000000..66ac754b
--- /dev/null
+++ b/libs/scenario_execution_docker/setup.cfg
@@ -0,0 +1,4 @@
+[develop]
+script_dir=$base/lib/scenario_execution_docker
+[install]
+install_scripts=$base/lib/scenario_execution_docker
diff --git a/libs/scenario_execution_docker/setup.py b/libs/scenario_execution_docker/setup.py
new file mode 100644
index 00000000..b46c4e63
--- /dev/null
+++ b/libs/scenario_execution_docker/setup.py
@@ -0,0 +1,51 @@
+# Copyright (C) 2024 Intel Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+
+""" Setup python package """
+from setuptools import find_namespace_packages, setup
+
+PACKAGE_NAME = 'scenario_execution_docker'
+
+setup(
+ name=PACKAGE_NAME,
+ version='1.2.0',
+ packages=find_namespace_packages(),
+ data_files=[
+ ('share/ament_index/resource_index/packages',
+ ['resource/' + PACKAGE_NAME]),
+ ('share/' + PACKAGE_NAME, ['package.xml'])
+ ],
+ install_requires=['setuptools'],
+ zip_safe=True,
+ maintainer='Intel Labs',
+ maintainer_email='scenario-execution@intel.com',
+ description='Scenario Execution library for Docker interactions',
+ license='Apache License 2.0',
+ tests_require=['pytest'],
+ include_package_data=True,
+ entry_points={
+ 'scenario_execution.actions': [
+ 'docker_run = scenario_execution_docker.actions.docker_run:DockerRun',
+ 'docker_exec = scenario_execution_docker.actions.docker_exec:DockerExec',
+ 'docker_copy = scenario_execution_docker.actions.docker_copy:DockerCopy',
+ 'docker_put = scenario_execution_docker.actions.docker_put:DockerPut',
+ ],
+ 'scenario_execution.osc_libraries': [
+ 'docker = '
+ 'scenario_execution_docker.get_osc_library:get_osc_library',
+ ]
+ },
+)
diff --git a/test/scenario_execution_docker_test/README.md b/test/scenario_execution_docker_test/README.md
new file mode 100644
index 00000000..024938d4
--- /dev/null
+++ b/test/scenario_execution_docker_test/README.md
@@ -0,0 +1,3 @@
+# Tests of Scenario Execution Library for Docker
+
+The `scenario_execution_docker_test` package tests functionality of `scenario_execution_docker`.
diff --git a/test/scenario_execution_docker_test/package.xml b/test/scenario_execution_docker_test/package.xml
new file mode 100644
index 00000000..e29c3e3a
--- /dev/null
+++ b/test/scenario_execution_docker_test/package.xml
@@ -0,0 +1,23 @@
+
+
+
+ scenario_execution_docker_test
+ 1.2.0
+ Tests for Scenario Execution library for Docker
+ Intel Labs
+ Intel Labs
+ Apache-2.0
+
+ scenario_execution_docker
+ scenario_execution_os
+ geometry_msgs
+
+ ament_copyright
+ ament_flake8
+ ament_pep257
+ python3-pytest
+
+
+ ament_python
+
+
diff --git a/test/scenario_execution_docker_test/resource/scenario_execution_docker_test b/test/scenario_execution_docker_test/resource/scenario_execution_docker_test
new file mode 100644
index 00000000..e69de29b
diff --git a/test/scenario_execution_docker_test/setup.cfg b/test/scenario_execution_docker_test/setup.cfg
new file mode 100644
index 00000000..84c2fa13
--- /dev/null
+++ b/test/scenario_execution_docker_test/setup.cfg
@@ -0,0 +1,4 @@
+[develop]
+script_dir=$base/lib/scenario_execution_docker_test
+[install]
+install_scripts=$base/lib/scenario_execution_docker_test
diff --git a/test/scenario_execution_docker_test/setup.py b/test/scenario_execution_docker_test/setup.py
new file mode 100644
index 00000000..967571ce
--- /dev/null
+++ b/test/scenario_execution_docker_test/setup.py
@@ -0,0 +1,42 @@
+# Copyright (C) 2024 Intel Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+
+""" Setup python package """
+from glob import glob
+import os
+from setuptools import find_namespace_packages, setup
+
+PACKAGE_NAME = 'scenario_execution_docker_test'
+
+setup(
+ name=PACKAGE_NAME,
+ version='1.2.0',
+ packages=find_namespace_packages(),
+ data_files=[
+ ('share/ament_index/resource_index/packages',
+ ['resource/' + PACKAGE_NAME]),
+ ('share/' + PACKAGE_NAME, ['package.xml']),
+ (os.path.join('share', PACKAGE_NAME, 'launch'), glob('launch/*launch.py')),
+ ],
+ install_requires=['setuptools'],
+ zip_safe=True,
+ maintainer='Intel Labs',
+ maintainer_email='scenario-execution@intel.com',
+ description='Tests for Scenario Execution library for Docker',
+ license='Apache License 2.0',
+ tests_require=['pytest'],
+ entry_points={}
+)
diff --git a/test/scenario_execution_docker_test/test/test_docker_copy.py b/test/scenario_execution_docker_test/test/test_docker_copy.py
new file mode 100644
index 00000000..e7d9b304
--- /dev/null
+++ b/test/scenario_execution_docker_test/test/test_docker_copy.py
@@ -0,0 +1,81 @@
+# Copyright (C) 2024 Intel Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+
+import py_trees
+import unittest
+import tempfile
+from scenario_execution import ScenarioExecution
+from scenario_execution.model.osc2_parser import OpenScenario2Parser
+from scenario_execution.model.model_to_py_tree import create_py_tree
+from scenario_execution.utils.logging import Logger
+
+from antlr4.InputStream import InputStream
+
+
+class TestDockerCopy(unittest.TestCase):
+ # pylint: disable=missing-function-docstring,missing-class-docstring
+
+ def setUp(self) -> None:
+ self.parser = OpenScenario2Parser(Logger('test', False))
+ self.tmp_dir = tempfile.TemporaryDirectory()
+ self.scenario_execution = ScenarioExecution(debug=False, log_model=False, live_tree=False,
+ scenario_file="test.osc", output_dir=self.tmp_dir.name)
+ self.tree = py_trees.composites.Sequence(name="", memory=True)
+
+ def tearDown(self):
+ self.tmp_dir.cleanup()
+
+ def parse(self, scenario_content):
+ parsed_tree = self.parser.parse_input_stream(InputStream(scenario_content))
+ model = self.parser.create_internal_model(parsed_tree, self.tree, "test.osc", False)
+ self.tree = create_py_tree(model, self.tree, self.parser.logger, False)
+ self.scenario_execution.tree = self.tree
+ self.scenario_execution.run()
+
+ def test_success(self):
+ self.parse("""
+import osc.docker
+import osc.helpers
+import osc.os
+
+scenario test_success:
+ timeout(10)
+ do parallel:
+ docker_run(image: 'ubuntu', command: 'sleep 5', detach: true, container_name: 'sleeping_beauty_copy_success', remove: true)
+ serial:
+ docker_exec(container: 'sleeping_beauty_copy_success', command: 'mkdir -p /tmp/test_dir/')
+ docker_exec(container: 'sleeping_beauty_copy_success', command: 'touch /tmp/test_dir/test.txt')
+ docker_exec(container: 'sleeping_beauty_copy_success', command: 'touch /tmp/test_dir/test_1.txt')
+ docker_copy(container: 'sleeping_beauty_copy_success', file_path: '/tmp/test_dir/')
+ check_file_exists(file_name: '""" + self.tmp_dir.name + '/test_dir/test.txt' + """')
+ check_file_exists(file_name: '""" + self.tmp_dir.name + '/test_dir/test_1.txt' + """')
+ emit end
+""")
+ self.assertTrue(self.scenario_execution.process_results())
+
+ def test_failure_missing_file(self):
+ self.parse("""
+import osc.docker
+import osc.helpers
+
+scenario test_fail:
+ timeout(10s)
+ do parallel:
+ docker_run(image: 'ubuntu', command: 'sleep 5', detach: true, container_name: 'sleeping_beauty_copy_fail', remove: true)
+ serial:
+ docker_copy(container: 'sleeping_beauty_copy_fail', file_path: '/tmp/test_dir/')
+""")
+ self.assertFalse(self.scenario_execution.process_results())
diff --git a/test/scenario_execution_docker_test/test/test_docker_exec.py b/test/scenario_execution_docker_test/test/test_docker_exec.py
new file mode 100644
index 00000000..30675dc8
--- /dev/null
+++ b/test/scenario_execution_docker_test/test/test_docker_exec.py
@@ -0,0 +1,84 @@
+# Copyright (C) 2024 Intel Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+
+import py_trees
+import unittest
+import tempfile
+from scenario_execution import ScenarioExecution
+from scenario_execution.model.osc2_parser import OpenScenario2Parser
+from scenario_execution.model.model_to_py_tree import create_py_tree
+from scenario_execution.utils.logging import Logger
+
+from antlr4.InputStream import InputStream
+
+
+class TestDockerExec(unittest.TestCase):
+ # pylint: disable=missing-function-docstring,missing-class-docstring
+
+ def setUp(self) -> None:
+ self.parser = OpenScenario2Parser(Logger('test', False))
+ self.scenario_execution = ScenarioExecution(debug=False, log_model=False, live_tree=False,
+ scenario_file="test.osc", output_dir=None)
+ self.tree = py_trees.composites.Sequence(name="", memory=True)
+ self.tmp_file = tempfile.NamedTemporaryFile()
+
+ def parse(self, scenario_content):
+ parsed_tree = self.parser.parse_input_stream(InputStream(scenario_content))
+ model = self.parser.create_internal_model(parsed_tree, self.tree, "test.osc", False)
+ self.tree = create_py_tree(model, self.tree, self.parser.logger, False)
+ self.scenario_execution.tree = self.tree
+ self.scenario_execution.run()
+
+ def test_success(self):
+ self.parse("""
+import osc.docker
+import osc.helpers
+
+scenario test_success:
+ timeout(30s)
+ do parallel:
+ docker_run(image: 'ubuntu', command: 'sleep 10', detach: true, container_name: 'sleeping_beauty_exec_success', remove: true)
+ serial:
+ docker_exec(container: 'sleeping_beauty_exec_success', command: 'echo hello world')
+ emit end
+""")
+ self.assertTrue(self.scenario_execution.process_results())
+
+ def test_failure(self):
+ self.parse("""
+import osc.docker
+import osc.helpers
+
+scenario test_failure:
+ timeout(15s)
+ do parallel:
+ docker_run(image: 'ubuntu', command: 'sleep 10', detach: true, container_name: 'sleeping_beauty_exec_fail', remove: true)
+ serial:
+ docker_exec(container: 'sleeping_beauty_exec_fail', command: 'ls UKNOWN_DIR')
+""")
+ self.assertFalse(self.scenario_execution.process_results())
+
+ def test_failure_container_not_running(self):
+ self.parse("""
+import osc.docker
+import osc.helpers
+
+scenario test_failure_container_not_running:
+ timeout(3s)
+ do serial:
+ docker_exec(container: 'sleeping_beauty', command: 'echo hello world')
+""")
+ self.assertFalse(self.scenario_execution.process_results())
diff --git a/test/scenario_execution_docker_test/test/test_docker_put.py b/test/scenario_execution_docker_test/test/test_docker_put.py
new file mode 100644
index 00000000..93872120
--- /dev/null
+++ b/test/scenario_execution_docker_test/test/test_docker_put.py
@@ -0,0 +1,73 @@
+# Copyright (C) 2024 Intel Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+
+import py_trees
+import unittest
+import tempfile
+from scenario_execution import ScenarioExecution
+from scenario_execution.model.osc2_parser import OpenScenario2Parser
+from scenario_execution.model.model_to_py_tree import create_py_tree
+from scenario_execution.utils.logging import Logger
+
+from antlr4.InputStream import InputStream
+
+
+class TestDockerPut(unittest.TestCase):
+ # pylint: disable=missing-function-docstring,missing-class-docstring
+
+ def setUp(self) -> None:
+ self.parser = OpenScenario2Parser(Logger('test', False))
+ self.scenario_execution = ScenarioExecution(debug=False, log_model=False, live_tree=False,
+ scenario_file="test.osc", output_dir=None)
+ self.tree = py_trees.composites.Sequence(name="", memory=True)
+ self.tmp_file = tempfile.NamedTemporaryFile()
+
+ def parse(self, scenario_content):
+ parsed_tree = self.parser.parse_input_stream(InputStream(scenario_content))
+ model = self.parser.create_internal_model(parsed_tree, self.tree, "test.osc", False)
+ self.tree = create_py_tree(model, self.tree, self.parser.logger, False)
+ self.scenario_execution.tree = self.tree
+ self.scenario_execution.run()
+
+ def test_success(self):
+ self.parse("""
+import osc.docker
+import osc.helpers
+
+scenario test_success:
+ timeout(10s)
+ do parallel:
+ docker_run(image: 'ubuntu', command: 'sleep 5', detach: true, container_name: 'sleeping_beauty_put', remove: true)
+ serial:
+ docker_put(container: 'sleeping_beauty_put', source_path: '""" + self.tmp_file.name + """', target_path: '/tmp/')
+ docker_exec(container: 'sleeping_beauty_put', command: 'test -f """ + self.tmp_file.name + """')
+ emit end
+""")
+ self.assertTrue(self.scenario_execution.process_results())
+
+ def test_failure_missing_file(self):
+ self.parse("""
+import osc.docker
+import osc.helpers
+
+scenario test_failure_unknown_file:
+ timeout(10s)
+ do parallel:
+ docker_run(image: 'ubuntu', command: 'sleep 5', detach: true, container_name: 'sleeping_beauty_put_fail', remove: true)
+ serial:
+ docker_put(container: 'sleeping_beauty_put_fail', source_path: 'UNKNOWN', target_path: '/tmp/')
+""")
+ self.assertFalse(self.scenario_execution.process_results())
diff --git a/test/scenario_execution_docker_test/test/test_docker_run.py b/test/scenario_execution_docker_test/test/test_docker_run.py
new file mode 100644
index 00000000..182818c3
--- /dev/null
+++ b/test/scenario_execution_docker_test/test/test_docker_run.py
@@ -0,0 +1,100 @@
+# Copyright (C) 2024 Intel Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+
+import py_trees
+import unittest
+import tempfile
+from scenario_execution import ScenarioExecution
+from scenario_execution.model.osc2_parser import OpenScenario2Parser
+from scenario_execution.model.model_to_py_tree import create_py_tree
+from scenario_execution.utils.logging import Logger
+
+from antlr4.InputStream import InputStream
+
+
+class TestDockerRun(unittest.TestCase):
+ # pylint: disable=missing-function-docstring,missing-class-docstring
+
+ def setUp(self) -> None:
+ self.parser = OpenScenario2Parser(Logger('test', False))
+ self.scenario_execution = ScenarioExecution(debug=False, log_model=False, live_tree=False,
+ scenario_file="test.osc", output_dir=None)
+ self.tree = py_trees.composites.Sequence(name="", memory=True)
+ self.tmp_dir = tempfile.TemporaryDirectory()
+
+ def tearDown(self):
+ self.tmp_dir.cleanup()
+
+ def parse(self, scenario_content):
+ parsed_tree = self.parser.parse_input_stream(InputStream(scenario_content))
+ model = self.parser.create_internal_model(parsed_tree, self.tree, "test.osc", False)
+ self.tree = create_py_tree(model, self.tree, self.parser.logger, False)
+ self.scenario_execution.tree = self.tree
+ self.scenario_execution.run()
+
+ def test_success_stream(self):
+ self.parse("""
+import osc.docker
+import osc.helpers
+
+scenario test:
+ timeout(10s)
+ do serial:
+ docker_run(image: 'ubuntu', command: 'echo hello world')
+ emit end
+""")
+ self.assertTrue(self.scenario_execution.process_results())
+
+ def test_success_detach(self):
+ self.parse("""
+import osc.docker
+import osc.helpers
+
+scenario test:
+ timeout(10s)
+ do serial:
+ docker_run(image: 'ubuntu', command: 'sleep 5', detach: true)
+ emit end
+""")
+ self.assertTrue(self.scenario_execution.process_results())
+
+ def test_fail_unknown_command(self):
+ self.parse("""
+import osc.docker
+import osc.helpers
+
+scenario test:
+ timeout(10s)
+ do serial:
+ docker_run(image: 'ubuntu', command: 'UKNOWN', detach: true)
+ emit end
+""")
+ self.assertFalse(self.scenario_execution.process_results())
+
+ def test_success_volume_mount(self):
+ self.parse("""
+import osc.docker
+import osc.helpers
+
+scenario test:
+ timeout(10s)
+ do parallel:
+ docker_run(image: 'ubuntu', command: 'sleep 5', detach: true, volumes: ['""" + self.tmp_dir.name + """:/data'], container_name: 'sleeping_beauty_run_volume')
+ serial:
+ docker_exec(container: 'sleeping_beauty_run_volume', command: 'ls /data')
+ emit end
+""")
+ self.assertTrue(self.scenario_execution.process_results())