/* * Copyright (c) 2020 Red Hat, Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * $Id: //eng/vdo-releases/aluminum/src/c++/vdo/base/vdoResize.c#15 $ */ #include "vdoResize.h" #include "logger.h" #include "adminCompletion.h" #include "completion.h" #include "recoveryJournal.h" #include "slabDepot.h" #include "slabSummary.h" #include "vdoInternal.h" #include "vdoLayout.h" typedef enum { GROW_PHYSICAL_PHASE_START = 0, GROW_PHYSICAL_PHASE_COPY_SUMMARY, GROW_PHYSICAL_PHASE_UPDATE_COMPONENTS, GROW_PHYSICAL_PHASE_USE_NEW_SLABS, GROW_PHYSICAL_PHASE_END, GROW_PHYSICAL_PHASE_ERROR, } GrowPhysicalPhase; static const char *GROW_PHYSICAL_PHASE_NAMES[] = { "GROW_PHYSICAL_PHASE_START", "GROW_PHYSICAL_PHASE_COPY_SUMMARY", "GROW_PHYSICAL_PHASE_UPDATE_COMPONENTS", "GROW_PHYSICAL_PHASE_USE_NEW_SLABS", "GROW_PHYSICAL_PHASE_END", "GROW_PHYSICAL_PHASE_ERROR", }; /** * Implements ThreadIDGetterForPhase. **/ __attribute__((warn_unused_result)) static ThreadID getThreadIDForPhase(AdminCompletion *adminCompletion) { return getAdminThread(getThreadConfig(adminCompletion->completion.parent)); } /** * Callback to initiate a grow physical, registered in performGrowPhysical(). * * @param completion The sub-task completion **/ static void growPhysicalCallback(VDOCompletion *completion) { AdminCompletion *adminCompletion = adminCompletionFromSubTask(completion); assertAdminOperationType(adminCompletion, ADMIN_OPERATION_GROW_PHYSICAL); assertAdminPhaseThread(adminCompletion, __func__, GROW_PHYSICAL_PHASE_NAMES); VDO *vdo = adminCompletion->completion.parent; switch (adminCompletion->phase++) { case GROW_PHYSICAL_PHASE_START: if (isReadOnly(vdo->readOnlyNotifier)) { logErrorWithStringError(VDO_READ_ONLY, "Can't grow physical size of a read-only VDO"); setCompletionResult(resetAdminSubTask(completion), VDO_READ_ONLY); break; } if (startOperationWithWaiter(&vdo->adminState, ADMIN_STATE_SUSPENDED_OPERATION, &adminCompletion->completion, NULL)) { // Copy the journal into the new layout. copyPartition(vdo->layout, RECOVERY_JOURNAL_PARTITION, resetAdminSubTask(completion)); } return; case GROW_PHYSICAL_PHASE_COPY_SUMMARY: copyPartition(vdo->layout, SLAB_SUMMARY_PARTITION, resetAdminSubTask(completion)); return; case GROW_PHYSICAL_PHASE_UPDATE_COMPONENTS: vdo->config.physicalBlocks = growVDOLayout(vdo->layout); updateSlabDepotSize(vdo->depot); saveVDOComponentsAsync(vdo, resetAdminSubTask(completion)); return; case GROW_PHYSICAL_PHASE_USE_NEW_SLABS: useNewSlabs(vdo->depot, resetAdminSubTask(completion)); return; case GROW_PHYSICAL_PHASE_END: setSlabSummaryOrigin(getSlabSummary(vdo->depot), getVDOPartition(vdo->layout, SLAB_SUMMARY_PARTITION)); setRecoveryJournalPartition(vdo->recoveryJournal, getVDOPartition(vdo->layout, RECOVERY_JOURNAL_PARTITION)); break; case GROW_PHYSICAL_PHASE_ERROR: enterReadOnlyMode(vdo->readOnlyNotifier, completion->result); break; default: setCompletionResult(resetAdminSubTask(completion), UDS_BAD_STATE); } finishVDOLayoutGrowth(vdo->layout); finishOperationWithResult(&vdo->adminState, completion->result); } /** * Handle an error during the grow physical process. * * @param completion The sub-task completion **/ static void handleGrowthError(VDOCompletion *completion) { adminCompletionFromSubTask(completion)->phase = GROW_PHYSICAL_PHASE_ERROR; growPhysicalCallback(completion); } /**********************************************************************/ int performGrowPhysical(VDO *vdo, BlockCount newPhysicalBlocks) { BlockCount oldPhysicalBlocks = vdo->config.physicalBlocks; // Skip any noop grows. if (oldPhysicalBlocks == newPhysicalBlocks) { return VDO_SUCCESS; } if (newPhysicalBlocks != getNextVDOLayoutSize(vdo->layout)) { /* * Either the VDO isn't prepared to grow, or it was prepared to grow * to a different size. Doing this check here relies on the fact that * the call to this method is done under the dmsetup message lock. */ finishVDOLayoutGrowth(vdo->layout); abandonNewSlabs(vdo->depot); return VDO_PARAMETER_MISMATCH; } // Validate that we are prepared to grow appropriately. BlockCount newDepotSize = getNextBlockAllocatorPartitionSize(vdo->layout); BlockCount preparedDepotSize = getNewDepotSize(vdo->depot); if (preparedDepotSize != newDepotSize) { return VDO_PARAMETER_MISMATCH; } int result = performAdminOperation(vdo, ADMIN_OPERATION_GROW_PHYSICAL, getThreadIDForPhase, growPhysicalCallback, handleGrowthError); if (result != VDO_SUCCESS) { return result; } logInfo("Physical block count was %llu, now %llu", oldPhysicalBlocks, newPhysicalBlocks); return VDO_SUCCESS; } /** * Callback to check that we're not in recovery mode, used in * prepareToGrowPhysical(). * * @param completion The sub-task completion **/ static void checkMayGrowPhysical(VDOCompletion *completion) { AdminCompletion *adminCompletion = adminCompletionFromSubTask(completion); assertAdminOperationType(adminCompletion, ADMIN_OPERATION_PREPARE_GROW_PHYSICAL); VDO *vdo = adminCompletion->completion.parent; assertOnAdminThread(vdo, __func__); resetAdminSubTask(completion); // This check can only be done from a base code thread. if (isReadOnly(vdo->readOnlyNotifier)) { finishCompletion(completion->parent, VDO_READ_ONLY); return; } // This check should only be done from a base code thread. if (inRecoveryMode(vdo)) { finishCompletion(completion->parent, VDO_RETRY_AFTER_REBUILD); return; } completeCompletion(completion->parent); } /**********************************************************************/ int prepareToGrowPhysical(VDO *vdo, BlockCount newPhysicalBlocks) { BlockCount currentPhysicalBlocks = vdo->config.physicalBlocks; if (newPhysicalBlocks < currentPhysicalBlocks) { return logErrorWithStringError(VDO_NOT_IMPLEMENTED, "Removing physical storage from a VDO is " "not supported"); } if (newPhysicalBlocks == currentPhysicalBlocks) { logWarning("Requested physical block count %" PRIu64 " not greater than %llu", newPhysicalBlocks, currentPhysicalBlocks); finishVDOLayoutGrowth(vdo->layout); abandonNewSlabs(vdo->depot); return VDO_PARAMETER_MISMATCH; } int result = performAdminOperation(vdo, ADMIN_OPERATION_PREPARE_GROW_PHYSICAL, getThreadIDForPhase, checkMayGrowPhysical, finishParentCallback); if (result != VDO_SUCCESS) { return result; } result = prepareToGrowVDOLayout(vdo->layout, currentPhysicalBlocks, newPhysicalBlocks, vdo->layer); if (result != VDO_SUCCESS) { return result; } BlockCount newDepotSize = getNextBlockAllocatorPartitionSize(vdo->layout); result = prepareToGrowSlabDepot(vdo->depot, newDepotSize); if (result != VDO_SUCCESS) { finishVDOLayoutGrowth(vdo->layout); return result; } return VDO_SUCCESS; }