From 63f949995892c8019c2f6418b5c313c150bd6ce3 Mon Sep 17 00:00:00 2001
From: David Lehman <dlehman@redhat.com>
Date: Tue, 20 Aug 2013 14:45:34 -0500
Subject: [PATCH 1/2] Add methods for manipulating the member set of existing
 containers.

Note that these methods all operate on both the on-disk data and the
StorageDevice instance. That means they will write changes to your disk
immediately.
---
 blivet/devicelibs/btrfs.py  |  12 +++-
 blivet/devicelibs/lvm.py    |  28 +++++++--
 blivet/devicelibs/mdraid.py |  18 ++++--
 blivet/devices.py           | 150 +++++++++++++++++++++++++++++++++++++++-----
 4 files changed, 180 insertions(+), 28 deletions(-)

diff --git a/blivet/devicelibs/btrfs.py b/blivet/devicelibs/btrfs.py
index bca1242..f68fce0 100644
--- a/blivet/devicelibs/btrfs.py
+++ b/blivet/devicelibs/btrfs.py
@@ -78,9 +78,17 @@ def create_volume(devices, label=None, data=None, metadata=None):
 
 # destroy is handled using wipefs
 
-# add device
+def add(mountpoint, devices):
+    if not os.path.ismount(mountpoint):
+        raise ValueError("volume not mounted")
+
+    return btrfs(["device", "add"] + devices + [mountpoint])
+
+def remove(mountpoint, devices):
+    if not os.path.ismount(mountpoint):
+        raise ValueError("volume not mounted")
 
-# remove device
+    return btrfs(["device", "delete"] + devices + [mountpoint])
 
 def create_subvolume(mountpoint, name):
     """Create a subvolume named name below mountpoint mountpoint."""
diff --git a/blivet/devicelibs/lvm.py b/blivet/devicelibs/lvm.py
index 411618e..cf9452d 100644
--- a/blivet/devicelibs/lvm.py
+++ b/blivet/devicelibs/lvm.py
@@ -258,6 +258,15 @@ def pvscan(device):
     except LVMError as msg:
         raise LVMError("pvscan failed for %s: %s" % (device, msg))
 
+def pvmove(source, dest):
+    """ Move physical extents from one PV to another. """
+    args = ["pvmove"] + _getConfigArgs() + [source, dest]
+
+    try:
+        lvm(args)
+    except LVMError as msg:
+        raise LVMError("pvmove failed for %s->%s: %s" % (source, dest, msg))
+
 def pvinfo(device):
     """
         If the PV was created with '--metadacopies 0', lvm will do some
@@ -334,16 +343,16 @@ def vgdeactivate(vg_name):
     except LVMError as msg:
         raise LVMError("vgdeactivate failed for %s: %s" % (vg_name, msg))
 
-def vgreduce(vg_name, pv_list, rm=False):
+def vgreduce(vg_name, pv_list, missing=False):
     """ Reduce a VG.
 
-    rm -> with RemoveMissing option.
-    Use pv_list when rm=False, otherwise ignore pv_list and call vgreduce with
-    the --removemissing option.
+    missing -> with RemoveMissing option.
+    Use pv_list when missing=False, otherwise ignore pv_list and call vgreduce
+    with the --removemissing option.
     """
     args = ["vgreduce"]
     args.extend(_getConfigArgs())
-    if rm:
+    if missing:
         args.extend(["--removemissing", "--force", vg_name])
     else:
         args.extend([vg_name] + pv_list)
@@ -353,6 +362,15 @@ def vgreduce(vg_name, pv_list, rm=False):
     except LVMError as msg:
         raise LVMError("vgreduce failed for %s: %s" % (vg_name, msg))
 
+def vgextend(vg_name, pv_list):
+    """ Add PV(s) to a VG. """
+    args = ["vgextend"] + _getConfigArgs() + [vg_name] + pv_list
+
+    try:
+        lvm(args)
+    except LVMError as msg:
+        raise LVMError("vgextend failed for %s: %s" % (vg_name, msg))
+
 def vginfo(vg_name):
     args = ["vgs", "--noheadings", "--nosuffix"] + \
             ["--units", "m"] + \
diff --git a/blivet/devicelibs/mdraid.py b/blivet/devicelibs/mdraid.py
index b5bf688..8c3156f 100644
--- a/blivet/devicelibs/mdraid.py
+++ b/blivet/devicelibs/mdraid.py
@@ -134,14 +134,24 @@ def mddestroy(device):
     except MDRaidError as msg:
         raise MDRaidError("mddestroy failed for %s: %s" % (device, msg))
 
-def mdadd(device):
-    args = ["--incremental", "--quiet"]
-    args.append(device)
+def mdadd(array, devices, incremental=False):
+    if incremental:
+        args = ["--incremental", "--quiet"]
+    else:
+        args = [array, "--add"]
+
+    args.extend(devices)
 
     try:
         mdadm(args)
     except MDRaidError as msg:
-        raise MDRaidError("mdadd failed for %s: %s" % (device, msg))
+        raise MDRaidError("mdadd failed for %s: %s" % (devices, msg))
+
+def mdremove(array, devices):
+    try:
+        mdadm([array, "--remove"] + devices)
+    except MDRaidError as msg:
+        raise MDRaidError("mdremove failed for %s: %s" % (devices, msg))
 
 def mdactivate(device, members=[], uuid=None):
     """Assemble devices given by members into a single device.
diff --git a/blivet/devices.py b/blivet/devices.py
index f3a13ac..b079a29 100644
--- a/blivet/devices.py
+++ b/blivet/devices.py
@@ -2283,14 +2283,42 @@ class LVMVolumeGroupDevice(DMDevice):
         lvm.vgdeactivate(self.name)
         lvm.vgremove(self.name)
 
-    def reduce(self, pv_list):
-        """ Remove the listed PVs from the VG. """
+    def reduce(self, pvs):
+        """ Remove the listed PVs from the VG.
+
+            NOTE: This performs the actual operation in addition to modifying
+                  this instance accordingly.
+        """
         log_method_call(self, self.name, status=self.status)
         if not self.exists:
             raise DeviceError("device has not been created", self.name)
 
-        lvm.vgreduce(self.name, pv_list)
-        # XXX do we need to notify the kernel?
+        non_members = [pv.name for pv in pvs if pv not in self.pvs]
+        if non_members:
+            log.error("cannot remove non-member pvs: %s" % non_members)
+            raise DeviceError("cannot remove pvs that are not part of the vg")
+
+        lvm.vgreduce(self.name, [pv.path for pv in pvs])
+        for pv in pvs:
+            self._removePV(pv)
+
+    def extend(self, pvs):
+        """ Add the listed PVs to the VG.
+
+            NOTE: This performs the actual operation in addition to modifying
+                  this instance accordingly.
+        """
+        if not self.exists:
+            raise DeviceError("device has not been created", self.name)
+
+        members = [pv.name for pv in pvs if pv in self.pvs]
+        if members:
+            log.error("cannot re-add member pvs: %s" % members)
+            raise DeviceError("cannot add pvs that are already part of the vg")
+
+        lvm.vgextend(self.name, [pv.path for pv in pvs])
+        for pv in pvs:
+            self._addPV(pv)
 
     def _addLogVol(self, lv):
         """ Add an LV to this VG. """
@@ -2323,10 +2351,6 @@ class LVMVolumeGroupDevice(DMDevice):
         if pv in self.pvs:
             raise ValueError("pv is already part of this vg")
 
-        # for the time being we will not allow vgextend
-        if self.exists:
-            raise DeviceError("cannot add pv to existing vg", self.name)
-
         self.parents.append(pv)
         pv.addChild()
 
@@ -2341,10 +2365,6 @@ class LVMVolumeGroupDevice(DMDevice):
         if not pv in self.pvs:
             raise ValueError("specified pv is not part of this vg")
 
-        # for the time being we will not allow vgreduce
-        if self.exists:
-            raise DeviceError("cannot remove pv from existing vg", self.name)
-
         self.parents.remove(pv)
         pv.removeChild()
 
@@ -3251,7 +3271,7 @@ class MDRaidArrayDevice(StorageDevice):
                 return
 
             try:
-                mdraid.mdadd(device.path)
+                mdraid.mdadd(None, [device.path], incremental=True)
                 # mdadd causes udev events
                 udev_settle()
             except MDRaidError as e:
@@ -3283,10 +3303,6 @@ class MDRaidArrayDevice(StorageDevice):
         if member in self.parents:
             raise ValueError("member is already part of this array")
 
-        # for the time being we will not allow adding members to existing arrays
-        if self.exists:
-            raise DeviceError("cannot add member to existing array", self.name)
-
         self.parents.append(member)
         member.addChild()
         self.memberDevices += 1
@@ -3439,6 +3455,43 @@ class MDRaidArrayDevice(StorageDevice):
                         metadataVer=self.metadataVersion,
                         bitmap=self.createBitmap)
 
+    def remove(self, members):
+        """ Remove the listed member devices from the array.
+
+            NOTE: This performs the actual operation in addition to modifying
+                  this instance accordingly.
+        """
+        log_method_call(self, self.name, status=self.status)
+        if not self.exists:
+            raise DeviceError("device has not been created", self.name)
+
+        non_members = [d.name for d in members if d not in self.devices]
+        if non_members:
+            log.error("cannot remove non-members: %s" % non_members)
+            raise DeviceError("cannot remove devices that are not part of the array")
+
+        mdraid.mdremove(self.path, [d.path for d in members])
+        for member in members:
+            self._removeMember(member)
+
+    def add(self, members):
+        """ Add the listed member devices to the array.
+
+            NOTE: This performs the actual operation in addition to modifying
+                  this instance accordingly.
+        """
+        if not self.exists:
+            raise DeviceError("device has not been created", self.name)
+
+        members = [d.name for d in members if d in self.parents]
+        if members:
+            log.error("cannot re-add member: %s" % members)
+            raise DeviceError("cannot add devices that are already part of the array")
+
+        mdraid.mdadd(self.path, [d.path for d in members])
+        for member in members:
+            self._addMember(member)
+
     @property
     def formatArgs(self):
         formatArgs = []
@@ -4631,6 +4684,69 @@ class BTRFSVolumeDevice(BTRFSDevice):
             device.setup(orig=True)
             DeviceFormat(device=device.path, exists=True).destroy()
 
+    def remove(self, members):
+        """ Remove the listed member devices from the volume.
+
+            NOTE: This performs the actual operation in addition to modifying
+                  this instance accordingly.
+        """
+        log_method_call(self, self.name, status=self.status)
+        if not self.exists:
+            raise DeviceError("device has not been created", self.name)
+
+        non_members = [d.name for d in members if d not in self.devices]
+        if non_members:
+            log.error("cannot remove non-members: %s" % non_members)
+            raise DeviceError("cannot remove devices that are not part of the volume")
+
+        try:
+            self._do_temp_mount(orig=True)
+        except FSError as e:
+            log.debug("btrfs temp mount failed: %s" % e)
+            raise
+
+        try:
+            btrfs.remove(self.originalFormat._mountpoint,
+                         [d.path for d in members])
+        except BTRFSError as e:
+            return
+        finally:
+            self._undo_temp_mount()
+
+        for member in members:
+            self._removeMember(member)
+
+    def add(self, members):
+        """ Add the listed member devices to the volume.
+
+            NOTE: This performs the actual operation in addition to modifying
+                  this instance accordingly.
+        """
+        if not self.exists:
+            raise DeviceError("device has not been created", self.name)
+
+        members = [d.name for d in members if d in self.parents]
+        if members:
+            log.error("cannot re-add member: %s" % members)
+            raise DeviceError("cannot add devices that are already part of the volume")
+
+        try:
+            self._do_temp_mount(orig=True)
+        except FSError as e:
+            log.debug("btrfs temp mount failed: %s" % e)
+            raise
+
+        try:
+            btrfs.add(self.originalFormat._mountpoint,
+                      [d.path for d in members])
+        except BTRFSError as e:
+            return
+        finally:
+            self._undo_temp_mount()
+
+        for member in members:
+            self._addMember(member)
+
     def populateKSData(self, data):
         super(BTRFSVolumeDevice, self).populateKSData(data)
         data.dataLevel = self.dataLevel
-- 
1.8.1.4

