Merge branch 'stable-2.14' into stable-2.15
authorHrvoje Ribicic <riba@google.com>
Fri, 4 Dec 2015 15:06:50 +0000 (16:06 +0100)
committerHrvoje Ribicic <riba@google.com>
Fri, 4 Dec 2015 16:55:39 +0000 (17:55 +0100)
* stable-2.14
  Fix lines with more than 80 characters
  Add more detach/attach sequence tests
  Allow disk attachment to diskless instances
  Improve tests for attaching disks

* stable-2.13
  (no changes)

* stable-2.12
  Restrict showing of DRBD secret using types
  Calculate correct affected nodes set in InstanceChangeGroup

* stable-2.11
  (no changes)

* stable-2.10
  (no changes)

* stable-2.9
  QA: Ensure the DRBD secret is not retrievable via RAPI
  Redact the DRBD secret in instance queries
  Do not attempt to use the DRBD secret in gnt-instance info

Signed-off-by: Hrvoje Ribicic <riba@google.com>
Reviewed-by: Lisa Velden <velden@google.com>

14 files changed:
lib/client/gnt_instance.py
lib/cmdlib/instance.py
lib/cmdlib/instance_query.py
lib/cmdlib/instance_set_params.py
lib/objects.py
lib/storage/drbd.py
qa/ganeti-qa.py
qa/qa_rapi.py
src/Ganeti/Config.hs
src/Ganeti/Objects.hs
src/Ganeti/Objects/Disk.hs
test/hs/Test/Ganeti/Objects.hs
test/py/cmdlib/instance_unittest.py
test/py/ganeti.storage.drbd_unittest.py

index 7a49d60..52da28e 100644 (file)
@@ -960,8 +960,7 @@ def _FormatDiskDetails(dev_type, dev, roman):
                 (drbd_info["secondary_node"],
                  compat.TryToRoman(drbd_info["secondary_minor"],
                                    convert=roman))),
-      ("port", str(compat.TryToRoman(drbd_info["port"], roman))),
-      ("auth key", str(drbd_info["secret"])),
+      ("port", str(compat.TryToRoman(drbd_info["port"], convert=roman))),
       ]
   elif dev_type == constants.DT_PLAIN:
     vg_name, lv_name = dev["logical_id"]
index a49e33c..4f46910 100644 (file)
@@ -746,7 +746,7 @@ class LUInstanceChangeGroup(LogicalUnit):
         self._LockInstancesNodes()
 
         # Lock all nodes in all potential target groups
-        lock_groups = (frozenset(self.owned_locks(locking.LEVEL_NODEGROUP)) -
+        lock_groups = (frozenset(self.owned_locks(locking.LEVEL_NODEGROUP)) |
                        self.cfg.GetInstanceNodeGroups(self.op.instance_uuid))
         member_nodes = [node_uuid
                         for group in lock_groups
index 1943eab..5aec4c1 100644 (file)
@@ -168,6 +168,7 @@ class LUInstanceQueryData(NoHooksLU):
 
     """
     drbd_info = None
+    output_logical_id = dev.logical_id
     if dev.dev_type in constants.DTS_DRBD:
       # we change the snode then (otherwise we use the one passed in)
       if dev.logical_id[0] == instance.primary_node:
@@ -184,8 +185,9 @@ class LUInstanceQueryData(NoHooksLU):
         "secondary_node": node_uuid2name_fn(snode_uuid),
         "secondary_minor": snode_minor,
         "port": dev.logical_id[2],
-        "secret": dev.logical_id[5],
       }
+      # replace the secret present at the end of the ids with None
+      output_logical_id = dev.logical_id[:-1] + (None,)
 
     dev_pstatus = self._ComputeBlockdevStatus(instance.primary_node,
                                               instance, dev)
@@ -202,7 +204,7 @@ class LUInstanceQueryData(NoHooksLU):
     return {
       "iv_name": dev.iv_name,
       "dev_type": dev.dev_type,
-      "logical_id": dev.logical_id,
+      "logical_id": output_logical_id,
       "drbd_info": drbd_info,
       "pstatus": dev_pstatus,
       "sstatus": dev_sstatus,
index 4958a91..911203e 100644 (file)
@@ -391,15 +391,15 @@ class LUInstanceSetParams(LogicalUnit):
 
     disk = self.GenericGetDiskInfo(uuid, name)
     instance_template = self.cfg.GetInstanceDiskTemplate(self.instance.uuid)
-    if (disk.dev_type != instance_template or
-        instance_template == constants.DT_DISKLESS):
+    if (disk.dev_type != instance_template and
+        instance_template != constants.DT_DISKLESS):
       raise errors.OpPrereqError("Instance has '%s' template while disk has"
                                  " '%s' template" %
                                  (instance_template, disk.dev_type),
                                  errors.ECODE_INVAL)
 
     instance_nodes = self.cfg.GetInstanceNodes(self.instance.uuid)
-    if not set(disk.nodes).issubset(set(instance_nodes)):
+    if not set(instance_nodes).issubset(set(disk.nodes)):
       raise errors.OpPrereqError("Disk nodes are %s while the instance's nodes"
                                  " are %s" %
                                  (disk.nodes, instance_nodes),
index 0ce3c0f..633353d 100644 (file)
@@ -832,10 +832,15 @@ class Disk(ConfigObject):
     standard python types.
 
     """
-    bo = super(Disk, self).ToDict()
+    bo = super(Disk, self).ToDict(_with_private=_with_private)
     if not include_dynamic_params and "dynamic_params" in bo:
       del bo["dynamic_params"]
 
+    if _with_private and "logical_id" in bo:
+      mutable_id = list(bo["logical_id"])
+      mutable_id[5] = mutable_id[5].Get()
+      bo["logical_id"] = tuple(mutable_id)
+
     for attr in ("children",):
       alist = bo.get(attr, None)
       if alist:
@@ -856,6 +861,12 @@ class Disk(ConfigObject):
       # we need a tuple of length six here
       if len(obj.logical_id) < 6:
         obj.logical_id += (None,) * (6 - len(obj.logical_id))
+      # If we do have a tuple of length 6, make the last entry (secret key)
+      # private
+      elif (len(obj.logical_id) == 6 and
+            not isinstance(obj.logical_id[-1], serializer.Private)):
+        obj.logical_id = obj.logical_id[:-1] + \
+                         (serializer.Private(obj.logical_id[-1]),)
     return obj
 
   def __str__(self):
index a33b0ed..5c4817b 100644 (file)
@@ -201,7 +201,9 @@ class DRBD8Dev(base.BlockDev):
     self._rhost = dyn_params[constants.DDP_REMOTE_IP]
     self._rport = unique_id[2]
     self._aminor = dyn_params[constants.DDP_LOCAL_MINOR]
-    self._secret = unique_id[5]
+    # The secret is wrapped in the Private data type, and it has to be extracted
+    # before use
+    self._secret = unique_id[5].Get()
 
     if children:
       if not _CanReadDevice(children[1].dev_path):
index 77ad23d..84468b2 100755 (executable)
@@ -869,6 +869,8 @@ def RunInstanceTests():
           RunExportImportTests(instance, inodes)
           RunHardwareFailureTests(instance, inodes)
           RunRepairDiskSizes()
+          RunTestIf(["rapi", "instance-data-censorship"],
+                    qa_rapi.TestInstanceDataCensorship, instance, inodes)
           RunTest(qa_instance.TestInstanceRemove, instance)
         finally:
           instance.Release()
index d997c24..a008247 100644 (file)
@@ -63,6 +63,7 @@ import qa_error
 import qa_logging
 import qa_utils
 
+from qa_instance import GetInstanceInfo
 from qa_instance import IsDiskReplacingSupported
 from qa_instance import IsFailoverSupported
 from qa_instance import IsMigrationSupported
@@ -1306,3 +1307,57 @@ def TestFilters():
              "GET", None)])
   for u in uuids:
     _DoTests([("/2/filters/%s" % u, lambda r: r is None, "DELETE", None)])
+
+
+_DRBD_SECRET_RE = re.compile('shared-secret.*"([0-9A-Fa-f]+)"')
+
+
+def _RetrieveSecret(instance, pnode):
+  """Retrieves the DRBD secret given an instance object and the primary node.
+
+  @type instance: L{qa_config._QaInstance}
+  @type pnode: L{qa_config._QaNode}
+
+  @rtype: string
+
+  """
+  instance_info = GetInstanceInfo(instance.name)
+
+  # We are interested in only the first disk on the primary
+  drbd_minor = instance_info["drbd-minors"][pnode.primary][0]
+
+  # This form should work for all DRBD versions
+  drbd_command = ("drbdsetup show %d; drbdsetup %d show || true" %
+                  (drbd_minor, drbd_minor))
+  instance_drbd_info = \
+    qa_utils.GetCommandOutput(pnode.primary, drbd_command)
+
+  match_obj = _DRBD_SECRET_RE.search(instance_drbd_info)
+  if match_obj is None:
+    raise qa_error.Error("Could not retrieve DRBD secret for instance %s from"
+                         " node %s." % (instance.name, pnode.primary))
+
+  return match_obj.groups(0)[0]
+
+
+def TestInstanceDataCensorship(instance, inodes):
+  """Test protection of sensitive instance data."""
+
+  if instance.disk_template != constants.DT_DRBD8:
+    print qa_utils.FormatInfo("Only the DRBD secret is a sensitive parameter"
+                              " right now, skipping for non-DRBD instance.")
+    return
+
+  drbd_secret = _RetrieveSecret(instance, inodes[0])
+
+  job_id = _rapi_client.GetInstanceInfo(instance.name)
+  if not _rapi_client.WaitForJobCompletion(job_id):
+    raise qa_error.Error("Could not fetch instance info for instance %s" %
+                         instance.name)
+  info_dict = _rapi_client.GetJobStatus(job_id)
+
+  if drbd_secret in str(info_dict):
+    print qa_utils.FormatInfo("DRBD secret: %s" % drbd_secret)
+    print qa_utils.FormatInfo("Retrieved data\n%s" % str(info_dict))
+    raise qa_error.Error("Found DRBD secret in contents of RAPI instance info"
+                         " call; see above.")
index 379df93..b902a32 100644 (file)
@@ -190,7 +190,7 @@ getMasterNodes cfg =
 
 -- | Get the list of master candidates, /not including/ the master itself.
 getMasterCandidates :: ConfigData -> [Node]
-getMasterCandidates cfg = 
+getMasterCandidates cfg =
   filter ((==) NRCandidate . getNodeRole cfg) . F.toList . configNodes $ cfg
 
 -- | Get the list of master candidates, /including/ the master.
@@ -426,7 +426,7 @@ getInstDisksFromObj cfg =
 -- | Collects a value for all DRBD disks
 collectFromDrbdDisks
   :: (Monoid a)
-  => (String -> String -> Int -> Int -> Int -> DRBDSecret -> a)
+  => (String -> String -> Int -> Int -> Int -> Private DRBDSecret -> a)
   -- ^ NodeA, NodeB, Port, MinorA, MinorB, Secret
   -> Disk -> a
 collectFromDrbdDisks f = col
@@ -438,7 +438,8 @@ collectFromDrbdDisks f = col
 
 -- | Returns the DRBD secrets of a given 'Disk'
 getDrbdSecretsForDisk :: Disk -> [DRBDSecret]
-getDrbdSecretsForDisk = collectFromDrbdDisks (\_ _ _ _ _ secret -> [secret])
+getDrbdSecretsForDisk = collectFromDrbdDisks
+                          (\_ _ _ _ _ (Private secret) -> [secret])
 
 -- | Returns the DRBD minors of a given 'Disk'
 getDrbdMinorsForDisk :: Disk -> [(Int, String)]
index c4c4cda..423f28e 100644 (file)
@@ -293,7 +293,6 @@ instance Monoid DataCollectorConfig where
     }
   mappend _ a = a
 
-
 -- * IPolicy definitions
 
 $(buildParam "ISpec" "ispec"
index 0a2e6db..c9f498d 100644 (file)
@@ -125,7 +125,7 @@ instance J.JSON LogicalVolume where
 -- correspondence between the 'DiskLogicalId' constructors and 'DiskTemplate'.
 data DiskLogicalId
   = LIDPlain LogicalVolume  -- ^ Volume group, logical volume
-  | LIDDrbd8 String String Int Int Int DRBDSecret
+  | LIDDrbd8 String String Int Int Int (Private DRBDSecret)
   -- ^ NodeA, NodeB, Port, MinorA, MinorB, Secret
   | LIDFile FileDriver String -- ^ Driver, path
   | LIDSharedFile FileDriver String -- ^ Driver, path
index e2a17a1..71a1d3c 100644 (file)
@@ -750,7 +750,8 @@ caseIncludeLogicalIdDrbd =
       time = TOD 0 0
       d = RealDisk $
         RealDiskData
-          (LIDDrbd8 "node1.example.com" "node2.example.com" 2000 1 5 "secret")
+          (LIDDrbd8 "node1.example.com" "node2.example.com" 2000 1 5
+           (Private "secret"))
           [ RealDisk $ RealDiskData (mkLIDPlain "onevg" "onelv") []
               ["node1.example.com", "node2.example.com"] "disk1" 1000 DiskRdWr
               Nothing Nothing Nothing "145145-asdf-sdf2-2134-asfd-534g2x"
index e837f0f..e1ddbb5 100644 (file)
@@ -2102,6 +2102,9 @@ class TestLUInstanceSetParams(CmdlibTestCase):
     self.mocked_running_inst_state = "running"
     self.mocked_running_inst_time = 10938474
 
+    self.mocked_disk_uuid = "mock_uuid_1134"
+    self.mocked_disk_name = "mock_disk_1134"
+
     bootid = "mock_bootid"
     storage_info = [
       {
@@ -2540,7 +2543,7 @@ class TestLUInstanceSetParams(CmdlibTestCase):
     op = self.CopyOpCode(self.op,
                          disks=[[constants.DDM_ADD, -1,
                                  {
-                                   "uuid": "mock_uuid_1134"
+                                   "uuid": self.mocked_disk_uuid
                                  }]])
     self.ExecOpCodeExpectException(
       op, errors.TypeEnforcementError, "Unknown parameter 'uuid'")
@@ -2684,11 +2687,12 @@ class TestLUInstanceSetParams(CmdlibTestCase):
   def testAttachDiskWrongTemplate(self):
     msg = "Instance has '%s' template while disk has '%s' template" % \
       (constants.DT_PLAIN, constants.DT_BLOCK)
-    self.cfg.AddOrphanDisk(name="mock_disk_1134", dev_type=constants.DT_BLOCK)
+    self.cfg.AddOrphanDisk(name=self.mocked_disk_name,
+                           dev_type=constants.DT_BLOCK)
     op = self.CopyOpCode(self.op,
                          disks=[[constants.DDM_ATTACH, -1,
                                  {
-                                   constants.IDISK_NAME: "mock_disk_1134"
+                                   constants.IDISK_NAME: self.mocked_disk_name
                                  }]],
                          )
     self.ExecOpCodeExpectOpPrereqError(op, msg)
@@ -2696,17 +2700,19 @@ class TestLUInstanceSetParams(CmdlibTestCase):
   def testAttachDiskWrongNodes(self):
     msg = "Disk nodes are \['mock_node_1134'\]"
 
-    self.cfg.AddOrphanDisk(name="mock_disk_1134", primary_node="mock_node_1134")
+    self.cfg.AddOrphanDisk(name=self.mocked_disk_name,
+                           primary_node="mock_node_1134")
     op = self.CopyOpCode(self.op,
                          disks=[[constants.DDM_ATTACH, -1,
                                  {
-                                   constants.IDISK_NAME: "mock_disk_1134"
+                                   constants.IDISK_NAME: self.mocked_disk_name
                                  }]],
                          )
     self.ExecOpCodeExpectOpPrereqError(op, msg)
 
   def testAttachDiskRunningInstance(self):
-    self.cfg.AddOrphanDisk(name="mock_disk_1134")
+    self.cfg.AddOrphanDisk(name=self.mocked_disk_name,
+                           primary_node=self.master.uuid)
     self.rpc.call_blockdev_assemble.return_value = \
       self.RpcResultsBuilder() \
         .CreateSuccessfulNodeResult(self.master,
@@ -2716,7 +2722,7 @@ class TestLUInstanceSetParams(CmdlibTestCase):
     op = self.CopyOpCode(self.running_op,
                          disks=[[constants.DDM_ATTACH, -1,
                                  {
-                                   constants.IDISK_NAME: "mock_disk_1134"
+                                   constants.IDISK_NAME: self.mocked_disk_name
                                  }]],
                          )
     self.ExecOpCode(op)
@@ -2724,7 +2730,8 @@ class TestLUInstanceSetParams(CmdlibTestCase):
     self.assertFalse(self.rpc.call_blockdev_shutdown.called)
 
   def testAttachDiskRunningInstanceNoWaitForSync(self):
-    self.cfg.AddOrphanDisk(name="mock_disk_1134")
+    self.cfg.AddOrphanDisk(name=self.mocked_disk_name,
+                           primary_node=self.master.uuid)
     self.rpc.call_blockdev_assemble.return_value = \
       self.RpcResultsBuilder() \
         .CreateSuccessfulNodeResult(self.master,
@@ -2734,7 +2741,7 @@ class TestLUInstanceSetParams(CmdlibTestCase):
     op = self.CopyOpCode(self.running_op,
                          disks=[[constants.DDM_ATTACH, -1,
                                  {
-                                   constants.IDISK_NAME: "mock_disk_1134"
+                                   constants.IDISK_NAME: self.mocked_disk_name
                                  }]],
                          wait_for_sync=False)
     self.ExecOpCode(op)
@@ -2742,11 +2749,12 @@ class TestLUInstanceSetParams(CmdlibTestCase):
     self.assertFalse(self.rpc.call_blockdev_shutdown.called)
 
   def testAttachDiskDownInstance(self):
-    self.cfg.AddOrphanDisk(name="mock_disk_1134")
+    self.cfg.AddOrphanDisk(name=self.mocked_disk_name,
+                           primary_node=self.master.uuid)
     op = self.CopyOpCode(self.op,
                          disks=[[constants.DDM_ATTACH, -1,
                                  {
-                                   constants.IDISK_NAME: "mock_disk_1134"
+                                   constants.IDISK_NAME: self.mocked_disk_name
                                  }]])
     self.ExecOpCode(op)
 
@@ -2754,11 +2762,11 @@ class TestLUInstanceSetParams(CmdlibTestCase):
     self.assertTrue(self.rpc.call_blockdev_shutdown.called)
 
   def testAttachDiskDownInstanceNoWaitForSync(self):
-    self.cfg.AddOrphanDisk(name="mock_disk_1134")
+    self.cfg.AddOrphanDisk(name=self.mocked_disk_name)
     op = self.CopyOpCode(self.op,
                          disks=[[constants.DDM_ATTACH, -1,
                                  {
-                                   constants.IDISK_NAME: "mock_disk_1134"
+                                   constants.IDISK_NAME: self.mocked_disk_name
                                  }]],
                          wait_for_sync=False)
     self.ExecOpCodeExpectOpPrereqError(
@@ -2766,7 +2774,8 @@ class TestLUInstanceSetParams(CmdlibTestCase):
           " and --no-wait-for-sync given.")
 
   def testHotAttachDisk(self):
-    self.cfg.AddOrphanDisk(name="mock_disk_1134")
+    self.cfg.AddOrphanDisk(name=self.mocked_disk_name,
+                           primary_node=self.master.uuid)
     self.rpc.call_blockdev_assemble.return_value = \
       self.RpcResultsBuilder() \
         .CreateSuccessfulNodeResult(self.master,
@@ -2776,7 +2785,7 @@ class TestLUInstanceSetParams(CmdlibTestCase):
     op = self.CopyOpCode(self.op,
                          disks=[[constants.DDM_ATTACH, -1,
                                  {
-                                   constants.IDISK_NAME: "mock_disk_1134"
+                                   constants.IDISK_NAME: self.mocked_disk_name
                                  }]],
                          hotplug=True)
     self.rpc.call_hotplug_supported.return_value = \
@@ -2826,7 +2835,7 @@ class TestLUInstanceSetParams(CmdlibTestCase):
     # well as the expected path where it will be moved.
     mock_disk = self.cfg.CreateDisk(
       name='mock_disk_1134', dev_type=constants.DT_FILE,
-      logical_id=('loop', '/tmp/instance/disk'))
+      logical_id=('loop', '/tmp/instance/disk'), primary_node=self.master.uuid)
 
     # Create a file-based instance
     file_disk = self.cfg.CreateDisk(
@@ -2835,7 +2844,7 @@ class TestLUInstanceSetParams(CmdlibTestCase):
     inst = self.cfg.AddNewInstance(name='instance',
                                    disk_template=constants.DT_FILE,
                                    disks=[file_disk, mock_disk],
-                                   )
+                                  )
 
     # Detach the disk and assert that it has been moved to the upper directory
     op = self.CopyOpCode(self.op,
@@ -2853,7 +2862,7 @@ class TestLUInstanceSetParams(CmdlibTestCase):
                          instance_name=inst.name,
                          disks=[[constants.DDM_ATTACH, -1,
                                  {
-                                   constants.IDISK_NAME: "mock_disk_1134"
+                                   constants.IDISK_NAME: self.mocked_disk_name
                                  }]],
                          )
     self.ExecOpCode(op)
@@ -2865,14 +2874,16 @@ class TestLUInstanceSetParams(CmdlibTestCase):
 
     Also, check if the operations succeed both with name and uuid.
     """
-    disk1 = self.cfg.CreateDisk(uuid="mock_uuid_1134")
-    disk2 = self.cfg.CreateDisk(name="mock_name_1134")
+    disk1 = self.cfg.CreateDisk(uuid=self.mocked_disk_uuid,
+                                primary_node=self.master.uuid)
+    disk2 = self.cfg.CreateDisk(name="mock_name_1134",
+                                primary_node=self.master.uuid)
 
     inst = self.cfg.AddNewInstance(disks=[disk1, disk2])
 
     op = self.CopyOpCode(self.op,
                          instance_name=inst.name,
-                         disks=[[constants.DDM_DETACH, "mock_uuid_1134",
+                         disks=[[constants.DDM_DETACH, self.mocked_disk_uuid,
                                  {}]])
     self.ExecOpCode(op)
     self.assertEqual([disk2], self.cfg.GetInstanceDisks(inst.uuid))
@@ -2881,7 +2892,7 @@ class TestLUInstanceSetParams(CmdlibTestCase):
                          instance_name=inst.name,
                          disks=[[constants.DDM_ATTACH, 0,
                                  {
-                                   'uuid': "mock_uuid_1134"
+                                   'uuid': self.mocked_disk_uuid
                                  }]])
     self.ExecOpCode(op)
     self.assertEqual([disk1, disk2], self.cfg.GetInstanceDisks(inst.uuid))
@@ -2902,6 +2913,61 @@ class TestLUInstanceSetParams(CmdlibTestCase):
     self.ExecOpCode(op)
     self.assertEqual([disk2, disk1], self.cfg.GetInstanceDisks(inst.uuid))
 
+  def testDetachAndAttachToDisklessInstance(self):
+    """Check if a disk can be detached and then re-attached if the instance is
+    diskless inbetween.
+
+    """
+    disk = self.cfg.CreateDisk(uuid=self.mocked_disk_uuid,
+                               primary_node=self.master.uuid)
+
+    inst = self.cfg.AddNewInstance(disks=[disk], primary_node=self.master)
+
+    op = self.CopyOpCode(self.op,
+                         instance_name=inst.name,
+                         disks=[[constants.DDM_DETACH,
+                         self.mocked_disk_uuid, {}]])
+
+    self.ExecOpCode(op)
+    self.assertEqual([], self.cfg.GetInstanceDisks(inst.uuid))
+
+    op = self.CopyOpCode(self.op,
+                         instance_name=inst.name,
+                         disks=[[constants.DDM_ATTACH, 0,
+                                 {
+                                   'uuid': self.mocked_disk_uuid
+                                 }]])
+    self.ExecOpCode(op)
+    self.assertEqual([disk], self.cfg.GetInstanceDisks(inst.uuid))
+
+  def testDetachAttachDrbdDisk(self):
+    """Check if a DRBD disk can be detached and then re-attached.
+
+    """
+    disk = self.cfg.CreateDisk(uuid=self.mocked_disk_uuid,
+                               primary_node=self.master.uuid,
+                               secondary_node=self.snode.uuid,
+                               dev_type=constants.DT_DRBD8)
+
+    inst = self.cfg.AddNewInstance(disks=[disk], primary_node=self.master)
+
+    op = self.CopyOpCode(self.op,
+                         instance_name=inst.name,
+                         disks=[[constants.DDM_DETACH,
+                         self.mocked_disk_uuid, {}]])
+
+    self.ExecOpCode(op)
+    self.assertEqual([], self.cfg.GetInstanceDisks(inst.uuid))
+
+    op = self.CopyOpCode(self.op,
+                         instance_name=inst.name,
+                         disks=[[constants.DDM_ATTACH, 0,
+                                 {
+                                   'uuid': self.mocked_disk_uuid
+                                 }]])
+    self.ExecOpCode(op)
+    self.assertEqual([disk], self.cfg.GetInstanceDisks(inst.uuid))
+
   def testRemoveDiskRemovesStorageDir(self):
     inst = self.cfg.AddNewInstance(disks=[self.cfg.CreateDisk(dev_type='file')])
     op = self.CopyOpCode(self.op,
index 9553e47..9a1894f 100755 (executable)
@@ -35,6 +35,7 @@ import os
 
 from ganeti import constants
 from ganeti import errors
+from ganeti import serializer
 from ganeti.storage import drbd
 from ganeti.storage import drbd_info
 from ganeti.storage import drbd_cmdgen
@@ -436,7 +437,8 @@ class TestDRBD8Construction(testutils.GanetiTestCase):
       drbd_info.DRBD8Info.CreateFromFile(
         filename=testutils.TestDataFilename("proc_drbd84.txt"))
 
-    self.test_unique_id = ("hosta.com", 123, "host2.com", 123, 0, "secret")
+    self.test_unique_id = ("hosta.com", 123, "host2.com", 123, 0,
+                           serializer.Private("secret"))
     self.test_dyn_params = {
       constants.DDP_LOCAL_IP: "192.0.2.1",
       constants.DDP_LOCAL_MINOR: 0,