Skip to content

Commit 09dea0e

Browse files
committed
[OMCSessionZMQDocker] add new class to use OMC in docker
1 parent b82b893 commit 09dea0e

3 files changed

Lines changed: 172 additions & 4 deletions

File tree

OMPython/OMCSession.py

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,3 +541,170 @@ def sendExpression(self, command, parsed=True):
541541
raise OMCSessionException("Cannot parse OMC result") from ex
542542
else:
543543
return result
544+
545+
546+
# noinspection PyPep8Naming
547+
class OMCSessionZMQDocker(OMCSessionZMQ):
548+
549+
def __init__(self,
550+
timeout: float = 10.00,
551+
docker: Optional[str] = None,
552+
dockerContainer: Optional[int] = None,
553+
dockerExtraArgs: Optional[list] = None,
554+
dockerOpenModelicaPath: str = "omc",
555+
dockerNetwork: Optional[str] = None,
556+
port: Optional[int] = None,
557+
omhome: Optional[str] = None):
558+
super().__init__(timeout=timeout, omhome=omhome)
559+
560+
if dockerExtraArgs is None:
561+
dockerExtraArgs = []
562+
563+
if docker is None and dockerContainer is None:
564+
raise OMCSessionException("One of docker or dockerContainer must be set!")
565+
566+
self._docker = docker
567+
self._dockerContainer = dockerContainer
568+
self._dockerExtraArgs = dockerExtraArgs
569+
self._dockerOpenModelicaPath = dockerOpenModelicaPath
570+
self._dockerNetwork = dockerNetwork
571+
572+
self._interactivePort = port
573+
574+
self._dockerCid: Optional[int] = None
575+
self._dockerCidFile: Optional[pathlib.Path] = None
576+
577+
@staticmethod
578+
def _filename_port(current_user: str, random_str: str) -> str:
579+
filename = f"openmodelica.{current_user}.port.{random_str}"
580+
581+
return filename
582+
583+
@staticmethod
584+
def _getuid():
585+
"""
586+
The uid to give to docker.
587+
On Windows, volumes are mapped with all files are chmod ugo+rwx,
588+
so uid does not matter as long as it is not the root user.
589+
"""
590+
return 1000 if sys.platform == 'win32' else os.getuid()
591+
592+
def _omc_command_get(self, omc_path_and_args_list) -> list:
593+
"""
594+
Define the command that will be called by the subprocess module.
595+
"""
596+
extraFlags = []
597+
598+
if (self._docker or self._dockerContainer) and sys.platform == "win32":
599+
extraFlags = ["-d=zmqDangerousAcceptConnectionsFromAnywhere"]
600+
if not self._interactivePort:
601+
raise OMCSessionException("docker on Windows requires knowing which port to connect to. For "
602+
"dockerContainer=..., the container needs to have already manually exposed "
603+
"this port when it was started (-p 127.0.0.1:n:n) or you get an error later.")
604+
605+
if self._docker:
606+
if sys.platform == "win32":
607+
if self._interactivePort is not None and isinstance(self._interactivePort, int):
608+
p = int(self._interactivePort)
609+
dockerNetworkStr = ["-p", "127.0.0.1:%d:%d" % (p, p)]
610+
else:
611+
raise OMCSessionException("Missing or invalid interactive port!")
612+
elif self._dockerNetwork == "host" or self._dockerNetwork is None:
613+
dockerNetworkStr = ["--network=host"]
614+
elif self._dockerNetwork == "separate":
615+
dockerNetworkStr = []
616+
extraFlags = ["-d=zmqDangerousAcceptConnectionsFromAnywhere"]
617+
else:
618+
raise OMCSessionException(f'dockerNetwork was set to {self._dockerNetwork}, '
619+
'but only \"host\" or \"separate\" is allowed')
620+
self._dockerCidFile = self._omc_file_log.parent / (self._omc_file_log.stem + ".docker.cid")
621+
622+
omcCommand = (["docker", "run",
623+
"--cidfile", self._dockerCidFile.as_posix(),
624+
"--rm",
625+
"--env", "USER=%s" % self._currentUser,
626+
"--user", str(self._getuid())]
627+
+ self._dockerExtraArgs
628+
+ dockerNetworkStr
629+
+ [self._docker, self._dockerOpenModelicaPath])
630+
elif self._dockerContainer:
631+
omcCommand = (["docker", "exec",
632+
"--env", "USER=%s" % self._currentUser,
633+
"--user", str(self._getuid())]
634+
+ self._dockerExtraArgs
635+
+ [self._dockerContainer, self._dockerOpenModelicaPath])
636+
self._dockerCid = self._dockerContainer
637+
else:
638+
raise OMCSessionException("Only for docker!")
639+
640+
if self._interactivePort:
641+
extraFlags = extraFlags + ["--interactivePort=%d" % int(self._interactivePort)]
642+
643+
omc_command = omcCommand + omc_path_and_args_list + extraFlags
644+
645+
return omc_command
646+
647+
def _omc_process_get(self, timeout): # output?
648+
omc_process = super()._omc_process_get(timeout=timeout)
649+
650+
if self._docker:
651+
for i in range(0, 40):
652+
try:
653+
with open(self._dockerCidFile, "r") as fin:
654+
self._dockerCid = fin.read().strip()
655+
except IOError:
656+
pass
657+
if self._dockerCid:
658+
break
659+
time.sleep(timeout / 40.0)
660+
try:
661+
os.remove(self._dockerCidFile)
662+
except FileNotFoundError:
663+
pass
664+
if self._dockerCid is None:
665+
logger.error("Docker did not start. Log-file says:\n%s" % (open(self._omc_filehandle_log.name).read()))
666+
raise OMCSessionException("Docker did not start (timeout=%f might be too short especially if you did "
667+
"not docker pull the image before this command)." % timeout)
668+
669+
if self._docker or self._dockerContainer:
670+
dockerTop = None
671+
if self._dockerNetwork == "separate":
672+
output = subprocess.check_output(["docker", "inspect", self._dockerCid]).decode().strip()
673+
self._serverIPAddress = json.loads(output)[0]["NetworkSettings"]["IPAddress"]
674+
for i in range(0, 40):
675+
if sys.platform == 'win32':
676+
break
677+
dockerTop = subprocess.check_output(["docker", "top", self._dockerCid]).decode().strip()
678+
omc_process = None
679+
for line in dockerTop.split("\n"):
680+
columns = line.split()
681+
if self._random_string in line:
682+
try:
683+
omc_process = DummyPopen(int(columns[1]))
684+
except psutil.NoSuchProcess:
685+
raise OMCSessionException(
686+
f"Could not find PID {dockerTop} - is this a docker instance spawned "
687+
f"without --pid=host?\nLog-file says:\n{open(self._omc_filehandle_log.name).read()}")
688+
break
689+
if omc_process is not None:
690+
break
691+
time.sleep(timeout / 40.0)
692+
if omc_process is None:
693+
raise OMCSessionException("Docker top did not contain omc process %s:\n%s\nLog-file says:\n%s"
694+
% (self._random_string, dockerTop,
695+
open(self._omc_filehandle_log.name).read()))
696+
697+
return omc_process
698+
699+
def _omc_port_get_main(self) -> Optional[str]:
700+
port = None
701+
702+
try:
703+
port = subprocess.check_output(args=["docker",
704+
"exec", str(self._dockerCid),
705+
"cat", self._omc_file_port.as_posix()],
706+
stderr=subprocess.DEVNULL).decode().strip()
707+
except subprocess.CalledProcessError:
708+
pass
709+
710+
return port

OMPython/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"""
3838

3939
from OMPython.ModelicaSystem import LinearizationResult, ModelicaSystem, ModelicaSystemCmd, ModelicaSystemError
40-
from OMPython.OMCSession import OMCSessionCmd, OMCSessionException, OMCSessionZMQ
40+
from OMPython.OMCSession import OMCSessionCmd, OMCSessionException, OMCSessionZMQ, OMCSessionZMQDocker
4141

4242
# global names imported if import 'from OMPython import *' is used
4343
__all__ = [
@@ -49,4 +49,5 @@
4949
'OMCSessionCmd',
5050
'OMCSessionException',
5151
'OMCSessionZMQ',
52+
'OMCSessionZMQDocker',
5253
]

tests/test_docker.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
class DockerTester(unittest.TestCase):
77
@pytest.mark.skip(reason="This test would fail")
88
def testDocker(self):
9-
om = OMPython.OMCSessionZMQ(docker="openmodelica/openmodelica:v1.16.1-minimal")
9+
om = OMPython.OMCSessionZMQDocker(docker="openmodelica/openmodelica:v1.16.1-minimal")
1010
assert om.sendExpression("getVersion()") == "OpenModelica 1.16.1"
11-
omInner = OMPython.OMCSessionZMQ(dockerContainer=om._dockerCid)
11+
omInner = OMPython.OMCSessionZMQDocker(dockerContainer=om._dockerCid)
1212
assert omInner.sendExpression("getVersion()") == "OpenModelica 1.16.1"
13-
om2 = OMPython.OMCSessionZMQ(docker="openmodelica/openmodelica:v1.16.1-minimal", port=11111)
13+
om2 = OMPython.OMCSessionZMQDocker(docker="openmodelica/openmodelica:v1.16.1-minimal", port=11111)
1414
assert om2.sendExpression("getVersion()") == "OpenModelica 1.16.1"
1515
del om2
1616
del omInner

0 commit comments

Comments
 (0)