/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.hops.erasure_coding; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.json.JSONException; import java.util.ArrayList; import java.util.List; public class SimpleRegeneratingCode extends ErasureCode { public static final Log LOG = LogFactory.getLog(SimpleRegeneratingCode.class); private int stripeSize; private int paritySize; private int paritySizeSRC; private int paritySizeRS; private int simpleParityDegree; private int[] generatingPolynomial; private int PRIMITIVE_ROOT = 2; private int[] primitivePower; private GaloisField GF = GaloisField.getInstance(); private int[] errSignature; private int[] dataBuff; private int[][] groupsTable; @Deprecated public SimpleRegeneratingCode(int stripeSize, int paritySize) { init(stripeSize, paritySize); } public SimpleRegeneratingCode() { } @Override public void init(Codec codec) { try { this.paritySizeSRC = codec.json.getInt("parity_length_src"); } catch (JSONException e) { LOG.error("Exception", e); } init(codec.stripeLength, codec.parityLength); LOG.info(" Initialized " + SimpleRegeneratingCode.class + " stripeLength:" + codec.stripeLength + " parityLength:" + codec.parityLength + " SRC parities:" + paritySizeSRC); } private void init(int stripeSize, int paritySize) { this.stripeSize = stripeSize; this.paritySize = paritySize; this.paritySizeRS = paritySize - paritySizeSRC; assert (stripeSize + paritySizeRS < GF.getFieldSize()); assert (paritySize >= paritySizeSRC); // The degree of a simple parity is the number of locations // combined into the single parity. The degree is a function // of the RS-stripe (stripe + RS parity) length. // (The number of SRC groups is paritySizeSRC + 1, because // one SRC parity is implied -- not stored). simpleParityDegree = (int) Math.ceil( (double) (stripeSize + paritySizeRS) / (double) (paritySizeSRC + 1)); while (simpleParityDegree * paritySizeSRC >= stripeSize + paritySizeRS) { LOG.info("\nInvalid code parameters." + " Reducing SRC parities to " + (paritySizeSRC - 1) + " Increasing RS parities to " + (paritySizeRS + 1)); this.paritySizeSRC--; this.paritySizeRS++; simpleParityDegree = (int) Math.ceil( (double) (stripeSize + paritySizeRS) / (double) (paritySizeSRC + 1)); } this.errSignature = new int[paritySizeRS]; this.dataBuff = new int[paritySizeRS + stripeSize]; this.primitivePower = new int[stripeSize + paritySizeRS]; // compute powers of the primitive root for (int i = 0; i < stripeSize + paritySizeRS; i++) { primitivePower[i] = GF.power(PRIMITIVE_ROOT, i); } // compute generating polynomial int[] gen = {1}; int[] poly = new int[2]; for (int i = 0; i < paritySizeRS; i++) { poly[0] = primitivePower[i]; poly[1] = 1; gen = GF.multiply(gen, poly); } // generating polynomial has all generating roots generatingPolynomial = gen; // groupsTable[][] // groupsTable[loc]: the SRC group neighbors of location loc. groupsTable = new int[paritySize + stripeSize][]; for (int i = 0; i < groupsTable.length; i++) { List<Integer> locationsInGroup = getSRCGroupNeighbors(i); groupsTable[i] = new int[locationsInGroup.size()]; int k = 0; for (int loc : locationsInGroup) { groupsTable[i][k++] = loc; } } } @Override public void encode(int[] message, int[] parity) { assert (message.length == stripeSize && parity.length == paritySize); // initialize data buffer for (int i = 0; i < paritySizeRS; i++) { dataBuff[i] = 0; } // put message in the data buffer for (int i = 0; i < stripeSize; i++) { dataBuff[i + paritySizeRS] = message[i]; } // calculate RS parities and copy into parity[] GF.remainder(dataBuff, generatingPolynomial); for (int i = 0; i < paritySizeRS; i++) { parity[i + paritySizeSRC] = dataBuff[i]; } // restore message in dataBuff for (int i = 0; i < stripeSize; i++) { dataBuff[i + paritySizeRS] = message[i]; } // compute the SRC parities and store into parity[] for (int i = 0; i < paritySizeSRC; i++) { parity[i] = 0; for (int j = simpleParityDegree * i; j < simpleParityDegree * (i + 1); j++) { parity[i] = GF.add(dataBuff[j], parity[i]); } } } /* * Perform Reed Solomon decoding. */ private void decodeReedSolomon(int[] data, int[] erasedLocations, int[] erasedValues) { if (erasedLocations.length == 0) { return; } assert (erasedLocations.length == erasedValues.length); assert (erasedLocations.length <= paritySizeRS); for (int i = 0; i < erasedLocations.length; i++) { data[erasedLocations[i]] = 0; } for (int i = 0; i < erasedLocations.length; i++) { errSignature[i] = primitivePower[erasedLocations[i]]; erasedValues[i] = GF.substitute(data, primitivePower[i]); } GF.solveVandermondeSystem(errSignature, erasedValues, erasedLocations.length); } /* * Performs Reed Solomon decoding, assuming that all positions not included in * erasedLocations are available. */ @Override public void decode(int[] data, int[] erasedLocations, int[] erasedValues) { decodeReedSolomon(data, erasedLocations, erasedValues); } @Override public void decode(int[] data, int[] erasedLocations, int[] erasedValues, int[] locationsToRead, int[] locationsNotToRead) { assert (erasedLocations.length == erasedValues.length); // CASE 1 : SINGLE ERASURE // If only one erasure is passed, perform a quick repair using the // local group (locationsToRead). if (erasedLocations.length == 1) { erasedValues[0] = 0; for (int i = 0; i < locationsToRead.length; i++) { erasedValues[0] = GF.add(data[locationsToRead[i]], erasedValues[0]); } return; } // CASE 2 : MULTIPLE ERASURES - NO CONFLICT if (!groupConflict(erasedLocations)) { for (int i = 0; i < erasedLocations.length; i++) { int[] singleErasedLocation = new int[1]; singleErasedLocation[0] = erasedLocations[i]; int[] singleErasedValue = new int[1]; singleErasedValue[0] = 0; decode(data, singleErasedLocation, singleErasedValue, groupsTable[erasedLocations[i]], null); erasedValues[i] = singleErasedValue[0]; } return; } // CASE 3 : MULTIPLE ERASURES - CONFLICT // According to locationsToReadForDecode(), locationsToRead should // be of length equal to stripeSize for RS decoding. assert (locationsToRead.length == stripeSize); assert (locationsNotToRead.length == stripeSize + paritySize - locationsToRead.length); // count the number of src parities that are erased int numOferasedSRCparities = 0; for (int i = 0; i < erasedLocations.length; i++) { if (erasedLocations[i] < paritySizeSRC) { numOferasedSRCparities++; } } int[] dataRS = new int[paritySizeRS + stripeSize]; for (int i = 0; i < paritySizeRS + stripeSize; i++) { dataRS[i] = data[i + paritySizeSRC]; } /* * erasedLocationsRS contains actual erased locations of the RS stripe * plus some locations that are not supposed to be read. */ int[] erasedLocationsRS = new int[locationsNotToRead.length - this.paritySizeSRC]; int k = 0; for (int i = 0; i < locationsNotToRead.length; i++) { if (locationsNotToRead[i] >= paritySizeSRC) { erasedLocationsRS[k++] = locationsNotToRead[i] - paritySizeSRC; } } int[] erasedValuesRS = new int[erasedLocationsRS.length]; decodeReedSolomon(dataRS, erasedLocationsRS, erasedValuesRS); for (int i = 0; i < erasedLocationsRS.length; i++) { data[paritySizeSRC + erasedLocationsRS[i]] = erasedValuesRS[i]; } // now that the RS part is all fixed, fix the simple parities for (int i = 0; i < erasedLocations.length; i++) { if (erasedLocations[i] < paritySizeSRC) { int par = erasedLocations[i]; data[par] = 0; for (int j = 0; j < groupsTable[erasedLocations[i]].length; j++) { data[par] = GF.add(data[groupsTable[erasedLocations[i]][j]], data[par]); } } } for (int i = 0; i < erasedLocations.length; i++) { erasedValues[i] = data[erasedLocations[i]]; } return; } @Override public int stripeSize() { return this.stripeSize; } @Override public int paritySize() { return this.paritySize; } @Override public int symbolSize() { return (int) Math.round(Math.log(GF.getFieldSize()) / Math.log(2)); } /** * Figure out which locations need to be read to decode erased locations. The * locations are specified as integers in the range [ 0, stripeSize() + * paritySize() ). Values in the range [ 0, paritySize() ) represent parity * data. Values in the range [ paritySize(), paritySize() + stripeSize() ) * represent message data. * * @param erasedLocations * The erased locations. * @return The locations to read. */ @Override public List<Integer> locationsToReadForDecode(List<Integer> erasedLocations) throws TooManyErasedLocations { //LOG.info("Erased locations: "+erasedLocations.toString()); List<Integer> locationsToRead; // If only one location is erased, return its local (src) group if (erasedLocations.size() == 1) { //locationsToRead = computeSRCGroupLocations(erasedLocations.get(0)); int loc = erasedLocations.get(0); locationsToRead = new ArrayList<Integer>(groupsTable[loc].length); for (int i = 0; i < groupsTable[loc].length; i++) { locationsToRead.add(groupsTable[loc][i]); } //LOG.info("locations to read: "+locationsToRead.toString()); return locationsToRead; } // If more than one location are erased, check if they belong to the same group. // If they do not belong to same group (- no conflict), add locations of each // separate group. int[] erasedLocationsArray = new int[erasedLocations.size()]; for (int i = 0; i < erasedLocations.size(); i++) { erasedLocationsArray[i] = erasedLocations.get(i); } if (!groupConflict(erasedLocationsArray)) { // we expect approximately simpleParityDegree locations to be read for each // erased location. locationsToRead = new ArrayList<Integer>(erasedLocations.size() * simpleParityDegree); // add unique locations for (int loc : erasedLocations) { for (int i = 0; i < groupsTable[loc].length; i++) { if (!locationsToRead.contains(groupsTable[loc][i])) { locationsToRead.add(groupsTable[loc][i]); } } } return locationsToRead; } // If more than one location is erased and there is at least a pair belonging to the // same group, we will have to perform Reed Solomon (RS) decoding. Hence, read // locations that are necessary for RS decoding. locationsToRead = new ArrayList<Integer>(stripeSize()); int limit = stripeSize() + paritySize(); //Loop through all possible locations in the stripe, omitting the SRC parities. for (int loc = paritySizeSRC; loc < limit; loc++) { //Is the location good. if (erasedLocations.indexOf(loc) == -1) { locationsToRead.add(loc); if (stripeSize() == locationsToRead.size()) { break; } } } // If we are are not able to fill up the locationsToRead list, // we did not find enough good locations. Throw TooManyErasedLocations. if (locationsToRead.size() != stripeSize()) { String locationsStr = ""; for (Integer erasedLocation : erasedLocations) { locationsStr += " " + erasedLocation; } throw new TooManyErasedLocations("Locations " + locationsStr); } return locationsToRead; } /* * Given a location loc, return a list with the other locations * belonging to the same SRC group as loc. */ private List<Integer> getSRCGroupNeighbors(int loc) { int limit = stripeSize() + paritySize(); /* A group is expected to have at most simpleParityDegree + 1 * locations. groupLocations will contain the simpleParityDegree * neighbors of loc. */ List<Integer> neighbors = new ArrayList<Integer>(simpleParityDegree); int group = getSRCGroup(loc); // CASE 1: Group with "stored" SRC parity. if (group < paritySizeSRC) { if (group != loc) // group equals the location of the SRC parity // Hence, add the location to the neighbors. { neighbors.add(group); } // add the rest neighbors (loc is excluded) for (int i = paritySizeSRC + group * simpleParityDegree; i < paritySizeSRC + (group + 1) * simpleParityDegree; i++) { if (i != loc) { neighbors.add(i); } } }// CASE 2: Group is the one with the "inferred" SRC parity. else { assert (loc >= paritySizeSRC); // All SRC parities are neighbors. for (int i = 0; i < paritySizeSRC; i++) { neighbors.add(i); } // Add the remaining (non SRC-parity) neighbors. for (int i = paritySizeSRC + group * simpleParityDegree; i < limit; i++) { if (i != loc) { neighbors.add(i); } } } return neighbors; } /* * Return the id of the SRC group location loc belongs to. * Return -1 for invalid loc. */ private int getSRCGroup(int loc) { int group = -1; if (0 <= loc && loc < paritySizeSRC) { group = loc; } else if (paritySizeSRC <= loc && loc < stripeSize + paritySize) { group = (int) (loc - paritySizeSRC) / simpleParityDegree; } else { group = -1; } return group; } /* * Check for conflict (- whether any two locations in locs * belong to the same SRC group. */ private boolean groupConflict(int[] locs) { int[] groups = new int[paritySizeSRC + 1]; for (int i = 0; i < groups.length; i++) { groups[i] = 0; } /* * if at least one position in locs is SRC parity, * mark the last group. */ for (int i = 0; i < locs.length; i++) { if (locs[i] < paritySizeSRC) { groups[paritySizeSRC] = 1; break; } } for (int i = 0; i < locs.length; i++) { if (groups[getSRCGroup(locs[i])]++ > 0) { return true; } } return false; } }