diff --git a/qa/ganeti-qa.py b/qa/ganeti-qa.py
index d16943d3d6e9bb0f261f44a62bfcff0bfd3b3303..bc31b7b955e3d039f75f2284fe25f91fedfeb0c2 100755
--- a/qa/ganeti-qa.py
+++ b/qa/ganeti-qa.py
@@ -528,7 +528,7 @@ def RunInstanceTests():
           if len(inodes) > 1:
             RunTestIf("group-rwops", qa_group.TestAssignNodesIncludingSplit,
                       constants.INITIAL_NODE_GROUP_NAME,
-                      inodes[0]["primary"], inodes[1]["primary"])
+                      inodes[0].primary, inodes[1].primary)
           if qa_config.TestEnabled("instance-convert-disk"):
             RunTest(qa_instance.TestInstanceShutdown, instance)
             RunTest(qa_instance.TestInstanceConvertDiskToPlain,
@@ -542,7 +542,6 @@ def RunInstanceTests():
           RunTest(qa_instance.TestInstanceRemove, instance)
         finally:
           instance.Release()
-
         del instance
       finally:
         qa_config.ReleaseManyNodes(inodes)
@@ -692,7 +691,7 @@ def main():
 
   qa_config.Load(config_file)
 
-  primary = qa_config.GetMasterNode()["primary"]
+  primary = qa_config.GetMasterNode().primary
   qa_utils.StartMultiplexer(primary)
   print ("SSH command for primary node: %s" %
          utils.ShellQuoteArgs(qa_utils.GetSSHCommand(primary, "")))
diff --git a/qa/qa_cluster.py b/qa/qa_cluster.py
index 28a34ab080758a670640aed39a3f05202d22ce52..10aedbe7dcf6fa364fa7ad9f80b7c2981d9e0ab7 100644
--- a/qa/qa_cluster.py
+++ b/qa/qa_cluster.py
@@ -60,7 +60,7 @@ def _CheckFileOnAllNodes(filename, content):
   """
   cmd = utils.ShellQuoteArgs(["cat", filename])
   for node in qa_config.get("nodes"):
-    AssertEqual(qa_utils.GetCommandOutput(node["primary"], cmd), content)
+    AssertEqual(qa_utils.GetCommandOutput(node.primary, cmd), content)
 
 
 # "gnt-cluster info" fields
@@ -81,7 +81,7 @@ def _GetBoolClusterField(field):
   """
   master = qa_config.GetMasterNode()
   infocmd = "gnt-cluster info"
-  info_out = qa_utils.GetCommandOutput(master["primary"], infocmd)
+  info_out = qa_utils.GetCommandOutput(master.primary, infocmd)
   ret = None
   for l in info_out.splitlines():
     m = _CIFIELD_RE.match(l)
@@ -123,7 +123,7 @@ def AssertClusterVerify(fail=False, errors=None):
   cvcmd = "gnt-cluster verify"
   mnode = qa_config.GetMasterNode()
   if errors:
-    cvout = GetCommandOutput(mnode["primary"], cvcmd + " --error-codes",
+    cvout = GetCommandOutput(mnode.primary, cvcmd + " --error-codes",
                              fail=True)
     actual = _GetCVErrorCodes(cvout)
     expected = compat.UniqueFrozenset(e for (_, e, _) in errors)
@@ -161,7 +161,7 @@ def TestClusterInit(rapi_user, rapi_secret):
     fh.write("%s %s write\n" % (rapi_user, rapi_secret))
     fh.flush()
 
-    tmpru = qa_utils.UploadFile(master["primary"], fh.name)
+    tmpru = qa_utils.UploadFile(master.primary, fh.name)
     try:
       AssertCommand(["mkdir", "-p", rapi_dir])
       AssertCommand(["mv", tmpru, pathutils.RAPI_USERS_FILE])
@@ -185,8 +185,8 @@ def TestClusterInit(rapi_user, rapi_secret):
       if spec:
         cmd.append("--specs-%s=%s=%d" % (spec_type, spec_val, spec))
 
-  if master.get("secondary", None):
-    cmd.append("--secondary-ip=%s" % master["secondary"])
+  if master.secondary:
+    cmd.append("--secondary-ip=%s" % master.secondary)
 
   vgname = qa_config.get("vg-name", None)
   if vgname:
@@ -294,7 +294,7 @@ def TestClusterEpo():
   master = qa_config.GetMasterNode()
 
   # Assert that OOB is unavailable for all nodes
-  result_output = GetCommandOutput(master["primary"],
+  result_output = GetCommandOutput(master.primary,
                                    "gnt-node list --verbose --no-headers -o"
                                    " powered")
   AssertEqual(compat.all(powered == "(unavail)"
@@ -306,13 +306,13 @@ def TestClusterEpo():
   AssertCommand(["gnt-cluster", "epo", "--all", "some_arg"], fail=True)
 
   # Unless --all is given master is not allowed to be in the list
-  AssertCommand(["gnt-cluster", "epo", "-f", master["primary"]], fail=True)
+  AssertCommand(["gnt-cluster", "epo", "-f", master.primary], fail=True)
 
   # This shouldn't fail
   AssertCommand(["gnt-cluster", "epo", "-f", "--all"])
 
   # All instances should have been stopped now
-  result_output = GetCommandOutput(master["primary"],
+  result_output = GetCommandOutput(master.primary,
                                    "gnt-instance list --no-headers -o status")
   # ERROR_down because the instance is stopped but not recorded as such
   AssertEqual(compat.all(status == "ERROR_down"
@@ -322,7 +322,7 @@ def TestClusterEpo():
   AssertCommand(["gnt-cluster", "epo", "--on", "-f", "--all"])
 
   # All instances should have been started now
-  result_output = GetCommandOutput(master["primary"],
+  result_output = GetCommandOutput(master.primary,
                                    "gnt-instance list --no-headers -o status")
   AssertEqual(compat.all(status == "running"
                          for status in result_output.splitlines()), True)
@@ -344,7 +344,7 @@ def TestDelay(node):
   AssertCommand(["gnt-debug", "delay", "1"])
   AssertCommand(["gnt-debug", "delay", "--no-master", "1"])
   AssertCommand(["gnt-debug", "delay", "--no-master",
-                 "-n", node["primary"], "1"])
+                 "-n", node.primary, "1"])
 
 
 def TestClusterReservedLvs():
@@ -457,7 +457,7 @@ def TestClusterRenewCrypto():
          "--rapi-certificate=/dev/null"]
   AssertCommand(cmd, fail=True)
 
-  rapi_cert_backup = qa_utils.BackupFile(master["primary"],
+  rapi_cert_backup = qa_utils.BackupFile(master.primary,
                                          pathutils.RAPI_CERT_FILE)
   try:
     # Custom RAPI certificate
@@ -468,7 +468,7 @@ def TestClusterRenewCrypto():
 
     utils.GenerateSelfSignedSslCert(fh.name, validity=validity)
 
-    tmpcert = qa_utils.UploadFile(master["primary"], fh.name)
+    tmpcert = qa_utils.UploadFile(master.primary, fh.name)
     try:
       AssertCommand(["gnt-cluster", "renew-crypto", "--force",
                      "--rapi-certificate=%s" % tmpcert])
@@ -481,7 +481,7 @@ def TestClusterRenewCrypto():
     cds_fh.write("\n")
     cds_fh.flush()
 
-    tmpcds = qa_utils.UploadFile(master["primary"], cds_fh.name)
+    tmpcds = qa_utils.UploadFile(master.primary, cds_fh.name)
     try:
       AssertCommand(["gnt-cluster", "renew-crypto", "--force",
                      "--cluster-domain-secret=%s" % tmpcds])
@@ -525,7 +525,7 @@ def TestClusterBurnin():
     if len(instances) < 1:
       raise qa_error.Error("Burnin needs at least one instance")
 
-    script = qa_utils.UploadFile(master["primary"], "../tools/burnin")
+    script = qa_utils.UploadFile(master.primary, "../tools/burnin")
     try:
       # Run burnin
       cmd = [script,
@@ -613,7 +613,7 @@ def TestClusterCopyfile():
   f.seek(0)
 
   # Upload file to master node
-  testname = qa_utils.UploadFile(master["primary"], f.name)
+  testname = qa_utils.UploadFile(master.primary, f.name)
   try:
     # Copy file to all nodes
     AssertCommand(["gnt-cluster", "copyfile", testname])
@@ -676,7 +676,7 @@ def TestExclStorSingleNode(node):
   """cluster-verify reports exclusive_storage set only on one node.
 
   """
-  node_name = node["primary"]
+  node_name = node.primary
   es_val = _GetBoolClusterField("exclusive_storage")
   assert not es_val
   AssertCommand(_BuildSetESCmd(True, node_name))
@@ -692,7 +692,7 @@ def TestExclStorSharedPv(node):
   vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
   lvname1 = _QA_LV_PREFIX + "vol1"
   lvname2 = _QA_LV_PREFIX + "vol2"
-  node_name = node["primary"]
+  node_name = node.primary
   AssertCommand(["lvcreate", "-L1G", "-n", lvname1, vgname], node=node_name)
   AssertClusterVerify(fail=True, errors=[constants.CV_ENODEORPHANLV])
   AssertCommand(["lvcreate", "-L1G", "-n", lvname2, vgname], node=node_name)
diff --git a/qa/qa_daemon.py b/qa/qa_daemon.py
index 6d2456cea948011a40b343db7b6a44370eea3549..ebdf85cd8570ad59f4b844648170dcbeaa9fdf8e 100644
--- a/qa/qa_daemon.py
+++ b/qa/qa_daemon.py
@@ -45,7 +45,7 @@ def _InstanceRunning(name):
 
   cmd = (utils.ShellQuoteArgs(["gnt-instance", "list", "-o", "status", name]) +
          ' | grep running')
-  ret = StartSSH(master["primary"], cmd).wait()
+  ret = StartSSH(master.primary, cmd).wait()
   return ret == 0
 
 
@@ -87,7 +87,7 @@ def TestPauseWatcher():
   AssertCommand(["gnt-cluster", "watcher", "pause", "4h"])
 
   cmd = ["gnt-cluster", "watcher", "info"]
-  output = GetCommandOutput(master["primary"],
+  output = GetCommandOutput(master.primary,
                             utils.ShellQuoteArgs(cmd))
   AssertMatch(output, r"^.*\bis paused\b.*")
 
@@ -101,7 +101,7 @@ def TestResumeWatcher():
   AssertCommand(["gnt-cluster", "watcher", "continue"])
 
   cmd = ["gnt-cluster", "watcher", "info"]
-  output = GetCommandOutput(master["primary"],
+  output = GetCommandOutput(master.primary,
                             utils.ShellQuoteArgs(cmd))
   AssertMatch(output, r"^.*\bis not paused\b.*")
 
diff --git a/qa/qa_env.py b/qa/qa_env.py
index bb2f39f570fe1c96b952a5f0db4a884e172a58fa..bb8ab3364b903d7d8ec590c1e79190daf6450bd6 100644
--- a/qa/qa_env.py
+++ b/qa/qa_env.py
@@ -75,9 +75,9 @@ def TestIcmpPing():
   pricmd = [pingprimary, "-e"]
   seccmd = [pingsecondary, "-e"]
   for i in nodes:
-    pricmd.append(i["primary"])
-    if i.get("secondary"):
-      seccmd.append(i["secondary"])
+    pricmd.append(i.primary)
+    if i.secondary:
+      seccmd.append(i.secondary)
 
   pristr = utils.ShellQuoteArgs(pricmd)
   if seccmd:
diff --git a/qa/qa_group.py b/qa/qa_group.py
index 2e872ba29e58b7df2104f3d46d23c17612aa6c2b..22f9f72dbefcd223d8b2c3e8a1127c01195681aa 100644
--- a/qa/qa_group.py
+++ b/qa/qa_group.py
@@ -128,7 +128,7 @@ def TestAssignNodesIncludingSplit(orig_group, node1, node2):
 
   (other_group, ) = qa_utils.GetNonexistentGroups(1)
 
-  master_node = qa_config.GetMasterNode()["primary"]
+  master_node = qa_config.GetMasterNode().primary
 
   def AssertInGroup(group, nodes):
     real_output = GetCommandOutput(master_node,
diff --git a/qa/qa_instance.py b/qa/qa_instance.py
index 693ce47df4ed1b282b81eebe1ef999668698bb15..57bf44662fb7843a2e7800e7233e1da03718d931 100644
--- a/qa/qa_instance.py
+++ b/qa/qa_instance.py
@@ -99,7 +99,7 @@ def _GetInstanceInfo(instance):
   """
   master = qa_config.GetMasterNode()
   infocmd = utils.ShellQuoteArgs(["gnt-instance", "info", instance])
-  info_out = qa_utils.GetCommandOutput(master["primary"], infocmd)
+  info_out = qa_utils.GetCommandOutput(master.primary, infocmd)
   re_node = re.compile(r"^\s+-\s+(?:primary|secondaries):\s+(\S.+)$")
   node_elem = r"([^,()]+)(?:\s+\([^)]+\))?"
   # re_nodelist matches a list of nodes returned by gnt-instance info, e.g.:
@@ -165,7 +165,7 @@ def _GetBoolInstanceField(instance, field):
   master = qa_config.GetMasterNode()
   infocmd = utils.ShellQuoteArgs(["gnt-instance", "list", "--no-headers",
                                   "-o", field, instance])
-  info_out = qa_utils.GetCommandOutput(master["primary"], infocmd).strip()
+  info_out = qa_utils.GetCommandOutput(master.primary, infocmd).strip()
   if info_out == "Y":
     return True
   elif info_out == "N":
@@ -194,14 +194,14 @@ def IsDiskReplacingSupported(instance):
 def TestInstanceAddWithPlainDisk(nodes):
   """gnt-instance add -t plain"""
   assert len(nodes) == 1
-  return _DiskTest(nodes[0]["primary"], "plain")
+  return _DiskTest(nodes[0].primary, "plain")
 
 
 @InstanceCheck(None, INST_UP, RETURN_VALUE)
 def TestInstanceAddWithDrbdDisk(nodes):
   """gnt-instance add -t drbd"""
   assert len(nodes) == 2
-  return _DiskTest(":".join(map(operator.itemgetter("primary"), nodes)),
+  return _DiskTest(":".join(map(operator.attrgetter("primary"), nodes)),
                    "drbd")
 
 
@@ -238,7 +238,7 @@ def TestInstanceReboot(instance):
 
   master = qa_config.GetMasterNode()
   cmd = ["gnt-instance", "list", "--no-headers", "-o", "status", name]
-  result_output = qa_utils.GetCommandOutput(master["primary"],
+  result_output = qa_utils.GetCommandOutput(master.primary,
                                             utils.ShellQuoteArgs(cmd))
   AssertEqual(result_output.strip(), constants.INSTST_RUNNING)
 
@@ -264,7 +264,7 @@ def _ReadSsconfInstanceList():
   cmd = ["cat", utils.PathJoin(pathutils.DATA_DIR,
                                "ssconf_%s" % constants.SS_INSTANCE_LIST)]
 
-  return qa_utils.GetCommandOutput(master["primary"],
+  return qa_utils.GetCommandOutput(master.primary,
                                    utils.ShellQuoteArgs(cmd)).splitlines()
 
 
@@ -506,7 +506,7 @@ def TestInstanceConvertDiskToPlain(instance, inodes):
   assert len(inodes) == 2
   AssertCommand(["gnt-instance", "modify", "-t", "plain", name])
   AssertCommand(["gnt-instance", "modify", "-t", "drbd",
-                 "-n", inodes[1]["primary"], name])
+                 "-n", inodes[1].primary, name])
 
 
 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
@@ -579,13 +579,13 @@ def TestReplaceDisks(instance, curr_nodes, other_nodes):
     # A placeholder; the actual command choice depends on use_ialloc
     None,
     # Restore the original secondary
-    ["--new-secondary=%s" % snode["primary"]],
+    ["--new-secondary=%s" % snode.primary],
     ]:
     if data is None:
       if use_ialloc:
         data = ["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT]
       else:
-        data = ["--new-secondary=%s" % othernode["primary"]]
+        data = ["--new-secondary=%s" % othernode.primary]
     AssertCommand(buildcmd(data))
 
   AssertCommand(buildcmd(["-a"]))
@@ -632,8 +632,8 @@ def TestRecreateDisks(instance, inodes, othernodes):
   """
   options = qa_config.get("options", {})
   use_ialloc = options.get("use-iallocators", True)
-  other_seq = ":".join([n["primary"] for n in othernodes])
-  orig_seq = ":".join([n["primary"] for n in inodes])
+  other_seq = ":".join([n.primary for n in othernodes])
+  orig_seq = ":".join([n.primary for n in inodes])
   # These fail because the instance is running
   _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
   if use_ialloc:
@@ -664,14 +664,14 @@ def TestRecreateDisks(instance, inodes, othernodes):
 def TestInstanceExport(instance, node):
   """gnt-backup export -n ..."""
   name = instance["name"]
-  AssertCommand(["gnt-backup", "export", "-n", node["primary"], name])
+  AssertCommand(["gnt-backup", "export", "-n", node.primary, name])
   return qa_utils.ResolveInstanceName(name)
 
 
 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
 def TestInstanceExportWithRemove(instance, node):
   """gnt-backup export --remove-instance"""
-  AssertCommand(["gnt-backup", "export", "-n", node["primary"],
+  AssertCommand(["gnt-backup", "export", "-n", node.primary,
                  "--remove-instance", instance["name"]])
 
 
@@ -688,9 +688,9 @@ def TestInstanceImport(newinst, node, expnode, name):
   cmd = (["gnt-backup", "import",
           "--disk-template=%s" % templ,
           "--no-ip-check",
-          "--src-node=%s" % expnode["primary"],
+          "--src-node=%s" % expnode.primary,
           "--src-dir=%s/%s" % (pathutils.EXPORT_DIR, name),
-          "--node=%s" % node["primary"]] +
+          "--node=%s" % node.primary] +
          _GetGenericAddParameters(newinst, force_mac=constants.VALUE_GENERATE))
   cmd.append(newinst["name"])
   AssertCommand(cmd)
@@ -699,7 +699,7 @@ def TestInstanceImport(newinst, node, expnode, name):
 
 def TestBackupList(expnode):
   """gnt-backup list"""
-  AssertCommand(["gnt-backup", "list", "--node=%s" % expnode["primary"]])
+  AssertCommand(["gnt-backup", "list", "--node=%s" % expnode.primary])
 
   qa_utils.GenericQueryTest("gnt-backup", query.EXPORT_FIELDS.keys(),
                             namefield=None, test_unknown=False)
@@ -726,6 +726,6 @@ def TestRemoveInstanceOfflineNode(instance, snode, set_offline, set_online):
   finally:
     set_online(snode)
   # Clean up the disks on the offline node
-  for minor in info["drbd-minors"][snode["primary"]]:
+  for minor in info["drbd-minors"][snode.primary]:
     AssertCommand(["drbdsetup", str(minor), "down"], node=snode)
   AssertCommand(["lvremove", "-f"] + info["volumes"], node=snode)
diff --git a/qa/qa_node.py b/qa/qa_node.py
index 261f95ffd99aac4bbe70f6945cf4b7c0e6d15a49..6081a0231b56b912a51fc09a788e64b43cde8ea1 100644
--- a/qa/qa_node.py
+++ b/qa/qa_node.py
@@ -37,16 +37,16 @@ from qa_utils import AssertCommand, AssertEqual
 
 def _NodeAdd(node, readd=False):
   if not readd and node.added:
-    raise qa_error.Error("Node %s already in cluster" % node["primary"])
+    raise qa_error.Error("Node %s already in cluster" % node.primary)
   elif readd and not node.added:
-    raise qa_error.Error("Node %s not yet in cluster" % node["primary"])
+    raise qa_error.Error("Node %s not yet in cluster" % node.primary)
 
   cmd = ["gnt-node", "add", "--no-ssh-key-check"]
-  if node.get("secondary", None):
-    cmd.append("--secondary-ip=%s" % node["secondary"])
+  if node.secondary:
+    cmd.append("--secondary-ip=%s" % node.secondary)
   if readd:
     cmd.append("--readd")
-  cmd.append(node["primary"])
+  cmd.append(node.primary)
 
   AssertCommand(cmd)
 
@@ -57,14 +57,14 @@ def _NodeAdd(node, readd=False):
 
 
 def _NodeRemove(node):
-  AssertCommand(["gnt-node", "remove", node["primary"]])
+  AssertCommand(["gnt-node", "remove", node.primary])
   node.MarkRemoved()
 
 
 def MakeNodeOffline(node, value):
   """gnt-node modify --offline=value"""
   # value in ["yes", "no"]
-  AssertCommand(["gnt-node", "modify", "--offline", value, node["primary"]])
+  AssertCommand(["gnt-node", "modify", "--offline", value, node.primary])
 
 
 def TestNodeAddAll():
@@ -128,7 +128,7 @@ def TestNodeStorage():
     cmd = ["gnt-node", "list-storage", "--storage-type", storage_type,
            "--output=node,name,allocatable", "--separator=|",
            "--no-headers"]
-    output = qa_utils.GetCommandOutput(master["primary"],
+    output = qa_utils.GetCommandOutput(master.primary,
                                        utils.ShellQuoteArgs(cmd))
 
     # Test with up to two devices
@@ -158,7 +158,7 @@ def TestNodeStorage():
         cmd = ["gnt-node", "list-storage", "--storage-type", storage_type,
                "--output=name,allocatable", "--separator=|",
                "--no-headers", node_name]
-        listout = qa_utils.GetCommandOutput(master["primary"],
+        listout = qa_utils.GetCommandOutput(master.primary,
                                             utils.ShellQuoteArgs(cmd))
         for line in listout.splitlines():
           (vfy_name, vfy_allocatable) = line.split("|")
@@ -182,10 +182,10 @@ def TestNodeFailover(node, node2):
                                      " it to have no primary instances.")
 
   # Fail over to secondary node
-  AssertCommand(["gnt-node", "failover", "-f", node["primary"]])
+  AssertCommand(["gnt-node", "failover", "-f", node.primary])
 
   # ... and back again.
-  AssertCommand(["gnt-node", "failover", "-f", node2["primary"]])
+  AssertCommand(["gnt-node", "failover", "-f", node2.primary])
 
 
 def TestNodeEvacuate(node, node2):
@@ -199,11 +199,11 @@ def TestNodeEvacuate(node, node2):
 
     # Evacuate all secondary instances
     AssertCommand(["gnt-node", "evacuate", "-f",
-                   "--new-secondary=%s" % node3["primary"], node2["primary"]])
+                   "--new-secondary=%s" % node3.primary, node2.primary])
 
     # ... and back again.
     AssertCommand(["gnt-node", "evacuate", "-f",
-                   "--new-secondary=%s" % node2["primary"], node3["primary"]])
+                   "--new-secondary=%s" % node2.primary, node3.primary])
   finally:
     node3.Release()
 
@@ -213,23 +213,23 @@ def TestNodeModify(node):
   for flag in ["master-candidate", "drained", "offline"]:
     for value in ["yes", "no"]:
       AssertCommand(["gnt-node", "modify", "--force",
-                     "--%s=%s" % (flag, value), node["primary"]])
+                     "--%s=%s" % (flag, value), node.primary])
 
   AssertCommand(["gnt-node", "modify", "--master-candidate=yes",
-                 "--auto-promote", node["primary"]])
+                 "--auto-promote", node.primary])
 
   # Test setting secondary IP address
-  AssertCommand(["gnt-node", "modify", "--secondary-ip=%s" % node["secondary"],
-                 node["primary"]])
+  AssertCommand(["gnt-node", "modify", "--secondary-ip=%s" % node.secondary,
+                 node.primary])
 
 
 def _CreateOobScriptStructure():
   """Create a simple OOB handling script and its structure."""
   master = qa_config.GetMasterNode()
 
-  data_path = qa_utils.UploadData(master["primary"], "")
-  verify_path = qa_utils.UploadData(master["primary"], "")
-  exit_code_path = qa_utils.UploadData(master["primary"], "")
+  data_path = qa_utils.UploadData(master.primary, "")
+  verify_path = qa_utils.UploadData(master.primary, "")
+  exit_code_path = qa_utils.UploadData(master.primary, "")
 
   oob_script = (("#!/bin/bash\n"
                  "echo \"$@\" > %s\n"
@@ -237,7 +237,7 @@ def _CreateOobScriptStructure():
                  "exit $(< %s)\n") %
                 (utils.ShellQuote(verify_path), utils.ShellQuote(data_path),
                  utils.ShellQuote(exit_code_path)))
-  oob_path = qa_utils.UploadData(master["primary"], oob_script, mode=0700)
+  oob_path = qa_utils.UploadData(master.primary, oob_script, mode=0700)
 
   return [oob_path, verify_path, data_path, exit_code_path]
 
@@ -245,7 +245,7 @@ def _CreateOobScriptStructure():
 def _UpdateOobFile(path, data):
   """Updates the data file with data."""
   master = qa_config.GetMasterNode()
-  qa_utils.UploadData(master["primary"], data, filename=path)
+  qa_utils.UploadData(master.primary, data, filename=path)
 
 
 def _AssertOobCall(verify_path, expected_args):
@@ -253,7 +253,7 @@ def _AssertOobCall(verify_path, expected_args):
   master = qa_config.GetMasterNode()
 
   verify_output_cmd = utils.ShellQuoteArgs(["cat", verify_path])
-  output = qa_utils.GetCommandOutput(master["primary"], verify_output_cmd,
+  output = qa_utils.GetCommandOutput(master.primary, verify_output_cmd,
                                      tty=False)
 
   AssertEqual(expected_args, output.strip())
@@ -265,8 +265,8 @@ def TestOutOfBand():
 
   node = qa_config.AcquireNode(exclude=master)
 
-  master_name = master["primary"]
-  node_name = node["primary"]
+  master_name = master.primary
+  node_name = node.primary
   full_node_name = qa_utils.ResolveNodeName(node)
 
   (oob_path, verify_path,
@@ -391,10 +391,10 @@ def TestOutOfBand():
     AssertCommand(["gnt-node", "health"], fail=True)
 
     # Different OOB script for node
-    verify_path2 = qa_utils.UploadData(master["primary"], "")
+    verify_path2 = qa_utils.UploadData(master.primary, "")
     oob_script = ("#!/bin/sh\n"
                   "echo \"$@\" > %s\n") % verify_path2
-    oob_path2 = qa_utils.UploadData(master["primary"], oob_script, mode=0700)
+    oob_path2 = qa_utils.UploadData(master.primary, oob_script, mode=0700)
 
     try:
       AssertCommand(["gnt-node", "modify", "--node-parameters",
@@ -424,4 +424,4 @@ def TestNodeListFields():
 
 def TestNodeListDrbd(node):
   """gnt-node list-drbd"""
-  AssertCommand(["gnt-node", "list-drbd", node["primary"]])
+  AssertCommand(["gnt-node", "list-drbd", node.primary])
diff --git a/qa/qa_os.py b/qa/qa_os.py
index b6724e0837790d17eca06c321b934f8410b5a399..f96e27eaef9aca19edb640ea878d4e0963e95c58 100644
--- a/qa/qa_os.py
+++ b/qa/qa_os.py
@@ -109,7 +109,7 @@ def _SetupTempOs(node, dirname, variant, valid):
   cmd = " && ".join(parts)
 
   print qa_utils.FormatInfo("Setting up %s with %s OS definition" %
-                            (node["primary"],
+                            (node.primary,
                              ["an invalid", "a valid"][int(valid)]))
 
   AssertCommand(cmd, node=node)
@@ -163,7 +163,7 @@ def _TestOs(mode, rapi_cb):
       AssertCommand(["gnt-os", "diagnose"], fail=(mode != _ALL_VALID))
 
       # Diagnose again, ignoring exit status
-      output = qa_utils.GetCommandOutput(master["primary"],
+      output = qa_utils.GetCommandOutput(master.primary,
                                          "gnt-os diagnose || :")
       for line in output.splitlines():
         if line.startswith("OS: %s [global status:" % name):
@@ -173,13 +173,13 @@ def _TestOs(mode, rapi_cb):
 
       # Check info for all
       cmd = ["gnt-os", "info"]
-      output = qa_utils.GetCommandOutput(master["primary"],
+      output = qa_utils.GetCommandOutput(master.primary,
                                          utils.ShellQuoteArgs(cmd))
       AssertIn("%s:" % name, output.splitlines())
 
       # Check info for OS
       cmd = ["gnt-os", "info", name]
-      output = qa_utils.GetCommandOutput(master["primary"],
+      output = qa_utils.GetCommandOutput(master.primary,
                                          utils.ShellQuoteArgs(cmd)).splitlines()
       AssertIn("%s:" % name, output)
       for (field, value) in [("valid", mode == _ALL_VALID),
@@ -189,7 +189,7 @@ def _TestOs(mode, rapi_cb):
 
       # Only valid OSes should be listed
       cmd = ["gnt-os", "list", "--no-headers"]
-      output = qa_utils.GetCommandOutput(master["primary"],
+      output = qa_utils.GetCommandOutput(master.primary,
                                          utils.ShellQuoteArgs(cmd))
       if mode == _ALL_VALID and not (hidden or blacklisted):
         assert_fn = AssertIn
diff --git a/qa/qa_rapi.py b/qa/qa_rapi.py
index 996c69d188b2d2d3f98828d0540012f86ab2d5a1..d21d19c4a37635b2dd64f2e0e6303314fdbefd5e 100644
--- a/qa/qa_rapi.py
+++ b/qa/qa_rapi.py
@@ -80,7 +80,7 @@ def Setup(username, password):
 
   # Write to temporary file
   _rapi_ca = tempfile.NamedTemporaryFile()
-  _rapi_ca.write(qa_utils.GetCommandOutput(master["primary"],
+  _rapi_ca.write(qa_utils.GetCommandOutput(master.primary,
                                            utils.ShellQuoteArgs(cmd)))
   _rapi_ca.flush()
 
@@ -88,7 +88,7 @@ def Setup(username, password):
   cfg_curl = rapi.client.GenericCurlConfig(cafile=_rapi_ca.name,
                                            proxy="")
 
-  _rapi_client = rapi.client.GanetiRapiClient(master["primary"], port=port,
+  _rapi_client = rapi.client.GanetiRapiClient(master.primary, port=port,
                                               username=username,
                                               password=password,
                                               curl_config_fn=cfg_curl)
@@ -418,7 +418,7 @@ def TestNode(node):
       _VerifyNode(node_data)
 
   _DoTests([
-    ("/2/nodes/%s" % node["primary"], _VerifyNode, "GET", None),
+    ("/2/nodes/%s" % node.primary, _VerifyNode, "GET", None),
     ("/2/nodes", _VerifyNodesList, "GET", None),
     ("/2/nodes?bulk=1", _VerifyNodesBulk, "GET", None),
     ])
@@ -575,7 +575,7 @@ def TestRapiInstanceAdd(node, use_client):
                                            constants.DT_PLAIN,
                                            disks, nics,
                                            os=qa_config.get("os"),
-                                           pnode=node["primary"],
+                                           pnode=node.primary,
                                            beparams=beparams)
     else:
       body = {
@@ -584,7 +584,7 @@ def TestRapiInstanceAdd(node, use_client):
         "name": instance["name"],
         "os_type": qa_config.get("os"),
         "disk_template": constants.DT_PLAIN,
-        "pnode": node["primary"],
+        "pnode": node.primary,
         "beparams": beparams,
         "disks": disks,
         "nics": nics,
@@ -783,9 +783,9 @@ def TestInterClusterInstanceMove(src_instance, dest_instance,
   # note: pnode:snode are the *current* nodes, so we move it first to
   # tnode:pnode, then back to pnode:snode
   for si, di, pn, sn in [(src_instance["name"], dest_instance["name"],
-                          tnode["primary"], pnode["primary"]),
+                          tnode.primary, pnode.primary),
                          (dest_instance["name"], src_instance["name"],
-                          pnode["primary"], snode["primary"])]:
+                          pnode.primary, snode.primary)]:
     cmd = [
       "../tools/move-instance",
       "--verbose",
@@ -796,8 +796,8 @@ def TestInterClusterInstanceMove(src_instance, dest_instance,
       "--dest-primary-node=%s" % pn,
       "--dest-secondary-node=%s" % sn,
       "--net=0:mac=%s" % constants.VALUE_GENERATE,
-      master["primary"],
-      master["primary"],
+      master.primary,
+      master.primary,
       si,
       ]
 
diff --git a/qa/qa_tags.py b/qa/qa_tags.py
index 5aa316dba2c670e26806eaf4e361c7462c439482..9aed419a96d2b7e9044096f36bc293d08ddbd5bd 100644
--- a/qa/qa_tags.py
+++ b/qa/qa_tags.py
@@ -72,7 +72,7 @@ def TestClusterTags():
 
 def TestNodeTags(node):
   """gnt-node tags"""
-  _TestTags(constants.TAG_NODE, node["primary"])
+  _TestTags(constants.TAG_NODE, node.primary)
 
 
 def TestGroupTags(group):
diff --git a/qa/qa_utils.py b/qa/qa_utils.py
index 0b3854d2ea620f610e2a431e253ac2e1e9d12016..61494147231b089923bd1b8ff2fb0c634aa19b4c 100644
--- a/qa/qa_utils.py
+++ b/qa/qa_utils.py
@@ -404,7 +404,7 @@ def _ResolveName(cmd, key):
   """
   master = qa_config.GetMasterNode()
 
-  output = GetCommandOutput(master["primary"], utils.ShellQuoteArgs(cmd))
+  output = GetCommandOutput(master.primary, utils.ShellQuoteArgs(cmd))
   for line in output.splitlines():
     (lkey, lvalue) = line.split(":", 1)
     if lkey == key:
@@ -427,7 +427,7 @@ def ResolveNodeName(node):
   """Gets the full name of a node.
 
   """
-  return _ResolveName(["gnt-node", "info", node["primary"]],
+  return _ResolveName(["gnt-node", "info", node.primary],
                       "Node name")
 
 
@@ -441,7 +441,7 @@ def GetNodeInstances(node, secondaries=False):
   # Get list of all instances
   cmd = ["gnt-instance", "list", "--separator=:", "--no-headers",
          "--output=name,pnode,snodes"]
-  output = GetCommandOutput(master["primary"], utils.ShellQuoteArgs(cmd))
+  output = GetCommandOutput(master.primary, utils.ShellQuoteArgs(cmd))
 
   instances = []
   for line in output.splitlines():
@@ -485,7 +485,7 @@ def _List(listcmd, fields, names):
   if names:
     cmd.extend(names)
 
-  return GetCommandOutput(master["primary"],
+  return GetCommandOutput(master.primary,
                           utils.ShellQuoteArgs(cmd)).splitlines()
 
 
@@ -541,7 +541,7 @@ def GenericQueryFieldsTest(cmd, fields):
 
   # Check listed fields (all, must be sorted)
   realcmd = [cmd, "list-fields", "--separator=|", "--no-headers"]
-  output = GetCommandOutput(master["primary"],
+  output = GetCommandOutput(master.primary,
                             utils.ShellQuoteArgs(realcmd)).splitlines()
   AssertEqual([line.split("|", 1)[0] for line in output],
               utils.NiceSort(fields))
@@ -570,7 +570,7 @@ def AddToEtcHosts(hostnames):
 
   """
   master = qa_config.GetMasterNode()
-  tmp_hosts = UploadData(master["primary"], "", mode=0644)
+  tmp_hosts = UploadData(master.primary, "", mode=0644)
 
   data = []
   for localhost in ("::1", "127.0.0.1"):
@@ -595,7 +595,7 @@ def RemoveFromEtcHosts(hostnames):
 
   """
   master = qa_config.GetMasterNode()
-  tmp_hosts = UploadData(master["primary"], "", mode=0644)
+  tmp_hosts = UploadData(master.primary, "", mode=0644)
   quoted_tmp_hosts = utils.ShellQuote(tmp_hosts)
 
   sed_data = " ".join(hostnames)
@@ -623,7 +623,7 @@ def RunInstanceCheck(instance, running):
   master_node = qa_config.GetMasterNode()
 
   # Build command to connect to master node
-  master_ssh = GetSSHCommand(master_node["primary"], "--")
+  master_ssh = GetSSHCommand(master_node.primary, "--")
 
   if running:
     running_shellval = "1"