// /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (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.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is part of dcm4che, an implementation of DICOM(TM) in * Java(TM), hosted at https://github.com/gunterze/dcm4che. * * The Initial Developer of the Original Code is * Agfa Healthcare. * Portions created by the Initial Developer are Copyright (C) 2011 * the Initial Developer. All Rights Reserved. * * Contributor(s): * See @authors listed below * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ package org.dcm4chee.storage.service.impl; import java.io.IOException; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentMap; import javax.enterprise.inject.Instance; import org.dcm4che3.net.Device; import org.dcm4chee.storage.conf.StorageDeviceExtension; import org.dcm4chee.storage.conf.StorageSystem; import org.dcm4chee.storage.conf.StorageSystemGroup; import org.dcm4chee.storage.conf.StorageSystemStatus; import org.dcm4chee.storage.spi.StorageSystemProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Encapsulation of logic for selecting storage systems used in store-pipeline. * * Selecting the storage systems has the possible side-effect of modifying the configuration. * Due to the design of the DICOM configuration these changes must be applied on a separate (= writeable) device instance. * The selector does this by first performing the storage system selection and then in a second step * apply the changes to the configuration. * * @author Alexander Hoermandinger <alexander.hoermandinger@agfa.com> * */ public class StorageSystemSelector { private static final Logger LOG = LoggerFactory.getLogger(StorageSystemSelector.class); private final StorageSystemGroup group; private final Instance<StorageSystemProvider> storageSystemProviders; private final Map<String,StorageSystemStatus> storageSystem2newStatus = new HashMap<>(); private final ConcurrentMap<String,Integer> storageGroup2ActiveStorageSystemIndex; private Integer activeStorageSystemIndex; private String[] activeStorageSystemIDs; private String nextStorageSystemID; private boolean configChanged; public StorageSystemSelector(StorageSystemGroup group, Instance<StorageSystemProvider> storageSystemProviders, ConcurrentMap<String,Integer> storageGroup2ActiveStorageSystemIndex) { this.group = group; this.storageSystemProviders = storageSystemProviders; this.storageGroup2ActiveStorageSystemIndex = storageGroup2ActiveStorageSystemIndex; activeStorageSystemIndex = storageGroup2ActiveStorageSystemIndex.get(group.getGroupID()); if(activeStorageSystemIndex == null) { activeStorageSystemIndex = 0; } activeStorageSystemIDs = group.getActiveStorageSystemIDs(); nextStorageSystemID = group.getNextStorageSystemID(); } public StorageSystem selectStorageSystem(long reserveSpace) { StorageSystem selected = nextActiveStorageSystem(); while (selected != null && !checkMinFreeSpace(selected, reserveSpace)) { deactivate(selected); selected = nextActiveStorageSystem(); configChanged = true; } StorageSystem system, start; start = system = getNextStorageSystem(); int parallelism = group.getParallelism(); while (system != null && activeStorageSystemIDs.length < parallelism) { if (!isActive(system) && checkMinFreeSpace(system, reserveSpace)) { activate(system, true); configChanged = true; if (selected == null) { selected = nextActiveStorageSystem(); } } if ((system = system.getNextStorageSystem()) == start) system = null; } storageGroup2ActiveStorageSystemIndex.put(group.getGroupID(), activeStorageSystemIndex); return selected; } public boolean isConfigurationChanged() { return configChanged; } public void mergeDeviceChanges(Device device) { if(configChanged) { StorageDeviceExtension storageExtension = device.getDeviceExtension(StorageDeviceExtension.class); StorageSystemGroup modifyGroup = storageExtension.getStorageSystemGroup(group.getGroupID()); modifyGroup.setActiveStorageSystemIDs(activeStorageSystemIDs); modifyGroup.setNextStorageSystemID(nextStorageSystemID); for(Entry<String,StorageSystemStatus> entry : storageSystem2newStatus.entrySet()) { StorageSystem storageSystem = modifyGroup.getStorageSystem(entry.getKey()); storageSystem.setStorageSystemStatus(entry.getValue()); } } } private StorageSystem nextActiveStorageSystem() { if (activeStorageSystemIDs.length == 0) return null; activeStorageSystemIndex %= activeStorageSystemIDs.length; String storageSystemID = activeStorageSystemIDs[activeStorageSystemIndex]; activeStorageSystemIndex++; return group.getStorageSystem(storageSystemID); } public void deactivate(StorageSystem storageSystem) { String systemID = storageSystem.getStorageSystemID(); for (int i = 0; i < activeStorageSystemIDs.length; i++) { if (activeStorageSystemIDs[i].equals(systemID)) { String[] dest = new String[activeStorageSystemIDs.length - 1]; System.arraycopy(activeStorageSystemIDs, 0, dest, 0, i); System.arraycopy(activeStorageSystemIDs, i + 1, dest, i, dest.length - i); activeStorageSystemIDs = dest; if (i < activeStorageSystemIndex) { activeStorageSystemIndex--; } return; } } } public void activate(StorageSystem storageSystem, boolean setNextStorageSystemID) { if (!isActive(storageSystem)) { int length = activeStorageSystemIDs.length; activeStorageSystemIDs = Arrays.copyOf(activeStorageSystemIDs, length+1); activeStorageSystemIDs[length] = storageSystem.getStorageSystemID(); if (setNextStorageSystemID) { nextStorageSystemID = storageSystem.getNextStorageSystemID(); } } } private boolean isActive(StorageSystem storageSystem) { return Arrays.asList(activeStorageSystemIDs).contains( storageSystem.getStorageSystemID()); } private StorageSystem getNextStorageSystem() { if (nextStorageSystemID == null) return null; return group.getStorageSystem(nextStorageSystemID); } public boolean checkMinFreeSpace(StorageSystem system, long reserveSpace) { if (!system.installed()) { return false; } if (system.isReadOnly()) { return false; } if (system.getStorageSystemStatus() != StorageSystemStatus.OK) { return false; } StorageSystemProvider provider = system.getStorageSystemProvider(storageSystemProviders); try { provider.checkWriteable(); if (system.getMinFreeSpace() != null) { if(system.getMinFreeSpaceInBytes() == -1L) system.setMinFreeSpaceInBytes(provider.getTotalSpace()*Integer.valueOf (system.getMinFreeSpace().replace("%", ""))/100); if(provider.getUsableSpace() < system.getMinFreeSpaceInBytes() + reserveSpace) { LOG.info("Update Status of {} to FULL", system); storageSystem2newStatus.put(system.getStorageSystemID(), StorageSystemStatus.FULL); configChanged = true; return false; } } } catch (IOException e) { LOG.warn("Update Status of {} to NOT_ACCESSABLE caused by", system, e); storageSystem2newStatus.put(system.getStorageSystemID(), StorageSystemStatus.NOT_ACCESSABLE); configChanged = true; return false; } return true; } }