diff --git a/doc/rapi.rst b/doc/rapi.rst
index 0e749122dbda550a82ee87859a42983f6654e2d0..a913b3215871724a6293f77f9fd836ce1e94ac87 100644
--- a/doc/rapi.rst
+++ b/doc/rapi.rst
@@ -464,6 +464,40 @@ Example::
       ...
     ]
 
+``/2/nodes/[node_name]/role``
++++++++++++++++++++++++++++++
+
+Manages node role.
+
+It supports the following commands: ``GET``, ``PUT``.
+
+The role is always one of the following:
+
+  - drained
+  - master
+  - master-candidate
+  - offline
+  - regular
+
+``GET``
+~~~~~~~
+
+Returns the current node role.
+
+Example::
+
+    "master-candidate"
+
+``PUT``
+~~~~~~~
+
+Change the node role.
+
+The request is a string which should be PUT to this URI. The result will be a
+job id.
+
+It supports the ``force`` argument.
+
 ``/2/nodes/[node_name]/tags``
 +++++++++++++++++++++++++++++
 
diff --git a/lib/rapi/connector.py b/lib/rapi/connector.py
index 43c15c0c74ebcae786466402b51cf4258083aae1..9c99dcfa6c9ccae0deabd0adbf531e6128f3f5b5 100644
--- a/lib/rapi/connector.py
+++ b/lib/rapi/connector.py
@@ -154,6 +154,7 @@ CONNECTOR.update({
   "/2/nodes": rlib2.R_2_nodes,
   re.compile(r'^/2/nodes/([\w\._-]+)$'): rlib2.R_2_nodes_name,
   re.compile(r'^/2/nodes/([\w\._-]+)/tags$'): rlib2.R_2_nodes_name_tags,
+  re.compile(r'^/2/nodes/([\w\._-]+)/role$'): rlib2.R_2_nodes_name_role,
   "/2/instances": rlib2.R_2_instances,
   re.compile(r'^/2/instances/([\w\._-]+)$'): rlib2.R_2_instances_name,
   re.compile(r'^/2/instances/([\w\._-]+)/tags$'): rlib2.R_2_instances_name_tags,
diff --git a/lib/rapi/rlib2.py b/lib/rapi/rlib2.py
index d79a0fc08888949e981b4de437d6c20340dd3e81..550692acd51e1be983fe7dd705e127e7de701011 100644
--- a/lib/rapi/rlib2.py
+++ b/lib/rapi/rlib2.py
@@ -30,7 +30,6 @@ from ganeti import cli
 from ganeti.rapi import baserlib
 
 
-
 I_FIELDS = ["name", "admin_state", "os",
             "pnode", "snodes",
             "disk_template",
@@ -48,6 +47,20 @@ N_FIELDS = ["name", "offline", "master_candidate", "drained",
             "ctotal", "cnodes", "csockets",
             ]
 
+_NR_DRAINED = "drained"
+_NR_MASTER_CANDIATE = "master-candidate"
+_NR_MASTER = "master"
+_NR_OFFLINE = "offline"
+_NR_REGULAR = "regular"
+
+_NR_MAP = {
+  "M": _NR_MASTER,
+  "C": _NR_MASTER_CANDIATE,
+  "D": _NR_DRAINED,
+  "O": _NR_OFFLINE,
+  "R": _NR_REGULAR,
+  }
+
 
 class R_version(baserlib.R_Generic):
   """/version resource.
@@ -190,6 +203,64 @@ class R_2_nodes_name(baserlib.R_Generic):
     return baserlib.MapFields(N_FIELDS, result[0])
 
 
+class R_2_nodes_name_role(baserlib.R_Generic):
+  """ /2/nodes/[node_name]/role resource.
+
+  """
+  def GET(self):
+    """Returns the current node role.
+
+    @return: Node role
+
+    """
+    node_name = self.items[0]
+    client = baserlib.GetClient()
+    result = client.QueryNodes(names=[node_name], fields=["role"],
+                               use_locking=self.useLocking())
+
+    return _NR_MAP[result[0][0]]
+
+  def PUT(self):
+    """Sets the node role.
+
+    @return: a job id
+
+    """
+    if not isinstance(self.req.request_body, basestring):
+      raise http.HttpBadRequest("Invalid body contents, not a string")
+
+    node_name = self.items[0]
+    role = self.req.request_body
+
+    if role == _NR_REGULAR:
+      candidate = False
+      offline = False
+      drained = False
+
+    elif role == _NR_MASTER_CANDIATE:
+      candidate = True
+      offline = drained = None
+
+    elif role == _NR_DRAINED:
+      drained = True
+      candidate = offline = None
+
+    elif role == _NR_OFFLINE:
+      offline = True
+      candidate = drained = None
+
+    else:
+      raise http.HttpBadRequest("Can't set '%s' role" % role)
+
+    op = opcodes.OpSetNodeParams(node_name=node_name,
+                                 master_candidate=candidate,
+                                 offline=offline,
+                                 drained=drained,
+                                 force=bool(self.useForce()))
+
+    return baserlib.SubmitJob([op])
+
+
 class R_2_instances(baserlib.R_Generic):
   """/2/instances resource.