/* * Copyright 2008-2009 LinkedIn, Inc * * Licensed 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 voldemort.server.http.gui; import java.io.File; import java.io.IOException; import java.util.List; import java.util.Map; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.log4j.Logger; import voldemort.VoldemortException; import voldemort.common.service.ServiceType; import voldemort.server.VoldemortConfig; import voldemort.server.VoldemortServer; import voldemort.server.http.VoldemortServletContextListener; import voldemort.server.storage.StorageService; import voldemort.store.StorageEngine; import voldemort.store.metadata.MetadataStore; import voldemort.store.readonly.FileFetcher; import voldemort.store.readonly.ReadOnlyStorageEngine; import voldemort.store.readonly.ReadOnlyUtils; import voldemort.utils.ByteArray; import voldemort.utils.ReflectUtils; import voldemort.utils.Utils; import com.google.common.collect.Lists; import com.google.common.collect.Maps; /** * A servlet that supports both manual or programmatic operations on read only * stores. The operations are * <ol> * <li>FETCH. Fetch the given files to the local node. Parameters: * operation="fetch", dir=[data-directory], store=[name-of-store], * pushVersion=[version-of-push]</li> * <li>SWAP. Swap the data directory atomically. Parameters: operation="swap", * store=[name-of-store]</li> * <li>ROLLBACK. Rollback the store to previous push version. Parameters: * operation="rollback", store=[name-of-store], * pushVersion=[version-of-push-to-rollback-to]</li> * </ol> * * DEPRECATED: Do not use anymore. This class has logic duplicated from the * {@link voldemort.server.protocol.admin.AdminServiceRequestHandler}. It has * not been maintained properly. * */ @Deprecated public class ReadOnlyStoreManagementServlet extends HttpServlet { private static final long serialVersionUID = 1; private static final Logger logger = Logger.getLogger(ReadOnlyStoreManagementServlet.class); private volatile List<ReadOnlyStorageEngine> stores; private VelocityEngine velocityEngine; private FileFetcher fileFetcher; private MetadataStore metadataStore; public ReadOnlyStoreManagementServlet() {} public ReadOnlyStoreManagementServlet(VoldemortServer server, VelocityEngine engine) { this.stores = getReadOnlyStores(server); this.velocityEngine = Utils.notNull(engine); setFetcherClass(server); } @Override public void init(ServletConfig config) throws ServletException { super.init(config); VoldemortServer server = (VoldemortServer) config.getServletContext() .getAttribute(VoldemortServletContextListener.SERVER_KEY); initMetadataStore(server); initStores(server); initVelocity(config); setFetcherClass(server); } public void initMetadataStore(VoldemortServer server) { this.metadataStore = Utils.notNull(server).getMetadataStore(); } public void initStores(VoldemortServer server) { this.stores = getReadOnlyStores(server); } public void initVelocity(ServletConfig config) { this.velocityEngine = (VelocityEngine) Utils.notNull(config.getServletContext() .getAttribute(VoldemortServletContextListener.VELOCITY_ENGINE_KEY)); } private void setFetcherClass(VoldemortServer server) { String className = server.getVoldemortConfig() .getAllProps() .getString("file.fetcher.class", null); if(className == null || className.trim().length() == 0) { this.fileFetcher = null; } else { try { logger.info("Loading fetcher " + className); Class<?> cls = Class.forName(className.trim()); this.fileFetcher = (FileFetcher) ReflectUtils.callConstructor(cls, new Class<?>[] { VoldemortConfig.class }, new Object[] { server.getVoldemortConfig() }); } catch(Exception e) { throw new VoldemortException("Error loading file fetcher class " + className, e); } } } private List<ReadOnlyStorageEngine> getReadOnlyStores(VoldemortServer server) { StorageService storage = (StorageService) Utils.notNull(server) .getService(ServiceType.STORAGE); List<ReadOnlyStorageEngine> l = Lists.newArrayList(); for(StorageEngine<ByteArray, byte[], byte[]> engine: storage.getStoreRepository() .getStorageEnginesByClass(ReadOnlyStorageEngine.class)) { l.add((ReadOnlyStorageEngine) engine); } return l; } @Override public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Map<String, Object> params = Maps.newHashMap(); params.put("stores", stores); velocityEngine.render("read-only-mgmt.vm", params, resp.getOutputStream()); } @Override public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { try { String operation = getRequired(req, "operation").toLowerCase(); if("swap".equals(operation)) { doSwap(req, resp); } else if("fetch".equals(operation)) { doFetch(req, resp); } else if("rollback".equals(operation)) { doRollback(req); } else if("failed-fetch".equals(operation)) { doFailedFetch(req); } else { throw new IllegalArgumentException("Unknown operation parameter: " + req.getParameter("operation")); } } catch(Exception e) { logger.error("Error while performing operation.", e); resp.sendError(500, "Error while performing operation: " + e.getMessage()); } } private void doFailedFetch(HttpServletRequest req) throws ServletException { String dir = getRequired(req, "dir"); String storeName = getRequired(req, "store"); try { if(!Utils.isReadableDir(dir)) throw new ServletException("Could not read folder " + dir + " correctly to delete it"); ReadOnlyStorageEngine store = this.getStore(storeName); if(store.getCurrentVersionId() == ReadOnlyUtils.getVersionId(new File(dir))) { logger.warn("Cannot delete " + dir + " for " + storeName + " since it is the current dir"); return; } Utils.rm(new File(dir)); } catch(Exception e) { throw new ServletException(e); } } private void doSwap(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { String dir = getRequired(req, "dir"); String storeName = getRequired(req, "store"); if(metadataStore != null && !metadataStore.getServerStateUnlocked() .equals(MetadataStore.VoldemortState.NORMAL_SERVER) && !metadataStore.getServerStateUnlocked() .equals(MetadataStore.VoldemortState.OFFLINE_SERVER)) { throw new ServletException("Voldemort server is neither in normal state nor in offline state"); } ReadOnlyStorageEngine store = this.getStore(storeName); if(store == null) throw new ServletException("'" + storeName + "' is not a registered read-only store."); if(!Utils.isReadableDir(dir)) throw new ServletException("Store directory '" + dir + "' is not a readable directory."); // Retrieve the current directory before swapping it String currentDirPath = store.getCurrentDirPath(); // Swap with the new directory store.swapFiles(dir); // Send back the previous directory resp.getWriter().write(currentDirPath); } private void doFetch(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { String fetchUrl = getRequired(req, "dir"); String storeName = getRequired(req, "store"); String pushVersionString = getOptional(req, "pushVersion"); ReadOnlyStorageEngine store = this.getStore(storeName); if(store == null) throw new ServletException("'" + storeName + "' is not a registered read-only store."); long pushVersion; if(pushVersionString == null) { // Find the max version long maxVersion; File[] storeDirList = ReadOnlyUtils.getVersionDirs(new File(store.getStoreDirPath())); if(storeDirList == null || storeDirList.length == 0) { throw new ServletException("Push version required since no version folders exist"); } else { maxVersion = ReadOnlyUtils.getVersionId(ReadOnlyUtils.findKthVersionedDir(storeDirList, storeDirList.length - 1, storeDirList.length - 1)[0]); } pushVersion = maxVersion + 1; } else { pushVersion = Long.parseLong(pushVersionString); if(pushVersion <= store.getCurrentVersionId()) throw new ServletException("Version of push specified (" + pushVersion + ") should be greater than current version " + store.getCurrentVersionId()); } // fetch the files if necessary File fetchDir = null; if(fileFetcher == null) { logger.warn("File fetcher class has not instantiated correctly. Assuming local file"); if(!Utils.isReadableDir(fetchUrl)) { throw new ServletException("Fetch url " + fetchUrl + " is not readable"); } fetchDir = new File(store.getStoreDirPath(), "version-" + Long.toString(pushVersion)); if(fetchDir.exists()) throw new ServletException("Version directory " + fetchDir.getAbsolutePath() + " already exists"); Utils.move(new File(fetchUrl), fetchDir); } else { logger.info("Executing fetch of " + fetchUrl); try { fetchDir = fileFetcher.fetch(fetchUrl, store.getStoreDirPath() + File.separator + "version-" + Long.toString(pushVersion)); if(fetchDir == null) { throw new ServletException("File fetcher failed for " + fetchUrl + " and store name = " + storeName + " due to incorrect input path/checksum error"); } else { logger.info("Fetch complete."); } } catch(Exception e) { throw new ServletException("Exception in Fetcher = " + e.getMessage()); } } resp.getWriter().write(fetchDir.getAbsolutePath()); } private void doRollback(HttpServletRequest req) throws ServletException { String storeName = getRequired(req, "store"); long pushVersion = Long.parseLong(getRequired(req, "pushVersion")); ReadOnlyStorageEngine store = getStore(storeName); if(store == null) throw new ServletException("'" + storeName + "' is not a registered read-only store."); try { File rollbackVersionDir = new File(store.getStoreDirPath(), "version-" + pushVersion); store.rollback(rollbackVersionDir); } catch(Exception e) { throw new ServletException("Exception in rollback = " + e.getMessage()); } } private String getOptional(HttpServletRequest req, String name) { return req.getParameter(name); } private String getRequired(HttpServletRequest req, String name) throws ServletException { String val = req.getParameter(name); if(val == null) throw new ServletException("Missing required parameter '" + name + "'."); return val; } private ReadOnlyStorageEngine getStore(String storeName) throws ServletException { for(ReadOnlyStorageEngine store: this.stores) if(store.getName().equals(storeName)) return store; throw new ServletException("'" + storeName + "' is not a registered read-only store."); } }