From 45e7b53b013999e4cc2f72c080651cc2ece5a346 Mon Sep 17 00:00:00 2001
From: Ludovic Pouzenc <lpouzenc@gmail.com>
Date: Sun, 28 Jun 2015 01:05:47 +0200
Subject: RAID 5 layout implementation (left/right (as)symmetric and RAID 0
 impl.

---
 myraid.py | 122 +++++++++++++++++++++++++++++++++++++++++---------------------
 1 file changed, 82 insertions(+), 40 deletions(-)

(limited to 'myraid.py')

diff --git a/myraid.py b/myraid.py
index 5b44fc4..3706c36 100644
--- a/myraid.py
+++ b/myraid.py
@@ -22,6 +22,8 @@ import logging, numpy
 
 class MyRaid():
     """Auxiliary class, managing RAID layer"""
+    RAID_TYPES = [ '0', '1', '5', '5+0' ]
+    RAID5_LAYOUTS = [ 'la', 'ra', 'ls', 'rs' ]
 
     def __init__(self, *args, **kwargs):
         self.disks = []
@@ -31,7 +33,7 @@ class MyRaid():
         self.raid_chunk_size = 65536
         self.raid_disk_order = []
         self.raid_disk_count = 0
-        self.raid_types = [ '0', '1', '5', '5+0' ]
+        self.raid_layout = 'ls'
 
     def get_raid_start(self):
         return self.raid_start
@@ -48,6 +50,9 @@ class MyRaid():
     def get_raid_disk_order_str(self):
         return ' '.join(map(str,self.raid_disk_order))
 
+    def get_raid_layout(self):
+        return self.raid_layout
+
     def set_disks(self, disks):
         self.disks = disks
 
@@ -79,6 +84,12 @@ class MyRaid():
         self.raid_disk_order = new_raid_disk_order
         self.raid_disk_count = len(new_raid_disk_order)
 
+    def set_raid_layout(self, new_raid_layout):
+        if new_raid_layout in MyRaid.RAID5_LAYOUTS:
+            self.raid_layout = new_raid_layout
+        else:
+            raise ValueError('raid_layout has to be one of %s'%' '.join(RAID_LAYOUTS))
+
     def sizeof_raid_result(self, raid_type):
         size = self.raid_end - self.raid_start
         if size <= 0 :
@@ -86,14 +97,11 @@ class MyRaid():
         else:
             return {
                 '0'  : size * self.raid_disk_count,
-                '1'  : size,
+                '1'  : size if self.raid_disk_count == 2 else 0,
                 '5'  : size * (self.raid_disk_count - 1) if self.raid_disk_count >= 3 else 0,
                 '5+0': size * (self.raid_disk_count - 2) if self.raid_disk_count >= 6 and self.raid_disk_count % 2 == 0 else 0,
                 }[raid_type]
 
-    def sizeof_disk_parity(self, raid_type):
-        return self.sizeof_disk_xor(raid_type) / self.raid_sector_size * 16
-
     def sizeof_disk_xor(self, raid_type):
         size = self.raid_end - self.raid_start
         if size <= 0:
@@ -101,16 +109,20 @@ class MyRaid():
         else:
             return {
                 '0'  : 0, # TODO Could contain some plain text error message
-                '1'  : size,
+                '1'  : size if self.raid_disk_count == 2 else 0,
                 '5'  : size if self.raid_disk_count >= 3 else 0,
                 '5+0': size if self.raid_disk_count >= 6 and self.raid_disk_count % 2 == 0 else 0,
                 }[raid_type]
 
+    def sizeof_disk_parity(self, raid_type):
+        return self.sizeof_disk_xor(raid_type) / self.raid_sector_size * 16
+
     def xor_blocks(self,fd_list, offset, size):
-        """TODO"""
+        """Compute bitwise XOR against a bunch of disks slice"""
         logging.info("Enter xor_blocks(fd_list,%d,%d)"%(offset, size))
         
-        assert(size % 8 == 0), "Size must be multiple of 8"
+        if size % 8 != 0:
+            raise ValueError('xor_blocks : size must be multiple of 8')
         dt = numpy.dtype('<Q8')
 
         fd_list[0].seek(offset)
@@ -146,53 +158,83 @@ class MyRaid():
         #logging.warn(binascii.hexlify(numpy_b1))
         return (result,numpy_b1)
 
+    def read_disk_xor(self,raid_type,offset,size):
+        raid_disks = [ self.disks[i] for i in self.raid_disk_order ]
+        return self.xor_blocks(raid_disks,offset,size)[1].tostring()
+
     def read_disk_parity(self,raid_type,offset,size):
-        """TODO"""
-        logging.warn("Enter check_data(%s,disks,%d,%d)"%(raid_type,offset,size))
+        """Returns textual information about parity status of each sector"""
+        logging.warn("Enter read_disk_parity(%s,%d,%d)"%(raid_type,offset,size))
         raid_disks = [ self.disks[i] for i in self.raid_disk_order ]
         start = self.raid_start + offset * self.raid_sector_size / 16
         end = start + size * self.raid_sector_size / 16
-        result = ''.join(
-                [ '0x%011x %c\n'%( addr, self.xor_blocks(raid_disks, addr, self.raid_sector_size)[0])
-                        for addr in range(start, end, self.raid_sector_size)
-                ])
-        
-        logging.warn("Exit. check_data(%s,disks,%d,%d)"%(raid_type,offset,size))
 
-        return result
+        #TODO : improove for nested levels
+        if raid_type in ['1','5', '5+0']:
+            result = ''.join(
+                    [ '0x%011x %c\n'%( addr, self.xor_blocks(raid_disks, addr, self.raid_sector_size)[0])
+                            for addr in range(start, end, self.raid_sector_size)
+                    ])
+        else:
+            result = None
 
-    def read_disk_xor(self,raid_type,offset,size):
-        raid_disks = [ self.disks[i] for i in self.raid_disk_order ]
-        return self.xor_blocks(raid_disks,offset,size)[1].tostring()
+        logging.warn("Exit. read_disk_parity(%s,%d,%d)"%(raid_type,offset,size))
+        return result
 
 
     def read_raid_result(self,raid_type,offset,size):
-        """TODO"""
+        """Returns actual RAID data"""
+        raid_disks = [ self.disks[i] for i in self.raid_disk_order ] # TODO A garder en attribut ?
         disk_count = len(self.raid_disk_order) # TODO doublon ?
-        raid_disks = [ self.disks[i] for i in self.raid_disk_order ] # A garder en attribut ?
-
-        # This code is RAID 5 only (left-assymetric)
-        if disk_count < 3:
-            return None
-
-        slice_no = offset / self.raid_chunk_size
-        slice_off = offset % self.raid_chunk_size 
-        segment=slice_no/(disk_count-1)
-        par_disk=(disk_count-1) - (segment % disk_count) # TODO : equivalent a : segment-1 % disk_count ?
-        data_disk=( par_disk + 1 + (slice_no % (disk_count-1)) ) % disk_count
-        off_disk = self.raid_start + segment * self.raid_chunk_size + slice_off
-
-        size2 = min(size, (slice_no+1) * self.raid_chunk_size - offset)
-
-        logging.info("raid.read_result(%s): offset=%d,slice_no=%d,slice_off=%d,segment=%d,par_disk=%d,data_disk=%d,off_disk=%d,size2=%d,slice_off+size2=%d" 
-        % (raid_type,offset,slice_no,slice_off,segment,par_disk,data_disk,off_disk,size2,slice_off+size2) )
 
+        if raid_type == '0':
+            segment_no = offset / self.raid_chunk_size
+            segment_off = offset % self.raid_chunk_size 
+            stripe_no = segment_no / disk_count
+            par_disk = -1
+            data_disk = segment_no % disk_count
+            off_disk = self.raid_start + stripe_no * self.raid_chunk_size + segment_off
+            size2 = min(size, (segment_no+1) * self.raid_chunk_size - offset)
+
+        elif raid_type == '1':
+            segment_no = -1
+            segment_off = -1
+            stripe_no = -1
+            par_disk = 1
+            data_disk = 0
+            off_disk = self.raid_start + offset
+            size2 = size
+
+        elif raid_type == '5':
+            segment_no = offset / self.raid_chunk_size
+            segment_off = offset % self.raid_chunk_size 
+            stripe_no = segment_no / (disk_count-1)
+
+            if self.raid_layout in ['ls','la']:
+                par_disk = (disk_count-1) - (stripe_no % disk_count)
+            else: # self.raid_layout in ['rs','ra']:
+                par_disk = stripe_no % disk_count
+
+            if self.raid_layout in ['ls','rs']:
+                data_disk = (par_disk+1 + (segment_no % (disk_count-1)) ) % disk_count
+            else: # self.raid_layout in ['la','ra']:
+                data_disk = segment_no % (disk_count-1)
+                if data_disk >= par_disk:
+                    data_disk = data_disk + 1
+
+            off_disk = self.raid_start + stripe_no * self.raid_chunk_size + segment_off
+            size2 = min(size, (segment_no+1) * self.raid_chunk_size - offset)
+
+        logging.info("raid.read_result(%s): offset=%d,segment_no=%d,segment_off=%d,stripe_no=%d,par_disk=%d,data_disk=%d,off_disk=%d,size2=%d,segment_off+size2=%d" 
+        % (raid_type,offset,segment_no,segment_off,stripe_no,par_disk,data_disk,off_disk,size2,segment_off+size2) )
+
+        #TODO recorver from parity if damaged sectors in data_disk
         data_fd = raid_disks[data_disk]
         data_fd.seek(off_disk)
         data = data_fd.read(size2)
-        
+
         # This kills performance but don't make short reads before EOF
-        #if size2 < size:
+        #if size2 > 0 and size2 < size:
         #    data += self.read_result(self,raid_type,offset+size2,size-size2)
 
         return data
-- 
cgit v1.2.3