diff --git a/src/execo_g5k/__init__.py b/src/execo_g5k/__init__.py
index 176c718e1c267d505311d83d50b00c493b97173f..95f95c4537c025802e4a9af9c24b9acd1aaf73e2 100644
--- a/src/execo_g5k/__init__.py
+++ b/src/execo_g5k/__init__.py
@@ -30,7 +30,7 @@ from .oargrid import oargridsub, oargriddel, \
     get_oargrid_job_oar_jobs, wait_oargrid_job_start, \
     get_oargrid_job_nodes, get_oargrid_job_key
 
-from .kadeploy import Deployment, Kadeployer, deploy
+from .kadeploy import Deployment, Kadeployer, deploy, KaconsoleProcess
 
 from .utils import get_kavlan_host_name, G5kAutoPortForwarder
 
diff --git a/src/execo_g5k/kadeploy.py b/src/execo_g5k/kadeploy.py
index 316dd4db82458a1174464eee4f3d30456e60061a..e14bef6a9c88665d558d0b4c906d4bd7db4bc47a 100644
--- a/src/execo_g5k/kadeploy.py
+++ b/src/execo_g5k/kadeploy.py
@@ -23,7 +23,8 @@ from execo.config import make_connection_params
 from execo.host import get_hosts_set, Host
 from execo.log import style, logger
 from execo.process import ProcessOutputHandler, get_process, \
-    handle_process_output
+    handle_process_output, SshProcess, ProcessLifecycleHandler, \
+    ExpectOutputHandler
 from execo.time_utils import format_seconds
 from execo.utils import comma_join, compact_output, singleton_to_collection
 from execo_g5k.config import default_frontend_connection_params
@@ -32,6 +33,7 @@ from .utils import get_default_frontend
 import copy
 import re
 import time
+import threading
 
 class Deployment(object):
     """A kadeploy3 deployment, POD style class."""
@@ -501,3 +503,60 @@ def deploy(deployment,
     logger.debug(style.emph("undeployed hosts:") + " %s", undeployed_hosts)
 
     return (deployed_hosts, undeployed_hosts)
+
+class KaconsoleProcessLifecycleHandler(ProcessLifecycleHandler):
+
+    def start(self, p):
+        p.expect('Connection to console .* opened',
+                 timeout=max(0, p.start_date + p.console_connection_timeout - time.time()) if p.console_connection_timeout else None,
+                 expect_output_handler=p.console_connection_expect_output_handler)
+        p.write("\x04\n\x04\n") # to make sure we disconnect if a console was previously opened, and to get the login prompt
+        p.expect('login: ',
+                 timeout=max(0, p.start_date + p.console_connection_timeout - time.time()) if p.console_connection_timeout else None,
+                 expect_output_handler=p.console_connection_expect_output_handler)
+        p.write(p.console_connection_params['user'] + "\n")
+        p.expect('Password: ',
+                 timeout=max(0, p.start_date + p.console_connection_timeout - time.time()) if p.console_connection_timeout else None,
+                 expect_output_handler=p.console_connection_expect_output_handler)
+        p.write(p.console_connection_params['password'] + "\n")
+        p.console_connected.set()
+
+class KaconsoleProcess(SshProcess):
+    """A connection to the console of a Grid5000 through kaconsole3.
+
+    Unlike other process classes, this class, once started, leaves the
+    connection opened, allowing to send commands (and expect outputs)
+    through the pipe.
+
+    Note that only one console can be simultaneously opened at one time to a node
+    """
+
+    def __init__(self, host, frontend_connection_params=None, connection_timeout=20, prompt='root@[^#]*#', **kwargs):
+        host = Host(host)
+        self.console_connection_timeout = connection_timeout
+        self.console_connection_params = make_connection_params(kwargs.get("connection_params"), {'user': 'root', 'password': 'grid5000'})
+        if 'connection_params' in kwargs: del kwargs['connection_params']
+        super().__init__("kaconsole3 " + host.address,
+                         host = get_frontend_host(_get_host_frontend(host)),
+                         connection_params = make_connection_params(frontend_connection_params, default_frontend_connection_params),
+                         **kwargs)
+        self.pty = True
+        self.prompt = prompt
+        self.console_connected = threading.Event()
+        self.console_connection_expect_output_handler = ExpectOutputHandler()
+        self.lifecycle_handlers.append(KaconsoleProcessLifecycleHandler())
+
+    def wait_connected(self, timeout=None):
+        if self.ended:
+            return False
+        if not self.console_connected.wait(timeout=timeout):
+            return False
+        return True
+
+    def expect_prompt(self, timeout=None):
+        wait_start = time.time()
+        if not self.wait_connected(timeout=timeout):
+            return None, None
+        return self.expect(self.prompt,
+                           timeout=max(0, wait_start + timeout - time.time()) if timeout else None,
+                           expect_output_handler=self.console_connection_expect_output_handler)