class_docstr: A `RuntimeGroup` is the representation of logically related `Runtimes`.
  
  The group provides convenient methods for managing those related `Runtimes`.
  Most methods are wrappers around their counterparts in the `Runtime` class.
  Typical usage examples are exposing a port (i.e. a service such as a DB) in
  the `RuntimeGroup`, transfer files, or execute  a `RuntimeTask` on the contained
  `Runtimes`. Additionally, all concrete RuntimeCluster (e.g. HyperoptCluster)
  implementations rely on `RuntimeGroups` for example.
  
  Examples:
  Execute a `RuntimeTask` in a `RuntimeGroup`
  
  ```python
  # Create instances
  group = RuntimeGroup([Runtime('host-1'), Runtime('host-2')])
  # group = RuntimeGroup(hosts=['host-1', 'host-2'])
  my_task = RuntimeTask('group-demo').run_command('echo Hello Group!')
  
  # Execute a RuntimeTask in a single Runtime
  single_task = group.execute_task(my_task)
  print(single_task.execution_log[0])
  
  # Execute a RuntimeTask in the whole RuntimeGroup
  task_list = group.execute_task(my_task, broadcast=True)
  
  # Execute a RuntimeTask on a single Runtime contained in the RuntimeGroup
  task = group.execute_task(my_task)
  ```
  
  A DB is running on localhost on port `local_port` and the DB is only accessible
  from localhost. But you also want to access the service on the other `Runtimes` on port
  `runtime_port`. Then you can use this method to expose the service which is running on the
  local machine to the remote machines.
  
  ```python
  # Expose a port to all Runtimes contained in the Runtime. If a port list is given the next free port is
  # chosen and returned.
  group_port = group.expose_port_to_runtimes(local_port=60000, runtime_port=list(range(60000,60010)))
  print('Local port 60000 is now exposed to port ' + str(group_port) + ' in the RuntimeGroup!')
  ```
  
  A DB is running on a remote host on port `runtime_port` and the DB is only accessible from the remote
  machine itself. But you also want to access the service to other `Runtimes` in the group. Then you can use
  this method to expose the service which is running on one `Runtime` to the whole group.
  
  ```python
  # Expose a port from a Runtime to all other ones in the RuntimeGroup. If a port list is given the next
  # free port is chosen and returned.
  group_port = group.expose_port_from_runtime_to_group(host='host-1', runtime_port=60000,
                                                          group_port=list(range(60000,60010)))
  print('Port 60000 of `host-1` is now exposed to port ' + str(group_port) + ' in the RuntimeGroup!')
  ```
class_docstr_tok: ["A","`","RuntimeGroup","`","is","the","representation","of","logically","related","`","Runtimes","`",".","The","group","provides","convenient","methods","for","managing","those","related","`","Runtimes","`",".","Most","methods","are","wrappers","around","their","counterparts","in","the","`","Runtime","`","class",".","Typical","usage","examples","are","exposing","a","port","(","i.e",".","a","service","such","as","a","DB",")","in","the","`","RuntimeGroup","`",",","transfer","files",",","or","execute","a","`","RuntimeTask","`","on","the","contained","`","Runtimes","`",".","Additionally",",","all","concrete","RuntimeCluster","(","e.g",".","HyperoptCluster",")","implementations","rely","on","`","RuntimeGroups","`","for","example",".","Examples",":","Execute","a","`","RuntimeTask","`","in","a","`","RuntimeGroup","`","``","`","python","#","Create","instances","group","=","RuntimeGroup","(","[","Runtime","(","'host-1","'",")",",","Runtime","(","'host-2","'",")","]",")","#","group","=","RuntimeGroup","(","hosts=","[","'host-1","'",",","'host-2","'","]",")","my_task","=","RuntimeTask","(","'group-demo","'",")",".run_command","(","'echo","Hello","Group","!","'",")","#","Execute","a","RuntimeTask","in","a","single","Runtime","single_task","=","group.execute_task","(","my_task",")","print","(","single_task.execution_log","[","0","]",")","#","Execute","a","RuntimeTask","in","the","whole","RuntimeGroup","task_list","=","group.execute_task","(","my_task",",","broadcast=True",")","#","Execute","a","RuntimeTask","on","a","single","Runtime","contained","in","the","RuntimeGroup","task","=","group.execute_task","(","my_task",")","``","`","A","DB","is","running","on","localhost","on","port","`","local_port","`","and","the","DB","is","only","accessible","from","localhost",".","But","you","also","want","to","access","the","service","on","the","other","`","Runtimes","`","on","port","`","runtime_port","`",".","Then","you","can","use","this","method","to","expose","the","service","which","is","running","on","the","local","machine","to","the","remote","machines",".","``","`","python","#","Expose","a","port","to","all","Runtimes","contained","in","the","Runtime",".","If","a","port","list","is","given","the","next","free","port","is","#","chosen","and","returned",".","group_port","=","group.expose_port_to_runtimes","(","local_port=60000",",","runtime_port=list","(","range","(","60000,60010",")",")",")","print","(","'Local","port","60000","is","now","exposed","to","port","'","+","str","(","group_port",")","+","'","in","the","RuntimeGroup","!","'",")","``","`","A","DB","is","running","on","a","remote","host","on","port","`","runtime_port","`","and","the","DB","is","only","accessible","from","the","remote","machine","itself",".","But","you","also","want","to","access","the","service","to","other","`","Runtimes","`","in","the","group",".","Then","you","can","use","this","method","to","expose","the","service","which","is","running","on","one","`","Runtime","`","to","the","whole","group",".","``","`","python","#","Expose","a","port","from","a","Runtime","to","all","other","ones","in","the","RuntimeGroup",".","If","a","port","list","is","given","the","next","#","free","port","is","chosen","and","returned",".","group_port","=","group.expose_port_from_runtime_to_group","(","host='host-1","'",",","runtime_port=60000",",","group_port=list","(","range","(","60000,60010",")",")",")","print","(","'Port","60000","of","`","host-1","`","is","now","exposed","to","port","'","+","str","(","group_port",")","+","'","in","the","RuntimeGroup","!","'",")","``","`"]
docstr: Expose a port from a `Runtime` to all other `Runtimes` in the `RuntimeGroup`.
  
  All traffic to the `group_port` is forwarded to the `runtime_port` of the runtime.
  
  Args:
      host: The host of the `Runtime`.
      runtime_port: The port on the runtime.
      group_port: The port on the other runtimes where the `runtime_port` shall be exposed to. May raise
                  PortInUseError if a single port is given. If a list is used to automatically find a free port
                  then a NoPortsLeftError may be raised. Defaults to runtime_port.
  
  Returns:
      int: The `group_port` that was eventually used.
  
  Raises:
      ValueError: If host is not contained.
      PortInUseError: If `group_port` is occupied on the local machine.
      NoPortsLeftError: If `group_ports` was given and none of the ports was free.
docstr_tok: ["Expose","a","port","from","a","`","Runtime","`","to","all","other","`","Runtimes","`","in","the","`","RuntimeGroup","`",".","All","traffic","to","the","`","group_port","`","is","forwarded","to","the","`","runtime_port","`","of","the","runtime",".","Args",":","host",":","The","host","of","the","`","Runtime","`",".","runtime_port",":","The","port","on","the","runtime",".","group_port",":","The","port","on","the","other","runtimes","where","the","`","runtime_port","`","shall","be","exposed","to",".","May","raise","PortInUseError","if","a","single","port","is","given",".","If","a","list","is","used","to","automatically","find","a","free","port","then","a","NoPortsLeftError","may","be","raised",".","Defaults","to","runtime_port",".","Returns",":","int",":","The","`","group_port","`","that","was","eventually","used",".","Raises",":","ValueError",":","If","host","is","not","contained",".","PortInUseError",":","If","`","group_port","`","is","occupied","on","the","local","machine",".","NoPortsLeftError",":","If","`","group_ports","`","was","given","and","none","of","the","ports","was","free","."]
code: def expose_port_from_runtime_to_group(
      self,
      host: str,
      runtime_port: int,
      group_port: Union[int, List[int], None] = None,
  ) -> int:
      """Expose a port from a `Runtime` to all other `Runtimes` in the `RuntimeGroup`.
  
      All traffic to the `group_port` is forwarded to the `runtime_port` of the runtime.
  
      Args:
          host: The host of the `Runtime`.
          runtime_port: The port on the runtime.
          group_port: The port on the other runtimes where the `runtime_port` shall be exposed to. May raise
                      PortInUseError if a single port is given. If a list is used to automatically find a free port
                      then a NoPortsLeftError may be raised. Defaults to runtime_port.
  
      Returns:
          int: The `group_port` that was eventually used.
  
      Raises:
          ValueError: If host is not contained.
          PortInUseError: If `group_port` is occupied on the local machine.
          NoPortsLeftError: If `group_ports` was given and none of the ports was free.
      """
      if not self.contains_runtime(host):
          raise ValueError(
              "Runtime " + host + " is not contained in the group."
          )
  
      self.log.info(
          f"Start exposing the port {str(runtime_port)} from Runtime {host} in the RuntimeGroup."
      )
  
      selected_group_port = group_port
  
      # 1. Determine a free group port
      if not selected_group_port or not isinstance(
          selected_group_port, list
      ):
          if not selected_group_port:
              selected_group_port = runtime_port
          if not isinstance(
              selected_group_port, list
          ) and not self.has_free_port(
              selected_group_port, exclude_hosts=[host]
          ):
              raise PortInUseError(selected_group_port, self)
  
      elif isinstance(selected_group_port, list):
          selected_group_port = self.get_free_port(selected_group_port)
  
      assert isinstance(selected_group_port, int)
  
      # 2. Determine a free port on localhost, since all traffic is tunneled over the local machine
      local_port = None
      if _utils.localhost_has_free_port(selected_group_port):
          local_port = selected_group_port
      else:
          # Get the first free port from the local port range
          for current_port in self._internal_port_range:
              if not _utils.localhost_has_free_port(current_port):
                  continue
              else:
                  local_port = current_port
                  self._internal_port_range = range(
                      current_port + 1, self._INTERNAL_PORT_MAX
                  )
  
      if not local_port:
          # self._internal_port_range = None <- does this makes sense? not sure!
          raise NoPortsLeftError()
  
      # 3. Finally expose the ports
      for runtime in self._runtimes.values():
          if runtime.host == host:
              process_key = runtime.expose_port_from_runtime(
                  runtime_port, local_port
              )
          else:
              process_key = runtime.expose_port_to_runtime(
                  local_port, selected_group_port
              )
          if process_key:
              self._proc_keys.append(process_key)
      self.log.debug(
          f"Port {str(selected_group_port)} used as selected_group_port."
      )
      return selected_group_port
  
imports: import atexit
  import logging
  import warnings
  from copy import deepcopy
  from multiprocessing import Pool, Process
  from typing import Dict, Generator, List, Optional, Union
  from storm import Storm
  from lazycluster import _utils
  from lazycluster.exceptions import InvalidRuntimeError, NoPortsLeftError, NoRuntimesDetectedError, PortInUseError
  from lazycluster.runtimes import Runtime, RuntimeTask
label_desc: Use image node_id 17 for calling the RuntimeGroup obj's underlying member method code with example usage: obj.expose_port_from_runtime_to_group(host, runtime_port, group_port) and returns: selected_group_port