diff --git a/lib/cmdlib.py b/lib/cmdlib.py
index 0630397806970c3648e4ff98be35dff8232cfaea..a13a8beb39ab0141b8f8bca0b2257e8e0be8e184 100644
--- a/lib/cmdlib.py
+++ b/lib/cmdlib.py
@@ -3790,11 +3790,14 @@ class LUAddNode(LogicalUnit):
       self.new_node = self.cfg.GetNodeInfo(node)
       assert self.new_node is not None, "Can't retrieve locked node %s" % node
     else:
+      # TODO: process an arbitrary non-default nodegroup
+      nodegroup = cfg.LookupNodeGroup(None)
       self.new_node = objects.Node(name=node,
                                    primary_ip=primary_ip,
                                    secondary_ip=secondary_ip,
                                    master_candidate=self.master_candidate,
-                                   offline=False, drained=False)
+                                   offline=False, drained=False,
+                                   nodegroup=nodegroup)
 
   def Exec(self, feedback_fn):
     """Adds the new node to the cluster.
diff --git a/lib/config.py b/lib/config.py
index 1383b773f6c32f0a16412627b1cbdbeef2a30a8b..f955b10bebd3b3688b7cab141ed33b6b39cf00c2 100644
--- a/lib/config.py
+++ b/lib/config.py
@@ -1065,6 +1065,7 @@ class ConfigWriter:
 
     node.serial_no = 1
     node.ctime = node.mtime = time.time()
+    self._UnlockedAddNodeToGroup(node.name, node.nodegroup)
     self._config_data.nodes[node.name] = node
     self._config_data.cluster.serial_no += 1
     self._WriteConfig()
@@ -1079,6 +1080,7 @@ class ConfigWriter:
     if node_name not in self._config_data.nodes:
       raise errors.ConfigurationError("Unknown node '%s'" % node_name)
 
+    self._UnlockedRemoveNodeFromGroup(self._config_data.nodes[node_name])
     del self._config_data.nodes[node_name]
     self._config_data.cluster.serial_no += 1
     self._WriteConfig()
@@ -1239,6 +1241,34 @@ class ConfigWriter:
 
     return mod_list
 
+  def _UnlockedAddNodeToGroup(self, node_name, nodegroup_uuid):
+    """Add a given node to the specified group.
+
+    """
+    if nodegroup_uuid not in self._config_data.nodegroups:
+      # This can happen if a node group gets deleted between its lookup and
+      # when we're adding the first node to it, since we don't keep a lock in
+      # the meantime. It's ok though, as we'll fail cleanly if the node group
+      # is not found anymore.
+      raise errors.OpExecError("Unknown nodegroup: %s" % nodegroup_uuid)
+    if node_name not in self._config_data.nodegroups[nodegroup_uuid].members:
+      self._config_data.nodegroups[nodegroup_uuid].members.append(node_name)
+
+  def _UnlockedRemoveNodeFromGroup(self, node):
+    """Remove a given node from its group.
+
+    """
+    nodegroup = node.nodegroup
+    if nodegroup not in self._config_data.nodegroups:
+      logging.warning("Warning: node '%s' has a non-existing nodegroup '%s'"
+                      " (while being removed from it)", node.name, nodegroup)
+    nodegroup_obj = self._config_data.nodegroups[nodegroup]
+    if node.name not in nodegroup_obj.members:
+      logging.warning("Warning: node '%s' not a member of its nodegroup '%s'"
+                      " (while being removed from it)", node.name, nodegroup)
+    else:
+      nodegroup_obj.members.remove(node.name)
+
   def _BumpSerialNo(self):
     """Bump up the serial number of the config.
 
@@ -1312,6 +1342,15 @@ class ConfigWriter:
           )
       self._config_data.nodegroups[default_nodegroup_uuid] = default_nodegroup
       modified = True
+    for node in self._config_data.nodes.values():
+      if not node.nodegroup:
+        node.nodegroup = self.LookupNodeGroup(None)
+        modified = True
+      # This is technically *not* an upgrade, but needs to be done both when
+      # nodegroups are being added, and upon normally loading the config,
+      # because the members list of a node group is discarded upon
+      # serializing/deserializing the object.
+      self._UnlockedAddNodeToGroup(node.name, node.nodegroup)
     if modified:
       self._WriteConfig()
       # This is ok even if it acquires the internal lock, as _UpgradeConfig is
diff --git a/lib/objects.py b/lib/objects.py
index 9cbbbee4142c180559b859459b1567f0716a1f5e..fe4775886c927143b312c6e9ddc21c5cf30caf8f 100644
--- a/lib/objects.py
+++ b/lib/objects.py
@@ -901,6 +901,7 @@ class Node(TaggableObject):
     "master_candidate",
     "offline",
     "drained",
+    "nodegroup",
     ] + _TIMESTAMPS + _UUID