diff options
author | Shireesh Anjal <shireesh@gluster.com> | 2011-11-25 20:13:35 +0530 |
---|---|---|
committer | Shireesh Anjal <shireesh@gluster.com> | 2011-11-25 20:13:35 +0530 |
commit | 1142b0e41de39010de7845cf70d71dbb001fc1dc (patch) | |
tree | 3513487f65c1a7df47996bd2852393aceaac1b8a /src/org.gluster.storage.management.gateway/src/org | |
parent | 92c52d8edf285945d31e446503fc742fde9dcc49 (diff) |
Renamed projects / packages com.gluster.* to org.gluster.*
Diffstat (limited to 'src/org.gluster.storage.management.gateway/src/org')
42 files changed, 7973 insertions, 0 deletions
diff --git a/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/data/ClusterInfo.java b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/data/ClusterInfo.java new file mode 100644 index 00000000..ba197b92 --- /dev/null +++ b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/data/ClusterInfo.java @@ -0,0 +1,79 @@ +/******************************************************************************* + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Gluster Management Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + *******************************************************************************/ +package org.gluster.storage.management.gateway.data; + +import java.util.ArrayList; +import java.util.List; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.OneToMany; + +import org.hibernate.cfg.AnnotationConfiguration; +import org.hibernate.tool.hbm2ddl.SchemaExport; + +@Entity(name="cluster_info") +public class ClusterInfo { + @Id + @GeneratedValue + private Integer id; + + private String name; + + @OneToMany(mappedBy="cluster") + private List<ServerInfo> servers = new ArrayList<ServerInfo>(); + + public void setId(Integer id) { + this.id = id; + } + + public Integer getId() { + return id; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setServers(List<ServerInfo> servers) { + this.servers = servers; + } + + public List<ServerInfo> getServers() { + return servers; + } + + public void addServer(ServerInfo server) { + servers.add(server); + } + + public static void main(String args[]) { + AnnotationConfiguration config = new AnnotationConfiguration(); + config.addAnnotatedClass(ClusterInfo.class); + config.addAnnotatedClass(ServerInfo.class); + config.configure(); + new SchemaExport(config).create(true, true); + } + +} diff --git a/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/data/GlusterDataSource.java b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/data/GlusterDataSource.java new file mode 100644 index 00000000..f0b0311e --- /dev/null +++ b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/data/GlusterDataSource.java @@ -0,0 +1,48 @@ +/** + * GlusterDataSource.java + * + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Gluster Management Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + */ +package org.gluster.storage.management.gateway.data; + +import javax.servlet.ServletContext; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.datasource.DriverManagerDataSource; +import org.springframework.stereotype.Component; + +@Component +public class GlusterDataSource extends DriverManagerDataSource { + @Autowired + ServletContext servletContext; + + public GlusterDataSource() { + setDriverClassName(org.apache.derby.jdbc.EmbeddedDriver.class.getName()); + + setUsername("gluster"); + // TODO: change to a stronger (encrypted) password + setPassword("gluster"); + } + + public DriverManagerDataSource getDataSource() { + // Database directory = work/data relative to context root + setUrl("jdbc:derby:" + servletContext.getRealPath("data") + ";create=true"); + + return this; + } +}
\ No newline at end of file diff --git a/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/data/PersistenceDao.java b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/data/PersistenceDao.java new file mode 100644 index 00000000..49348084 --- /dev/null +++ b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/data/PersistenceDao.java @@ -0,0 +1,113 @@ +/******************************************************************************* + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Gluster Management Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + *******************************************************************************/ +package org.gluster.storage.management.gateway.data; + +import java.util.List; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.EntityTransaction; +import javax.persistence.PersistenceUnit; +import javax.persistence.Query; + +/** + * + */ +public class PersistenceDao<T> { + private Class<T> type; + + private EntityManager entityManager; + + @PersistenceUnit + private EntityManagerFactory entityManagerFactory; + + public PersistenceDao(Class<T> type) { + this.type = type; + } + + public EntityTransaction startTransaction() { + EntityTransaction txn = getEntityManager().getTransaction(); + txn.begin(); + return txn; + } + + private synchronized EntityManager getEntityManager() { + if (entityManager == null) { + entityManager = entityManagerFactory.createEntityManager(); + } + return entityManager; + } + + public Object getSingleResult(String query) { + return getEntityManager().createQuery(query).getSingleResult(); + } + + public Object getSingleResult(String queryString, String... params) { + return createQuery(queryString, params).getSingleResult(); + } + + private Query createQuery(String queryString, String... params) { + Query query = getEntityManager().createQuery(queryString); + for (int i = 0; i < params.length; i++) { + query.setParameter(i + 1, params[i]); + } + return query; + } + + public Object getSingleResultFromSQL(String sqlQuery) { + return getEntityManager().createNativeQuery(sqlQuery).getSingleResult(); + } + + @SuppressWarnings("rawtypes") + public List findBySQL(String sqlQuery) { + return getEntityManager().createNativeQuery(sqlQuery).getResultList(); + } + + public T findById(int id) { + return getEntityManager().find(type, id); + } + + @SuppressWarnings("unchecked") + public List<T> findAll() { + return getEntityManager().createQuery("select t from " + type.getName() + " t").getResultList(); + } + + @SuppressWarnings("unchecked") + public List<T> findBy(String whereClause) { + return getEntityManager().createQuery("select t from " + type.getName() + " t where " + whereClause) + .getResultList(); + } + + @SuppressWarnings("unchecked") + public List<T> findBy(String whereClause, String... params) { + return createQuery("select t from " + type.getName() + " t where " + whereClause, params).getResultList(); + } + + public void save(Object obj) { + getEntityManager().persist(obj); + } + + public T update(T obj) { + return getEntityManager().merge(obj); + } + + public void delete(Object obj) { + getEntityManager().remove(obj); + } +} diff --git a/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/data/ServerInfo.java b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/data/ServerInfo.java new file mode 100644 index 00000000..84df2f1a --- /dev/null +++ b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/data/ServerInfo.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Gluster Management Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + *******************************************************************************/ +package org.gluster.storage.management.gateway.data; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; + +/** + * + */ +@Entity(name="server_info") +public class ServerInfo { + @Id + @GeneratedValue + private Integer id; + + private String name; + + @ManyToOne + @JoinColumn(name="cluster_id") + private ClusterInfo cluster; + + public ServerInfo() { + } + + public ServerInfo(String name) { + setName(name); + } + + public void setId(Integer id) { + this.id = id; + } + + public Integer getId() { + return id; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setCluster(ClusterInfo cluster) { + this.cluster = cluster; + } + + public ClusterInfo getCluster() { + return cluster; + } +} diff --git a/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/filters/AuditFilter.java b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/filters/AuditFilter.java new file mode 100644 index 00000000..76b329ee --- /dev/null +++ b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/filters/AuditFilter.java @@ -0,0 +1,48 @@ +/** + * + */ +package org.gluster.storage.management.gateway.filters; + +import java.security.Principal; + +import org.apache.log4j.Logger; + +import com.sun.jersey.spi.container.ContainerRequest; +import com.sun.jersey.spi.container.ContainerRequestFilter; +import com.sun.jersey.spi.container.ContainerResponse; +import com.sun.jersey.spi.container.ContainerResponseFilter; +import com.sun.jersey.spi.container.ResourceFilter; + +/** + * Resource filter for maintaining audit trail of resource access + */ +public class AuditFilter implements ResourceFilter, ContainerRequestFilter, ContainerResponseFilter { + private static final Logger logger = Logger.getLogger(AuditFilter.class); + + @Override + public ContainerRequestFilter getRequestFilter() { + return this; + } + + @Override + public ContainerResponseFilter getResponseFilter() { + return this; + } + + @Override + public ContainerRequest filter(ContainerRequest req) { + Principal principal = req.getUserPrincipal(); + if(principal != null) { + logger.info("REQUEST from [" + principal.getName() + "] : [" + req.getMethod() + "][" + req.getPath() + "]"); + } else { + logger.info("REQUEST [" + req.getMethod() + "][" + req.getPath() + "]"); + } + return req; + } + + @Override + public ContainerResponse filter(ContainerRequest req, ContainerResponse response) { + logger.info("RESPONSE: [" + req.getMethod() + "][" + req.getPath() + "]"); + return response; + } +} diff --git a/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/filters/AuthenticationFailureFilter.java b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/filters/AuthenticationFailureFilter.java new file mode 100644 index 00000000..4bb7f37c --- /dev/null +++ b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/filters/AuthenticationFailureFilter.java @@ -0,0 +1,105 @@ +/******************************************************************************* + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Gluster Management Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + *******************************************************************************/ +package org.gluster.storage.management.gateway.filters; + +import java.io.CharArrayWriter; +import java.io.IOException; +import java.io.PrintWriter; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; +import javax.ws.rs.core.Response; + +/** + * @author root + * + */ +public class AuthenticationFailureFilter implements Filter { + + /* + * (non-Javadoc) + * + * @see javax.servlet.Filter#destroy() + */ + @Override + public void destroy() { + // TODO Auto-generated method stub + + } + + public class CharResponseWrapper extends HttpServletResponseWrapper { + private CharArrayWriter output; + + public String toString() { + return output.toString(); + } + + public CharResponseWrapper(HttpServletResponse response) { + super(response); + output = new CharArrayWriter(); + } + + public PrintWriter getWriter() { + return new PrintWriter(output); + } + } + + /* + * (non-Javadoc) + * + * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, + * javax.servlet.FilterChain) + */ + @Override + public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, + ServletException { + HttpServletRequest request = (HttpServletRequest) req; + if (request.getRequestURI().contains("download")) { + chain.doFilter(req, res); + return; + } + + CharResponseWrapper wrapper = new CharResponseWrapper((HttpServletResponse) res); + chain.doFilter(req, wrapper); + + if(wrapper.getStatus() == Response.Status.UNAUTHORIZED.ordinal()) { + PrintWriter out = res.getWriter(); + out.println("<status><code>1</code><message>Authentication Failed!</message></status>"); + } + } + + /* + * (non-Javadoc) + * + * @see javax.servlet.Filter#init(javax.servlet.FilterConfig) + */ + @Override + public void init(FilterConfig arg0) throws ServletException { + // TODO Auto-generated method stub + + } + +} diff --git a/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/filters/GlusterResourceFilterFactory.java b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/filters/GlusterResourceFilterFactory.java new file mode 100644 index 00000000..8772cdcd --- /dev/null +++ b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/filters/GlusterResourceFilterFactory.java @@ -0,0 +1,31 @@ +/** + * + */ +package org.gluster.storage.management.gateway.filters; + +import java.util.ArrayList; +import java.util.List; + +import com.sun.jersey.api.model.AbstractMethod; +import com.sun.jersey.spi.container.ResourceFilter; +import com.sun.jersey.spi.container.ResourceFilterFactory; + +/** + * Gluster resource filter factory. As of now, this creates only one filter - the audit filter {@code AuditFilter} + */ +public class GlusterResourceFilterFactory implements ResourceFilterFactory { + + public GlusterResourceFilterFactory() { + } + + /* (non-Javadoc) + * @see com.sun.jersey.spi.container.ResourceFilterFactory#create(com.sun.jersey.api.model.AbstractMethod) + */ + @Override + public List<ResourceFilter> create(AbstractMethod arg0) { + List<ResourceFilter> filters = new ArrayList<ResourceFilter>(); + filters.add(new AuditFilter()); + + return filters; + } +} diff --git a/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/listeners/ShutdownListener.java b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/listeners/ShutdownListener.java new file mode 100644 index 00000000..8c6bb42b --- /dev/null +++ b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/listeners/ShutdownListener.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Gluster Management Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + *******************************************************************************/ +package org.gluster.storage.management.gateway.listeners; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; + +import org.gluster.storage.management.gateway.utils.DBUtil; + + +/** + * Shuts down the Derby database when gateway is being stopped. + */ +public class ShutdownListener implements ServletContextListener { + + /* (non-Javadoc) + * @see javax.servlet.ServletContextListener#contextDestroyed(javax.servlet.ServletContextEvent) + */ + @Override + public void contextDestroyed(ServletContextEvent arg0) { + // Embedded derby must be shut down when the gateway stops. + DBUtil.shutdownDerby(); + System.gc(); + } + + /* (non-Javadoc) + * @see javax.servlet.ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent) + */ + @Override + public void contextInitialized(ServletContextEvent arg0) { + } +} diff --git a/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/resources/v1_0/AbstractResource.java b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/resources/v1_0/AbstractResource.java new file mode 100644 index 00000000..8e26c838 --- /dev/null +++ b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/resources/v1_0/AbstractResource.java @@ -0,0 +1,177 @@ +/******************************************************************************* + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Gluster Management Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + *******************************************************************************/ +package org.gluster.storage.management.gateway.resources.v1_0; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.URI; + +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; +import javax.ws.rs.core.StreamingOutput; +import javax.ws.rs.core.UriInfo; + +/** + * + */ +public class AbstractResource { + @Context + protected UriInfo uriInfo; + + /** + * Creates a response with HTTP status code of 201 (created) and sets the "location" header to the URI created using + * the given path relative to current path. + * + * @param relativePath + * relative path of the created resource - will be set in the "location" header of response. + * @return the {@link Response} object + */ + protected Response createdResponse(String relativePath) { + return Response.created(createRelatriveURI(relativePath)).build(); + } + + /** + * Creates a response with HTTP status code of 204 (no content) + * @return the {@link Response} object + */ + protected Response noContentResponse() { + return Response.noContent().build(); + } + + /** + * Creates a response with HTTP status code of 202 (accepted), also setting the location header to given location. + * This is typically done while triggering long running tasks + * + * @param locationURI + * URI to be appended to the base URI + * @return the {@link Response} object + */ + protected Response acceptedResponse(String locationURI) { + return Response.status(Status.ACCEPTED).location(createAbsoluteURI(locationURI)).build(); + } + + /** + * Creates a response with HTTP status code of 404 (not found), also setting the given message in the response body + * + * @param message + * Message to be set in the response body + * @return the {@link Response} object + */ + protected Response notFoundResponse(String message) { + return Response.status(Status.NOT_FOUND).type(MediaType.TEXT_HTML).entity(message).build(); + } + + /** + * Creates a new URI that is relative to the <b>base URI</b> of the application + * @param uriString URI String to be appended to the base URI + * @return newly created URI + */ + private URI createAbsoluteURI(String uriString) { + return uriInfo.getBaseUriBuilder().path(uriString).build(); + } + + /** + * Creates a response with HTTP status code of 204 (no content), also setting the location header to given location + * @param location path of the location to be set relative to current path + * @return the {@link Response} object + */ + protected Response noContentResponse(String location) { + return Response.noContent().location(createRelatriveURI(location)).build(); + } + + /** + * Creates a URI relative to current URI + * @param location path relative to current URI + * @return newly created URI + */ + protected URI createRelatriveURI(String location) { + return uriInfo.getAbsolutePathBuilder().path(location).build(); + } + + /** + * Creates a response with HTTP status code of 500 (internal server error) and sets the error message in the + * response body + * + * @param errMessage + * Error message to be set in the response body + * @return the {@link Response} object + */ + protected Response errorResponse(String errMessage) { + return Response.serverError().type(MediaType.TEXT_HTML).entity(errMessage).build(); + } + + /** + * Creates a response with HTTP status code of 400 (bad request) and sets the error message in the + * response body + * + * @param errMessage + * Error message to be set in the response body + * @return the {@link Response} object + */ + protected Response badRequestResponse(String errMessage) { + return Response.status(Status.BAD_REQUEST).type(MediaType.TEXT_HTML).entity(errMessage).build(); + } + + /** + * Creates a response with HTTP status code of 401 (unauthorized) + * + * @return the {@link Response} object + */ + protected Response unauthorizedResponse() { + return Response.status(Status.UNAUTHORIZED).build(); + } + + /** + * Creates an OK response and sets the entity in the response body. + * + * @param entity + * Entity to be set in the response body + * @param mediaType + * Media type to be set on the response + * @return the {@link Response} object + */ + protected Response okResponse(Object entity, String mediaType) { + return Response.ok(entity).type(mediaType).build(); + } + + /** + * Creates a streaming output response and sets the given streaming output in the response. Typically used for + * "download" requests + * + * @param entity + * Entity to be set in the response body + * @param mediaType + * Media type to be set on the response + * @return the {@link Response} object + */ + protected Response streamingOutputResponse(StreamingOutput output) { + return Response.ok(output).type(MediaType.APPLICATION_OCTET_STREAM).build(); + } + + protected StreamingOutput createStreamingOutput(final byte[] data) { + return new StreamingOutput() { + @Override + public void write(OutputStream output) throws IOException { + output.write(data); + } + }; + } +} diff --git a/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/resources/v1_0/ClustersResource.java b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/resources/v1_0/ClustersResource.java new file mode 100644 index 00000000..3c9b3bd2 --- /dev/null +++ b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/resources/v1_0/ClustersResource.java @@ -0,0 +1,126 @@ +/******************************************************************************* + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Gluster Management Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + *******************************************************************************/ +package org.gluster.storage.management.gateway.resources.v1_0; + +import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_CLUSTER_NAME; +import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_SERVER_NAME; +import static org.gluster.storage.management.core.constants.RESTConstants.PATH_PARAM_CLUSTER_NAME; +import static org.gluster.storage.management.core.constants.RESTConstants.RESOURCE_PATH_CLUSTERS; + +import java.util.ArrayList; +import java.util.List; + +import javax.ws.rs.DELETE; +import javax.ws.rs.FormParam; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.apache.log4j.Logger; +import org.gluster.storage.management.core.exceptions.GlusterValidationException; +import org.gluster.storage.management.core.response.ClusterNameListResponse; +import org.gluster.storage.management.gateway.data.ClusterInfo; +import org.gluster.storage.management.gateway.services.ClusterService; +import org.springframework.stereotype.Component; + +import com.sun.jersey.api.core.InjectParam; +import com.sun.jersey.spi.resource.Singleton; + +/** + * + */ +@Component +@Singleton +@Path(RESOURCE_PATH_CLUSTERS) +public class ClustersResource extends AbstractResource { + @InjectParam + private ClusterService clusterService; + private static final Logger logger = Logger.getLogger(ClustersResource.class); + + @GET + @Produces(MediaType.APPLICATION_XML) + public ClusterNameListResponse getClusters() { + List<ClusterInfo> clusters = clusterService.getAllClusters(); + List<String> clusterList = new ArrayList<String>(); + for (ClusterInfo cluster : clusters) { + clusterList.add(cluster.getName()); + } + return new ClusterNameListResponse(clusterList); + } + + @POST + public Response createCluster(@FormParam(FORM_PARAM_CLUSTER_NAME) String clusterName) { + if(clusterName == null || clusterName.isEmpty()) { + throw new GlusterValidationException("Parameter [" + FORM_PARAM_CLUSTER_NAME + "] is missing in request!"); + } + + if(clusterService.getCluster(clusterName) != null) { + throw new GlusterValidationException("Cluster [" + clusterName + "] already exists!"); + } + + clusterService.createCluster(clusterName); + return createdResponse(clusterName); + } + + @PUT + public Response registerCluster(@FormParam(FORM_PARAM_CLUSTER_NAME) String clusterName, + @FormParam(FORM_PARAM_SERVER_NAME) String knownServer) { + if(clusterName == null || clusterName.isEmpty()) { + throw new GlusterValidationException("Parameter [" + FORM_PARAM_CLUSTER_NAME + "] is missing in request!"); + } + + if(knownServer == null || knownServer.isEmpty()) { + throw new GlusterValidationException("Parameter [" + FORM_PARAM_SERVER_NAME + "] is missing in request!"); + } + + if(clusterService.getCluster(clusterName) != null) { + throw new GlusterValidationException("Cluster [" + clusterName + "] already exists!"); + } + + ClusterInfo mappedCluster = clusterService.getClusterForServer(knownServer); + if(mappedCluster != null) { + throw new GlusterValidationException("Server [" + knownServer + "] is already present in cluster [" + + mappedCluster.getName() + "]!"); + } + + clusterService.registerCluster(clusterName, knownServer); + return noContentResponse(clusterName); + } + + @Path("{" + PATH_PARAM_CLUSTER_NAME + "}") + @DELETE + public Response unregisterCluster(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName) { + if(clusterName == null || clusterName.isEmpty()) { + throw new GlusterValidationException("Parameter [" + FORM_PARAM_CLUSTER_NAME + "] is missing in request!"); + } + + ClusterInfo cluster = clusterService.getCluster(clusterName); + if(cluster == null) { + throw new GlusterValidationException("Cluster [" + clusterName + "] does not exist!"); + } + + clusterService.unregisterCluster(cluster); + return noContentResponse(); + } +} diff --git a/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/resources/v1_0/DiscoveredServersResource.java b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/resources/v1_0/DiscoveredServersResource.java new file mode 100644 index 00000000..12358f56 --- /dev/null +++ b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/resources/v1_0/DiscoveredServersResource.java @@ -0,0 +1,101 @@ +/******************************************************************************* + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Gluster Management Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + *******************************************************************************/ +package org.gluster.storage.management.gateway.resources.v1_0; + +import static org.gluster.storage.management.core.constants.RESTConstants.PATH_PARAM_SERVER_NAME; +import static org.gluster.storage.management.core.constants.RESTConstants.QUERY_PARAM_DETAILS; +import static org.gluster.storage.management.core.constants.RESTConstants.RESOURCE_PATH_DISCOVERED_SERVERS; + +import java.util.List; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.gluster.storage.management.core.model.Server; +import org.gluster.storage.management.core.response.ServerListResponse; +import org.gluster.storage.management.core.response.ServerNameListResponse; +import org.gluster.storage.management.gateway.services.DiscoveredServerService; +import org.springframework.stereotype.Component; + +import com.sun.jersey.api.core.InjectParam; +import com.sun.jersey.spi.resource.Singleton; + +@Component +@Singleton +@Path(RESOURCE_PATH_DISCOVERED_SERVERS) +public class DiscoveredServersResource extends AbstractResource { + @InjectParam + private DiscoveredServerService discoveredServerService; + + @GET + @Produces(MediaType.APPLICATION_XML) + public Response getDiscoveredServersXML(@QueryParam(QUERY_PARAM_DETAILS) Boolean details) { + return getDiscoveredServersResponse(details, MediaType.APPLICATION_XML); + } + + @GET + @Produces(MediaType.APPLICATION_JSON) + public Response getDiscoveredServersJSON(@QueryParam(QUERY_PARAM_DETAILS) Boolean details) { + return getDiscoveredServersResponse(details, MediaType.APPLICATION_JSON); + } + + private Response getDiscoveredServersResponse(Boolean details, String mediaType) { + if(details != null && details == true) { + try { + List<Server> discoveredServers = discoveredServerService.getDiscoveredServerDetails(); + return okResponse(new ServerListResponse(discoveredServers), mediaType); + } catch(Exception e) { + return errorResponse(e.getMessage()); + } + } else { + return okResponse(new ServerNameListResponse(discoveredServerService.getDiscoveredServerNames()), mediaType); + } + } + + @Path("{" + PATH_PARAM_SERVER_NAME + "}") + @GET + @Produces(MediaType.APPLICATION_XML) + public Response getDiscoveredServerXML(@PathParam(PATH_PARAM_SERVER_NAME) String serverName) { + return getDiscoveredServerResponse(serverName, MediaType.APPLICATION_XML); + } + + @Path("{" + PATH_PARAM_SERVER_NAME + "}") + @GET + @Produces(MediaType.APPLICATION_JSON) + public Response getDiscoveredServerJSON(@PathParam(PATH_PARAM_SERVER_NAME) String serverName) { + return getDiscoveredServerResponse(serverName, MediaType.APPLICATION_JSON); + } + + private Response getDiscoveredServerResponse(String serverName, String mediaType) { + if(serverName == null || serverName.isEmpty()) { + return badRequestResponse("Server name must not be empty!"); + } + try { + return okResponse(discoveredServerService.getDiscoveredServer(serverName), mediaType); + } catch (Exception e) { + // TODO: Log the exception + return errorResponse(e.getMessage()); + } + } +} diff --git a/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/resources/v1_0/GenericExceptionMapper.java b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/resources/v1_0/GenericExceptionMapper.java new file mode 100644 index 00000000..dfe4c85c --- /dev/null +++ b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/resources/v1_0/GenericExceptionMapper.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Gluster Management Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + *******************************************************************************/ +package org.gluster.storage.management.gateway.resources.v1_0; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.ResponseBuilder; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; + +import org.apache.log4j.Logger; +import org.gluster.storage.management.core.exceptions.GlusterValidationException; + + +@Provider +public class GenericExceptionMapper implements ExceptionMapper<Exception> { + private static final Logger logger = Logger.getLogger(GenericExceptionMapper.class); + + /* (non-Javadoc) + * @see javax.ws.rs.ext.ExceptionMapper#toResponse(java.lang.Throwable) + */ + @Override + public Response toResponse(Exception exception) { + ResponseBuilder builder; + if (exception instanceof GlusterValidationException) { + builder = Response.status(Response.Status.BAD_REQUEST); + } else { + builder = Response.status(Response.Status.INTERNAL_SERVER_ERROR); + } + + String errMsg = exception.getMessage(); + if(errMsg == null) { + errMsg = "Following exception occurred : " + exception.getClass().getName(); + StackTraceElement[] stackTrace = exception.getStackTrace(); + if(stackTrace.length > 0) { + errMsg += " at [" + stackTrace[0].getClassName() + "][" + stackTrace[0].getLineNumber() + "]"; + } + } + + logger.error(errMsg, exception); + + return builder.entity(errMsg).type(MediaType.TEXT_PLAIN).build(); + } +} diff --git a/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/resources/v1_0/GlusterServersResource.java b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/resources/v1_0/GlusterServersResource.java new file mode 100644 index 00000000..1b9a3b71 --- /dev/null +++ b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/resources/v1_0/GlusterServersResource.java @@ -0,0 +1,343 @@ +/******************************************************************************* + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Gluster Management Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + *******************************************************************************/ +package org.gluster.storage.management.gateway.resources.v1_0; + +import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_FSTYPE; +import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_MOUNTPOINT; +import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_SERVER_NAME; +import static org.gluster.storage.management.core.constants.RESTConstants.PATH_PARAM_CLUSTER_NAME; +import static org.gluster.storage.management.core.constants.RESTConstants.PATH_PARAM_DISK_NAME; +import static org.gluster.storage.management.core.constants.RESTConstants.PATH_PARAM_SERVER_NAME; +import static org.gluster.storage.management.core.constants.RESTConstants.QUERY_PARAM_DETAILS; +import static org.gluster.storage.management.core.constants.RESTConstants.QUERY_PARAM_INTERFACE; +import static org.gluster.storage.management.core.constants.RESTConstants.QUERY_PARAM_MAX_COUNT; +import static org.gluster.storage.management.core.constants.RESTConstants.QUERY_PARAM_NEXT_TO; +import static org.gluster.storage.management.core.constants.RESTConstants.QUERY_PARAM_PERIOD; +import static org.gluster.storage.management.core.constants.RESTConstants.QUERY_PARAM_TYPE; +import static org.gluster.storage.management.core.constants.RESTConstants.RESOURCE_DISKS; +import static org.gluster.storage.management.core.constants.RESTConstants.RESOURCE_FSTYPES; +import static org.gluster.storage.management.core.constants.RESTConstants.RESOURCE_PATH_CLUSTERS; +import static org.gluster.storage.management.core.constants.RESTConstants.RESOURCE_SERVERS; +import static org.gluster.storage.management.core.constants.RESTConstants.RESOURCE_STATISTICS; +import static org.gluster.storage.management.core.constants.RESTConstants.RESOURCE_TASKS; +import static org.gluster.storage.management.core.constants.RESTConstants.STATISTICS_TYPE_CPU; +import static org.gluster.storage.management.core.constants.RESTConstants.STATISTICS_TYPE_MEMORY; +import static org.gluster.storage.management.core.constants.RESTConstants.STATISTICS_TYPE_NETWORK; + +import java.util.ArrayList; +import java.util.List; + +import javax.ws.rs.DELETE; +import javax.ws.rs.FormParam; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.gluster.storage.management.core.constants.GlusterConstants; +import org.gluster.storage.management.core.constants.RESTConstants; +import org.gluster.storage.management.core.exceptions.GlusterValidationException; +import org.gluster.storage.management.core.model.GlusterServer; +import org.gluster.storage.management.core.model.ServerStats; +import org.gluster.storage.management.core.model.TaskStatus; +import org.gluster.storage.management.core.response.FsTypeListResponse; +import org.gluster.storage.management.core.response.GlusterServerListResponse; +import org.gluster.storage.management.core.response.ServerNameListResponse; +import org.gluster.storage.management.gateway.data.ClusterInfo; +import org.gluster.storage.management.gateway.services.ClusterService; +import org.gluster.storage.management.gateway.services.GlusterServerService; +import org.gluster.storage.management.gateway.tasks.InitializeDiskTask; +import org.gluster.storage.management.gateway.utils.CpuStatsFactory; +import org.gluster.storage.management.gateway.utils.MemoryStatsFactory; +import org.gluster.storage.management.gateway.utils.NetworkStatsFactory; +import org.gluster.storage.management.gateway.utils.StatsFactory; +import org.springframework.stereotype.Component; + +import com.sun.jersey.api.core.InjectParam; +import com.sun.jersey.spi.resource.Singleton; + +@Component +@Singleton +@Path(RESOURCE_PATH_CLUSTERS + "/{" + PATH_PARAM_CLUSTER_NAME + "}/" + RESOURCE_SERVERS) +public class GlusterServersResource extends AbstractResource { + + public static final String HOSTNAMETAG = "hostname:"; + + @InjectParam + private TasksResource taskResource; + + @InjectParam + private ClusterService clusterService; + + @InjectParam + private CpuStatsFactory cpuStatsFactory; + + @InjectParam + private MemoryStatsFactory memoryStatsFactory; + + @InjectParam + private NetworkStatsFactory networkStatsFactory; + + @InjectParam + private GlusterServerService glusterServerService; + + @GET + @Produces(MediaType.APPLICATION_JSON) + public Response getGlusterServersJSON(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, + @QueryParam(QUERY_PARAM_DETAILS) Boolean details, @QueryParam(QUERY_PARAM_MAX_COUNT) Integer maxCount, + @QueryParam(QUERY_PARAM_NEXT_TO) String previousServerName) { + return getGlusterServers(clusterName, MediaType.APPLICATION_JSON, details, maxCount, previousServerName); + } + + @GET + @Produces(MediaType.APPLICATION_XML) + public Response getGlusterServersXML(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, + @QueryParam(QUERY_PARAM_DETAILS) Boolean details, @QueryParam(QUERY_PARAM_MAX_COUNT) Integer maxCount, + @QueryParam(QUERY_PARAM_NEXT_TO) String previousServerName) { + return getGlusterServers(clusterName, MediaType.APPLICATION_XML, details, maxCount, previousServerName); + } + + private Response getGlusterServers(String clusterName, String mediaType, Boolean fetchDetails, Integer maxCount, + String previousServerName) { + if(fetchDetails == null) { + // by default, fetch the server list + fetchDetails = false; + } + + List<GlusterServer> glusterServers = new ArrayList<GlusterServer>(); + + if (clusterName == null || clusterName.isEmpty()) { + return badRequestResponse("Cluster name must not be empty!"); + } + + ClusterInfo cluster = clusterService.getCluster(clusterName); + if (cluster == null) { + return notFoundResponse("Cluster [" + clusterName + "] not found!"); + } + + if (cluster.getServers().size() == 0) { + return okResponse(new GlusterServerListResponse(glusterServers), mediaType); + } + + try { + glusterServers = glusterServerService.getGlusterServers(clusterName, fetchDetails, maxCount, previousServerName); + } catch (Exception e) { + return errorResponse(e.getMessage()); + } + + if(fetchDetails) { + return okResponse(new GlusterServerListResponse(glusterServers), mediaType); + } else { + // no details to be fetched. Return list of server names. + return okResponse(new ServerNameListResponse(getServerNames(glusterServers)), mediaType); + } + } + + private List<String> getServerNames(List<GlusterServer> glusterServers) { + List<String> serverNames = new ArrayList<String>(); + for(GlusterServer server : glusterServers) { + serverNames.add(server.getName()); + } + return serverNames; + } + + @GET + @Path("{" + PATH_PARAM_SERVER_NAME + "}") + @Produces(MediaType.APPLICATION_XML) + public Response getGlusterServerXML(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, + @PathParam(PATH_PARAM_SERVER_NAME) String serverName) { + return getGlusterServerResponse(clusterName, serverName, MediaType.APPLICATION_XML); + } + + @GET + @Path("{" + PATH_PARAM_SERVER_NAME + "}") + @Produces(MediaType.APPLICATION_JSON) + public Response getGlusterServerJSON(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, + @PathParam(PATH_PARAM_SERVER_NAME) String serverName) { + return getGlusterServerResponse(clusterName, serverName, MediaType.APPLICATION_JSON); + } + + private Response getGlusterServerResponse(String clusterName, String serverName, String mediaType) { + try { + return okResponse(glusterServerService.getGlusterServer(clusterName, serverName, true), mediaType); + } catch (Exception e) { + return errorResponse(e.getMessage()); + } + } + + @POST + public Response addServer(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, + @FormParam(FORM_PARAM_SERVER_NAME) String serverName) { + return createdResponse(glusterServerService.addServerToCluster(clusterName, serverName)); + } + + @DELETE + @Path("{" + PATH_PARAM_SERVER_NAME + "}") + public Response removeServer(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, + @PathParam(PATH_PARAM_SERVER_NAME) String serverName) { + glusterServerService.removeServerFromCluster(clusterName, serverName); + return noContentResponse(); + } + + @GET + @Produces(MediaType.APPLICATION_XML) + @Path("{" + PATH_PARAM_SERVER_NAME + "}/" + RESOURCE_FSTYPES) + public FsTypeListResponse getFsTypes(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, @PathParam(PATH_PARAM_SERVER_NAME) String serverName) { + List<String> fsTypes = glusterServerService.getFsTypes(clusterName, serverName); + return new FsTypeListResponse(fsTypes); + } + + @PUT + @Produces(MediaType.APPLICATION_XML) + @Path("{" + PATH_PARAM_SERVER_NAME + "}/" + RESOURCE_DISKS + "/{" + PATH_PARAM_DISK_NAME + "}") + public Response initializeDisk(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, + @PathParam(PATH_PARAM_SERVER_NAME) String serverName, @PathParam(PATH_PARAM_DISK_NAME) String diskName, + @FormParam(FORM_PARAM_FSTYPE) String fsType, @FormParam(FORM_PARAM_MOUNTPOINT) String mountPoint) { + + if (clusterName == null || clusterName.isEmpty()) { + return badRequestResponse("Cluster name must not be empty!"); + } + + if (serverName == null || serverName.isEmpty()) { + return badRequestResponse("Server name must not be empty!"); + } + + if (diskName == null || diskName.isEmpty()) { + return badRequestResponse("Disk name must not be empty!"); + } + + if (fsType == null || fsType.isEmpty()) { + fsType = GlusterConstants.FSTYPE_DEFAULT; + // return badRequestResponse("Parameter [" + FORM_PARAM_FSTYPE + "] is missing in request!"); + } + + InitializeDiskTask initializeTask = new InitializeDiskTask(clusterService, clusterName, serverName, diskName, fsType, mountPoint); + try { + initializeTask.start(); + // Check the initialize disk status + TaskStatus taskStatus = initializeTask.checkStatus(); + initializeTask.getTaskInfo().setStatus(taskStatus); + taskResource.addTask(clusterName, initializeTask); + + return acceptedResponse(RESTConstants.RESOURCE_PATH_CLUSTERS + "/" + clusterName + "/" + RESOURCE_TASKS + "/" + + initializeTask.getId()); + } catch (Exception e) { + return errorResponse(e.getMessage()); + } + } + + @GET + @Produces(MediaType.APPLICATION_XML) + @Path(RESOURCE_STATISTICS) + public Response getAggregatedPerformanceDataXML(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, + @QueryParam(QUERY_PARAM_TYPE) String type, @QueryParam(QUERY_PARAM_PERIOD) String period) { + return getAggregaredPerformanceData(clusterName, type, period, MediaType.APPLICATION_XML); + } + + @GET + @Produces(MediaType.APPLICATION_JSON) + @Path(RESOURCE_STATISTICS) + public Response getAggregaredPerformanceDataJSON(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, + @QueryParam(QUERY_PARAM_TYPE) String type, @QueryParam(QUERY_PARAM_PERIOD) String period) { + return getAggregaredPerformanceData(clusterName, type, period, MediaType.APPLICATION_JSON); + } + + @GET + @Produces(MediaType.APPLICATION_XML) + @Path("{" + PATH_PARAM_SERVER_NAME + "}/" + RESOURCE_STATISTICS) + public Response getPerformanceDataXML(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, @PathParam(PATH_PARAM_SERVER_NAME) String serverName, + @QueryParam(QUERY_PARAM_TYPE) String type, @QueryParam(QUERY_PARAM_PERIOD) String period, + @QueryParam(QUERY_PARAM_INTERFACE) String networkInterface) { + return getPerformanceData(clusterName, serverName, type, period, networkInterface, MediaType.APPLICATION_XML); + } + + @GET + @Produces(MediaType.APPLICATION_JSON) + @Path("{" + PATH_PARAM_SERVER_NAME + "}/" + RESOURCE_STATISTICS) + public Response getPerformanceDataJSON(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, @PathParam(PATH_PARAM_SERVER_NAME) String serverName, + @QueryParam(QUERY_PARAM_TYPE) String type, @QueryParam(QUERY_PARAM_PERIOD) String period, + @QueryParam(QUERY_PARAM_INTERFACE) String networkInterface) { + return getPerformanceData(clusterName, serverName, type, period, networkInterface, MediaType.APPLICATION_JSON); + } + + private Response getAggregaredPerformanceData(String clusterName, String type, String period, String mediaType) { + if (clusterName == null || clusterName.isEmpty()) { + throw new GlusterValidationException("Cluster name must not be empty!"); + } + + if (type == null || type.isEmpty()) { + throw new GlusterValidationException("Statistics type name must not be empty!"); + } + + if (period == null || period.isEmpty()) { + throw new GlusterValidationException("Statistics period name must not be empty! Valid values are 1d/1w/1m/1y"); + } + + ClusterInfo cluster = clusterService.getCluster(clusterName); + if (cluster == null) { + return notFoundResponse("Cluster [" + clusterName + "] not found!"); + } + + if (cluster.getServers().isEmpty()) { + // cluster is empty. return empty stats. + return okResponse(new ServerStats(), mediaType); + } + + List<String> serverNames = getServerNames(glusterServerService.getGlusterServers(clusterName, false, null, null)); + return okResponse(getStatsFactory(type).fetchAggregatedStats(serverNames, period), mediaType); + } + + private Response getPerformanceData(String clusterName, String serverName, String type, String period, String networkInterface, String mediaType) { + if (clusterName == null || clusterName.isEmpty()) { + throw new GlusterValidationException("Cluster name must not be empty!"); + } + + if (serverName == null || serverName.isEmpty()) { + throw new GlusterValidationException("Server name must not be empty!"); + } + + if (type == null || type.isEmpty()) { + throw new GlusterValidationException("Statistics type name must not be empty!"); + } + + if (period == null || period.isEmpty()) { + throw new GlusterValidationException("Statistics period name must not be empty! Valid values are 1d/1w/1m/1y"); + } + + return okResponse(getStatsFactory(type).fetchStats(serverName, period, networkInterface), mediaType); + } + + private StatsFactory getStatsFactory(String type) { + if(type.equals(STATISTICS_TYPE_CPU)) { + return cpuStatsFactory; + } else if(type.equals(STATISTICS_TYPE_MEMORY)) { + return memoryStatsFactory; + } else if(type.equals(STATISTICS_TYPE_NETWORK)) { + return networkStatsFactory; + } else { + throw new GlusterValidationException("Invalid server statistics type [" + type + "]. Valid values are [" + + STATISTICS_TYPE_CPU + ", " + STATISTICS_TYPE_NETWORK + ", " + STATISTICS_TYPE_MEMORY + "]"); + } + } +} diff --git a/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/resources/v1_0/KeysResource.java b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/resources/v1_0/KeysResource.java new file mode 100644 index 00000000..3063c23e --- /dev/null +++ b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/resources/v1_0/KeysResource.java @@ -0,0 +1,155 @@ +/** + * KeysResource.java + * + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Gluster Management Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + */ +package org.gluster.storage.management.gateway.resources.v1_0; + +import static org.gluster.storage.management.core.constants.RESTConstants.RESOURCE_PATH_KEYS; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.text.SimpleDateFormat; +import java.util.Date; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.apache.log4j.Logger; +import org.gluster.storage.management.core.exceptions.GlusterRuntimeException; +import org.gluster.storage.management.core.utils.FileUtil; +import org.gluster.storage.management.core.utils.ProcessResult; +import org.gluster.storage.management.core.utils.ProcessUtil; +import org.gluster.storage.management.gateway.utils.SshUtil; + +import com.sun.jersey.multipart.FormDataParam; + +@Path(RESOURCE_PATH_KEYS) +public class KeysResource extends AbstractResource { + private static final Logger logger = Logger.getLogger(KeysResource.class); + private ProcessUtil processUtil = new ProcessUtil(); + + @GET + @Produces(MediaType.APPLICATION_OCTET_STREAM) + public Response exportSshkeys() { + File archiveFile = new File(createSskKeyZipFile()); + byte[] data = FileUtil.readFileAsByteArray(archiveFile); + archiveFile.delete(); + return streamingOutputResponse(createStreamingOutput(data)); + } + + private String createSskKeyZipFile() { + String targetDir = FileUtil.getTempDirName(); + String zipFile = targetDir + File.separator + "ssh-keys.tar"; + String sourcePrivateKeyFile = SshUtil.PRIVATE_KEY_FILE.getAbsolutePath(); + String sourcePublicKeyFile = SshUtil.PUBLIC_KEY_FILE.getAbsolutePath(); + String targetPrivateKeyFile = targetDir + File.separator + SshUtil.PRIVATE_KEY_FILE.getName(); + String targetPubKeyFile = targetDir + File.separator + SshUtil.PUBLIC_KEY_FILE.getName(); + + if (!SshUtil.PRIVATE_KEY_FILE.isFile()) { + throw new GlusterRuntimeException("No private key file [" + SshUtil.PRIVATE_KEY_FILE.getName() + "] found!"); + } + + if (!SshUtil.PUBLIC_KEY_FILE.isFile()) { + throw new GlusterRuntimeException("No public key file [" + SshUtil.PUBLIC_KEY_FILE.getName() + "] found!"); + } + + // Copy keys to temp folder + ProcessResult result = processUtil.executeCommand("cp", sourcePrivateKeyFile, targetPrivateKeyFile); + if (!result.isSuccess()) { + throw new GlusterRuntimeException("Failed to copy key files! [" + result.getOutput() + "]"); + } + result = processUtil.executeCommand("cp", sourcePublicKeyFile, targetPubKeyFile); + if (!result.isSuccess()) { + throw new GlusterRuntimeException("Failed to copy key files! [" + result.getOutput() + "]"); + } + + // To compress the key files + result = processUtil.executeCommand("tar", "cvf", zipFile, "-C", targetDir, SshUtil.PRIVATE_KEY_FILE.getName(), + SshUtil.PUBLIC_KEY_FILE.getName()); + if (!result.isSuccess()) { + throw new GlusterRuntimeException("Failed to compress key files! [" + result.getOutput() + "]"); + } + + // To remove the copied key files + try { + processUtil.executeCommand("rm", "-f", targetPrivateKeyFile, targetPubKeyFile); // Ignore the errors if any + } catch (Exception e) { + logger.warn(e.toString()); + } + return zipFile; + } + + @POST + @Consumes(MediaType.MULTIPART_FORM_DATA) + public Response importSshKeys(@FormDataParam("file") InputStream uploadedInputStream) { + File uploadedFile = new File(System.getProperty("java.io.tmpdir") + File.separator + "keys.tar"); + String timestamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()); + + writeToFile(uploadedInputStream, uploadedFile.getAbsolutePath()); + + // To backup existing SSH private and public keys, if exist. + if (SshUtil.PRIVATE_KEY_FILE.isFile()) { + if (!SshUtil.PRIVATE_KEY_FILE.renameTo(new File(SshUtil.PRIVATE_KEY_FILE.getAbsolutePath() + "-" + timestamp))) { + throw new GlusterRuntimeException("Unable to backup private key!"); + } + } + + if (SshUtil.PUBLIC_KEY_FILE.isFile()) { + if (!SshUtil.PUBLIC_KEY_FILE + .renameTo(new File(SshUtil.PUBLIC_KEY_FILE.getAbsolutePath() + "-" + timestamp))) { + throw new GlusterRuntimeException("Unable to backup public key!"); + } + } + // Extract SSH private and public key files. + ProcessResult output = processUtil.executeCommand("tar", "xvf", uploadedFile.getAbsolutePath(), "-C", + SshUtil.SSH_AUTHORIZED_KEYS_DIR_LOCAL); + uploadedFile.delete(); + if (!output.isSuccess()) { + String errMsg = "Error in importing SSH keys: [" + output.toString() + "]"; + logger.error(errMsg); + throw new GlusterRuntimeException(errMsg); + } + return createdResponse("SSH Key imported successfully"); + } + + // save uploaded file to the file (with path) + private void writeToFile(InputStream inputStream, String toFile) { + try { + int read = 0; + byte[] bytes = new byte[1024]; + + OutputStream out = new FileOutputStream(new File(toFile)); + while ((read = inputStream.read(bytes)) != -1) { + out.write(bytes, 0, read); + } + out.flush(); + out.close(); + } catch (IOException e) { + throw new GlusterRuntimeException(e.getMessage()); + } + } +} diff --git a/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/resources/v1_0/TasksResource.java b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/resources/v1_0/TasksResource.java new file mode 100644 index 00000000..b57232fe --- /dev/null +++ b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/resources/v1_0/TasksResource.java @@ -0,0 +1,226 @@ +/** + * TaskResource.java + * + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Gluster Management Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + */ +package org.gluster.storage.management.gateway.resources.v1_0; + +import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_OPERATION; +import static org.gluster.storage.management.core.constants.RESTConstants.PATH_PARAM_CLUSTER_NAME; +import static org.gluster.storage.management.core.constants.RESTConstants.PATH_PARAM_TASK_ID; +import static org.gluster.storage.management.core.constants.RESTConstants.RESOURCE_PATH_CLUSTERS; +import static org.gluster.storage.management.core.constants.RESTConstants.RESOURCE_TASKS; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.ws.rs.DELETE; +import javax.ws.rs.FormParam; +import javax.ws.rs.GET; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.gluster.storage.management.core.constants.RESTConstants; +import org.gluster.storage.management.core.exceptions.GlusterRuntimeException; +import org.gluster.storage.management.core.exceptions.GlusterValidationException; +import org.gluster.storage.management.core.model.Status; +import org.gluster.storage.management.core.model.TaskInfo; +import org.gluster.storage.management.core.response.TaskInfoListResponse; +import org.gluster.storage.management.gateway.tasks.Task; +import org.springframework.stereotype.Component; + +import com.sun.jersey.spi.resource.Singleton; + +@Path(RESOURCE_PATH_CLUSTERS + "/{" + PATH_PARAM_CLUSTER_NAME + "}/" + RESOURCE_TASKS) +@Singleton +@Component + +public class TasksResource extends AbstractResource { + private Map<String, Map<String, Task>> tasksMap = new HashMap<String, Map<String, Task>>(); + + public TasksResource() { + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + public void addTask(String clusterName, Task task) { + Map clusterTaskMap = tasksMap.get(clusterName); + if(clusterTaskMap == null) { + clusterTaskMap = new HashMap<String, Task>(); + tasksMap.put(clusterName, clusterTaskMap); + } + // Remove the task if already exist + if (clusterTaskMap.containsKey(task.getId())) { + removeTask(clusterName, task); + } + clusterTaskMap.put(task.getId(), task); + } + + @SuppressWarnings("rawtypes") + public void removeTask(String clusterName, Task task) { + Map clusterTaskMap = tasksMap.get(clusterName); + if (clusterTaskMap != null) { + clusterTaskMap.remove(task.getId()); + } + } + + public List<TaskInfo> getAllTasksInfo(String clusterName) { + Map<String, Task> clusterTasksMap = tasksMap.get(clusterName); + List<TaskInfo> allTasksInfo = new ArrayList<TaskInfo>(); + if ( clusterTasksMap == null) { + return allTasksInfo; + } + for (Map.Entry<String, Task> entry : clusterTasksMap.entrySet()) { + checkTaskStatus(clusterName, entry.getKey()); + allTasksInfo.add(entry.getValue().getTaskInfo()); // TaskInfo with latest status + } + return allTasksInfo; + } + + public Task getTask(String clusterName, String taskId) { + Map<String, Task> clusterTasksMap = tasksMap.get(clusterName); + for (Map.Entry<String, Task> entry : clusterTasksMap.entrySet()) { + if (entry.getValue().getId().equals(taskId)) { + return entry.getValue(); + } + } + return null; + } + + public List<Task> getAllTasks(String clusterName) { + Map<String, Task> clusterTasksMap = tasksMap.get(clusterName); + List<Task> tasks = new ArrayList<Task>(); + for (Map.Entry<String, Task> entry : clusterTasksMap.entrySet()) { + checkTaskStatus(clusterName, entry.getKey()); + tasks.add( (Task) entry.getValue()); + } + return tasks; + } + + @GET + @Produces(MediaType.APPLICATION_XML) + public Response getTasks(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName) { + try { + return okResponse(new TaskInfoListResponse(getAllTasksInfo(clusterName)), MediaType.APPLICATION_XML); + } catch (GlusterRuntimeException e) { + return errorResponse(e.getMessage()); + } + } + + @GET + @Path("/{" + PATH_PARAM_TASK_ID + "}") + @Produces(MediaType.APPLICATION_XML) + public Response getTaskStatus(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, + @PathParam(PATH_PARAM_TASK_ID) String taskId) { + try { + Task task = checkTaskStatus(clusterName, taskId); + return okResponse(task.getTaskInfo(), MediaType.APPLICATION_XML); + } catch (GlusterRuntimeException e) { + return errorResponse(e.getMessage()); + } + } + + private Task checkTaskStatus(String clusterName, String taskId) { + Task task = getTask(clusterName, taskId); + // No status check required if the task already complete or failure + if (task.getTaskInfo().getStatus().getCode() == Status.STATUS_CODE_FAILURE + || task.getTaskInfo().getStatus().getCode() == Status.STATUS_CODE_SUCCESS) { + return task; + } + task.getTaskInfo().setStatus(task.checkStatus()); + return task; + } + + @PUT + @Path("/{" + PATH_PARAM_TASK_ID + "}") + @Produces(MediaType.APPLICATION_XML) + public Response performTask(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, + @PathParam(PATH_PARAM_TASK_ID) String taskId, @FormParam(FORM_PARAM_OPERATION) String taskOperation) { + Task task = getTask(clusterName, taskId); + + try { + if (taskOperation.equals(RESTConstants.TASK_RESUME)) { + task.resume(); + } else if (taskOperation.equals(RESTConstants.TASK_PAUSE)) { + task.pause(); + } else if (taskOperation.equals(RESTConstants.TASK_STOP)) { + // task.stop(); + clearTask(clusterName, taskId, taskOperation); // Stop and remove from the task list + } else if (taskOperation.equals(RESTConstants.TASK_COMMIT)) { + task.commit(); + } + return (Response) noContentResponse(); + } catch(GlusterValidationException ve) { + return badRequestResponse(ve.getMessage()); + } catch (GlusterRuntimeException e) { + return errorResponse(e.getMessage()); + } + } + + @DELETE + @Path("/{" + PATH_PARAM_TASK_ID + "}") + @Produces(MediaType.APPLICATION_XML) + public Response clearTask(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, + @PathParam(PATH_PARAM_TASK_ID) String taskId, @QueryParam(FORM_PARAM_OPERATION) String taskOperation) { + Task task = getTask(clusterName, taskId); + if (task == null) { + return notFoundResponse("Requested task not found!"); + } + + if(clusterName == null || clusterName.isEmpty()) { + return badRequestResponse("Parameter [" + PATH_PARAM_CLUSTER_NAME + "] is missing in request!"); + } + + if(taskOperation == null || taskOperation.isEmpty()) { + int taskStatus = task.getTaskInfo().getStatus().getCode(); + if (taskStatus == Status.STATUS_CODE_SUCCESS || taskStatus == Status.STATUS_CODE_FAILURE) { + taskOperation = RESTConstants.TASK_DELETE; + } else { + taskOperation = RESTConstants.TASK_STOP; + } +// return badRequestResponse("Parameter [" + FORM_PARAM_OPERATION + "] is missing in request!"); + } + + if(!taskOperation.equals(RESTConstants.TASK_STOP) && !taskOperation.equals(RESTConstants.TASK_DELETE)) { + return badRequestResponse("Invalid value [" + taskOperation + "] for parameter [" + FORM_PARAM_OPERATION + + "]"); + } + + try { + if (taskOperation.equals(RESTConstants.TASK_STOP)) { + task.stop(); + // On successful, intentionally stopped task can be removed from task list also + taskOperation = RESTConstants.TASK_DELETE; + } + + if (taskOperation.equals(RESTConstants.TASK_DELETE)) { + removeTask(clusterName, task); + } + + return noContentResponse(); + } catch (Exception e) { + return errorResponse(e.getMessage()); + } + } +} diff --git a/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/resources/v1_0/UsersResource.java b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/resources/v1_0/UsersResource.java new file mode 100644 index 00000000..6594ed0f --- /dev/null +++ b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/resources/v1_0/UsersResource.java @@ -0,0 +1,124 @@ +/******************************************************************************* + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Gluster Management Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + *******************************************************************************/ +package org.gluster.storage.management.gateway.resources.v1_0; + +import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_NEW_PASSWORD; +import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_OLD_PASSWORD; +import static org.gluster.storage.management.core.constants.RESTConstants.PATH_PARAM_USER; +import static org.gluster.storage.management.core.constants.RESTConstants.RESOURCE_PATH_USERS; + +import javax.ws.rs.FormParam; +import javax.ws.rs.GET; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.apache.log4j.Logger; +import org.gluster.storage.management.core.exceptions.GlusterRuntimeException; +import org.gluster.storage.management.core.exceptions.GlusterValidationException; +import org.gluster.storage.management.core.model.Status; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.dao.SaltSource; +import org.springframework.security.authentication.encoding.PasswordEncoder; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.provisioning.JdbcUserDetailsManager; +import org.springframework.stereotype.Component; + +import com.sun.jersey.spi.resource.Singleton; + +@Singleton +@Component +@Path(RESOURCE_PATH_USERS) +public class UsersResource extends AbstractResource { + @Autowired + private JdbcUserDetailsManager jdbcUserService; + + @Autowired + private PasswordEncoder passwordEncoder; + + @Autowired + private SaltSource saltSource; + + @Autowired + private UserDetailsService userDetailsService; + + private static final Logger logger = Logger.getLogger(UsersResource.class); + + @Path("{" + PATH_PARAM_USER + "}") + @GET + @Produces(MediaType.APPLICATION_XML) + public Response authenticateXML(@PathParam("user") String user) { + // success only if the user passed in query is same as the one passed in security header + // spring security would have already authenticated the user credentials + return getAuthenticationResponse(user, MediaType.APPLICATION_XML); + } + + @Path("{" + PATH_PARAM_USER + "}") + @GET + @Produces(MediaType.APPLICATION_JSON) + public Response authenticateJSON(@PathParam("user") String user) { + // success only if the user passed in query is same as the one passed in security header + // spring security would have already authenticated the user credentials + return getAuthenticationResponse(user, MediaType.APPLICATION_JSON); + } + + public Response getAuthenticationResponse(String user, String mediaType) { + return (SecurityContextHolder.getContext().getAuthentication().getName().equals(user) ? okResponse( + Status.STATUS_SUCCESS, mediaType) : unauthorizedResponse()); + } + + @Path("{" + PATH_PARAM_USER + "}") + @PUT + public Response changePassword(@PathParam(PATH_PARAM_USER) String username, + @FormParam(FORM_PARAM_OLD_PASSWORD) String oldPassword, + @FormParam(FORM_PARAM_NEW_PASSWORD) String newPassword) { + try { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + String loggedInUser = ((UserDetails)auth.getPrincipal()).getUsername(); + if(!loggedInUser.equals(username)) { + // Temporary check as we currently have only one user. + throw new GlusterValidationException("User [" + loggedInUser + + "] is not allowed to change password of user [" + username + "]!"); + } + + UserDetails user = userDetailsService.loadUserByUsername(username); + Object salt = saltSource.getSalt(user); + + String actualOldPasswordEncoded = ((UserDetails)auth.getPrincipal()).getPassword(); + String oldPasswordEncoded = passwordEncoder.encodePassword(oldPassword, salt); + if(!oldPasswordEncoded.equals(actualOldPasswordEncoded)) { + throw new GlusterValidationException("Invalid old password!"); + } + + String encodedNewPassword = passwordEncoder.encodePassword(newPassword, salt); + jdbcUserService.changePassword(oldPassword, encodedNewPassword); + } catch (Exception ex) { + String errMsg = "Could not change password. Error: [" + ex.getMessage() + "]"; + logger.error(errMsg, ex); + throw new GlusterRuntimeException(errMsg); + } + return noContentResponse(); + } +} diff --git a/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/resources/v1_0/VolumesResource.java b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/resources/v1_0/VolumesResource.java new file mode 100644 index 00000000..18ed0314 --- /dev/null +++ b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/resources/v1_0/VolumesResource.java @@ -0,0 +1,383 @@ +/** + * VolumesResource.java + * + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Gluster Management Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + */ +package org.gluster.storage.management.gateway.resources.v1_0; + +import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_ACCESS_PROTOCOLS; +import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_AUTO_COMMIT; +import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_BRICKS; +import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_CIFS_ENABLE; +import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_CIFS_USERS; +import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_FIX_LAYOUT; +import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_FORCE; +import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_FORCED_DATA_MIGRATE; +import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_MIGRATE_DATA; +import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_OPERATION; +import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_REPLICA_COUNT; +import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_SOURCE; +import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_STRIPE_COUNT; +import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_TARGET; +import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_TRANSPORT_TYPE; +import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_VOLUME_NAME; +import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_VOLUME_OPTIONS; +import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_VOLUME_TYPE; +import static org.gluster.storage.management.core.constants.RESTConstants.PATH_PARAM_CLUSTER_NAME; +import static org.gluster.storage.management.core.constants.RESTConstants.PATH_PARAM_VOLUME_NAME; +import static org.gluster.storage.management.core.constants.RESTConstants.QUERY_PARAM_BRICKS; +import static org.gluster.storage.management.core.constants.RESTConstants.QUERY_PARAM_BRICK_NAME; +import static org.gluster.storage.management.core.constants.RESTConstants.QUERY_PARAM_DELETE_OPTION; +import static org.gluster.storage.management.core.constants.RESTConstants.QUERY_PARAM_DOWNLOAD; +import static org.gluster.storage.management.core.constants.RESTConstants.QUERY_PARAM_FROM_TIMESTAMP; +import static org.gluster.storage.management.core.constants.RESTConstants.QUERY_PARAM_LINE_COUNT; +import static org.gluster.storage.management.core.constants.RESTConstants.QUERY_PARAM_LOG_SEVERITY; +import static org.gluster.storage.management.core.constants.RESTConstants.QUERY_PARAM_MAX_COUNT; +import static org.gluster.storage.management.core.constants.RESTConstants.QUERY_PARAM_NEXT_TO; +import static org.gluster.storage.management.core.constants.RESTConstants.QUERY_PARAM_TO_TIMESTAMP; +import static org.gluster.storage.management.core.constants.RESTConstants.RESOURCE_BRICKS; +import static org.gluster.storage.management.core.constants.RESTConstants.RESOURCE_DEFAULT_OPTIONS; +import static org.gluster.storage.management.core.constants.RESTConstants.RESOURCE_DOWNLOAD; +import static org.gluster.storage.management.core.constants.RESTConstants.RESOURCE_LOGS; +import static org.gluster.storage.management.core.constants.RESTConstants.RESOURCE_OPTIONS; +import static org.gluster.storage.management.core.constants.RESTConstants.RESOURCE_PATH_CLUSTERS; +import static org.gluster.storage.management.core.constants.RESTConstants.RESOURCE_TASKS; +import static org.gluster.storage.management.core.constants.RESTConstants.RESOURCE_VOLUMES; + +import java.io.File; +import java.util.Arrays; +import java.util.List; + +import javax.ws.rs.DELETE; +import javax.ws.rs.FormParam; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.apache.log4j.Logger; +import org.gluster.storage.management.core.constants.RESTConstants; +import org.gluster.storage.management.core.exceptions.GlusterRuntimeException; +import org.gluster.storage.management.core.exceptions.GlusterValidationException; +import org.gluster.storage.management.core.model.Volume; +import org.gluster.storage.management.core.model.VolumeLogMessage; +import org.gluster.storage.management.core.model.Volume.VOLUME_STATUS; +import org.gluster.storage.management.core.model.Volume.VOLUME_TYPE; +import org.gluster.storage.management.core.response.LogMessageListResponse; +import org.gluster.storage.management.core.response.VolumeListResponse; +import org.gluster.storage.management.core.response.VolumeOptionInfoListResponse; +import org.gluster.storage.management.core.utils.FileUtil; +import org.gluster.storage.management.gateway.services.ClusterService; +import org.gluster.storage.management.gateway.services.VolumeService; + +import com.sun.jersey.api.core.InjectParam; +import com.sun.jersey.spi.resource.Singleton; + +@Singleton +@Path(RESOURCE_PATH_CLUSTERS + "/{" + PATH_PARAM_CLUSTER_NAME + "}/" + RESOURCE_VOLUMES) +public class VolumesResource extends AbstractResource { + private static final Logger logger = Logger.getLogger(VolumesResource.class); + + @InjectParam + private ClusterService clusterService; + + @InjectParam + private VolumeService volumeService; + + @GET + @Produces({ MediaType.APPLICATION_XML }) + public Response getVolumesXML(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, + @QueryParam(QUERY_PARAM_MAX_COUNT) Integer maxCount, + @QueryParam(QUERY_PARAM_NEXT_TO) String previousVolumeName) { + return okResponse(new VolumeListResponse(volumeService.getVolumes(clusterName, maxCount, previousVolumeName)), + MediaType.APPLICATION_XML); + } + + @GET + @Produces({ MediaType.APPLICATION_JSON }) + public Response getVolumesJSON(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, + @QueryParam(QUERY_PARAM_MAX_COUNT) Integer maxCount, + @QueryParam(QUERY_PARAM_NEXT_TO) String previousVolumeName) { + return okResponse(new VolumeListResponse(volumeService.getVolumes(clusterName, maxCount, previousVolumeName)), + MediaType.APPLICATION_JSON); + } + + @POST + @Produces(MediaType.APPLICATION_XML) + public Response createVolume(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, + @FormParam(FORM_PARAM_VOLUME_NAME) String volumeName, @FormParam(FORM_PARAM_VOLUME_TYPE) String volumeType, + @FormParam(FORM_PARAM_TRANSPORT_TYPE) String transportType, + @FormParam(FORM_PARAM_REPLICA_COUNT) Integer replicaCount, + @FormParam(FORM_PARAM_STRIPE_COUNT) Integer stripeCount, @FormParam(FORM_PARAM_BRICKS) String bricks, + @FormParam(FORM_PARAM_ACCESS_PROTOCOLS) String accessProtocols, + @FormParam(FORM_PARAM_VOLUME_OPTIONS) String options, @FormParam(FORM_PARAM_CIFS_USERS) String cifsUsers) { + int count = 0; + if (clusterName == null || clusterName.isEmpty()) { + return badRequestResponse("Cluster name must not be empty!"); + } + + String missingParam = checkMissingParamsForCreateVolume(volumeName, volumeType, transportType, bricks, + accessProtocols, options); + if (missingParam != null) { + throw new GlusterValidationException("Parameter [" + missingParam + "] is missing in request!"); + } + + // For missing parameter, let default value + if (volumeType.equals(VOLUME_TYPE.REPLICATE.toString()) + || volumeType.equals(VOLUME_TYPE.DISTRIBUTED_REPLICATE.toString())) { + count = (replicaCount == null) ? Volume.DEFAULT_REPLICA_COUNT : replicaCount; + } else if (volumeType.equals(VOLUME_TYPE.STRIPE.toString()) + || volumeType.equals(VOLUME_TYPE.DISTRIBUTED_STRIPE.toString())) { + count = (stripeCount == null) ? Volume.DEFAULT_STRIPE_COUNT : stripeCount; + } + + volumeService.createVolume(clusterName, volumeName, volumeType, transportType, count, bricks, accessProtocols, + options, cifsUsers); + return createdResponse(volumeName); + } + + /** + * Returns name of the missing parameter if any. If all parameters are present, + */ + private String checkMissingParamsForCreateVolume(String volumeName, String volumeType, + String transportType, String bricks, String accessProtocols, + String options) { + + return (volumeName == null || volumeName.isEmpty()) ? FORM_PARAM_VOLUME_NAME : + (volumeType == null || volumeType.isEmpty()) ? FORM_PARAM_VOLUME_TYPE : + (transportType == null || transportType.isEmpty()) ? FORM_PARAM_TRANSPORT_TYPE : + (bricks == null || bricks.isEmpty()) ? FORM_PARAM_BRICKS : + (accessProtocols == null || accessProtocols.isEmpty()) ? FORM_PARAM_ACCESS_PROTOCOLS : + (options == null || options.isEmpty()) ? FORM_PARAM_VOLUME_OPTIONS : + null; + } + + @GET + @Path("{" + PATH_PARAM_VOLUME_NAME + "}") + @Produces(MediaType.APPLICATION_XML) + public Response getVolumeXML(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, + @PathParam(PATH_PARAM_VOLUME_NAME) String volumeName) { + return okResponse(volumeService.getVolume(clusterName, volumeName), MediaType.APPLICATION_XML); + } + + @GET + @Path("{" + PATH_PARAM_VOLUME_NAME + "}") + @Produces(MediaType.APPLICATION_JSON) + public Response getVolumeJSON(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, + @PathParam(PATH_PARAM_VOLUME_NAME) String volumeName) { + return okResponse(volumeService.getVolume(clusterName, volumeName), MediaType.APPLICATION_JSON); + } + + @PUT + @Path("{" + PATH_PARAM_VOLUME_NAME + "}") + public Response performOperation(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, + @PathParam(PATH_PARAM_VOLUME_NAME) String volumeName, @FormParam(FORM_PARAM_OPERATION) String operation, + @FormParam(FORM_PARAM_FIX_LAYOUT) Boolean isFixLayout, + @FormParam(FORM_PARAM_MIGRATE_DATA) Boolean isMigrateData, + @FormParam(FORM_PARAM_FORCED_DATA_MIGRATE) Boolean isForcedDataMigrate, + @FormParam(FORM_PARAM_CIFS_ENABLE) Boolean enableCifs, @FormParam(FORM_PARAM_CIFS_USERS) String cifsUsers, + @FormParam(FORM_PARAM_BRICKS) String bricks, @FormParam(FORM_PARAM_FORCE) Boolean force) { + if (clusterName == null || clusterName.isEmpty()) { + throw new GlusterValidationException("Cluster name must not be empty!"); + } + + if (volumeName == null || volumeName.isEmpty()) { + throw new GlusterValidationException("Volume name must not be empty!"); + } + + if (clusterService.getCluster(clusterName) == null) { + throw new GlusterValidationException("Cluster [" + clusterName + "] not found!"); + } + + try { + if (operation.equals(RESTConstants.TASK_REBALANCE_START)) { + String taskId = volumeService.rebalanceStart(clusterName, volumeName, isFixLayout, isMigrateData, isForcedDataMigrate); + return acceptedResponse(RESTConstants.RESOURCE_PATH_CLUSTERS + "/" + clusterName + "/" + RESOURCE_TASKS + + "/" + taskId); + } else if (operation.equals(RESTConstants.TASK_REBALANCE_STOP)) { + volumeService.rebalanceStop(clusterName, volumeName); + } else if (operation.equals(RESTConstants.FORM_PARAM_CIFS_CONFIG)) { + Volume newVolume = volumeService.getVolume(clusterName, volumeName); + if (enableCifs) { + // After add/modify volume cifs users, start/restart the cifs service + volumeService.createCIFSUsers(clusterName, volumeName, cifsUsers); + if (newVolume.getStatus() == VOLUME_STATUS.ONLINE) { + volumeService.startCifsReExport(clusterName, volumeName); + } + } else { + // Stop the Cifs service and delete the users (!important) + if (newVolume.getStatus() == VOLUME_STATUS.ONLINE) { + volumeService.stopCifsReExport(clusterName, volumeName); + } + volumeService.deleteCifsUsers(clusterName, volumeName); + } + } else if (operation.equals(RESTConstants.TASK_LOG_ROTATE)) { + List<String> brickList = Arrays.asList(bricks.split(",")); + volumeService.logRotate(clusterName, volumeName, brickList); + } else { + if (force == null) { + force = false; + } + volumeService.performVolumeOperation(clusterName, volumeName, operation, force); + } + return noContentResponse(); + } catch(Exception e) { + return errorResponse(e.getMessage()); + } + } + + @DELETE + @Path("{" + PATH_PARAM_VOLUME_NAME + "}") + public Response deleteVolume(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, + @PathParam(PATH_PARAM_VOLUME_NAME) String volumeName, + @QueryParam(QUERY_PARAM_DELETE_OPTION) Boolean deleteFlag) { + volumeService.deleteVolume(clusterName, volumeName, deleteFlag); + + return noContentResponse(); + } + + @DELETE + @Path("{" + PATH_PARAM_VOLUME_NAME + "}/" + RESOURCE_BRICKS) + public Response removeBricks(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, + @PathParam(PATH_PARAM_VOLUME_NAME) String volumeName, @QueryParam(QUERY_PARAM_BRICKS) String bricks, + @QueryParam(QUERY_PARAM_DELETE_OPTION) Boolean deleteFlag) { + volumeService.removeBricksFromVolume(clusterName, volumeName, bricks, deleteFlag); + return noContentResponse(); + } + + @POST + @Path("{" + PATH_PARAM_VOLUME_NAME + " }/" + RESOURCE_OPTIONS) + public Response setOption(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, + @PathParam(PATH_PARAM_VOLUME_NAME) String volumeName, + @FormParam(RESTConstants.FORM_PARAM_OPTION_KEY) String key, + @FormParam(RESTConstants.FORM_PARAM_OPTION_VALUE) String value) { + volumeService.setVolumeOption(clusterName, volumeName, key, value); + + return createdResponse(key); + } + + @PUT + @Path("{" + PATH_PARAM_VOLUME_NAME + " }/" + RESOURCE_OPTIONS) + public Response resetOptions(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, + @PathParam(PATH_PARAM_VOLUME_NAME) String volumeName) { + volumeService.resetVolumeOptions(clusterName, volumeName); + return noContentResponse(); + } + + @GET + @Path(RESOURCE_DEFAULT_OPTIONS) + @Produces(MediaType.APPLICATION_XML) + public VolumeOptionInfoListResponse getOptionsInfoXML(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName) { + return volumeService.getVolumeOptionsInfo(clusterName); + } + + @GET + @Path(RESOURCE_DEFAULT_OPTIONS) + @Produces(MediaType.APPLICATION_JSON) + public VolumeOptionInfoListResponse getOptionsInfoJSON(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName) { + return volumeService.getVolumeOptionsInfo(clusterName); + } + + @GET + @Produces(MediaType.APPLICATION_OCTET_STREAM) + @Path("{" + PATH_PARAM_VOLUME_NAME + "}/" + RESOURCE_LOGS + "/" + RESOURCE_DOWNLOAD) + public Response downloadLogs(@PathParam(PATH_PARAM_CLUSTER_NAME) final String clusterName, + @PathParam(PATH_PARAM_VOLUME_NAME) final String volumeName) { + if (clusterName == null || clusterName.isEmpty()) { + return badRequestResponse("Cluster name must not be empty!"); + } + + if (volumeName == null || volumeName.isEmpty()) { + return badRequestResponse("Volume name must not be empty!"); + } + + if (clusterService.getCluster(clusterName) == null) { + return notFoundResponse("Cluster [" + clusterName + "] not found!"); + } + + try { + final Volume volume = volumeService.getVolume(clusterName, volumeName); + File archiveFile = new File(volumeService.downloadLogs(volume)); + byte[] data = FileUtil.readFileAsByteArray(archiveFile); + archiveFile.delete(); + return streamingOutputResponse(createStreamingOutput(data)); + } catch (Exception e) { + logger.error("Volume [" + volumeName + "] doesn't exist in cluster [" + clusterName + "]! [" + + e.getStackTrace() + "]"); + throw (GlusterRuntimeException) e; + } + } + + @GET + @Path("{" + PATH_PARAM_VOLUME_NAME + "}/" + RESOURCE_LOGS) + @Produces(MediaType.APPLICATION_XML) + public Response getLogsXML(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, + @PathParam(PATH_PARAM_VOLUME_NAME) String volumeName, @QueryParam(QUERY_PARAM_BRICK_NAME) String brickName, + @QueryParam(QUERY_PARAM_LOG_SEVERITY) String severity, + @QueryParam(QUERY_PARAM_FROM_TIMESTAMP) String fromTimestamp, + @QueryParam(QUERY_PARAM_TO_TIMESTAMP) String toTimestamp, + @QueryParam(QUERY_PARAM_LINE_COUNT) Integer lineCount, @QueryParam(QUERY_PARAM_DOWNLOAD) Boolean download) { + return getLogs(clusterName, volumeName, brickName, severity, fromTimestamp, toTimestamp, lineCount, MediaType.APPLICATION_XML); + } + + @GET + @Path("{" + PATH_PARAM_VOLUME_NAME + "}/" + RESOURCE_LOGS) + @Produces(MediaType.APPLICATION_JSON) + public Response getLogsJSON(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, + @PathParam(PATH_PARAM_VOLUME_NAME) String volumeName, @QueryParam(QUERY_PARAM_BRICK_NAME) String brickName, + @QueryParam(QUERY_PARAM_LOG_SEVERITY) String severity, + @QueryParam(QUERY_PARAM_FROM_TIMESTAMP) String fromTimestamp, + @QueryParam(QUERY_PARAM_TO_TIMESTAMP) String toTimestamp, + @QueryParam(QUERY_PARAM_LINE_COUNT) Integer lineCount, @QueryParam(QUERY_PARAM_DOWNLOAD) Boolean download) { + return getLogs(clusterName, volumeName, brickName, severity, fromTimestamp, toTimestamp, lineCount, MediaType.APPLICATION_JSON); + } + + private Response getLogs(String clusterName, String volumeName, String brickName, String severity, + String fromTimestamp, String toTimestamp, Integer lineCount, String mediaType) { + List<VolumeLogMessage> logMessages = volumeService.getLogs(clusterName, volumeName, brickName, severity, + fromTimestamp, toTimestamp, lineCount); + + return okResponse(new LogMessageListResponse(logMessages), mediaType); + } + + @POST + @Path("{" + PATH_PARAM_VOLUME_NAME + "}/" + RESOURCE_BRICKS) + public Response addBricks(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, + @PathParam(PATH_PARAM_VOLUME_NAME) String volumeName, @FormParam(FORM_PARAM_BRICKS) String bricks) { + volumeService.addBricksToVolume(clusterName, volumeName, bricks); + + return createdResponse(volumeName + "/" + RESOURCE_BRICKS); + } + + @PUT + @Path("{" + PATH_PARAM_VOLUME_NAME + "}/" + RESOURCE_BRICKS) + public Response migrateBrick(@PathParam(PATH_PARAM_CLUSTER_NAME) String clusterName, + @PathParam(PATH_PARAM_VOLUME_NAME) String volumeName, @FormParam(FORM_PARAM_SOURCE) String fromBrick, + @FormParam(FORM_PARAM_TARGET) String toBrick, @FormParam(FORM_PARAM_AUTO_COMMIT) Boolean autoCommit) { + + String taskId = volumeService.migrateBrickStart(clusterName, volumeName, fromBrick, toBrick, autoCommit); + + return acceptedResponse(RESTConstants.RESOURCE_PATH_CLUSTERS + "/" + clusterName + "/" + RESOURCE_TASKS + "/" + + taskId); + } +} diff --git a/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/security/GlusterUserDetailsService.java b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/security/GlusterUserDetailsService.java new file mode 100644 index 00000000..a73f2d61 --- /dev/null +++ b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/security/GlusterUserDetailsService.java @@ -0,0 +1,31 @@ +/** + * GlusterUserDetailsService.java + * + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Gluster Management Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + */ +package org.gluster.storage.management.gateway.security; + +import org.springframework.security.core.userdetails.UserDetailsService; + +/** + * + */ +public interface GlusterUserDetailsService extends UserDetailsService { + void changePassword(String username, String password); +} + diff --git a/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/security/UserAuthDao.java b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/security/UserAuthDao.java new file mode 100644 index 00000000..b0473f0c --- /dev/null +++ b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/security/UserAuthDao.java @@ -0,0 +1,57 @@ +/** + * UserAuthDao.java + * + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Gluster Management Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + */ +package org.gluster.storage.management.gateway.security; + +import org.gluster.storage.management.core.exceptions.GlusterRuntimeException; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.DefaultTransactionDefinition; + + +/** + * + */ +public class UserAuthDao extends JdbcDaoImpl implements GlusterUserDetailsService { + + /* + * (non-Javadoc) + * + * @see org.gluster.storage.management.gateway.security.GlusterUserDetailsService#changePassword(java.lang.String, + * java.lang.String) + */ + @Override + public void changePassword(String username, String password) { + DataSourceTransactionManager txnManager = new DataSourceTransactionManager(); + txnManager.setDataSource(getDataSource()); + + TransactionDefinition def = new DefaultTransactionDefinition(); + TransactionStatus status = txnManager.getTransaction(def); + try { + getJdbcTemplate().update("UPDATE USERS SET PASSWORD = ? WHERE USERNAME = ?", password, username); + txnManager.commit(status); + } catch(Exception e) { + txnManager.rollback(status); + throw new GlusterRuntimeException("Exception while changing password of user [" + username + "]. Error: " + e.getMessage()); + } + } +} diff --git a/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/services/AbstractGlusterInterface.java b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/services/AbstractGlusterInterface.java new file mode 100644 index 00000000..99935749 --- /dev/null +++ b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/services/AbstractGlusterInterface.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Gluster Management Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + *******************************************************************************/ +package org.gluster.storage.management.gateway.services; + +import org.gluster.storage.management.gateway.utils.ServerUtil; +import org.springframework.beans.factory.annotation.Autowired; + + +/** + * Abstract Gluster Interface - provides functionality common across all versions of GlusterFS e.g. version check. + */ +public abstract class AbstractGlusterInterface implements GlusterInterface { + + @Autowired + protected ServerUtil serverUtil; + + @Override + public String getVersion(String serverName) { + return serverUtil.executeOnServer(serverName, "gluster --version").split("\n")[0].replaceAll("glusterfs ", "") + .replaceAll(" built.*", ""); + } +} diff --git a/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/services/ClusterService.java b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/services/ClusterService.java new file mode 100644 index 00000000..e7a5af3d --- /dev/null +++ b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/services/ClusterService.java @@ -0,0 +1,269 @@ +/******************************************************************************* + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Gluster Management Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + *******************************************************************************/ +package org.gluster.storage.management.gateway.services; + +import java.util.ArrayList; +import java.util.List; + +import javax.persistence.EntityTransaction; + +import org.apache.log4j.Logger; +import org.gluster.storage.management.core.constants.CoreConstants; +import org.gluster.storage.management.core.exceptions.ConnectionException; +import org.gluster.storage.management.core.exceptions.GlusterRuntimeException; +import org.gluster.storage.management.core.model.GlusterServer; +import org.gluster.storage.management.core.utils.LRUCache; +import org.gluster.storage.management.gateway.data.ClusterInfo; +import org.gluster.storage.management.gateway.data.PersistenceDao; +import org.gluster.storage.management.gateway.data.ServerInfo; +import org.gluster.storage.management.gateway.utils.ServerUtil; +import org.gluster.storage.management.gateway.utils.SshUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + + +/** + * Service class for functionality related to clusters + */ +@Component +public class ClusterService { + @Autowired + private PersistenceDao<ClusterInfo> clusterDao; + + @Autowired + private PersistenceDao<ServerInfo> serverDao; + + @Autowired + private GlusterServerService glusterServerService; + + @Autowired + private SshUtil sshUtil; + + @Autowired + private ServerUtil serverUtil; + + private LRUCache<String, GlusterServer> onlineServerCache = new LRUCache<String, GlusterServer>(3); + + private static final Logger logger = Logger.getLogger(ClusterService.class); + + public void addOnlineServer(String clusterName, GlusterServer server) { + onlineServerCache.put(clusterName, server); + } + + public void removeOnlineServer(String clusterName) { + onlineServerCache.remove(clusterName); + } + + // uses cache + public GlusterServer getOnlineServer(String clusterName, String exceptServerName) { + GlusterServer server = onlineServerCache.get(clusterName); + if (server != null && !server.getName().equalsIgnoreCase(exceptServerName)) { + return server; + } + + return getNewOnlineServer(clusterName, exceptServerName); + } + + public GlusterServer getNewOnlineServer(String clusterName) { + return getNewOnlineServer(clusterName, ""); + } + + public GlusterServer getOnlineServer(String clusterName) { + return getOnlineServer(clusterName, ""); + } + + // Doesn't use cache + public GlusterServer getNewOnlineServer(String clusterName, String exceptServerName) { + ClusterInfo cluster = getCluster(clusterName); + if (cluster == null) { + throw new GlusterRuntimeException("Cluster [" + clusterName + "] is not found!"); + } + + for (ServerInfo serverInfo : cluster.getServers()) { + GlusterServer server = new GlusterServer(serverInfo.getName()); + try { + serverUtil.fetchServerDetails(server); // Online status come with server details + // server is online. add it to cache and return + if (server.isOnline() && !server.getName().equalsIgnoreCase(exceptServerName)) { + addOnlineServer(clusterName, server); + return server; + } + } catch (ConnectionException e) { + // server is offline. continue checking next one. + continue; + } + } + + // no online server found. + throw new GlusterRuntimeException("No online server found in cluster [" + clusterName + "]"); + } + + public List<ClusterInfo> getAllClusters() { + return clusterDao.findAll(); + } + + public ClusterInfo getCluster(String clusterName) { + List<ClusterInfo> clusters = clusterDao.findBy("UPPER(name) = ?1", clusterName.toUpperCase()); + if(clusters.size() == 0) { + return null; + } + + return clusters.get(0); + } + + public ClusterInfo getClusterForServer(String serverName) { + List<ServerInfo> servers = serverDao.findBy("UPPER(name) = ?1", serverName.toUpperCase()); + if(servers.size() == 0) { + return null; + } + + return servers.get(0).getCluster(); + } + + public void createCluster(String clusterName) { + EntityTransaction txn = clusterDao.startTransaction(); + ClusterInfo cluster = new ClusterInfo(); + cluster.setName(clusterName); + + try { + clusterDao.save(cluster); + txn.commit(); + } catch (RuntimeException e) { + txn.rollback(); + logger.error("Exception while trying to save cluster [" + clusterName + "] : [" + e.getMessage() + "]", e); + throw e; + } + } + + public void registerCluster(String clusterName, String knownServer) { + EntityTransaction txn = clusterDao.startTransaction(); + ClusterInfo cluster = new ClusterInfo(); + cluster.setName(clusterName); + + GlusterServer server = new GlusterServer(knownServer); + try { + List<GlusterServer> glusterServers = glusterServerService.getGlusterServers(server.getName()); + List<ServerInfo> servers = new ArrayList<ServerInfo>(); + for(GlusterServer glusterServer : glusterServers) { + String serverName = glusterServer.getName(); + + serverUtil.fetchServerDetails(glusterServer); + if(glusterServer.isOnline()) { + checkAndSetupPublicKey(serverName); + } + + ServerInfo serverInfo = new ServerInfo(serverName); + serverInfo.setCluster(cluster); + clusterDao.save(serverInfo); + servers.add(serverInfo); + } + cluster.setServers(servers); + clusterDao.save(cluster); + txn.commit(); + } catch(RuntimeException e) { + logger.error("Error in registering cluster [" + clusterName + "] : " + e.getMessage(), e); + txn.rollback(); + logger.error("Error in registering cluster [" + clusterName + "] : " + e.getMessage(), e); + throw e; + } + } + + private void checkAndSetupPublicKey(String serverName) { + if(sshUtil.isPublicKeyInstalled(serverName)) { + return; + } + + if(!sshUtil.hasDefaultPassword(serverName)) { + // public key not installed, default password doesn't work. can't install public key + throw new GlusterRuntimeException( + "Gluster Management Gateway uses the default password to set up keys on the server." + + CoreConstants.NEWLINE + "However it seems that the password on server [" + serverName + + "] has been changed manually." + CoreConstants.NEWLINE + + "Please reset it back to the standard default password and try again."); + } + + // install public key (this will also disable password based ssh login) + sshUtil.installPublicKey(serverName); + } + + public void unregisterCluster(String clusterName) { + ClusterInfo cluster = getCluster(clusterName); + + if (cluster == null) { + throw new GlusterRuntimeException("Cluster [" + clusterName + "] doesn't exist!"); + } + + unregisterCluster(cluster); + } + + public void unregisterCluster(ClusterInfo cluster) { + EntityTransaction txn = clusterDao.startTransaction(); + try { + for(ServerInfo server : cluster.getServers()) { + clusterDao.delete(server); + } + cluster.getServers().clear(); + clusterDao.update(cluster); + clusterDao.delete(cluster); + txn.commit(); + } catch (RuntimeException e) { + logger.error("Error in unregistering cluster [" + cluster.getName() + "] : " + e.getMessage(), e); + txn.rollback(); + throw e; + } + } + + public void mapServerToCluster(String clusterName, String serverName) { + EntityTransaction txn = clusterDao.startTransaction(); + ClusterInfo cluster = getCluster(clusterName); + ServerInfo server = new ServerInfo(serverName); + server.setCluster(cluster); + try { + clusterDao.save(server); + cluster.addServer(server); + clusterDao.update(cluster); + txn.commit(); + } catch (Exception e) { + txn.rollback(); + throw new GlusterRuntimeException("Couldn't create cluster-server mapping [" + clusterName + "][" + + serverName + "]! Error: " + e.getMessage(), e); + } + } + + public void unmapServerFromCluster(String clusterName, String serverName) { + EntityTransaction txn = clusterDao.startTransaction(); + ClusterInfo cluster = getCluster(clusterName); + List<ServerInfo> servers = cluster.getServers(); + for(ServerInfo server : servers) { + if(server.getName().equalsIgnoreCase(serverName)) { + servers.remove(server); + clusterDao.delete(server); + break; + } + } + try { + clusterDao.update(cluster); + txn.commit(); + } catch(Exception e) { + txn.rollback(); + throw new GlusterRuntimeException("Couldn't unmap server [" + serverName + "] from cluster [" + clusterName + + "]! Error: " + e.getMessage(), e); + } + } +} diff --git a/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/services/DiscoveredServerService.java b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/services/DiscoveredServerService.java new file mode 100644 index 00000000..95a8eecb --- /dev/null +++ b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/services/DiscoveredServerService.java @@ -0,0 +1,130 @@ +/******************************************************************************* + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Gluster Management Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + *******************************************************************************/ +package org.gluster.storage.management.gateway.services; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.log4j.Logger; +import org.gluster.storage.management.core.exceptions.GlusterRuntimeException; +import org.gluster.storage.management.core.model.Server; +import org.gluster.storage.management.core.utils.ProcessUtil; +import org.gluster.storage.management.gateway.utils.ServerUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + + +/** + * + */ +@Component +public class DiscoveredServerService { + @Autowired + protected ServerUtil serverUtil; + + private List<String> discoveredServerNames = new ArrayList<String>(); + private static final Logger logger = Logger.getLogger(DiscoveredServerService.class); + + public List<Server> getDiscoveredServerDetails() { + try { + List<Server> discoveredServers = Collections.synchronizedList(new ArrayList<Server>()); + List<Thread> threads = createThreads(discoveredServers); + ProcessUtil.waitForThreads(threads); + return discoveredServers; + } catch (Exception e) { + String errMsg = "Exception while fetching details of discovered servers! Error: [" + e.getMessage() + "]"; + logger.error(errMsg, e); + throw new GlusterRuntimeException(errMsg, e); + } + } + + /** + * Creates threads that will run in parallel and fetch details of all discovered servers + * @param discoveredServers The list to be populated with details of discovered servers + * @return + * @throws InterruptedException + */ + private List<Thread> createThreads(List<Server> discoveredServers) throws InterruptedException { + List<String> discoveredServerNames = getDiscoveredServerNames(); + List<Thread> threads = new ArrayList<Thread>(); + for (int i = discoveredServerNames.size() - 1; i >= 0; i--) { + Thread thread = new DiscoveredServerDetailsThread(discoveredServers, discoveredServerNames.get(i)); + threads.add(thread); + thread.start(); + if (i >= 5 && i % 5 == 0) { + // After every 5 servers, wait for 1 second so that we don't end up with too many running threads + Thread.sleep(1000); + } + } + return threads; + } + + public List<String> getDiscoveredServerNames() { + return discoveredServerNames; + } + + public void setDiscoveredServerNames(List<String> discoveredServerNames) { + synchronized (discoveredServerNames) { + this.discoveredServerNames = discoveredServerNames; + } + } + + public void removeDiscoveredServer(String serverName) { + discoveredServerNames.remove(serverName); + } + + public void addDiscoveredServer(String serverName) { + discoveredServerNames.add(serverName); + } + + public Server getDiscoveredServer(String serverName) { + Server server = new Server(serverName); + serverUtil.fetchServerDetails(server); + return server; + } + + public class DiscoveredServerDetailsThread extends Thread { + private List<Server> servers; + private String serverName; + private final Logger logger = Logger.getLogger(DiscoveredServerDetailsThread.class); + + /** + * Private constructor called on each thread + * @param servers The list to be populated with fetched server details + * @param serverName Name of the server whose details should be fetched by this thread + */ + private DiscoveredServerDetailsThread(List<Server> servers, String serverName) { + this.servers = servers; + this.serverName = serverName; + } + + @Override + public void run() { + try { + logger.info("fetching details of discovered server [" + serverName + "] - start"); + servers.add(getDiscoveredServer(serverName)); + logger.info("fetching details of discovered server [" + serverName + "] - end"); + } catch(Exception e) { + logger.warn("fetching details of discovered server [" + serverName + "] - error", e); + // eat the exception as we can't consider this server as a discovered server any more + } + } + } +} diff --git a/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/services/Gluster323InterfaceService.java b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/services/Gluster323InterfaceService.java new file mode 100644 index 00000000..ec7ae77e --- /dev/null +++ b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/services/Gluster323InterfaceService.java @@ -0,0 +1,598 @@ +/******************************************************************************* + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Gluster Management Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + *******************************************************************************/ +package org.gluster.storage.management.gateway.services; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.log4j.Logger; +import org.gluster.storage.management.core.constants.CoreConstants; +import org.gluster.storage.management.core.constants.GlusterConstants; +import org.gluster.storage.management.core.exceptions.GlusterRuntimeException; +import org.gluster.storage.management.core.model.Brick; +import org.gluster.storage.management.core.model.Status; +import org.gluster.storage.management.core.model.TaskStatus; +import org.gluster.storage.management.core.model.Volume; +import org.gluster.storage.management.core.model.Brick.BRICK_STATUS; +import org.gluster.storage.management.core.model.Volume.TRANSPORT_TYPE; +import org.gluster.storage.management.core.model.Volume.VOLUME_STATUS; +import org.gluster.storage.management.core.model.Volume.VOLUME_TYPE; +import org.gluster.storage.management.core.response.VolumeOptionInfoListResponse; +import org.gluster.storage.management.core.utils.StringUtil; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + + +/** + * Gluster Interface for GlusterFS version 3.2.3 + */ +@Component +@Lazy(value=true) +public class Gluster323InterfaceService extends AbstractGlusterInterface { + + private static final String VOLUME_NAME_PFX = "Volume Name:"; + private static final String VOLUME_TYPE_PFX = "Type:"; + private static final String VOLUME_STATUS_PFX = "Status:"; + private static final String VOLUME_NUMBER_OF_BRICKS = "Number of Bricks:"; + private static final String VOLUME_TRANSPORT_TYPE_PFX = "Transport-type:"; + private static final String VOLUME_BRICKS_GROUP_PFX = "Bricks"; + private static final String VOLUME_OPTIONS_RECONFIG_PFX = "Options Reconfigured"; + private static final String VOLUME_LOG_LOCATION_PFX = "log file location:"; + private static final String VOLUME_TYPE_DISTRIBUTE = "Distribute"; + private static final String VOLUME_TYPE_REPLICATE = "Replicate"; + private static final String VOLUME_TYPE_DISTRIBUTED_REPLICATTE = "Distributed-Replicate"; + private static final String VOLUME_TYPE_STRIPE = "Stripe"; + private static final String VOLUME_TYPE_DISTRIBUTED_STRIPE = "Distributed-Stripe"; + + private static final String BRICK_STATUS_SCRIPT = "get_brick_status.py"; + private static final Logger logger = Logger.getLogger(Gluster323InterfaceService.class); + + /* (non-Javadoc) + * @see org.gluster.storage.management.gateway.utils.GlusterInterface#addServer(java.lang.String, java.lang.String) + */ + @Override + public void addServer(String existingServer, String newServer) { + serverUtil.executeOnServer(existingServer, "gluster peer probe " + newServer); + // reverse peer probe to ensure that host names appear in peer status on both sides + serverUtil.executeOnServer(newServer, "gluster peer probe " + existingServer); + } + + /* (non-Javadoc) + * @see org.gluster.storage.management.gateway.utils.GlusterInterface#startVolume(java.lang.String, java.lang.String) + */ + @Override + public void startVolume(String volumeName, String knownServer, Boolean force) { + serverUtil.executeOnServer(knownServer, "gluster volume start " + volumeName + ((force) ? " force" : "")); + } + + /* (non-Javadoc) + * @see org.gluster.storage.management.gateway.utils.GlusterInterface#stopVolume(java.lang.String, java.lang.String) + */ + @Override + public void stopVolume(String volumeName, String knownServer, Boolean force) { + serverUtil.executeOnServer(knownServer, "gluster --mode=script volume stop " + volumeName + + ((force) ? " force" : "")); + } + + /* (non-Javadoc) + * @see org.gluster.storage.management.gateway.utils.GlusterInterface#resetOptions(java.lang.String, java.lang.String) + */ + @Override + public void resetOptions(String volumeName, String knownServer) { + serverUtil.executeOnServer(knownServer, "gluster volume reset " + volumeName); + } + + /* (non-Javadoc) + * @see org.gluster.storage.management.gateway.utils.GlusterInterface#createVolume(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.Integer, java.lang.String, java.lang.String, java.lang.String) + */ + @Override + public void createVolume(String knownServer, String volumeName, String volumeTypeStr, String transportTypeStr, + Integer count, String bricks, String accessProtocols, String options) { + + // TODO: Disable NFS if required depending on value of accessProtocols + VOLUME_TYPE volType = Volume.getVolumeTypeByStr(volumeTypeStr); + String volTypeArg = null; + if (volType == VOLUME_TYPE.REPLICATE || volType == VOLUME_TYPE.DISTRIBUTED_REPLICATE) { + volTypeArg = "replica"; + } else if (volType == VOLUME_TYPE.STRIPE || volType == VOLUME_TYPE.DISTRIBUTED_STRIPE) { + volTypeArg = "stripe"; + } + + String transportTypeArg = null; + TRANSPORT_TYPE transportType = Volume.getTransportTypeByStr(transportTypeStr); + transportTypeArg = (transportType == TRANSPORT_TYPE.ETHERNET) ? "tcp" : "rdma"; + + String command = prepareVolumeCreateCommand(volumeName, StringUtil.extractList(bricks, ","), count, + volTypeArg, transportTypeArg); + + serverUtil.executeOnServer(knownServer, command); + + try { + createOptions(volumeName, StringUtil.extractMap(options, ",", "="), knownServer); + } catch(Exception e) { + throw new GlusterRuntimeException( + "Volume created successfully, however following errors occurred while setting options: " + + CoreConstants.NEWLINE + e.getMessage()); + } + } + + private String prepareVolumeCreateCommand(String volumeName, List<String> brickDirectories, int count, + String volumeType, String transportTypeStr) { + StringBuilder command = new StringBuilder("gluster volume create " + volumeName + " "); + if (volumeType != null) { + command.append(volumeType + " " + count + " "); + } + command.append("transport " + transportTypeStr); + for (String brickDir : brickDirectories) { + command.append(" " + brickDir); + } + return command.toString(); + } + + /* (non-Javadoc) + * @see org.gluster.storage.management.gateway.utils.GlusterInterface#createOptions(java.lang.String, java.util.Map, java.lang.String) + */ + @Override + public void createOptions(String volumeName, Map<String, String> options, String knownServer) { + String errors = ""; + if (options != null) { + for (Entry<String, String> option : options.entrySet()) { + String key = option.getKey(); + String value = option.getValue(); + + try { + setOption(volumeName, key, value, knownServer); + } catch(Exception e) { + // append error + errors += e.getMessage() + CoreConstants.NEWLINE; + } + } + } + if (!errors.trim().isEmpty()) { + throw new GlusterRuntimeException("Errors while setting option(s) on volume [" + volumeName + "] : " + + errors.trim()); + } + } + + /* (non-Javadoc) + * @see org.gluster.storage.management.gateway.utils.GlusterInterface#setOption(java.lang.String, java.lang.String, java.lang.String, java.lang.String) + */ + @Override + public void setOption(String volumeName, String key, String value, String knownServer) { + serverUtil.executeOnServer(knownServer, "gluster volume set " + volumeName + " " + key + " " + "\"" + + value + "\""); + } + + /* (non-Javadoc) + * @see org.gluster.storage.management.gateway.utils.GlusterInterface#deleteVolume(java.lang.String, java.lang.String) + */ + @Override + public void deleteVolume(String volumeName, String knownServer) { + serverUtil.executeOnServer(knownServer, "gluster --mode=script volume delete " + volumeName); + } + + private String getVolumeInfo(String volumeName, String knownServer) { + return serverUtil.executeOnServer(knownServer, "gluster volume info " + volumeName, String.class); + } + + private String getVolumeInfo(String knownServer) { + return serverUtil.executeOnServer(knownServer, "gluster volume info", String.class); + } + + private boolean readVolumeType(Volume volume, String line) { + String volumeType = StringUtil.extractToken(line, VOLUME_TYPE_PFX); + if (volumeType != null) { + if (volumeType.equals(VOLUME_TYPE_DISTRIBUTE)) { + volume.setVolumeType(VOLUME_TYPE.DISTRIBUTE); + + } else if (volumeType.equals(VOLUME_TYPE_REPLICATE)) { + volume.setVolumeType(VOLUME_TYPE.REPLICATE); + volume.setReplicaCount(Volume.DEFAULT_REPLICA_COUNT); + + } else if ( volumeType.equals(VOLUME_TYPE_DISTRIBUTED_REPLICATTE) ){ + volume.setVolumeType(VOLUME_TYPE.DISTRIBUTED_REPLICATE); + volume.setReplicaCount(Volume.DEFAULT_REPLICA_COUNT); + + } else if ( volumeType.equals(VOLUME_TYPE_STRIPE) ){ + volume.setVolumeType(VOLUME_TYPE.STRIPE); + volume.setReplicaCount(Volume.DEFAULT_REPLICA_COUNT); + + } else if ( volumeType.equals(VOLUME_TYPE_DISTRIBUTED_STRIPE) ){ + volume.setVolumeType(VOLUME_TYPE.DISTRIBUTED_STRIPE); + volume.setReplicaCount(Volume.DEFAULT_STRIPE_COUNT); + } + return true; + } + return false; + } + + private void readReplicaOrStripeCount(Volume volume, String line) { + if (StringUtil.extractToken(line, "x") != null) { + // expected formated of line is "Number of Bricks: 3 x 2 = 6" + int count = Integer.parseInt(line.split("x")[1].split("=")[0].trim()); + if (volume.getVolumeType() == VOLUME_TYPE.STRIPE + || volume.getVolumeType() == VOLUME_TYPE.DISTRIBUTED_STRIPE) { + volume.setStripeCount(count); + } else if (volume.getVolumeType() == VOLUME_TYPE.REPLICATE + || volume.getVolumeType() == VOLUME_TYPE.DISTRIBUTED_REPLICATE) { + volume.setReplicaCount(count); + volume.setStripeCount(0); + } + } + return; + } + + private boolean readVolumeStatus(Volume volume, String line) { + String volumeStatus = StringUtil.extractToken(line, VOLUME_STATUS_PFX); + if (volumeStatus != null) { + volume.setStatus(volumeStatus.equals("Started") ? VOLUME_STATUS.ONLINE : VOLUME_STATUS.OFFLINE); + return true; + } + return false; + } + + private boolean readTransportType(Volume volume, String line) { + String transportType = StringUtil.extractToken(line, VOLUME_TRANSPORT_TYPE_PFX); + if (transportType != null) { + volume.setTransportType(transportType.equals("tcp") ? TRANSPORT_TYPE.ETHERNET : TRANSPORT_TYPE.INFINIBAND); + return true; + } + return false; + } + + private boolean readBrick(Volume volume, String brickLine) { + BRICK_STATUS brickStatus; + if (brickLine.matches("Brick[0-9]+:.*")) { + // line: "Brick1: server1:/export/md0/volume-name" + String brickName = brickLine.split(": ")[1]; + String[] brickParts = brickLine.split(":"); + String serverName = brickParts[1].trim(); + String brickDir = brickParts[2].trim(); + //To get the brick status + brickStatus = getBrickStatus(serverName, volume.getName(), brickName); + + addBrickToVolume(volume, serverName, brickDir, brickStatus); + return true; + } + return false; + } + + private void addBrickToVolume(Volume volume, String serverName, String brickDir, BRICK_STATUS status) { + volume.addBrick(new Brick(serverName, status, brickDir)); + } + + // Do not throw exception, Gracefully handle as Offline brick. + private BRICK_STATUS getBrickStatus(String serverName, String volumeName, String brick){ + try { + String output = serverUtil.executeScriptOnServer(serverName, BRICK_STATUS_SCRIPT + " " + volumeName + + " " + brick, String.class); + if (output.equals(CoreConstants.ONLINE)) { + return BRICK_STATUS.ONLINE; + } else { + return BRICK_STATUS.OFFLINE; + } + } catch(Exception e) { // Particularly interested on ConnectionExecption, if the server is offline + logger.warn("Exception while fetching brick status for [" + volumeName + "][" + brick + + "]. Marking it as offline!", e); + return BRICK_STATUS.OFFLINE; + } + } + + private boolean readBrickGroup(String line) { + return StringUtil.extractToken(line, VOLUME_BRICKS_GROUP_PFX) != null; + } + + private boolean readOptionReconfigGroup(String line) { + return StringUtil.extractToken(line, VOLUME_OPTIONS_RECONFIG_PFX) != null; + } + + private boolean readOption(Volume volume, String line) { + if (line.matches("^[^:]*:.*$")) { + int index = line.indexOf(':'); + volume.setOption(line.substring(0, index).trim(), line.substring(index + 1, line.length()).trim()); + + if (line.substring(0, index).trim().equals(Volume.OPTION_NFS_DISABLE)) { + if (line.substring(index + 1, line.length()).trim().equals(GlusterConstants.ON)) { + volume.disableNFS(); + } else { + volume.enableNFS(); + } + } + + return true; + } + return false; + } + + /* (non-Javadoc) + * @see org.gluster.storage.management.gateway.utils.GlusterInterface#getVolume(java.lang.String, java.lang.String) + */ + @Override + public Volume getVolume(String volumeName, String knownServer) { + return parseVolumeInfo(getVolumeInfo(volumeName, knownServer)).get(0); + } + + /* (non-Javadoc) + * @see org.gluster.storage.management.gateway.utils.GlusterInterface#getAllVolumes(java.lang.String) + */ + @Override + public List<Volume> getAllVolumes(String knownServer) { + return parseVolumeInfo(getVolumeInfo(knownServer)); + } + + private List<Volume> parseVolumeInfo(String volumeInfoText) { + List<Volume> volumes = new ArrayList<Volume>(); + boolean isBricksGroupFound = false; + boolean isOptionReconfigFound = false; + Volume volume = null; + + for (String line : volumeInfoText.split(CoreConstants.NEWLINE)) { + String volumeName = StringUtil.extractToken(line, VOLUME_NAME_PFX); + if (volumeName != null) { + if (volume != null) { + volumes.add(volume); + } + + // prepare next volume to be read + volume = new Volume(); + volume.setName(volumeName); + isBricksGroupFound = isOptionReconfigFound = false; + continue; + } + + if (readVolumeType(volume, line)) + continue; + if (StringUtil.extractToken(line, VOLUME_NUMBER_OF_BRICKS) != null) { + readReplicaOrStripeCount(volume, line); + } + if (readVolumeStatus(volume, line)) + continue; + if (readTransportType(volume, line)) + continue; + if (readBrickGroup(line)) { + isBricksGroupFound = true; + continue; + } + + if (isBricksGroupFound) { + if (readBrick(volume, line)) { + continue; + } else { + isBricksGroupFound = false; + } + } + + if (readOptionReconfigGroup(line)) { + isOptionReconfigFound = true; + continue; + } + + if (isOptionReconfigFound) { + if (readOption(volume, line)) { + continue; + } else { + isOptionReconfigFound = false; + } + } + } + + // add the last read volume + if (volume != null) { + volumes.add(volume); + } + + return volumes; + } + + /* (non-Javadoc) + * @see org.gluster.storage.management.gateway.utils.GlusterInterface#addBricks(java.lang.String, java.util.List, java.lang.String) + */ + @Override + public void addBricks(String volumeName, List<String> bricks, String knownServer) { + StringBuilder command = new StringBuilder("gluster volume add-brick " + volumeName); + for (String brickDir : bricks) { + command.append(" " + brickDir); + } + + serverUtil.executeOnServer(knownServer, command.toString()); + } + + /* (non-Javadoc) + * @see org.gluster.storage.management.gateway.utils.GlusterInterface#getLogLocation(java.lang.String, java.lang.String, java.lang.String) + */ + @Override + public String getLogLocation(String volumeName, String brickName, String knownServer) { + String command = "gluster volume log locate " + volumeName + " " + brickName; + String output = serverUtil.executeOnServer(knownServer, command, String.class); + if (output.startsWith(VOLUME_LOG_LOCATION_PFX)) { + return output.substring(VOLUME_LOG_LOCATION_PFX.length()).trim(); + } + + throw new GlusterRuntimeException("Couldn't parse output of command [" + command + "]. Output [" + output + + "] doesn't start with prefix [" + VOLUME_LOG_LOCATION_PFX + "]"); + } + + /* (non-Javadoc) + * @see org.gluster.storage.management.gateway.utils.GlusterInterface#getLogFileNameForBrickDir(java.lang.String) + */ + @Override + public String getLogFileNameForBrickDir(String serverName, String brickDir) { + String logFileName = brickDir; + if (logFileName.length() > 0 && logFileName.charAt(0) == '/') { + logFileName = logFileName.replaceFirst("/", ""); + } + logFileName = logFileName.replaceAll("/", "-") + ".log"; + return logFileName; + } + + /* (non-Javadoc) + * @see org.gluster.storage.management.gateway.utils.GlusterInterface#removeBricks(java.lang.String, java.util.List, java.lang.String) + */ + @Override + public void removeBricks(String volumeName, List<String> bricks, String knownServer) { + StringBuilder command = new StringBuilder("gluster --mode=script volume remove-brick " + volumeName); + for (String brickDir : bricks) { + command.append(" " + brickDir); + } + serverUtil.executeOnServer(knownServer, command.toString()); + } + + /* (non-Javadoc) + * @see org.gluster.storage.management.gateway.utils.GlusterInterface#removeServer(java.lang.String, java.lang.String) + */ + @Override + public void removeServer(String existingServer, String serverName) { + serverUtil.executeOnServer(existingServer, "gluster --mode=script peer detach " + serverName); + } + + /* (non-Javadoc) + * @see org.gluster.storage.management.gateway.utils.GlusterInterface#checkRebalanceStatus(java.lang.String, java.lang.String) + */ + @Override + public TaskStatus checkRebalanceStatus(String serverName, String volumeName) { + String command = "gluster volume rebalance " + volumeName + " status"; + String output = serverUtil.executeOnServer(serverName, command, String.class).trim(); + TaskStatus taskStatus = new TaskStatus(); + if (output.matches("^rebalance completed.*")) { + taskStatus.setCode(Status.STATUS_CODE_SUCCESS); + } else if (output.matches(".*in progress.*")) { + taskStatus.setCode(Status.STATUS_CODE_RUNNING); + } else { + taskStatus.setCode(Status.STATUS_CODE_FAILURE); + } + taskStatus.setMessage(output); + return taskStatus; + } + + /* (non-Javadoc) + * @see org.gluster.storage.management.gateway.utils.GlusterInterface#stopRebalance(java.lang.String, java.lang.String) + */ + @Override + public void stopRebalance(String serverName, String volumeName) { + String command = "gluster volume rebalance " + volumeName + " stop"; + serverUtil.executeOnServer(serverName, command); + } + + /** + * Performs given Brick Migration (replace-brick) Operation on given volume + * + * @param serverName + * The server on which the Gluster command will be executed. This must be part of the cluster to which + * the volume belongs. + * @param volumeName + * Volume on which the Brick Migration Operation is to be executed + * @param fromBrick + * The source Brick (being replaced) + * @param toBrick + * The destination Brick (which is replacing the source Brick) + * @param operation + * @return + */ + private String performBrickMigrationOperation(String serverName, String volumeName, String fromBrick, + String toBrick, String operation) { + String command = "gluster volume replace-brick " + volumeName + " " + fromBrick + " " + toBrick + " " + + operation; + return serverUtil.executeOnServer(serverName, command, String.class); + } + + /* + * (non-Javadoc) + * + * @see org.gluster.storage.management.gateway.utils.GlusterInterface#executeBrickMigration(java.lang.String, + * java.lang.String, java.lang.String, java.lang.String) + */ + @Override + public void startBrickMigration(String serverName, String volumeName, String fromBrick, String toBrick) { + performBrickMigrationOperation(serverName, volumeName, fromBrick, toBrick, "start"); + } + + /* + * (non-Javadoc) + * @see org.gluster.storage.management.gateway.services.GlusterInterface#pauseBrickMigration(java.lang.String, java.lang.String, java.lang.String, java.lang.String) + */ + @Override + public void pauseBrickMigration(String serverName, String volumeName, String fromBrick, String toBrick) { + performBrickMigrationOperation(serverName, volumeName, fromBrick, toBrick, "pause"); + } + + /* + * (non-Javadoc) + * @see org.gluster.storage.management.gateway.services.GlusterInterface#stopBrickMigration(java.lang.String, java.lang.String, java.lang.String, java.lang.String) + */ + @Override + public void stopBrickMigration(String serverName, String volumeName, String fromBrick, String toBrick) { + performBrickMigrationOperation(serverName, volumeName, fromBrick, toBrick, "abort"); + } + + /* + * (non-Javadoc) + * @see org.gluster.storage.management.gateway.services.GlusterInterface#commitBrickMigration(java.lang.String, java.lang.String, java.lang.String, java.lang.String) + */ + @Override + public void commitBrickMigration(String serverName, String volumeName, String fromBrick, String toBrick) { + performBrickMigrationOperation(serverName, volumeName, fromBrick, toBrick, "commit"); + } + + /* + * (non-Javadoc) + * @see org.gluster.storage.management.gateway.services.GlusterInterface#checkBrickMigrationStatus(java.lang.String, java.lang.String, java.lang.String, java.lang.String) + */ + @Override + public TaskStatus checkBrickMigrationStatus(String serverName, String volumeName, String fromBrick, String toBrick) { + String output = performBrickMigrationOperation(serverName, volumeName, fromBrick, toBrick, "status"); + + TaskStatus taskStatus = new TaskStatus(); + if (output.matches("^Number of files migrated.*Migration complete$") + || output.matches("^Number of files migrated = 0 .*Current file=")) { + // Note: Workaround - if no file in the volume brick to migrate, + // Gluster CLI is not giving proper (complete) status + taskStatus.setCode(Status.STATUS_CODE_COMMIT_PENDING); + taskStatus.setMessage(output.replaceAll("Migration complete", "Commit pending")); + } else if (output.matches("^Number of files migrated.*Current file=.*")) { + taskStatus.setCode(Status.STATUS_CODE_RUNNING); + } else if (output.matches("^replace brick has been paused.*")) { + taskStatus.setCode(Status.STATUS_CODE_PAUSE); + } else { + taskStatus.setCode(Status.STATUS_CODE_FAILURE); + } + + taskStatus.setMessage(output); + return taskStatus; + } + + /* (non-Javadoc) + * @see org.gluster.storage.management.gateway.utils.GlusterInterface#getVolumeOptionsInfo(java.lang.String) + */ + @Override + public VolumeOptionInfoListResponse getVolumeOptionsInfo(String serverName) { + return serverUtil.executeOnServer(serverName, "gluster volume set help-xml", VolumeOptionInfoListResponse.class); + } + + public void logRotate(String volumeName, List<String> brickList, String knownServer) { + if (brickList == null || brickList.size() > 0) { + for (String brickDir : brickList) { + serverUtil.executeOnServer(knownServer, "gluster volume log rotate " + volumeName + " " + brickDir); + } + } else { + serverUtil.executeOnServer(knownServer, "gluster volume log rotate " + volumeName); + } + } +} diff --git a/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/services/GlusterInterface.java b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/services/GlusterInterface.java new file mode 100644 index 00000000..71c9e081 --- /dev/null +++ b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/services/GlusterInterface.java @@ -0,0 +1,369 @@ +package org.gluster.storage.management.gateway.services; + +import java.util.List; +import java.util.Map; + +import org.gluster.storage.management.core.model.TaskStatus; +import org.gluster.storage.management.core.model.Volume; +import org.gluster.storage.management.core.model.Volume.TRANSPORT_TYPE; +import org.gluster.storage.management.core.model.Volume.VOLUME_TYPE; +import org.gluster.storage.management.core.response.VolumeOptionInfoListResponse; + + +/** + * Interface for interacting with GlusterFS. Every version of GlusterFS supported by the Gluster Management Gateway will + * have a corresponding implementation of this interface. + */ +public interface GlusterInterface { + + /** + * Returns the GlusterFS version on given server. + * + * @param serverName + * Server on which Gluster version is to be checked. + * @return + */ + public abstract String getVersion(String serverName); + + /** + * Adds the new server to an existing cluster. + * + * @param existingServer + * Server part of the existing cluster. + * @param newServer + * Server to be added to the cluster. + */ + public abstract void addServer(String existingServer, String newServer); + + /** + * Removes given server from the cluster by executing appropriate Gluster command on given server. + * + * @param existingServer + * Server part of the existing cluster. + * @param serverName + * Server to be removed from the cluster. + */ + public abstract void removeServer(String existingServer, String serverName); + + /** + * Starts the given volume by executing appropriate Gluster command on given server. + * + * @param volumeName + * Volume to be started. + * @param serverName + * Server on which the Gluster command is to be executed. This server must be part of the cluster to + * which the volume belongs. + * @param force + * Flag indicating whether the "force" option should be used for starting the Volume. This is typically + * used when Volume is already started, but at least one of its bricks is offline, and results in + * bringing up the offline bricks. + */ + public abstract void startVolume(String volumeName, String serverName, Boolean force); + + /** + * Stops the given volume by executing appropriate Gluster command on given server. + * + * @param volumeName + * Volume to be stopped. + * @param serverName + * Server on which the Gluster command is to be executed. This server must be part of the cluster to + * which the volume belongs. + * @param force + * Flag indicating whether the Volume should be stopped forcefully. This is typically used if the regular + * stop option fails because of issues like rebalance / brick migration / geo-replication being in + * progress. This results in forcefully stopping the volume, leaving the other processes intact. + */ + public abstract void stopVolume(String volumeName, String serverName, Boolean force); + + /** + * Resets volume options on the given volume by executing appropriate Gluster command on given server. + * + * @param volumeName + * Volume on which options are to be reset. + * @param serverName + * Server on which the Gluster command is to be executed. This server must be part of the cluster to + * which the volume belongs. + */ + public abstract void resetOptions(String volumeName, String serverName); + + /** + * Creates a volume on given volume using given properties. + * + * @param serverName + * Server on which the Gluster command for creating the volume will be executed. This must be part of the + * cluster in which the volume is to be created. + * @param volumeName + * Name of the volume. + * @param volumeType + * Type of the volume e.g. DISTRIBUTE, REPLICATE, STRIPE, etc. See {@link VOLUME_TYPE} for full list of + * valid values. + * @param transportType + * Transport type of the volume e.g. ETHERNET. See {@link TRANSPORT_TYPE} for full list of valid values. + * @param replOrStripeCount + * Replica Count or Stripe count depending on the volume type. Ignored in case of pure distribute volumes + * (no replicate, no stripe). + * @param bricks + * Comma separated list of volume brick directories in following format: <br> + * server1:dir1,server2:dir2,server3:dir3,...,servern:dirn + * @param accessProtocols + * Optional parameter indicating access protocols to be enabled for the volume. If empty/null, GLUSTERFS + * and NFS will be enabled. + * @param options + * A comma separated list of volume options to be set on the newly created volume in following format: <br> + * key1=value1,key2=value2,key3=value3,...,keyn=valuen + */ + public abstract void createVolume(String serverName, String volumeName, String volumeType, String transportType, + Integer replOrStripeCount, String bricks, String accessProtocols, String options); + + /** + * Creates / Sets the given options on the given volume by executing appropriate Gluster command on the given + * server. + * + * @param volumeName + * Volume on which the options are to be set. + * @param options + * Map containing the volume options to be set. Key = option key, Value = option value. + * @param serverName + * The server on which the Gluster command will be executed. This must be part of the cluster to which + * the volume belongs. + */ + public abstract void createOptions(String volumeName, Map<String, String> options, String serverName); + + /** + * Sets the given option on given volume by executing appropriate Gluster command on the given server. + * + * @param volumeName + * Volume on which the option is to be set. + * @param key + * Option key (name) + * @param value + * Option value + * @param serverName + * The server on which the Gluster command will be executed. This must be part of the cluster to which + * the volume belongs. + */ + public abstract void setOption(String volumeName, String key, String value, String serverName); + + /** + * Deletes the given volume by executing appropriate Gluster command on the given server. + * + * @param volumeName + * Volume to be deleted. + * @param serverName + * The server on which the Gluster command will be executed. This must be part of the cluster to which + * the volume belongs. + */ + public abstract void deleteVolume(String volumeName, String serverName); + + /** + * Fetches properties of the given Volume by executing appropriate Gluster command on the given server. + * + * @param volumeName + * Volume whose properties are to be fetched. + * @param serverName + * The server on which the Gluster command will be executed. This must be part of the cluster to which + * the volume belongs. + * @return A {@link Volume} object containing all properties of the given volume + */ + public abstract Volume getVolume(String volumeName, String serverName); + + /** + * Fetches the list of all volumes (along with their properties) by executing appropriate Gluster command on the + * given server. + * + * @param serverName + * The server on which the Gluster command will be executed. This must be part of the cluster to which + * the volume belongs. + * @return A list of {@link Volume} objects representing every volume present in the cluster to which the given + * server belongs. + */ + public abstract List<Volume> getAllVolumes(String serverName); + + /** + * Adds given list of bricks to given Volume by executing appropriate Gluster command on the + * given server. + * + * @param volumeName + * Volume to which the bricks are to be added. + * @param bricks + * List of bricks to be added, each in the format serverName:brickDirectory + * @param serverName + * The server on which the Gluster command will be executed. This must be part of the cluster to which + * the volume belongs. + */ + public abstract void addBricks(String volumeName, List<String> bricks, String serverName); + + /** + * Removes given list of bricks from given volume by executing appropriate Gluster command on the + * given server. + * + * @param volumeName + * Volume from which the bricks are to be removed + * @param bricks + * List of bricks to be removed, each in the format serverName:brickDirectory + * @param serverName + * The server on which the Gluster command will be executed. This must be part of the cluster to which + * the volume belongs. + */ + public abstract void removeBricks(String volumeName, List<String> bricks, String serverName); + + /** + * Returns the log location of given brick of given volume by executing appropriate Gluster command on the + * given server. + * + * @param volumeName + * Volume for which log location is to be fetched. + * @param brickName + * Brick of the volume for which log location is to be fetched. + * @param serverName + * The server on which the Gluster command will be executed. This must be part of the cluster to which + * the volume belongs. + * @return Full path of the log file location (directory) for the given Volume Brick. + */ + public abstract String getLogLocation(String volumeName, String brickName, String serverName); + + /** + * Returns the log file name for given brick directory. + * + * @param serverName + * Server to which the brick belongs + * @param brickDir + * Brick directory for which log file name is to be returned. + * @return The log file name (without path) for the given brick directory. + */ + public abstract String getLogFileNameForBrickDir(String serverName, String brickDir); + + /** + * Checks the status of "Rebalance" operation on given Volume by executing appropriate Gluster command on the + * given server. + * + * @param serverName + * The server on which the Gluster command will be executed. This must be part of the cluster to which + * the volume belongs. + * @param volumeName + * Volume whose rebalance status is to be checked. + * @return Object of {@link TaskStatus} representing the status of Volume Rebalance. + */ + public abstract TaskStatus checkRebalanceStatus(String serverName, String volumeName); + + /** + * Stops "Rebalance" operation running on given Volume by executing appropriate Gluster command on the + * given server. + * + * @param serverName + * The server on which the Gluster command will be executed. This must be part of the cluster to which + * the volume belongs. + * @param volumeName + * Volume whose Rebalance is to be stopped. + */ + public abstract void stopRebalance(String serverName, String volumeName); + + /** + * Starts Brick Migration (replace-brick) on given Volume by executing appropriate Gluster command on the + * given server. + * + * @param serverName + * The server on which the Gluster command will be executed. This must be part of the cluster to which + * the volume belongs. + * @param volumeName + * Volume whose Brick is to be migrated/replaced. + * @param fromBrick + * The source Brick (to be replaced). + * @param toBrick + * The destination Brick (will replace the source Brick). + */ + public abstract void startBrickMigration(String serverName, String volumeName, String fromBrick, String toBrick); + + /** + * Pauses Brick Migration (replace-brick) running on given Volume by executing appropriate Gluster command on the + * given server. + * + * @param serverName + * The server on which the Gluster command will be executed. This must be part of the cluster to which + * the volume belongs. + * @param volumeName + * Volume whose Brick is being migrated/replaced. + * @param fromBrick + * The source Brick (being replaced). + * @param toBrick + * The destination Brick (which is replacing the source Brick). + */ + public abstract void pauseBrickMigration(String serverName, String volumeName, String fromBrick, String toBrick); + + /** + * Aborts Brick Migration (replace-brick) running on given Volume by executing appropriate Gluster command on the + * given server. + * + * @param serverName + * The server on which the Gluster command will be executed. This must be part of the cluster to which + * the volume belongs. + * @param volumeName + * Volume whose Brick is being migrated/replaced. + * @param fromBrick + * The source Brick (being replaced). + * @param toBrick + * The destination Brick (which is replacing the source Brick) + */ + public abstract void stopBrickMigration(String serverName, String volumeName, String fromBrick, String toBrick); + + /** + * Commits Brick Migration (replace-brick) running on given Volume by executing appropriate Gluster command on the + * given server. + * + * @param serverName + * The server on which the Gluster command will be executed. This must be part of the cluster to which + * the volume belongs. + * @param volumeName + * Volume whose Brick is being migrated/replaced. + * @param fromBrick + * The source Brick (being replaced). + * @param toBrick + * The destination Brick (which is replacing the source Brick) + */ + public abstract void commitBrickMigration(String serverName, String volumeName, String fromBrick, String toBrick); + + /** + * Checks status of Brick Migration (replace-brick) running on given Volume by executing appropriate Gluster command + * on the given server. + * + * @param serverName + * The server on which the Gluster command will be executed. This must be part of the cluster to which + * the volume belongs. + * @param volumeName + * Volume whose Brick is being migrated/replaced. + * @param fromBrick + * The source Brick (being replaced). + * @param toBrick + * The destination Brick (which is replacing the source Brick) + * @return A {@link TaskStatus} object representing the status of Brick Migration + */ + public abstract TaskStatus checkBrickMigrationStatus(String serverName, String volumeName, String fromBrick, + String toBrick); + + /** + * Returns information about all the supported Volume Options by executing appropriate Gluster command + * on the given server. + * + * @param serverName + * The server on which the Gluster command will be executed. This must be part of the cluster to which + * the volume belongs. + * @return A {@link VolumeOptionInfoListResponse} object containing information about each and every supported + * Volume Option + */ + public abstract VolumeOptionInfoListResponse getVolumeOptionsInfo(String serverName); + + /** + * Rotates the logs for given Bricks of given Volume by executing appropriate Gluster command + * on the given server. + * + * @param volumeName + * Volume whose logs are to be rotated. + * @param brickList + * List of bricks whose logs are to be rotated, each in the format serverName:brickDirectory <br> + * This is an optional parameter. If null or empty, all logs of the Volume will be rotated. + * @param serverName + * The server on which the Gluster command will be executed. This must be part of the cluster to which + * the volume belongs. + */ + public abstract void logRotate(String volumeName, List<String> brickList, String serverName); +}
\ No newline at end of file diff --git a/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/services/GlusterInterfaceService.java b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/services/GlusterInterfaceService.java new file mode 100644 index 00000000..07e152b8 --- /dev/null +++ b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/services/GlusterInterfaceService.java @@ -0,0 +1,256 @@ +/** + * GlusterInterfaceService.java + * + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Gluster Management Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + */ +package org.gluster.storage.management.gateway.services; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.gluster.storage.management.core.model.TaskStatus; +import org.gluster.storage.management.core.model.Volume; +import org.gluster.storage.management.core.response.VolumeOptionInfoListResponse; +import org.springframework.stereotype.Component; + + +@Component +public class GlusterInterfaceService extends AbstractGlusterInterface { + private HashMap<String, GlusterInterface> glusterInterfaces = new HashMap<String, GlusterInterface>(); + + /** + * Returns an instance of the Gluster Interface for given version of GlusterFS + * @param glusterFsVersion + * @return + */ + private GlusterInterface getGlusterInterfaceForVersion(String glusterFsVersion) { + GlusterInterface glusterInterface = glusterInterfaces.get(glusterFsVersion); + if(glusterInterface != null) { + return glusterInterface; + } + + glusterInterface = serverUtil.getBean(Gluster323InterfaceService.class); + glusterInterfaces.put(glusterFsVersion, glusterInterface); + return glusterInterface; + } + + /** + * Returns an instance of Gluster Interface for the version of GlusterFS installed on given server. + * + * @param serverName + * @return + */ + private GlusterInterface getGlusterInterface(String serverName) { + return getGlusterInterfaceForVersion(getVersion(serverName)); + } + + /* (non-Javadoc) + * @see org.gluster.storage.management.gateway.utils.GlusterInterface#addServer(java.lang.String, java.lang.String) + */ + @Override + public void addServer(String existingServer, String newServer) { + getGlusterInterface(existingServer).addServer(existingServer, newServer); + } + + /* (non-Javadoc) + * @see org.gluster.storage.management.gateway.utils.GlusterInterface#startVolume(java.lang.String, java.lang.String) + */ + @Override + public void startVolume(String volumeName, String knownServer, Boolean force) { + getGlusterInterface(knownServer).startVolume(volumeName, knownServer, force); + } + + /* (non-Javadoc) + * @see org.gluster.storage.management.gateway.utils.GlusterInterface#stopVolume(java.lang.String, java.lang.String) + */ + @Override + public void stopVolume(String volumeName, String knownServer, Boolean force) { + getGlusterInterface(knownServer).stopVolume(volumeName, knownServer, force); + } + + public void logRotate(String volumeName, List<String> brickList, String knownServer) { + getGlusterInterface(knownServer).logRotate(volumeName, brickList, knownServer); + } + + /* (non-Javadoc) + * @see org.gluster.storage.management.gateway.utils.GlusterInterface#resetOptions(java.lang.String, java.lang.String) + */ + @Override + public void resetOptions(String volumeName, String knownServer) { + getGlusterInterface(knownServer).resetOptions(volumeName, knownServer); + } + + /* (non-Javadoc) + * @see org.gluster.storage.management.gateway.utils.GlusterInterface#createVolume(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.Integer, java.lang.String, java.lang.String, java.lang.String) + */ + @Override + public void createVolume(String knownServer, String volumeName, String volumeTypeStr, String transportTypeStr, + Integer count, String bricks, String accessProtocols, String options) { + getGlusterInterface(knownServer).createVolume(knownServer, volumeName, volumeTypeStr, transportTypeStr, count, + bricks, accessProtocols, options); + } + + /* (non-Javadoc) + * @see org.gluster.storage.management.gateway.utils.GlusterInterface#createOptions(java.lang.String, java.util.Map, java.lang.String) + */ + @Override + public void createOptions(String volumeName, Map<String, String> options, String knownServer) { + getGlusterInterface(knownServer).createOptions(volumeName, options, knownServer); + } + + /* (non-Javadoc) + * @see org.gluster.storage.management.gateway.utils.GlusterInterface#setOption(java.lang.String, java.lang.String, java.lang.String, java.lang.String) + */ + @Override + public void setOption(String volumeName, String key, String value, String knownServer) { + getGlusterInterface(knownServer).setOption(volumeName, key, value, knownServer); + } + + /* (non-Javadoc) + * @see org.gluster.storage.management.gateway.utils.GlusterInterface#deleteVolume(java.lang.String, java.lang.String) + */ + @Override + public void deleteVolume(String volumeName, String knownServer) { + getGlusterInterface(knownServer).deleteVolume(volumeName, knownServer); + } + + /* (non-Javadoc) + * @see org.gluster.storage.management.gateway.utils.GlusterInterface#getVolume(java.lang.String, java.lang.String) + */ + @Override + public Volume getVolume(String volumeName, String knownServer) { + return getGlusterInterface(knownServer).getVolume(volumeName, knownServer); + } + + /* (non-Javadoc) + * @see org.gluster.storage.management.gateway.utils.GlusterInterface#getAllVolumes(java.lang.String) + */ + @Override + public List<Volume> getAllVolumes(String knownServer) { + return getGlusterInterface(knownServer).getAllVolumes(knownServer); + } + + /* (non-Javadoc) + * @see org.gluster.storage.management.gateway.utils.GlusterInterface#addBricks(java.lang.String, java.util.List, java.lang.String) + */ + @Override + public void addBricks(String volumeName, List<String> bricks, String knownServer) { + getGlusterInterface(knownServer).addBricks(volumeName, bricks, knownServer); + } + + /* (non-Javadoc) + * @see org.gluster.storage.management.gateway.utils.GlusterInterface#getLogLocation(java.lang.String, java.lang.String, java.lang.String) + */ + @Override + public String getLogLocation(String volumeName, String brickName, String knownServer) { + return getGlusterInterface(knownServer).getLogLocation(volumeName, brickName, knownServer); + } + + /* (non-Javadoc) + * @see org.gluster.storage.management.gateway.utils.GlusterInterface#getLogFileNameForBrickDir(java.lang.String) + */ + @Override + public String getLogFileNameForBrickDir(String serverName, String brickDir) { + return getGlusterInterface(serverName).getLogFileNameForBrickDir(serverName, brickDir); + } + + /* (non-Javadoc) + * @see org.gluster.storage.management.gateway.utils.GlusterInterface#removeBricks(java.lang.String, java.util.List, java.lang.String) + */ + @Override + public void removeBricks(String volumeName, List<String> bricks, String knownServer) { + getGlusterInterface(knownServer).removeBricks(volumeName, bricks, knownServer); + } + + /* (non-Javadoc) + * @see org.gluster.storage.management.gateway.utils.GlusterInterface#removeServer(java.lang.String, java.lang.String) + */ + @Override + public void removeServer(String existingServer, String serverName) { + getGlusterInterface(serverName).removeServer(existingServer, serverName); + } + + /* (non-Javadoc) + * @see org.gluster.storage.management.gateway.utils.GlusterInterface#checkRebalanceStatus(java.lang.String, java.lang.String) + */ + @Override + public TaskStatus checkRebalanceStatus(String serverName, String volumeName) { + return getGlusterInterface(serverName).checkRebalanceStatus(serverName, volumeName); + } + + /* (non-Javadoc) + * @see org.gluster.storage.management.gateway.utils.GlusterInterface#stopRebalance(java.lang.String, java.lang.String) + */ + @Override + public void stopRebalance(String serverName, String volumeName) { + getGlusterInterface(serverName).stopRebalance(serverName, volumeName); + } + + /* (non-Javadoc) + * @see org.gluster.storage.management.gateway.utils.GlusterInterface#executeBrickMigration(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String) + */ + @Override + public void startBrickMigration(String onlineServerName, String volumeName, String fromBrick, String toBrick) { + getGlusterInterface(onlineServerName).startBrickMigration(onlineServerName, volumeName, fromBrick, toBrick); + } + + /* + * (non-Javadoc) + * @see org.gluster.storage.management.gateway.services.GlusterInterface#pauseBrickMigration(java.lang.String, java.lang.String, java.lang.String, java.lang.String) + */ + @Override + public void pauseBrickMigration(String serverName, String volumeName, String fromBrick, String toBrick) { + getGlusterInterface(serverName).pauseBrickMigration(serverName, volumeName, fromBrick, toBrick); + } + + /* + * (non-Javadoc) + * @see org.gluster.storage.management.gateway.services.GlusterInterface#stopBrickMigration(java.lang.String, java.lang.String, java.lang.String, java.lang.String) + */ + @Override + public void stopBrickMigration(String serverName, String volumeName, String fromBrick, String toBrick) { + getGlusterInterface(serverName).stopBrickMigration(serverName, volumeName, fromBrick, toBrick); + } + + /* + * (non-Javadoc) + * @see org.gluster.storage.management.gateway.services.GlusterInterface#commitBrickMigration(java.lang.String, java.lang.String, java.lang.String, java.lang.String) + */ + @Override + public void commitBrickMigration(String serverName, String volumeName, String fromBrick, String toBrick) { + getGlusterInterface(serverName).commitBrickMigration(serverName, volumeName, fromBrick, toBrick); + } + + /* + * (non-Javadoc) + * @see org.gluster.storage.management.gateway.services.GlusterInterface#checkBrickMigrationStatus(java.lang.String, java.lang.String, java.lang.String, java.lang.String) + */ + @Override + public TaskStatus checkBrickMigrationStatus(String serverName, String volumeName, String fromBrick, String toBrick) { + return getGlusterInterface(serverName).checkBrickMigrationStatus(serverName, volumeName, fromBrick, toBrick); + } + + /* (non-Javadoc) + * @see org.gluster.storage.management.gateway.utils.GlusterInterface#getVolumeOptionsInfo(java.lang.String) + */ + @Override + public VolumeOptionInfoListResponse getVolumeOptionsInfo(String serverName) { + return getGlusterInterface(serverName).getVolumeOptionsInfo(serverName); + } +} diff --git a/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/services/GlusterServerService.java b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/services/GlusterServerService.java new file mode 100644 index 00000000..58482958 --- /dev/null +++ b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/services/GlusterServerService.java @@ -0,0 +1,530 @@ +/******************************************************************************* + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Gluster Management Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + *******************************************************************************/ +package org.gluster.storage.management.gateway.services; + +import static org.gluster.storage.management.core.constants.RESTConstants.FORM_PARAM_SERVER_NAME; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.log4j.Logger; +import org.gluster.storage.management.core.constants.CoreConstants; +import org.gluster.storage.management.core.exceptions.ConnectionException; +import org.gluster.storage.management.core.exceptions.GlusterRuntimeException; +import org.gluster.storage.management.core.exceptions.GlusterValidationException; +import org.gluster.storage.management.core.model.GlusterServer; +import org.gluster.storage.management.core.model.Server; +import org.gluster.storage.management.core.model.Server.SERVER_STATUS; +import org.gluster.storage.management.core.response.StringListResponse; +import org.gluster.storage.management.core.utils.GlusterCoreUtil; +import org.gluster.storage.management.core.utils.ProcessUtil; +import org.gluster.storage.management.core.utils.StringUtil; +import org.gluster.storage.management.gateway.data.ClusterInfo; +import org.gluster.storage.management.gateway.data.ServerInfo; +import org.gluster.storage.management.gateway.utils.ServerUtil; +import org.gluster.storage.management.gateway.utils.SshUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + + +/** + * + */ +@Component +public class GlusterServerService { + private static final String HOSTNAME_PFX = "Hostname:"; + private static final String UUID_PFX = "Uuid:"; + private static final String STATE_PFX = "State:"; + private static final String GLUSTER_SERVER_STATUS_ONLINE = "Peer in Cluster (Connected)"; + private static final String GLUSTERD_INFO_FILE = "/etc/glusterd/glusterd.info"; + + @Autowired + protected ServerUtil serverUtil; + + @Autowired + private ClusterService clusterService; + + @Autowired + private GlusterInterfaceService glusterUtil; + + @Autowired + private SshUtil sshUtil; + + @Autowired + private VolumeService volumeService; + + @Autowired + private DiscoveredServerService discoveredServerService; + + private static final Logger logger = Logger.getLogger(GlusterServerService.class); + + public List<GlusterServer> getGlusterServers(String clusterName, boolean fetchDetails, Integer maxCount, + String previousServerName) { + List<GlusterServer> glusterServers; + GlusterServer onlineServer = clusterService.getOnlineServer(clusterName); + if (onlineServer == null) { + throw new GlusterRuntimeException("No online servers found in cluster [" + clusterName + "]"); + } + + try { + glusterServers = getGlusterServers(clusterName, onlineServer, fetchDetails, maxCount, previousServerName); + } catch (Exception e) { + // check if online server has gone offline. If yes, try again one more time. + if (e instanceof ConnectionException || serverUtil.isServerOnline(onlineServer) == false) { + // online server has gone offline! try with a different one. + onlineServer = clusterService.getNewOnlineServer(clusterName); + if (onlineServer == null) { + throw new GlusterRuntimeException("No online servers found in cluster [" + clusterName + "]"); + } + glusterServers = getGlusterServers(clusterName, onlineServer, fetchDetails, maxCount, previousServerName); + } else { + throw new GlusterRuntimeException(e.getMessage()); + } + + } + return glusterServers; + } + + private List<GlusterServer> getGlusterServers(String clusterName, GlusterServer onlineServer, boolean fetchDetails, + Integer maxCount, String previousServerName) { + List<GlusterServer> glusterServers; + try { + glusterServers = getGlusterServers(onlineServer.getName()); + } catch (Exception e) { + // check if online server has gone offline. If yes, try again one more time. + if (e instanceof ConnectionException || serverUtil.isServerOnline(onlineServer) == false) { + // online server has gone offline! try with a different one. + onlineServer = clusterService.getNewOnlineServer(clusterName); + if (onlineServer == null) { + throw new GlusterRuntimeException("No online servers found in cluster [" + clusterName + "]"); + } + glusterServers = getGlusterServers(onlineServer.getName()); + } else { + throw new GlusterRuntimeException(e.getMessage()); + } + } + + // skip the servers by maxCount / previousServerName + glusterServers = GlusterCoreUtil.skipEntities(glusterServers, maxCount, previousServerName); + + if (fetchDetails) { + String errMsg = fetchDetailsOfServers(Collections.synchronizedList(glusterServers)); + if (!errMsg.isEmpty()) { + throw new GlusterRuntimeException("Couldn't fetch details for server(s): " + errMsg); + } + } + return glusterServers; + } + + /* (non-Javadoc) + * @see org.gluster.storage.management.gateway.utils.GlusterInterface#getGlusterServer(org.gluster.storage.management.core.model.GlusterServer, java.lang.String) + */ + public GlusterServer getGlusterServer(String onlineServer, String serverName) { + List<GlusterServer> servers = getGlusterServers(onlineServer); + for (GlusterServer server : servers) { + if (server.getName().equalsIgnoreCase(serverName)) { + return server; + } + } + + // Server not found. It's possible that the server name returned by glusterfs is actually IP address + // Hence fetch details of all servers and then compare the host names again. + String errMsg = fetchDetailsOfServers(Collections.synchronizedList(servers)); + if (!errMsg.isEmpty()) { + throw new GlusterRuntimeException("Couldn't fetch details for server(s): " + errMsg); + } + for (GlusterServer server : servers) { + if (server.getName().equalsIgnoreCase(serverName)) { + return server; + } + } + + // still not found! + return null; + } + + private String getUuid(String serverName) { + return serverUtil.executeOnServer(serverName, "cat " + GLUSTERD_INFO_FILE, String.class).split("=")[1]; + } + + /* (non-Javadoc) + * @see org.gluster.storage.management.gateway.utils.GlusterInterface#getGlusterServers(org.gluster.storage.management.core.model.GlusterServer) + */ + public List<GlusterServer> getGlusterServers(String knownServerName) { + String output = getPeerStatus(knownServerName); + + GlusterServer knownServer = new GlusterServer(knownServerName); + knownServer.setUuid(getUuid(knownServerName)); + + List<GlusterServer> glusterServers = new ArrayList<GlusterServer>(); + glusterServers.add(knownServer); + + GlusterServer server = null; + boolean foundHost = false; + boolean foundUuid = false; + for (String line : output.split(CoreConstants.NEWLINE)) { + if (foundHost && foundUuid) { + // Host and UUID is found, we should look for state + String state = StringUtil.extractToken(line, STATE_PFX); + if (state != null) { + server.setStatus(state.contains(GLUSTER_SERVER_STATUS_ONLINE) ? SERVER_STATUS.ONLINE + : SERVER_STATUS.OFFLINE); + // Completed populating current server. Add it to the list + // and reset all related variables. + glusterServers.add(server); + + foundHost = false; + foundUuid = false; + server = null; + } + } else if (foundHost) { + // Host is found, look for UUID + String uuid = StringUtil.extractToken(line, UUID_PFX); + if (uuid != null) { + server.setUuid(uuid); + foundUuid = true; + } + } else { + // Look for the next host + if (server == null) { + server = new GlusterServer(); + } + String hostName = StringUtil.extractToken(line, HOSTNAME_PFX); + if (hostName != null) { + server.setName(hostName); + foundHost = true; + } + } + + } + return glusterServers; + } + + /** + * @param knownServer + * A known server on which the gluster command will be executed to fetch peer status + * @return Outout of the "gluster peer status" command + */ + private String getPeerStatus(String knownServer) { + return serverUtil.executeOnServer(knownServer, "gluster peer status", String.class); + } + + private String fetchDetailsOfServers(List<GlusterServer> glusterServers) { + try { + List<String> errors = Collections.synchronizedList(new ArrayList<String>()); + + List<Thread> threads = createThreads(glusterServers, errors); + ProcessUtil.waitForThreads(threads); + + return prepareErrorMessage(errors); + } catch(InterruptedException e) { + String errMsg = "Exception while fetching details of servers! Error: [" + e.getMessage() + "]"; + logger.error(errMsg, e); + throw new GlusterRuntimeException(errMsg, e); + } + } + + private String prepareErrorMessage(List<String> errors) { + String errMsg = ""; + for(String error : errors) { + if(!errMsg.isEmpty()) { + errMsg += CoreConstants.NEWLINE; + } + errMsg += error; + } + + return errMsg; + } + + /** + * Creates threads that will run in parallel and fetch details of given gluster servers + * @param discoveredServers The list to be populated with details of gluster servers + * @param errors List to be populated with errors if any + * @return + * @throws InterruptedException + */ + private List<Thread> createThreads(List<GlusterServer> glusterServers, List<String> errors) + throws InterruptedException { + List<Thread> threads = new ArrayList<Thread>(); + for (int i = glusterServers.size()-1; i >= 0 ; i--) { + Thread thread = new ServerDetailsThread(glusterServers.get(i), errors); + threads.add(thread); + thread.start(); + if(i >= 5 && i % 5 == 0) { + // After every 5 servers, wait for 1 second so that we don't end up with too many running threads + Thread.sleep(1000); + } + } + return threads; + } + + public class ServerDetailsThread extends Thread { + private List<String> errors; + private GlusterServer server; + private final Logger logger = Logger.getLogger(ServerDetailsThread.class); + + /** + * Private constructor called on each thread + * @param server The server whose details are to be fetched by this thread + * @param errors + */ + private ServerDetailsThread(GlusterServer server, List<String> errors) { + this.errors = errors; + this.server = server; + } + + @Override + public void run() { + try { + logger.info("fetching details of server [" + server.getName() + "] - start"); + serverUtil.fetchServerDetails(server); + logger.info("fetching details of server [" + server.getName() + "] - end"); + } catch (Exception e) { + logger.error("fetching details of server [" + server.getName() + "] - error", e); + errors.add(server.getName() + " : [" + e.getMessage() + "]"); + } + } + } + + public GlusterServer getGlusterServer(String clusterName, String serverName, Boolean fetchDetails) { + if (clusterName == null || clusterName.isEmpty()) { + throw new GlusterValidationException("Cluster name must not be empty!"); + } + + if (serverName == null || serverName.isEmpty()) { + throw new GlusterValidationException("Server name must not be empty!"); + } + + ClusterInfo cluster = clusterService.getCluster(clusterName); + if (cluster == null) { + throw new GlusterRuntimeException("Cluster [" + clusterName + "] not found!"); + } + + GlusterServer onlineServer = clusterService.getOnlineServer(clusterName); + if (onlineServer == null) { + throw new GlusterRuntimeException("No online servers found in cluster [" + clusterName + "]"); + } + + return getGlusterServer(clusterName, serverName, onlineServer, fetchDetails); + } + + private GlusterServer getGlusterServer(String clusterName, String serverName, GlusterServer onlineServer, + Boolean fetchDetails) { + GlusterServer server = null; + try { + server = getGlusterServer(onlineServer.getName(), serverName); + } catch (Exception e) { + // check if online server has gone offline. If yes, try again one more time. + if (e instanceof ConnectionException || serverUtil.isServerOnline(onlineServer) == false) { + // online server has gone offline! try with a different one. + onlineServer = clusterService.getNewOnlineServer(clusterName); + if (onlineServer == null) { + throw new GlusterRuntimeException("No online servers found in cluster [" + clusterName + "]"); + } + server = getGlusterServer(onlineServer.getName(), serverName); + } else { + throw new GlusterRuntimeException(e.getMessage()); + } + } + + if (fetchDetails && server.isOnline()) { + serverUtil.fetchServerDetails(server); + } + return server; + } + + public boolean isValidServer(String clusterName, String serverName) { + try { + GlusterServer server = getGlusterServer(clusterName, serverName, false); + return server != null; + } catch(Exception e) { + return false; + } + } + + public void removeServerFromCluster(String clusterName, String serverName) { + if (clusterName == null || clusterName.isEmpty()) { + throw new GlusterValidationException("Cluster name must not be empty!"); + } + + if (serverName == null || serverName.isEmpty()) { + throw new GlusterValidationException("Server name must not be empty!"); + } + + ClusterInfo cluster = clusterService.getCluster(clusterName); + if (cluster == null) { + throw new GlusterValidationException("Cluster [" + clusterName + "] not found!"); + } + + List<ServerInfo> servers = cluster.getServers(); + if (servers == null || servers.isEmpty() || !containsServer(servers, serverName)) { + throw new GlusterValidationException("Server [" + serverName + "] is not attached to cluster [" + + clusterName + "]!"); + } + + if (servers.size() == 1) { + // Only one server mapped to the cluster, no "peer detach" required. + // remove the cached online server for this cluster if present + clusterService.removeOnlineServer(clusterName); + } else { + // get an online server that is not same as the server being removed + GlusterServer onlineServer = clusterService.getOnlineServer(clusterName, serverName); + if (onlineServer == null) { + throw new GlusterRuntimeException("No online server found in cluster [" + clusterName + "]"); + } + + try { + glusterUtil.removeServer(onlineServer.getName(), serverName); + } catch (Exception e) { + // check if online server has gone offline. If yes, try again one more time. + if (e instanceof ConnectionException || serverUtil.isServerOnline(onlineServer) == false) { + // online server has gone offline! try with a different one. + onlineServer = clusterService.getNewOnlineServer(clusterName, serverName); + if (onlineServer == null) { + throw new GlusterRuntimeException("No online server found in cluster [" + clusterName + "]"); + } + glusterUtil.removeServer(onlineServer.getName(), serverName); + } else { + throw new GlusterRuntimeException(e.getMessage()); + } + } + + clusterService.unmapServerFromCluster(clusterName, serverName); + + // since the server is removed from the cluster, it is now available to be added to other clusters. + // Hence add it back to the discovered servers list. + discoveredServerService.addDiscoveredServer(serverName); + + try { + if (serverUtil.isServerOnline(new Server(serverName))) { + volumeService.clearCifsConfiguration(clusterName, onlineServer.getName(), serverName); + } + } catch (Exception e1) { + throw new GlusterRuntimeException( + "Server removed from cluster, however deleting cifs configuration failed ! [ " + + e1.getMessage() + "]"); + } + if (onlineServer.getName().equals(serverName)) { + // since the cached server has been removed from the cluster, remove it from the cache + clusterService.removeOnlineServer(clusterName); + } + } + } + + private boolean containsServer(List<ServerInfo> servers, String serverName) { + for (ServerInfo server : servers) { + if (server.getName().toUpperCase().equals(serverName.toUpperCase())) { + return true; + } + } + return false; + } + + /** + * Adds given server to cluster and returns its host name. e.g. If serverName passed is an IP address, this method + * will return the host name of the machine with given IP address. + * + * @param clusterName + * @param serverName + * @return + */ + public String addServerToCluster(String clusterName, String serverName) { + if (clusterName == null || clusterName.isEmpty()) { + throw new GlusterValidationException("Cluster name must not be empty!"); + } + + if (serverName == null || serverName.isEmpty()) { + throw new GlusterValidationException("Parameter [" + FORM_PARAM_SERVER_NAME + "] is missing in request!"); + } + + ClusterInfo cluster = clusterService.getCluster(clusterName); + if (cluster == null) { + throw new GlusterValidationException("Cluster [" + clusterName + "] not found!"); + } + + boolean publicKeyInstalled = sshUtil.isPublicKeyInstalled(serverName); + if (!publicKeyInstalled && !sshUtil.hasDefaultPassword(serverName)) { + // public key not installed, default password doesn't work. return with error. + throw new GlusterRuntimeException("Gluster Management Gateway uses the default password to set up keys on the server." + + CoreConstants.NEWLINE + "However it seems that the password on server [" + serverName + + "] has been changed manually." + CoreConstants.NEWLINE + + "Please reset it back to the standard default password and try again."); + } + + String hostName = serverUtil.fetchHostName(serverName); + List<ServerInfo> servers = cluster.getServers(); + if (servers != null && !servers.isEmpty()) { + // cluster has at least one existing server, so that peer probe can be performed + performAddServer(clusterName, hostName); + } else { + // this is the first server to be added to the cluster, which means no + // gluster CLI operation required. just add it to the cluster-server mapping + } + + // add the cluster-server mapping + clusterService.mapServerToCluster(clusterName, hostName); + + // since the server is added to a cluster, it should not more be considered as a + // discovered server available to other clusters + discoveredServerService.removeDiscoveredServer(hostName); + + if (!publicKeyInstalled) { + try { + // install public key (this will also disable password based ssh login) + sshUtil.installPublicKey(hostName); + } catch (Exception e) { + throw new GlusterRuntimeException("Public key could not be installed on [" + hostName + "]! Error: [" + + e.getMessage() + "]"); + } + } + return hostName; + } + + private void performAddServer(String clusterName, String serverName) { + GlusterServer onlineServer = clusterService.getOnlineServer(clusterName); + if (onlineServer == null) { + throw new GlusterRuntimeException("No online server found in cluster [" + clusterName + "]"); + } + + try { + glusterUtil.addServer(onlineServer.getName(), serverName); + } catch (Exception e) { + // check if online server has gone offline. If yes, try again one more time. + if (e instanceof ConnectionException || serverUtil.isServerOnline(onlineServer) == false) { + // online server has gone offline! try with a different one. + onlineServer = clusterService.getNewOnlineServer(clusterName); + if (onlineServer == null) { + throw new GlusterRuntimeException("No online server found in cluster [" + clusterName + "]"); + } + glusterUtil.addServer(onlineServer.getName(), serverName); + } else { + throw new GlusterRuntimeException(e.getMessage()); + } + } + } + + public List<String> getFsTypes(String clusterName, String serverName) { + if (isValidServer(clusterName, serverName)) { + return serverUtil.getFsTypes(serverName); + } else { + throw new GlusterRuntimeException(serverName + " does not belong to the cluster [" + clusterName + "]"); + } + } +} diff --git a/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/services/VolumeService.java b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/services/VolumeService.java new file mode 100644 index 00000000..4235d674 --- /dev/null +++ b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/services/VolumeService.java @@ -0,0 +1,984 @@ +/******************************************************************************* + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Gluster Management Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + *******************************************************************************/ +package org.gluster.storage.management.gateway.services; + +import static org.gluster.storage.management.core.constants.RESTConstants.QUERY_PARAM_BRICKS; +import static org.gluster.storage.management.core.constants.RESTConstants.TASK_START; +import static org.gluster.storage.management.core.constants.RESTConstants.TASK_STOP; + +import java.io.File; +import java.io.FileOutputStream; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.List; + +import org.apache.log4j.Logger; +import org.gluster.storage.management.core.constants.CoreConstants; +import org.gluster.storage.management.core.exceptions.ConnectionException; +import org.gluster.storage.management.core.exceptions.GlusterRuntimeException; +import org.gluster.storage.management.core.exceptions.GlusterValidationException; +import org.gluster.storage.management.core.model.Brick; +import org.gluster.storage.management.core.model.GlusterServer; +import org.gluster.storage.management.core.model.Volume; +import org.gluster.storage.management.core.model.VolumeLogMessage; +import org.gluster.storage.management.core.model.Server.SERVER_STATUS; +import org.gluster.storage.management.core.model.Volume.NAS_PROTOCOL; +import org.gluster.storage.management.core.model.Volume.VOLUME_STATUS; +import org.gluster.storage.management.core.model.Volume.VOLUME_TYPE; +import org.gluster.storage.management.core.response.LogMessageListResponse; +import org.gluster.storage.management.core.response.VolumeOptionInfoListResponse; +import org.gluster.storage.management.core.utils.DateUtil; +import org.gluster.storage.management.core.utils.FileUtil; +import org.gluster.storage.management.core.utils.GlusterCoreUtil; +import org.gluster.storage.management.core.utils.ProcessResult; +import org.gluster.storage.management.core.utils.ProcessUtil; +import org.gluster.storage.management.gateway.data.ClusterInfo; +import org.gluster.storage.management.gateway.resources.v1_0.TasksResource; +import org.gluster.storage.management.gateway.tasks.MigrateBrickTask; +import org.gluster.storage.management.gateway.tasks.RebalanceVolumeTask; +import org.gluster.storage.management.gateway.utils.ServerUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + + +/** + * + */ +@Component +public class VolumeService { + private static final String ALL_SERVERS_FILE_NAME = "servers"; + private static final String VOLUME_GET_CIFS_USERS_SCRIPT = "get_volume_user_cifs.py"; + private static final String VOLUME_CIFS_GRUN_SCRIPT = "grun.py"; + private static final String VOLUME_CREATE_CIFS_SCRIPT = "create_volume_cifs_all.py"; + private static final String VOLUME_MODIFY_CIFS_SCRIPT = "update_volume_cifs_all.py"; + private static final String VOLUME_START_CIFS_PEER_SCRIPT = "start_volume_cifs.py"; + private static final String VOLUME_STOP_CIFS_PEER_SCRIPT = "stop_volume_cifs.py"; + private static final String VOLUME_DELETE_CIFS_SCRIPT = "delete_volume_cifs_all.py"; + private static final String VOLUME_BRICK_LOG_SCRIPT = "get_volume_brick_log.py"; + private static final String VOLUME_DIRECTORY_CLEANUP_SCRIPT = "clear_volume_directory.py"; + private static final String REMOVE_SERVER_VOLUME_CIFS_CONFIG = "remove_server_volume_cifs_config.py"; + private static final String ALL_ONLINE_VOLUMES_FILE_NAME = "volumes"; + + @Autowired + private ClusterService clusterService; + + @Autowired + private GlusterInterfaceService glusterUtil; + + @Autowired + private GlusterServerService glusterServerService; + + @Autowired + protected ServerUtil serverUtil; + + // TODO: To be replaced with taskService + @Autowired + private TasksResource taskResource; + + private static final Logger logger = Logger.getLogger(VolumeService.class); + + public void addBricksToVolume(String clusterName, String volumeName, String bricks) { + if (clusterName == null || clusterName.isEmpty()) { + throw new GlusterValidationException("Cluster name must not be empty!"); + } + + if (volumeName == null || volumeName.isEmpty()) { + throw new GlusterValidationException("Cluster name must not be empty!"); + } + + if (bricks == null || bricks.isEmpty()) { + throw new GlusterValidationException("Bricks must not be empty!"); + } + + if (clusterService.getCluster(clusterName) == null) { + throw new GlusterValidationException("Cluster [" + clusterName + "] not found!"); + } + + GlusterServer onlineServer = clusterService.getOnlineServer(clusterName); + if (onlineServer == null) { + throw new GlusterRuntimeException("No online servers found in cluster [" + clusterName + "]"); + } + + List<String> brickList = Arrays.asList(bricks.split(",")); + try { + glusterUtil.addBricks(volumeName, brickList, onlineServer.getName()); + } catch (Exception e) { + // check if online server has gone offline. If yes, try again one more time. + if (e instanceof ConnectionException || serverUtil.isServerOnline(onlineServer) == false) { + // online server has gone offline! try with a different one. + onlineServer = clusterService.getNewOnlineServer(clusterName); + if (onlineServer == null) { + throw new GlusterRuntimeException("No online servers found in cluster [" + clusterName + "]"); + } + glusterUtil.addBricks(volumeName, brickList, onlineServer.getName()); + } else { + throw new GlusterRuntimeException(e.getMessage()); + } + } + } + + public Volume getVolume(String clusterName, String volumeName) { + if (clusterName == null || clusterName.isEmpty()) { + throw new GlusterValidationException("Cluster name must not be empty!"); + } + + if (clusterService.getCluster(clusterName) == null) { + throw new GlusterValidationException("Cluster [" + clusterName + "] not found!"); + } + + GlusterServer onlineServer = clusterService.getOnlineServer(clusterName); + if (onlineServer == null) { + throw new GlusterRuntimeException("No online servers found in cluster [" + clusterName + "]"); + } + + Volume volume; + try { + volume = glusterUtil.getVolume(volumeName, onlineServer.getName()); + // Collect the CIFS users if CIFS Re-exported + fetchVolumeCifsUsers(clusterName, volume); + } catch (Exception e) { + // check if online server has gone offline. If yes, try again one more time. + if (e instanceof ConnectionException || serverUtil.isServerOnline(onlineServer) == false) { + // online server has gone offline! try with a different one. + onlineServer = clusterService.getNewOnlineServer(clusterName); + if (onlineServer == null) { + throw new GlusterRuntimeException("No online servers found in cluster [" + clusterName + "]"); + } + volume = glusterUtil.getVolume(volumeName, onlineServer.getName()); + // Collect the CIFS users if CIFS Re-exported + fetchVolumeCifsUsers(clusterName, volume); + } else { + throw new GlusterRuntimeException(e.getMessage()); + } + } + return volume; + } + + public List<Volume> getVolumes(String clusterName, Integer maxCount, String previousVolumeName) { + List<Volume> volumes = getVolumes(clusterName); + // Skip the volumes by maxCount / previousServerName + volumes = GlusterCoreUtil.skipEntities(volumes, maxCount, previousVolumeName); + // fetch CIFS users of the volumes + fetchCifsUsers(clusterName, volumes); + + return volumes; + } + + private List<Volume> getVolumes(String clusterName) { + if (clusterName == null || clusterName.isEmpty()) { + throw new GlusterValidationException("Cluster name must not be empty!"); + } + + ClusterInfo cluster = clusterService.getCluster(clusterName); + if (cluster == null) { + throw new GlusterValidationException("Cluster [" + clusterName + "] not found!"); + } + + if(cluster.getServers().size() == 0) { + // no server added yet. return an empty array. + return new ArrayList<Volume>(); + } + + GlusterServer onlineServer = clusterService.getOnlineServer(clusterName); + if (onlineServer == null) { + throw new GlusterRuntimeException("No online servers found in cluster [" + clusterName + "]"); + } + + try { + return glusterUtil.getAllVolumes(onlineServer.getName()); + } catch (Exception e) { + // online server has gone offline! try with a different one. + onlineServer = clusterService.getNewOnlineServer(clusterName); + if (onlineServer == null) { + throw new GlusterRuntimeException("No online servers found in cluster [" + clusterName + "]"); + } + + return glusterUtil.getAllVolumes(onlineServer.getName()); + } + } + + private void fetchVolumeCifsUsers(String clusterName, Volume volume) { + List<String> users = new ArrayList<String>(); + try { + ProcessResult result = serverUtil + .executeGlusterScript(true, VOLUME_GET_CIFS_USERS_SCRIPT, volume.getName()); + if (!result.isSuccess()) { + throw new GlusterRuntimeException(result.toString()); + } + String output = result.getOutput().trim(); + if (output.isEmpty()) { + volume.disableCifs(); + } else { + users = Arrays.asList(output.split(CoreConstants.NEWLINE)); + volume.enableCifs(); + volume.setCifsUsers(users); + } + } catch (Exception e) { + throw new GlusterRuntimeException("Error in fetching CIFS users [" + volume.getName() + "]: " + + e.getMessage()); + } + return; + } + + private void fetchCifsUsers(String clusterName, List<Volume> volumes) { + for (Volume volume: volumes) { + fetchVolumeCifsUsers(clusterName, volume); + } + } + + private File createOnlineServerList(String clusterName) { + String timestamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()); + String clusterServersListFile = FileUtil.getTempDirName() + CoreConstants.FILE_SEPARATOR + + ALL_SERVERS_FILE_NAME + "_" + timestamp; + + try { + GlusterServer onlineServer = clusterService.getOnlineServer(clusterName); + List<GlusterServer> glusterServers = glusterServerService.getGlusterServers(onlineServer.getName()); + File serversFile = new File(clusterServersListFile); + FileOutputStream fos = new FileOutputStream(serversFile); + for (GlusterServer server : glusterServers) { + if (server.getStatus() == SERVER_STATUS.ONLINE) { + fos.write((server.getName() + CoreConstants.NEWLINE).getBytes()); + } + } + fos.close(); + return serversFile; + } catch (Exception e) { + throw new GlusterRuntimeException("Error in preparing server list: [" + e.getMessage() + "]"); + } + } + + public void startCifsReExport(String clusterName, String volumeName) { + try { + File file = createOnlineServerList(clusterName); + ProcessResult result = serverUtil.executeGlusterScript(true, VOLUME_CIFS_GRUN_SCRIPT, + file.getAbsolutePath(), VOLUME_START_CIFS_PEER_SCRIPT, volumeName); + file.delete(); + if (!result.isSuccess()) { + throw new GlusterRuntimeException(result.toString()); + } + } catch (Exception e) { + throw new GlusterRuntimeException("Error in starting CIFS services for volume [" + volumeName + "]: " + + e.getMessage()); + } + } + + public void stopCifsReExport(String clusterName, String volumeName) { + try { + File file = createOnlineServerList(clusterName); + ProcessResult result = serverUtil.executeGlusterScript(true, VOLUME_CIFS_GRUN_SCRIPT, + file.getAbsolutePath(), VOLUME_STOP_CIFS_PEER_SCRIPT, volumeName); + file.delete(); + if (!result.isSuccess()) { + throw new GlusterRuntimeException(result.toString()); + } + } catch (Exception e) { + throw new GlusterRuntimeException("Error in stoping CIFS services for volume [" + volumeName + "]: " + + e.getMessage()); + } + } + + public void deleteCifsUsers(String clusterName, String volumeName) { + try { + File file = createOnlineServerList(clusterName); + ProcessResult result = serverUtil.executeGlusterScript(true, VOLUME_DELETE_CIFS_SCRIPT, + file.getAbsolutePath(), volumeName); + file.delete(); + if (!result.isSuccess()) { + throw new GlusterRuntimeException(result.toString()); + } + } catch (Exception e) { + throw new GlusterRuntimeException("Error in deleting CIFS configuration [" + volumeName + "]: " + + e.getMessage()); + } + } + + public void createCIFSUsers(String clusterName, String volumeName, String cifsUsers) { + try { + File file = createOnlineServerList(clusterName); + List<String> arguments = new ArrayList<String>(); + arguments.add(file.getAbsolutePath()); + arguments.add(volumeName); + arguments.addAll( Arrays.asList(cifsUsers.replaceAll(" ", "").split(","))); + ProcessResult result = serverUtil.executeGlusterScript(true, VOLUME_CREATE_CIFS_SCRIPT, arguments); + file.delete(); + Volume volume = getVolume(clusterName, volumeName); + // If the volume service is already in running, create user may start CIFS re-export automatically. + if (volume.getStatus() == VOLUME_STATUS.ONLINE) { + startCifsReExport(clusterName, volumeName); + } + /* + * else { stopCifsReExport(clusterName, volumeName); } + */ + if (!result.isSuccess()) { + throw new GlusterRuntimeException(result.toString()); + } + } catch (Exception e) { + throw new GlusterRuntimeException("Error in creating CIFS configuration [" + volumeName + "]: " + + e.getMessage()); + } + } + + @Deprecated + public void modifyCIFSUsers(String clusterName, String volumeName, String cifsUsers) { + try { + File file = createOnlineServerList(clusterName); + List<String> arguments = new ArrayList<String>(); + arguments.add(file.getAbsolutePath()); + arguments.add(volumeName); + arguments.addAll( Arrays.asList(cifsUsers.split(","))); + ProcessResult result = serverUtil.executeGlusterScript(true, VOLUME_MODIFY_CIFS_SCRIPT, arguments); + file.delete(); + if (!result.isSuccess()) { + throw new GlusterRuntimeException(result.toString()); + } + } catch (Exception e) { + throw new GlusterRuntimeException("Error in updating CIFS configuration [" + volumeName + "]: " + + e.getMessage()); + } + } + + // To clear all the volume CIFS configurations from the server + public void clearCifsConfiguration(String clusterName, String onlineServerName, String serverName) { + File volumesFile = createOnlineVolumeList(clusterName, onlineServerName); + if (volumesFile == null) { + return; + } + try { + removeServerVolumeCifsConfig(serverName, volumesFile.getAbsolutePath()); + volumesFile.delete(); + } catch(Exception e) { + volumesFile.delete(); + throw new GlusterRuntimeException("Error in clearing volume CIFS configuration: [" + e.getMessage() + "]"); + } + } + + private File createOnlineVolumeList(String clusterName, String onlineServerName) { + String timestamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()); + String volumeListFileName = FileUtil.getTempDirName() + CoreConstants.FILE_SEPARATOR + + ALL_ONLINE_VOLUMES_FILE_NAME + "_" + timestamp; + try { + List<Volume> volumes = getVolumes(clusterName); + if (volumes == null || volumes.size() == 0) { + return null; + } + File volumesFile = new File(volumeListFileName); + FileOutputStream fos = new FileOutputStream(volumesFile); + for (Volume volume : volumes) { + if (volume.getStatus() == VOLUME_STATUS.ONLINE) { + fos.write((volume.getName() + CoreConstants.NEWLINE).getBytes()); + } + } + fos.close(); + return volumesFile; + } catch (Exception e) { + throw new GlusterRuntimeException("Error in preparing volume list: [" + e.getMessage() + "]"); + } + } + + + public void removeServerVolumeCifsConfig(String serverName, String volumesFileName) { + ProcessResult result = serverUtil.executeGlusterScript(true, REMOVE_SERVER_VOLUME_CIFS_CONFIG, serverName, + volumesFileName); + if (!result.isSuccess()) { + throw new GlusterRuntimeException(result.toString()); + } + } + + public void createVolume(String clusterName, String volumeName, String volumeType, String transportType, + Integer count, String bricks, String accessProtocols, String options, + String cifsUsers) { + if (clusterService.getCluster(clusterName) == null) { + throw new GlusterValidationException("Cluster [" + clusterName + "] not found!"); + } + + if ((volumeType.equals(VOLUME_TYPE.REPLICATE.toString()) || volumeType.equals(VOLUME_TYPE.DISTRIBUTED_REPLICATE + .toString())) && count <= 0) { + throw new GlusterValidationException("Replica count must be a positive integer"); + } + + if ((volumeType.equals(VOLUME_TYPE.STRIPE.toString()) || volumeType.equals(VOLUME_TYPE.DISTRIBUTED_STRIPE + .toString())) && count <= 0) { + throw new GlusterValidationException("Stripe count must be a positive integer"); + } + + GlusterServer onlineServer = clusterService.getOnlineServer(clusterName); + if (onlineServer == null) { + throw new GlusterRuntimeException("No online servers found in cluster [" + clusterName + "]"); + } + + try { + glusterUtil.createVolume(onlineServer.getName(), volumeName, volumeType, transportType, count, + bricks, accessProtocols, options); + + } catch (Exception e) { + // check if online server has gone offline. If yes, try again one more time. + if (e instanceof ConnectionException || serverUtil.isServerOnline(onlineServer) == false) { + // online server has gone offline! try with a different one. + onlineServer = clusterService.getNewOnlineServer(clusterName); + if (onlineServer == null) { + throw new GlusterRuntimeException("No online servers found in cluster [" + clusterName + "]"); + } + glusterUtil.createVolume(onlineServer.getName(), volumeName, volumeType, transportType, count, + bricks, accessProtocols, options); + } else { + throw new GlusterRuntimeException(e.getMessage()); + } + } + + List<String> nasProtocols = Arrays.asList(accessProtocols.split(",")); + // if cifs enabled + if (nasProtocols.contains(NAS_PROTOCOL.CIFS.toString())) { + try { + createCIFSUsers(clusterName, volumeName, cifsUsers); + } catch (Exception e) { + throw new GlusterRuntimeException(CoreConstants.NEWLINE + e.getMessage()); + } + } + } + + public String downloadLogs(Volume volume) { + // create temporary directory + File tempDir = FileUtil.createTempDir(); + String tempDirPath = tempDir.getPath(); + + for (Brick brick : volume.getBricks()) { + String logDir = glusterUtil.getLogLocation(volume.getName(), brick.getQualifiedName(), + brick.getServerName()); + String logFileName = glusterUtil.getLogFileNameForBrickDir(brick.getServerName(), brick.getBrickDirectory()); + String logFilePath = logDir + CoreConstants.FILE_SEPARATOR + logFileName; + + serverUtil.getFileFromServer(brick.getServerName(), logFilePath, tempDirPath); + + String fetchedLogFile = tempDirPath + File.separator + logFileName; + // append log file name with server name so that log files don't overwrite each other + // in cases where the brick log file names are same on multiple servers + String localLogFile = tempDirPath + File.separator + brick.getServerName() + "-" + logFileName; + + FileUtil.renameFile(fetchedLogFile, localLogFile); + } + + String gzipPath = FileUtil.getTempDirName() + CoreConstants.FILE_SEPARATOR + volume.getName() + "-logs.tar.gz"; + ProcessUtil.executeCommand("tar", "czvf", gzipPath, "-C", tempDir.getParent(), tempDir.getName()); + + // delete the temp directory + FileUtil.recursiveDelete(tempDir); + + return gzipPath; + } + + public List<VolumeLogMessage> getLogs(String clusterName, String volumeName, String brickName, String severity, + String fromTimestamp, String toTimestamp, Integer lineCount) { + if (clusterName == null || clusterName.isEmpty()) { + throw new GlusterValidationException("Cluster name must not be empty!"); + } + + if (volumeName == null || volumeName.isEmpty()) { + throw new GlusterValidationException("Volume name must not be empty!"); + } + + if (clusterService.getCluster(clusterName) == null) { + throw new GlusterValidationException("Cluster [" + clusterName + "] not found!"); + } + + if (lineCount == null || lineCount == 0) { + lineCount = 100; + } + + List<VolumeLogMessage> logMessages = null; + Volume volume = getVolume(clusterName, volumeName); + + if (brickName == null || brickName.isEmpty() || brickName.equals(CoreConstants.ALL)) { + logMessages = getLogsForAllBricks(volume, lineCount); + } else { + // fetch logs for given brick of the volume + for (Brick brick : volume.getBricks()) { + if (brick.getQualifiedName().equals(brickName)) { + logMessages = getBrickLogs(volume, brick, lineCount); + break; + } + } + } + + filterLogsBySeverity(logMessages, severity); + filterLogsByTime(logMessages, fromTimestamp, toTimestamp); + return logMessages; + } + + private void filterLogsByTime(List<VolumeLogMessage> logMessages, String fromTimestamp, String toTimestamp) { + Date fromTime = null, toTime = null; + + if (fromTimestamp != null && !fromTimestamp.isEmpty()) { + fromTime = DateUtil.stringToDate(fromTimestamp); + } + + if (toTimestamp != null && !toTimestamp.isEmpty()) { + toTime = DateUtil.stringToDate(toTimestamp); + } + + List<VolumeLogMessage> messagesToRemove = new ArrayList<VolumeLogMessage>(); + for (VolumeLogMessage logMessage : logMessages) { + Date logTimestamp = logMessage.getTimestamp(); + if (fromTime != null && logTimestamp.before(fromTime)) { + messagesToRemove.add(logMessage); + continue; + } + + if (toTime != null && logTimestamp.after(toTime)) { + messagesToRemove.add(logMessage); + } + } + logMessages.removeAll(messagesToRemove); + } + + private void filterLogsBySeverity(List<VolumeLogMessage> logMessages, String severity) { + if (severity == null || severity.isEmpty()) { + return; + } + + List<VolumeLogMessage> messagesToRemove = new ArrayList<VolumeLogMessage>(); + for (VolumeLogMessage logMessage : logMessages) { + if (!logMessage.getSeverity().equals(severity)) { + messagesToRemove.add(logMessage); + } + } + logMessages.removeAll(messagesToRemove); + } + + private List<VolumeLogMessage> getLogsForAllBricks(Volume volume, Integer lineCount) { + List<VolumeLogMessage> logMessages; + logMessages = new ArrayList<VolumeLogMessage>(); + // fetch logs for every brick of the volume + for (Brick brick : volume.getBricks()) { + logMessages.addAll(getBrickLogs(volume, brick, lineCount)); + } + + // Sort the log messages based on log timestamp + Collections.sort(logMessages, new Comparator<VolumeLogMessage>() { + @Override + public int compare(VolumeLogMessage message1, VolumeLogMessage message2) { + return message1.getTimestamp().compareTo(message2.getTimestamp()); + } + }); + + return logMessages; + } + + private List<VolumeLogMessage> getBrickLogs(Volume volume, Brick brick, Integer lineCount) + throws GlusterRuntimeException { + String logDir = glusterUtil.getLogLocation(volume.getName(), brick.getQualifiedName(), brick.getServerName()); + String logFileName = glusterUtil.getLogFileNameForBrickDir(brick.getServerName(), brick.getBrickDirectory()); + String logFilePath = logDir + CoreConstants.FILE_SEPARATOR + logFileName; + + // Usage: get_volume_disk_log.py <volumeName> <diskName> <lineCount> + LogMessageListResponse response = serverUtil.executeScriptOnServer(brick.getServerName(), + VOLUME_BRICK_LOG_SCRIPT + " " + logFilePath + " " + lineCount, LogMessageListResponse.class); + + // populate disk and trim other fields + List<VolumeLogMessage> logMessages = response.getLogMessages(); + for (VolumeLogMessage logMessage : logMessages) { + logMessage.setBrick(brick.getQualifiedName()); + } + return logMessages; + } + + public String migrateBrickStart(String clusterName, String volumeName, String fromBrick, String toBrick, + Boolean autoCommit) { + if (clusterName == null || clusterName.isEmpty()) { + throw new GlusterValidationException("Cluster name must not be empty!"); + } + + if (volumeName == null || volumeName.isEmpty()) { + throw new GlusterValidationException("Volume name must not be empty!"); + } + + if (fromBrick == null || fromBrick.isEmpty()) { + throw new GlusterValidationException("From brick must not be empty!"); + } + + if (toBrick == null || toBrick.isEmpty()) { + throw new GlusterValidationException("To brick must not be empty!"); + } + + if (clusterService.getCluster(clusterName) == null) { + throw new GlusterValidationException("Cluster [" + clusterName + "] not found!"); + } + + GlusterServer onlineServer = clusterService.getOnlineServer(clusterName); + if (onlineServer == null) { + throw new GlusterRuntimeException("No online servers found in cluster [" + clusterName + "]"); + } + + if(autoCommit == null) { + autoCommit = false; + } + + MigrateBrickTask migrateDiskTask = new MigrateBrickTask(clusterService, clusterName, volumeName, fromBrick, + toBrick); + migrateDiskTask.setAutoCommit(autoCommit); + migrateDiskTask.start(); + taskResource.addTask(clusterName, migrateDiskTask); + return migrateDiskTask.getTaskInfo().getName(); // Return Task ID + } + + private String getLayout(Boolean isFixLayout, Boolean isMigrateData, + Boolean isForcedDataMigrate) { + String layout = ""; + if (isForcedDataMigrate) { + layout = "forced-data-migrate"; + } else if (isMigrateData) { + layout = "migrate-data"; + } else if (isFixLayout) { + layout = "fix-layout"; + } + return layout; + } + + public String rebalanceStart(String clusterName, String volumeName, Boolean isFixLayout, Boolean isMigrateData, + Boolean isForcedDataMigrate) { + RebalanceVolumeTask rebalanceTask = new RebalanceVolumeTask(clusterService, clusterName, volumeName, getLayout( + isFixLayout, isMigrateData, isForcedDataMigrate)); + rebalanceTask.start(); + taskResource.addTask(clusterName, rebalanceTask); + return rebalanceTask.getId(); + } + + public void rebalanceStop(String clusterName, String volumeName) { + // TODO: arrive at the task id and fetch it + String taskId = ""; + + taskResource.getTask(clusterName, taskId).stop(); + } + + public void startVolume(String clusterName, GlusterServer onlineServer, Volume volume, Boolean force) { + glusterUtil.startVolume(volume.getName(), onlineServer.getName(), force); + + // call the start_volume_cifs.py script only if the volume is cifs enabled + if (volume.isCifsEnable()) { + startCifsReExport(clusterName, volume.getName()); + } + } + + public void stopVolume(String clusterName, GlusterServer onlineServer, Volume volume, Boolean force) { + glusterUtil.stopVolume(volume.getName(), onlineServer.getName(), force); + + // call the stop_volume_cifs.py script only if the volume is cifs enabled + if (volume.isCifsEnable()) { + stopCifsReExport(clusterName, volume.getName()); + } + } + + public void logRotate(String clusterName, String volumeName, List<String> brickList) { + GlusterServer onlineServer = clusterService.getOnlineServer(clusterName); + try { + if (onlineServer == null) { + throw new GlusterRuntimeException("No online servers found in cluster [" + clusterName + "]"); + } + + glusterUtil.logRotate(volumeName, brickList, onlineServer.getName()); + } catch (Exception e) { + // check if online server has gone offline. If yes, try again one more time. + if (e instanceof ConnectionException || serverUtil.isServerOnline(onlineServer) == false) { + // online server has gone offline! try with a different one. + onlineServer = clusterService.getNewOnlineServer(clusterName); + glusterUtil.logRotate(volumeName, brickList, onlineServer.getName()); + } else { + throw new GlusterRuntimeException("Volume [" + volumeName + "] log rotation failed!", e); + } + } + } + + public void performVolumeOperation(String clusterName, String volumeName, String operation, Boolean force) { + GlusterServer onlineServer = clusterService.getOnlineServer(clusterName); + try { + if (onlineServer == null) { + throw new GlusterRuntimeException("No online servers found in cluster [" + clusterName + "]"); + } + + performOperation(clusterName, volumeName, operation, onlineServer, force); + } catch (Exception e) { + // check if online server has gone offline. If yes, try again one more time. + if (e instanceof ConnectionException || serverUtil.isServerOnline(onlineServer) == false) { + // online server has gone offline! try with a different one. + onlineServer = clusterService.getNewOnlineServer(clusterName); + performOperation(clusterName, volumeName, operation, onlineServer, force); + } else { + throw new GlusterRuntimeException(e.getMessage()); + } + } + } + + private void performOperation(String clusterName, String volumeName, String operation, GlusterServer onlineServer, + Boolean force) { + Volume volume = null; + try { + volume = getVolume(clusterName, volumeName); + } catch (Exception e) { + throw new GlusterRuntimeException("Could not fetch volume info for volume [" + volumeName + "]" + + e.getMessage()); + } + + if (operation.equals(TASK_START)) { + startVolume(clusterName, onlineServer, volume, force); + } else if (operation.equals(TASK_STOP)) { + stopVolume(clusterName, onlineServer, volume, force); + } else { + throw new GlusterValidationException("Invalid operation code [" + operation + "]"); + } + } + + public void removeBricksFromVolume(String clusterName, String volumeName, String bricks, Boolean deleteFlag) { + // Convert from comma separated string (query parameter) + List<String> brickList = Arrays.asList(bricks.split(",")); + if (clusterName == null || clusterName.isEmpty()) { + throw new GlusterValidationException("Cluster name must not be empty!"); + } + + if (volumeName == null || volumeName.isEmpty()) { + throw new GlusterValidationException("Volume name must not be empty!"); + } + + if (bricks == null || bricks.isEmpty()) { + throw new GlusterValidationException("Parameter [" + QUERY_PARAM_BRICKS + "] is missing in request!"); + } + + if (clusterService.getCluster(clusterName) == null) { + throw new GlusterValidationException("Cluster [" + clusterName + "] not found!"); + } + + if(deleteFlag == null) { + deleteFlag = false; + } + + GlusterServer onlineServer = clusterService.getOnlineServer(clusterName); + if (onlineServer == null) { + throw new GlusterRuntimeException("No online servers found in cluster [" + clusterName + "]"); + } + + removeBricks(clusterName, volumeName, brickList, onlineServer); + cleanupDirectories(brickList, volumeName, brickList.size(), deleteFlag); + } + + private void removeBricks(String clusterName, String volumeName, List<String> brickList, GlusterServer onlineServer) { + try { + glusterUtil.removeBricks(volumeName, brickList, onlineServer.getName()); + } catch (Exception e) { + // check if online server has gone offline. If yes, try again one more time. + if (e instanceof ConnectionException || serverUtil.isServerOnline(onlineServer) == false) { + // online server has gone offline! try with a different one. + onlineServer = clusterService.getNewOnlineServer(clusterName); + if (onlineServer == null) { + throw new GlusterRuntimeException("No online servers found in cluster [" + clusterName + "]"); + } + glusterUtil.removeBricks(volumeName, brickList, onlineServer.getName()); + } else { + throw new GlusterRuntimeException(e.getMessage()); + } + } + } + + private void cleanupDirectories(List<String> bricks, String volumeName, int maxIndex, boolean deleteFlag) { + String errors = ""; + for (int i = 0; i < maxIndex; i++) { + String[] brickInfo = bricks.get(i).split(":"); + String serverName = brickInfo[0]; + String brickDirectory = brickInfo[1]; + + try { + serverUtil.executeScriptOnServer(serverName, VOLUME_DIRECTORY_CLEANUP_SCRIPT + " " + + brickDirectory + " " + (deleteFlag ? "-d" : "")); + } catch(Exception e) { + logger.error("Error while cleaning brick [" + serverName + ":" + brickDirectory + "] of volume [" + + volumeName + "] : " + e.getMessage(), e); + errors += "[" + brickDirectory + "] => " + e.getMessage() + CoreConstants.NEWLINE; + } + } + if(!errors.trim().isEmpty()) { + throw new GlusterRuntimeException("Volume directory cleanup errors: " + errors.trim()); + } + } + + public void deleteVolume(String clusterName, String volumeName, Boolean deleteFlag) { + if (clusterName == null || clusterName.isEmpty()) { + throw new GlusterValidationException("Cluster name must not be empty"); + } + + if (volumeName == null || volumeName.isEmpty()) { + throw new GlusterValidationException("Volume name must not be empty"); + } + + if (clusterService.getCluster(clusterName) == null) { + throw new GlusterValidationException("Cluster [" + clusterName + "] not found!"); + } + + GlusterServer onlineServer = clusterService.getOnlineServer(clusterName); + if(onlineServer == null) { + throw new GlusterRuntimeException("No online servers found in cluster [" + clusterName + "]"); + } + + if (deleteFlag == null) { + deleteFlag = false; + } + + Volume volume = getVolume(clusterName, volumeName); + + List<Brick> bricks = volume.getBricks(); + glusterUtil.deleteVolume(volumeName, onlineServer.getName()); + + try { + postDelete(volumeName, bricks, deleteFlag); + if (volume.isCifsEnable()) { + if (volume.getStatus() == VOLUME_STATUS.ONLINE) { + stopCifsReExport(clusterName, volumeName); + } + deleteCifsUsers(clusterName, volumeName); + } + } catch(Exception e) { + throw new GlusterRuntimeException("Volume [" + volumeName + + "] deleted from cluster, however following error(s) occurred: " + CoreConstants.NEWLINE + + e.getMessage()); + } + } + + private void postDelete(String volumeName, List<Brick> bricks, boolean deleteFlag) { + for (Brick brick : bricks) { + String brickDirectory = brick.getBrickDirectory(); + // String mountPoint = brickDirectory.substring(0, brickDirectory.lastIndexOf("/")); + + serverUtil.executeScriptOnServer(brick.getServerName(), VOLUME_DIRECTORY_CLEANUP_SCRIPT + " " + + brickDirectory + " " + (deleteFlag ? "-d" : "")); + } + } + + public void resetVolumeOptions(String clusterName, String volumeName) { + if (clusterName == null || clusterName.isEmpty()) { + throw new GlusterValidationException("Cluster name must not be empty!"); + } + + if(volumeName == null || volumeName.isEmpty()) { + throw new GlusterValidationException("Volume name must not be empty!"); + } + + if (clusterService.getCluster(clusterName) == null) { + throw new GlusterValidationException("Cluster [" + clusterName + "] not found!"); + } + + GlusterServer onlineServer = clusterService.getOnlineServer(clusterName); + if (onlineServer == null) { + throw new GlusterRuntimeException("No online servers found in cluster [" + clusterName + "]"); + } + + try { + glusterUtil.resetOptions(volumeName, onlineServer.getName()); + } catch (Exception e) { + // check if online server has gone offline. If yes, try again one more time. + if (e instanceof ConnectionException || serverUtil.isServerOnline(onlineServer) == false) { + // online server has gone offline! try with a different one. + onlineServer = clusterService.getNewOnlineServer(clusterName); + if (onlineServer == null) { + throw new GlusterRuntimeException("No online servers found in cluster [" + clusterName + "]"); + } + + glusterUtil.resetOptions(volumeName, onlineServer.getName()); + } else { + throw new GlusterRuntimeException(e.getMessage()); + } + + } + } + + public void setVolumeOption(String clusterName, String volumeName, String key, String value) { + if (clusterName == null || clusterName.isEmpty()) { + throw new GlusterValidationException("Cluster name must not be empty!"); + } + + if(volumeName == null || volumeName.isEmpty()) { + throw new GlusterValidationException("Volume name must not be empty!"); + } + + if(key == null || key.isEmpty()) { + throw new GlusterValidationException("Option key must not be empty!"); + } + + if(value == null || value.isEmpty()) { + throw new GlusterValidationException("Option value must not be empty!"); + } + + if (clusterService.getCluster(clusterName) == null) { + throw new GlusterRuntimeException("Cluster [" + clusterName + "] not found!"); + } + + GlusterServer onlineServer = clusterService.getOnlineServer(clusterName); + if (onlineServer == null) { + throw new GlusterRuntimeException("No online servers found in cluster [" + clusterName + "]"); + } + + try { + glusterUtil.setOption(volumeName, key, value, onlineServer.getName()); + } catch (Exception e) { + // check if online server has gone offline. If yes, try again one more time. + if (e instanceof ConnectionException || serverUtil.isServerOnline(onlineServer) == false) { + // online server has gone offline! try with a different one. + onlineServer = clusterService.getNewOnlineServer(clusterName); + if (onlineServer == null) { + throw new GlusterRuntimeException("No online servers found in cluster [" + clusterName + "]"); + } + glusterUtil.setOption(volumeName, key, value, onlineServer.getName()); + } else { + throw new GlusterRuntimeException(e.getMessage()); + } + } + } + + public VolumeOptionInfoListResponse getVolumeOptionsInfo(String clusterName) { + if (clusterName == null || clusterName.isEmpty()) { + throw new GlusterValidationException("Cluster name must not be empty!"); + } + + ClusterInfo cluster = clusterService.getCluster(clusterName); + if (cluster == null) { + throw new GlusterValidationException("Cluster [" + clusterName + "] not found!"); + } + + if(cluster.getServers().isEmpty()) { + throw new GlusterValidationException("Cluster [" + clusterName + "] is empty! Can't fetch Volume Options Information!"); + } + + GlusterServer onlineServer = clusterService.getOnlineServer(clusterName); + if (onlineServer == null) { + throw new GlusterRuntimeException("No online servers found in cluster [" + clusterName + "]"); + } + + try { + return glusterUtil.getVolumeOptionsInfo(onlineServer.getName()); + } catch (Exception e) { + // check if online server has gone offline. If yes, try again one more time. + if (e instanceof ConnectionException || serverUtil.isServerOnline(onlineServer) == false) { + onlineServer = clusterService.getNewOnlineServer(clusterName); + return glusterUtil.getVolumeOptionsInfo(onlineServer.getName()); + } else { + throw new GlusterRuntimeException("Fetching volume options info failed! [" + e.getMessage() + "]"); + } + } + } +} diff --git a/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/tasks/InitServerTask.java b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/tasks/InitServerTask.java new file mode 100644 index 00000000..3193d926 --- /dev/null +++ b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/tasks/InitServerTask.java @@ -0,0 +1,161 @@ +/** + * GlusterServerInitializer.java + * + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Gluster Management Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + */ +package org.gluster.storage.management.gateway.tasks; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import javax.servlet.ServletContext; + +import org.apache.derby.tools.ij; +import org.gluster.storage.management.core.constants.CoreConstants; +import org.gluster.storage.management.core.exceptions.GlusterRuntimeException; +import org.gluster.storage.management.gateway.data.ClusterInfo; +import org.gluster.storage.management.gateway.data.PersistenceDao; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.RowCallbackHandler; +import org.springframework.jdbc.core.support.JdbcDaoSupport; +import org.springframework.security.authentication.dao.SaltSource; +import org.springframework.security.authentication.encoding.PasswordEncoder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; + + +/** + * Initializes the Gluster Management Server. + */ +public class InitServerTask extends JdbcDaoSupport { + @Autowired + private PasswordEncoder passwordEncoder; + + @Autowired + private SaltSource saltSource; + + @Autowired + private UserDetailsService userDetailsService; + + @Autowired + private String appVersion; + + @Autowired + private PersistenceDao<ClusterInfo> clusterDao; + + @Autowired + ServletContext servletContext; + + private static final String SCRIPT_DIR = "data/scripts/"; + + public void securePasswords() { + getJdbcTemplate().query("select username, password from users", new RowCallbackHandler() { + @Override + public void processRow(ResultSet rs) throws SQLException { + String username = rs.getString(1); + String password = rs.getString(2); + UserDetails user = userDetailsService.loadUserByUsername(username); + + String encodedPassword = passwordEncoder.encodePassword(password, saltSource.getSalt(user)); + getJdbcTemplate().update("update users set password = ? where username = ?", encodedPassword, username); + logger.debug("Updating password for username: " + username); + } + }); + } + + private void executeScript(File script) { + ByteArrayOutputStream sqlOut = new ByteArrayOutputStream(); + int numOfExceptions; + try { + numOfExceptions = ij.runScript(getJdbcTemplate().getDataSource().getConnection(), new FileInputStream( + script), CoreConstants.ENCODING_UTF8, sqlOut, CoreConstants.ENCODING_UTF8); + String output = sqlOut.toString(); + sqlOut.close(); + logger.debug("Data script [" + script.getName() + "] returned with exit status [" + numOfExceptions + + "] and output [" + output + "]"); + if (numOfExceptions != 0) { + throw new GlusterRuntimeException("Server data initialization script [ " + script.getName() + + "] failed with [" + numOfExceptions + "] exceptions! [" + output + "]"); + } + } catch (Exception ex) { + throw new GlusterRuntimeException("Server data initialization script [" + script.getName() + "] failed!", + ex); + } + } + + private void initDatabase() { + logger.info("Initializing server data..."); + executeScriptsFrom(getDirFromRelativePath(SCRIPT_DIR + appVersion)); + + securePasswords(); // encrypt the passwords + } + + private File getDirFromRelativePath(String relativePath) { + String scriptDirPath = servletContext.getRealPath(relativePath); + File scriptDir = new File(scriptDirPath); + return scriptDir; + } + + private void executeScriptsFrom(File scriptDir) { + if (!scriptDir.exists()) { + throw new GlusterRuntimeException("Script directory [" + scriptDir.getAbsolutePath() + "] doesn't exist!"); + } + + List<File> scripts = Arrays.asList(scriptDir.listFiles()); + if(scripts.size() == 0) { + throw new GlusterRuntimeException("Script directory [" + scriptDir.getAbsolutePath() + "] is empty!"); + } + + Collections.sort(scripts); + for (File script : scripts) { + executeScript(script); + } + } + + /** + * Initializes the server database, if running for the first time. + */ + public synchronized void initServer() { + try { + String dbVersion = getDBVersion(); + if (!appVersion.equals(dbVersion)) { + logger.info("App version [" + appVersion + "] differs from data version [" + dbVersion + + "]. Trying to upgrade data..."); + upgradeData(dbVersion, appVersion); + } + } catch (Exception ex) { + logger.info("No cluster created yet. DB version query failed with error [" + ex.getMessage() + "]", ex); + // Database not created yet. Create it! + initDatabase(); + } + } + + private void upgradeData(String fromVersion, String toVersion) { + executeScriptsFrom(getDirFromRelativePath(SCRIPT_DIR + fromVersion + "-" + toVersion)); + } + + private String getDBVersion() { + return (String) clusterDao.getSingleResultFromSQL("select version from version"); + } +} diff --git a/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/tasks/InitializeDiskTask.java b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/tasks/InitializeDiskTask.java new file mode 100644 index 00000000..45be980a --- /dev/null +++ b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/tasks/InitializeDiskTask.java @@ -0,0 +1,198 @@ +/** + * InitializeDiskTask.java + * + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Gluster Management Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + */ +package org.gluster.storage.management.gateway.tasks; + +import org.gluster.storage.management.core.constants.GlusterConstants; +import org.gluster.storage.management.core.exceptions.ConnectionException; +import org.gluster.storage.management.core.model.InitDiskStatusResponse; +import org.gluster.storage.management.core.model.Status; +import org.gluster.storage.management.core.model.TaskInfo; +import org.gluster.storage.management.core.model.TaskStatus; +import org.gluster.storage.management.core.model.InitDiskStatusResponse.FORMAT_STATUS; +import org.gluster.storage.management.core.model.TaskInfo.TASK_TYPE; +import org.gluster.storage.management.gateway.services.ClusterService; +import org.gluster.storage.management.gateway.utils.ServerUtil; +import org.springframework.context.ApplicationContext; +import org.springframework.web.context.ContextLoader; + +import com.sun.jersey.core.util.Base64; + +public class InitializeDiskTask extends Task { + + private static final String INITIALIZE_DISK_SCRIPT = "format_device.py"; + private static final String INITIALIZE_DISK_STATUS_SCRIPT = "get_format_device_status.py"; + + private String serverName; + private String diskName; + private String fsType; + private String mountPoint; + private ServerUtil serverUtil; + + public InitializeDiskTask(ClusterService clusterService, String clusterName, String serverName, String diskName, + String fsType, String mountPoint) { + // Reference contains "Server:disk" + super(clusterService, clusterName, TASK_TYPE.DISK_FORMAT, serverName + ":" + diskName, "Initialize disk " + + serverName + ":" + diskName, false, false, false); + + setServerName(serverName); + setDiskName(diskName); + setFsType(fsType); + setMountpoint(mountPoint); + taskInfo.setName(getId()); + init(); + } + + public InitializeDiskTask(ClusterService clusterService, String clusterName, TaskInfo info) { + super(clusterService, clusterName, info); + init(); + } + + private void init() { + ApplicationContext ctx = ContextLoader.getCurrentWebApplicationContext(); + serverUtil = ctx.getBean(ServerUtil.class); + } + + @Override + public String getId() { + return new String( + Base64.encode(getClusterName() + "-" + taskInfo.getType() + "-" + serverName + ":" + diskName)); + } + + @Override + public void resume() { + getTaskInfo().setStatus( + new TaskStatus(new Status(Status.STATUS_CODE_FAILURE, + "Stop/Pause/Resume is not supported in Disk Initialization"))); + } + + @Override + public void stop() { + getTaskInfo().setStatus( + new TaskStatus(new Status(Status.STATUS_CODE_FAILURE, + "Stop/Pause/Resume is not supported in Disk Initialization"))); + } + + @Override + public void pause() { + getTaskInfo().setStatus( + new TaskStatus(new Status(Status.STATUS_CODE_FAILURE, + "Stop/Pause/Resume is not supported in Disk Initialization"))); + } + + @Override + public void commit() { + // TODO Auto-generated method stub + } + + @Override + public TASK_TYPE getType() { + return TASK_TYPE.DISK_FORMAT; + } + + + @Override + public void start() { + try { + startInitializeDisk(serverName); + } catch(ConnectionException e) { + // online server might have gone offline. update the failure status + getTaskInfo().setStatus(new TaskStatus(new Status(Status.STATUS_CODE_FAILURE, e.getMessage()))); + } + } + + private void startInitializeDisk(String serverName) { + String output = serverUtil.executeScriptOnServer(serverName, INITIALIZE_DISK_SCRIPT + " " + getFsType() + " \"" + + getMountpoint() + "\" " + getDiskName() ); + TaskStatus taskStatus = new TaskStatus(new Status(Status.STATUS_CODE_RUNNING, output)); + taskStatus.setPercentageSupported((getFsType().equals(GlusterConstants.FSTYPE_XFS)) ? false : true); + getTaskInfo().setStatus(taskStatus); + } + + @Override + public TaskStatus checkStatus() { + + try { + return getInitializingDeviceStatus(serverName, getDiskName()); + } catch(ConnectionException e) { + // online server might have gone offline. update the failure status + return new TaskStatus(new Status(Status.STATUS_CODE_FAILURE, e.getMessage())); + } + } + + private TaskStatus getInitializingDeviceStatus(String serverName, String diskName) { + InitDiskStatusResponse initDiskStatusResponse; + TaskStatus taskStatus = new TaskStatus(); + + try { + initDiskStatusResponse = serverUtil.executeScriptOnServer(serverName, INITIALIZE_DISK_STATUS_SCRIPT + " " + + diskName, InitDiskStatusResponse.class); + } catch(RuntimeException e) { + taskStatus.setCode(Status.STATUS_CODE_FAILURE); + taskStatus.setMessage(e.getMessage()); + throw e; + } + + if (initDiskStatusResponse.getFormatStatus() == FORMAT_STATUS.COMPLETED) { + taskStatus.setCode(Status.STATUS_CODE_SUCCESS); + } else if (initDiskStatusResponse.getFormatStatus() == FORMAT_STATUS.IN_PROGRESS) { + taskStatus.setCode(Status.STATUS_CODE_RUNNING); + taskStatus.setPercentCompleted(Math.round(initDiskStatusResponse.getCompletedBlocks() + / initDiskStatusResponse.getTotalBlocks() * 100)); + } else if(initDiskStatusResponse.getFormatStatus() == FORMAT_STATUS.NOT_RUNNING) { + taskStatus.setCode(Status.STATUS_CODE_FAILURE); + } + + taskStatus.setMessage(initDiskStatusResponse.getMessage()); + return taskStatus; + } + + public void setDiskName(String diskName) { + this.diskName = diskName; + } + + public String getDiskName() { + return diskName; + } + + public void setServerName(String serverName) { + this.serverName = serverName; + } + + public String getServerName() { + return serverName; + } + + public void setFsType(String fsType) { + this.fsType = fsType; + } + + public String getFsType() { + return fsType; + } + + public void setMountpoint(String deviceMountPoint) { + this.mountPoint = deviceMountPoint; + } + + public String getMountpoint() { + return mountPoint; + } +} diff --git a/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/tasks/MigrateBrickTask.java b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/tasks/MigrateBrickTask.java new file mode 100644 index 00000000..8a31f9a9 --- /dev/null +++ b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/tasks/MigrateBrickTask.java @@ -0,0 +1,220 @@ +/** + * MigrateDiskTask.java + * + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Gluster Management Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + */ +package org.gluster.storage.management.gateway.tasks; + +import org.gluster.storage.management.core.exceptions.ConnectionException; +import org.gluster.storage.management.core.exceptions.GlusterRuntimeException; +import org.gluster.storage.management.core.model.Status; +import org.gluster.storage.management.core.model.TaskStatus; +import org.gluster.storage.management.core.model.TaskInfo.TASK_TYPE; +import org.gluster.storage.management.gateway.services.ClusterService; +import org.gluster.storage.management.gateway.services.GlusterInterfaceService; +import org.gluster.storage.management.gateway.utils.ServerUtil; +import org.springframework.context.ApplicationContext; +import org.springframework.web.context.ContextLoader; + +import com.sun.jersey.core.util.Base64; + +public class MigrateBrickTask extends Task { + + private String fromBrick; + private String toBrick; + private Boolean autoCommit; + private GlusterInterfaceService glusterInterface; + protected ServerUtil serverUtil; + + public String getFromBrick() { + return fromBrick; + } + + public void setFromBrick(String fromBrick) { + this.fromBrick = fromBrick; + } + + public String getToBrick() { + return toBrick; + } + + public void setToBrick(String toBrick) { + this.toBrick = toBrick; + } + + public Boolean getAutoCommit() { + return autoCommit; + } + + public void setAutoCommit(Boolean autoCommit) { + this.autoCommit = autoCommit; + } + + public MigrateBrickTask(ClusterService clusterService, String clusterName, String volumeName, String fromBrick, + String toBrick) { + super(clusterService, clusterName, TASK_TYPE.BRICK_MIGRATE, volumeName + "#" + fromBrick + "#" + toBrick, + "Brick Migration on volume [" + volumeName + "] from [" + fromBrick + "] to [" + toBrick + "]", true, + true, true); + setFromBrick(fromBrick); + setToBrick(toBrick); + taskInfo.setName(getId()); + init(); + } + + private void init() { + ApplicationContext ctx = ContextLoader.getCurrentWebApplicationContext(); + glusterInterface = ctx.getBean(GlusterInterfaceService.class); + serverUtil = ctx.getBean(ServerUtil.class); + } + + @Override + public String getId() { + return new String(Base64.encode(clusterName + "-" + taskInfo.getType() + "-" + taskInfo.getReference() + "-" + fromBrick + "-" + + toBrick)); + } + + @Override + public void start() { + try { + startMigration(getOnlineServer().getName()); + } catch (Exception e) { + // check if online server has gone offline. If yes, try again one more time. + if (e instanceof ConnectionException || serverUtil.isServerOnline(getOnlineServer()) == false) { + // online server might have gone Offline. try with a new one. + startMigration(getNewOnlineServer().getName()); + } else { + throw new GlusterRuntimeException(e.getMessage()); + } + } + } + + private void startMigration(String onlineServerName) { + String volumeName = getTaskInfo().getReference().split("#")[0]; + glusterInterface.startBrickMigration(onlineServerName, volumeName, getFromBrick(), getToBrick()); + getTaskInfo().setStatus(new TaskStatus(new Status(Status.STATUS_CODE_RUNNING, "Brick Migration Started."))); + } + + @Override + public void pause() { + try { + pauseMigration(getOnlineServer().getName()); + } catch (Exception e) { + // check if online server has gone offline. If yes, try again one more time. + if (e instanceof ConnectionException || serverUtil.isServerOnline(getOnlineServer()) == false) { + // online server might have gone offline. try with a new one. + pauseMigration(getNewOnlineServer().getName()); + } else { + throw new GlusterRuntimeException(e.getMessage()); + } + } + } + + private void pauseMigration(String onlineServer) { + String volumeName = getTaskInfo().getReference().split("#")[0]; + glusterInterface.pauseBrickMigration(onlineServer, volumeName, getFromBrick(), getToBrick()); + TaskStatus taskStatus = new TaskStatus(); + taskStatus.setCode(Status.STATUS_CODE_PAUSE); + taskStatus.setMessage("Brick Migration Paused"); + getTaskInfo().setStatus(taskStatus); + } + + @Override + public void resume() { + start(); + } + + @Override + public void commit() { + try { + commitMigration(getOnlineServer().getName()); + } catch (Exception e) { + // check if online server has gone offline. If yes, try again one more time. + if (e instanceof ConnectionException || serverUtil.isServerOnline(getOnlineServer()) == false) { + // online server might have gone offline. try with a new one. + commitMigration(getNewOnlineServer().getName()); + } else { + throw new GlusterRuntimeException(e.getMessage()); + } + } + } + + private void commitMigration(String serverName) { + String volumeName = getTaskInfo().getReference().split("#")[0]; + glusterInterface.commitBrickMigration(serverName, volumeName, getFromBrick(), getToBrick()); + TaskStatus taskStatus = new TaskStatus(); + taskStatus.setCode(Status.STATUS_CODE_SUCCESS); + taskStatus.setMessage("Brick Migration Committed."); + getTaskInfo().setStatus(taskStatus); + } + + @Override + public void stop() { + try { + stopMigration(getOnlineServer().getName()); + } catch (Exception e) { + // check if online server has gone offline. If yes, try again one more time. + if (e instanceof ConnectionException || serverUtil.isServerOnline(getOnlineServer()) == false) { + // online server might have gone offline. try with a new one. + stopMigration(getNewOnlineServer().getName()); + } else { + throw new GlusterRuntimeException(e.getMessage()); + } + } + } + + private void stopMigration(String serverName) { + String volumeName = getTaskInfo().getReference().split("#")[0]; + glusterInterface.stopBrickMigration(serverName, volumeName, getFromBrick(), getToBrick()); + TaskStatus taskStatus = new TaskStatus(); + taskStatus.setCode(Status.STATUS_CODE_SUCCESS); + taskStatus.setMessage("Brick Migration Stopped"); + getTaskInfo().setStatus(taskStatus); + } + + @Override + public TaskStatus checkStatus() { + try { + return checkMigrationStatus(getOnlineServer().getName()); + } catch (Exception e) { + // check if online server has gone offline. If yes, try again one more time. + if (e instanceof ConnectionException || serverUtil.isServerOnline(getOnlineServer()) == false) { + // online server might have gone offline. try with a new one. + return checkMigrationStatus(getNewOnlineServer().getName()); + } + } + return null; + } + + private TaskStatus checkMigrationStatus(String serverName) { + // For committed task, status command (CLI) is invalid, just return current status + if (taskInfo.getStatus().getCode() == Status.STATUS_CODE_SUCCESS) { + return taskInfo.getStatus(); + } + + String volumeName = getTaskInfo().getReference().split("#")[0]; + TaskStatus taskStatus = glusterInterface.checkBrickMigrationStatus(serverName, volumeName, getFromBrick(), + getToBrick()); + if (autoCommit && taskStatus.isCommitPending()) { + commitMigration(serverName); + return taskInfo.getStatus(); // return the committed status + } + + taskInfo.setStatus(taskStatus); // Update the task status + return taskStatus; + } +} diff --git a/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/tasks/RebalanceVolumeTask.java b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/tasks/RebalanceVolumeTask.java new file mode 100644 index 00000000..288179e9 --- /dev/null +++ b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/tasks/RebalanceVolumeTask.java @@ -0,0 +1,141 @@ +/** + * RebalanceVolumeTask.java + * + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Gluster Management Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + */ +package org.gluster.storage.management.gateway.tasks; + +import org.gluster.storage.management.core.exceptions.ConnectionException; +import org.gluster.storage.management.core.exceptions.GlusterRuntimeException; +import org.gluster.storage.management.core.model.Status; +import org.gluster.storage.management.core.model.TaskStatus; +import org.gluster.storage.management.core.model.TaskInfo.TASK_TYPE; +import org.gluster.storage.management.gateway.services.ClusterService; +import org.gluster.storage.management.gateway.services.GlusterInterfaceService; +import org.gluster.storage.management.gateway.utils.ServerUtil; +import org.springframework.context.ApplicationContext; +import org.springframework.web.context.ContextLoader; + +import com.sun.jersey.core.util.Base64; + +public class RebalanceVolumeTask extends Task { + + private String layout; + private String serverName; + private ServerUtil serverUtil; + private GlusterInterfaceService glusterUtil; + + public RebalanceVolumeTask(ClusterService clusterService, String clusterName, String volumeName, String layout) { + super(clusterService, clusterName, TASK_TYPE.VOLUME_REBALANCE, volumeName, "Volume " + volumeName + + " Rebalance", false, true, false); + setLayout(layout); + taskInfo.setName(getId()); + init(); + } + + private void init() { + ApplicationContext ctx = ContextLoader.getCurrentWebApplicationContext(); + serverUtil = ctx.getBean(ServerUtil.class); + glusterUtil = ctx.getBean(GlusterInterfaceService.class); + } + + @Override + public String getId() { + return new String(Base64.encode(getClusterName() + "-" + taskInfo.getType() + "-" + taskInfo.getReference())); + } + + @Override + public void start() { + try { + serverName = getOnlineServer().getName(); + startRebalance(serverName); + } catch (Exception e) { + // check if online server has gone offline. If yes, try again one more time. + if (e instanceof ConnectionException || serverUtil.isServerOnline(getOnlineServer()) == false) { + // online server might have gone offline. try with a new one + serverName = getNewOnlineServer().getName(); + startRebalance(serverName); + } else { + throw new GlusterRuntimeException(e.getMessage()); + } + } + } + + private void startRebalance(String serverName) { + String command = "gluster volume rebalance " + getTaskInfo().getReference() + " " + getLayout() + " start"; + String output = serverUtil.executeOnServer(serverName, command); + getTaskInfo().setStatus(new TaskStatus(new Status(Status.STATUS_CODE_RUNNING, output))); + } + + @Override + public void resume() { + getTaskInfo().setStatus( + new TaskStatus(new Status(Status.STATUS_CODE_FAILURE, + "Pause/Resume is not supported in Volume Rebalance"))); + } + + @Override + public void stop() { + try { + glusterUtil.stopRebalance(serverName, getTaskInfo().getReference()); + } catch (Exception e) { + // check if online server has gone offline. If yes, try again one more time. + if (e instanceof ConnectionException || serverUtil.isServerOnline(getOnlineServer()) == false) { + // online server might have gone offline. update the failure status + getTaskInfo().setStatus(new TaskStatus(new Status(Status.STATUS_CODE_FAILURE, e.getMessage()))); + } else { + throw new GlusterRuntimeException(e.getMessage()); + } + } + } + + @Override + public void pause() { + getTaskInfo().setStatus( + new TaskStatus(new Status(Status.STATUS_CODE_FAILURE, + "Pause/Resume is not supported in Volume Rebalance"))); + } + + @Override + public TaskStatus checkStatus() { + try { + return glusterUtil.checkRebalanceStatus(serverName, getTaskInfo().getReference()); + } catch (Exception e) { + // check if online server has gone offline. If yes, try again one more time. + if (e instanceof ConnectionException || serverUtil.isServerOnline(getOnlineServer()) == false) { + // online server might have gone offline. update the failure status + getTaskInfo().setStatus(new TaskStatus(new Status(Status.STATUS_CODE_FAILURE, e.getMessage()))); + return getTaskInfo().getStatus(); + } + } + return null; + } + + public void setLayout(String layout) { + this.layout = layout; + } + + public String getLayout() { + return layout; + } + + @Override + public void commit() { + // TODO Auto-generated method stub + } +} diff --git a/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/tasks/ServerSyncTask.java b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/tasks/ServerSyncTask.java new file mode 100644 index 00000000..c3073d97 --- /dev/null +++ b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/tasks/ServerSyncTask.java @@ -0,0 +1,168 @@ +/** + * ServerDiscoveryTask.java + * + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Gluster Management Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + */ +package org.gluster.storage.management.gateway.tasks; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.log4j.Logger; +import org.gluster.storage.management.core.constants.CoreConstants; +import org.gluster.storage.management.core.constants.GlusterConstants; +import org.gluster.storage.management.core.model.GlusterServer; +import org.gluster.storage.management.core.utils.GlusterCoreUtil; +import org.gluster.storage.management.core.utils.ProcessResult; +import org.gluster.storage.management.gateway.data.ClusterInfo; +import org.gluster.storage.management.gateway.data.PersistenceDao; +import org.gluster.storage.management.gateway.data.ServerInfo; +import org.gluster.storage.management.gateway.services.ClusterService; +import org.gluster.storage.management.gateway.services.DiscoveredServerService; +import org.gluster.storage.management.gateway.services.GlusterServerService; +import org.gluster.storage.management.gateway.utils.ServerUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + + +/** + * Task for syncing server details. This performs two things: <br> + * 1. Auto-discovery of servers eligible to be added to the Gluster cluster. <br> + * 2. Syncing of cluster-server mapping with actual servers of the cluster. This mapping can go out of sync if user + * adds/removes servers manually using the CLI. + */ +@Component +public class ServerSyncTask { + private static final String SCRIPT_NAME_SFX = "-discover-servers.py"; + + @Autowired + private ServerUtil serverUtil; + + @Autowired + private DiscoveredServerService discoveredServersService; + + @Autowired + private GlusterServerService glusterServerService; + + @Autowired + private String discoveryMechanism; + + @Autowired + private ClusterService clusterService; + + @Autowired + private PersistenceDao<ClusterInfo> clusterDao; + + private static final Logger logger = Logger.getLogger(ServerSyncTask.class); + + public void perform() { + discoverServers(); + syncClusterServerMapping(); + } + + private void syncClusterServerMapping() { + List<ClusterInfo> clusters = clusterService.getAllClusters(); + for(ClusterInfo cluster : clusters) { + try { + List<ServerInfo> servers = cluster.getServers(); + if(servers.isEmpty()) { + logger.info("Cluster [" + cluster.getName() + "] is empty, nothing to sync!"); + continue; + } + List<GlusterServer> actualServers = glusterServerService.getGlusterServers(cluster.getName(), false, + null, null); + updateRemovedServers(cluster, servers, actualServers); + updateAddedServers(cluster, servers, actualServers); + } catch(Exception e) { + // log error and continue with next cluster + logger.error("Couldn't sync cluster-server mapping for cluster [" + cluster.getName() + "]!", e); + continue; + } + } + } + + private void updateAddedServers(ClusterInfo cluster, List<ServerInfo> servers, List<GlusterServer> actualServers) { + List<String> addedServers = findAddedServers(cluster.getName(), servers, actualServers); + for(String addedServer : addedServers) { + clusterService.mapServerToCluster(cluster.getName(), addedServer); + } + } + + private void updateRemovedServers(ClusterInfo cluster, List<ServerInfo> servers, List<GlusterServer> actualServers) { + List<String> removedServers = findRemovedServers(servers, actualServers); + for(String removedServer : removedServers) { + clusterService.unmapServerFromCluster(cluster.getName(), removedServer); + } + } + + private List<String> findRemovedServers(List<ServerInfo> servers, List<GlusterServer> actualServers) { + List<String> removedServers = new ArrayList<String>(); + + for(ServerInfo server : servers) { + if (!GlusterCoreUtil.containsEntityWithName(actualServers, server.getName(), true)) { + removedServers.add(server.getName()); + } + } + return removedServers; + } + + private List<String> findAddedServers(String clusterName, List<ServerInfo> servers, List<GlusterServer> actualServers) { + List<String> addedServers = new ArrayList<String>(); + for(GlusterServer actualServer : actualServers) { + if(!serverExists(servers, actualServer.getName())) { + addedServers.add(actualServer.getName()); + } + } + return addedServers; + } + + private boolean serverExists(List<ServerInfo> servers, String name) { + for(ServerInfo server : servers) { + if(server.getName().equalsIgnoreCase(name)) { + return true; + } + } + return false; + } + + @SuppressWarnings("unchecked") + private void discoverServers() { + if(discoveryMechanism.equals(GlusterConstants.NONE)) { + return; + } + + List<String> serverNameList = new ArrayList<String>(); + + ProcessResult result = serverUtil.executeGlusterScript(true, discoveryMechanism + SCRIPT_NAME_SFX, new ArrayList<String>()); + if(result.isSuccess()) { + List<String> existingServers = clusterDao.findBySQL("select name from server_info"); + String serverNames = result.getOutput(); + String[] parts = serverNames.split(CoreConstants.NEWLINE); + for(String serverName : parts) { + // The server discovery mechanism will return every server that has not been "peer probed". However we + // need to filter out those servers that are the "first" server of a new cluster, and hence are still + // not peer probed. + if(!existingServers.contains(serverName)) { + serverNameList.add(serverName); + } + } + } + + discoveredServersService.setDiscoveredServerNames(serverNameList); + } +}
\ No newline at end of file diff --git a/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/tasks/Task.java b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/tasks/Task.java new file mode 100644 index 00000000..0fee3c2e --- /dev/null +++ b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/tasks/Task.java @@ -0,0 +1,113 @@ +/** + * Task.java + * + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Gluster Management Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + */ +package org.gluster.storage.management.gateway.tasks; + +import org.gluster.storage.management.core.model.GlusterServer; +import org.gluster.storage.management.core.model.TaskInfo; +import org.gluster.storage.management.core.model.TaskStatus; +import org.gluster.storage.management.core.model.TaskInfo.TASK_TYPE; +import org.gluster.storage.management.gateway.services.ClusterService; + + +public abstract class Task { + public String[] TASK_TYPE_STR = { "Format Disk", "Migrate Brick", "Volume Rebalance" }; + + protected TaskInfo taskInfo; + protected String clusterName; + private ClusterService clusterService; + + public Task(ClusterService clusterService, String clusterName, TASK_TYPE type, String reference, String desc, + boolean canPause, boolean canStop, boolean canCommit) { + TaskInfo taskInfo = new TaskInfo(); + taskInfo.setType(type); + taskInfo.setReference(reference); + taskInfo.setDescription(desc); + taskInfo.setPauseSupported(canPause); + taskInfo.setStopSupported(canStop); + taskInfo.setCommitSupported(canCommit); + + init(clusterService, clusterName, taskInfo); + + } + + public Task(ClusterService clusterService, String clusterName, TaskInfo taskInfo) { + init(clusterService, clusterName, taskInfo); + } + + private void init(ClusterService clusterService, String clusterName, TaskInfo taskInfo) { + this.clusterService = clusterService; + setClusterName(clusterName); + setTaskInfo(taskInfo); + } + + protected GlusterServer getOnlineServer() { + return clusterService.getOnlineServer(clusterName); + } + + protected GlusterServer getNewOnlineServer() { + return clusterService.getNewOnlineServer(clusterName); + } + + protected GlusterServer getNewOnlineServer(String exceptServerName) { + return clusterService.getNewOnlineServer(clusterName, exceptServerName); + } + + public String getTypeStr() { + return TASK_TYPE_STR[taskInfo.getType().ordinal()]; + } + + public TASK_TYPE getType() { + return getTaskInfo().getType(); + } + + public String getClusterName() { + return clusterName; + } + + public void setClusterName(String clusterName) { + this.clusterName = clusterName; + } + + public TaskInfo getTaskInfo() { + return taskInfo; + } + + public void setTaskInfo(TaskInfo info) { + this.taskInfo = info; + } + + public abstract String getId(); + + public abstract void start(); + + public abstract void resume(); + + public abstract void stop(); + + public abstract void pause(); + + public abstract void commit(); + + /** + * This method should check current status of the task and update it's taskInfo accordingly + */ + public abstract TaskStatus checkStatus(); +} diff --git a/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/utils/AbstractStatsFactory.java b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/utils/AbstractStatsFactory.java new file mode 100644 index 00000000..ebc1ccba --- /dev/null +++ b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/utils/AbstractStatsFactory.java @@ -0,0 +1,162 @@ +/******************************************************************************* + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Gluster Management Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + *******************************************************************************/ +package org.gluster.storage.management.gateway.utils; + +import java.util.List; + +import org.apache.log4j.Logger; +import org.gluster.storage.management.core.exceptions.GlusterRuntimeException; +import org.gluster.storage.management.core.model.ServerStats; +import org.gluster.storage.management.core.model.ServerStatsRow; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + + +/** + * + */ +@Component +public abstract class AbstractStatsFactory implements StatsFactory { + @Autowired + protected ServerUtil serverUtil; + + private Logger logger = Logger.getLogger(AbstractStatsFactory.class); + + protected ServerStats getFirstOnlineServerStats(List<String> serverNames, String period, + boolean removeServerOnError, boolean removeOnlineServer) { + for(int i = serverNames.size() - 1; i >= 0; i--) { + String serverName = serverNames.get(i); + try { + ServerStats stats = fetchStats(serverName, period); + if(removeOnlineServer) { + serverNames.remove(serverName); + } + return stats; + } catch(Exception e) { + // server might be offline - continue with next one + logger.warn("Couldn't fetch stats from server [" + serverName + "]!", e); + if(removeServerOnError) { + serverNames.remove(serverName); + } + continue; + } + } + throw new GlusterRuntimeException("All servers offline!"); + } + + protected void aggregateStats(List<String> serverNames, ServerStats aggregatedStats, String period) { + if(serverNames.isEmpty()) { + return; + } + + int rowCount = aggregatedStats.getMetadata().getRowCount(); + int columnCount = aggregatedStats.getMetadata().getLegend().size(); + int[][] dataCount = initDataCountArray(rowCount, columnCount); + + List<ServerStats> allStats = serverUtil.executeScriptOnServers(serverNames, getStatsScriptName() + " " + period, ServerStats.class, false); + + for (ServerStats stats : allStats) { + // add to aggregated stats + addServerStats(stats, aggregatedStats, dataCount); + } + + averageAggregatedStats(aggregatedStats, dataCount); + } + + /** + * + * @param statsToBeAdded + * @param targetStats + * @param dataCount Each element of this matrix will be incremented for every valid element added + * @return + */ + protected List<ServerStatsRow> addServerStats(ServerStats statsToBeAdded, ServerStats targetStats, int[][] dataCount) { + List<ServerStatsRow> serverStatsRows = statsToBeAdded.getRows(); + for (int rowNum = 0; rowNum < serverStatsRows.size() && rowNum < targetStats.getMetadata().getRowCount() + && rowNum < dataCount.length; rowNum++) { + ServerStatsRow row = serverStatsRows.get(rowNum); + List<Double> rowData = row.getUsageData(); + + List<Double> aggregatedStatsRowData = targetStats.getRows().get(rowNum).getUsageData(); + for(int i = 1; i < targetStats.getMetadata().getLegend().size(); i++) { + // Add the data + Double data = rowData.get(i); + if(!data.isNaN()) { + // data is available. add it. + Double oldData = aggregatedStatsRowData.get(i); + if(oldData.isNaN()) { + oldData = 0d; + } + aggregatedStatsRowData.set(i, oldData + data); + // increment record count. this will be used for calculating average of aggregated data. + dataCount[rowNum][i]++; + } + } + } + return serverStatsRows; + } + + protected void averageAggregatedStats(ServerStats aggregatedStats, int[][] dataCount) { + List<ServerStatsRow> rows = aggregatedStats.getRows(); + for(int rowNum = 0; rowNum < rows.size() && rowNum < dataCount.length; rowNum++) { + List<Double> data = rows.get(rowNum).getUsageData(); + for(int columnNum = 0; columnNum < data.size(); columnNum++) { + data.set(columnNum, data.get(columnNum) / dataCount[rowNum][columnNum]); + } + } + } + + protected int[][] initDataCountArray(int rowCount, int columnCount) { + int[][] dataCount = new int[rowCount][columnCount]; + // initialize all data counts to 1 + for(int rowNum = 0; rowNum < rowCount; rowNum++) { + for(int columnNum = 0; columnNum < columnCount; columnNum++) { + dataCount[rowNum][columnNum] = 1; + } + } + return dataCount; + } + + @Override + public ServerStats fetchAggregatedStats(List<String> serverNames, String period) { + if(serverNames == null || serverNames.size() == 0) { + throw new GlusterRuntimeException("No server names passed to fetchAggregaredStats!"); + } + + ServerStats firstServerStats = getFirstOnlineServerStats(serverNames, period, true, true); + + ServerStats aggregatedStats = new ServerStats(firstServerStats); + aggregateStats(serverNames, aggregatedStats, period); + return aggregatedStats; + } + + @Override + public ServerStats fetchStats(String serverName, String period, String...args) { + String argsStr = ""; + for (String arg : args) { + if(arg != null) { + argsStr += " " + arg; + } + } + return serverUtil.executeScriptOnServer(serverName, getStatsScriptName() + argsStr + " " + period, + ServerStats.class); + } + + public abstract String getStatsScriptName(); +} diff --git a/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/utils/CpuStatsFactory.java b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/utils/CpuStatsFactory.java new file mode 100644 index 00000000..412794eb --- /dev/null +++ b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/utils/CpuStatsFactory.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Gluster Management Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + *******************************************************************************/ +package org.gluster.storage.management.gateway.utils; + +import org.springframework.stereotype.Component; + +/** + * + */ +@Component +public class CpuStatsFactory extends AbstractStatsFactory { + + private static final String CPU_STATS_SCRIPT = "get_rrd_cpu_details.py"; + + @Override + public String getStatsScriptName() { + return CPU_STATS_SCRIPT; + } + +} diff --git a/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/utils/DBUtil.java b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/utils/DBUtil.java new file mode 100644 index 00000000..1c186a0a --- /dev/null +++ b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/utils/DBUtil.java @@ -0,0 +1,84 @@ +/******************************************************************************* + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Gluster Management Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + *******************************************************************************/ +package org.gluster.storage.management.gateway.utils; + +import java.sql.DriverManager; +import java.sql.SQLException; + +import org.apache.log4j.Logger; +import org.gluster.storage.management.core.constants.CoreConstants; + + +/** + * + */ +public class DBUtil { + private static final Logger logger = Logger.getLogger(DBUtil.class); + public static void shutdownDerby() { + try { + // the shutdown=true attribute shuts down Derby + DriverManager.getConnection("jdbc:derby:;shutdown=true"); + + // To shut down a specific database only, but keep the + // engine running (for example for connecting to other + // databases), specify a database in the connection URL: + //DriverManager.getConnection("jdbc:derby:" + dbName + ";shutdown=true"); + } catch (Exception e) { + if(e instanceof SQLException) { + SQLException se = (SQLException) e; + if (((se.getErrorCode() == 50000) && ("XJ015".equals(se.getSQLState())))) { + // we got the expected exception + logger.info("Derby shut down normally"); + // Note that for single database shutdown, the expected + // SQL state is "08006", and the error code is 45000. + } else { + // if the error code or SQLState is different, we have + // an unexpected exception (shutdown failed) + logger.error("Derby did not shut down normally!" + inspectSQLException(se), se); + } + } else { + logger.error("Derby did not shut down normally! [" + e.getMessage() + "]", e); + } + } + // force garbage collection to unload the EmbeddedDriver + // so Derby can be restarted + System.gc(); + } + + /** + * Extracts details of an SQLException chain to <code>String</code>. + * Details included are SQL State, Error code, Exception message. + * + * @param e the SQLException from which to print details. + */ + private static String inspectSQLException(SQLException e) + { + // Unwraps the entire exception chain to unveil the real cause of the + // Exception. + String errMsg = ""; + while (e != null) + { + errMsg += "\n----- SQLException -----" + CoreConstants.NEWLINE + " SQL State: " + e.getSQLState() + + CoreConstants.NEWLINE + " Error Code: " + e.getErrorCode() + CoreConstants.NEWLINE + + " Message: " + e.getMessage(); + e = e.getNextException(); + } + return errMsg; + } +} diff --git a/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/utils/MemoryStatsFactory.java b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/utils/MemoryStatsFactory.java new file mode 100644 index 00000000..691662e6 --- /dev/null +++ b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/utils/MemoryStatsFactory.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Gluster Management Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + *******************************************************************************/ +package org.gluster.storage.management.gateway.utils; + +import java.util.List; + +import org.gluster.storage.management.core.model.ServerStats; +import org.gluster.storage.management.core.model.ServerStatsRow; +import org.springframework.stereotype.Component; + + +/** + * + */ +@Component +public class MemoryStatsFactory extends AbstractStatsFactory { + + private static final String MEM_STATS_SCRIPT = "get_rrd_memory_details.py"; + + @Override + public String getStatsScriptName() { + return MEM_STATS_SCRIPT; + } + + @Override + public ServerStats fetchStats(String serverName, String period, String... args) { + ServerStats stats = super.fetchStats(serverName, period, args); + + // stats returned by rrd script contains five columns - user, free, cache, buffer, total + // out of this, the "user" memory includes cached and buffer. We remove them to get the + // actual memory used by "user" + for(ServerStatsRow row : stats.getRows()) { + List<Double> data = row.getUsageData(); + Double user = data.get(0); + Double free = data.get(1); + Double cache = data.get(2); + Double buffer = data.get(3); + Double total = data.get(4); + + Double actualUser = user - cache - buffer; + + // convert all figures from bytes to percentages + data.set(0, (actualUser * 100) / total); + data.set(1, (free * 100) / total); + data.set(2, (cache * 100) / total); + data.set(3, (buffer * 100) / total); + data.set(4, (total * 100) / total); + } + + return stats; + } +} diff --git a/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/utils/NetworkStatsFactory.java b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/utils/NetworkStatsFactory.java new file mode 100644 index 00000000..03c252a9 --- /dev/null +++ b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/utils/NetworkStatsFactory.java @@ -0,0 +1,173 @@ +/******************************************************************************* + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Gluster Management Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + *******************************************************************************/ +package org.gluster.storage.management.gateway.utils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.log4j.Logger; +import org.gluster.storage.management.core.exceptions.GlusterRuntimeException; +import org.gluster.storage.management.core.model.NetworkInterface; +import org.gluster.storage.management.core.model.Server; +import org.gluster.storage.management.core.model.ServerStats; +import org.gluster.storage.management.core.model.ServerStatsRow; +import org.gluster.storage.management.core.utils.ProcessUtil; +import org.springframework.stereotype.Component; + + +/** + * + */ +@Component +public class NetworkStatsFactory extends AbstractStatsFactory { + private static final Logger logger = Logger.getLogger(NetworkStatsFactory.class); + private static final String NETWORK_STATS_SCRIPT = "get_rrd_net_details.py"; + private int[][] dataCount; + + @Override + public String getStatsScriptName() { + return NETWORK_STATS_SCRIPT; + } + + @Override + protected ServerStats getFirstOnlineServerStats(List<String> serverNames, String period, + boolean removeServerOnError, boolean removeOnlineServer) { + ServerStats firstOnlineServerStats = null; + for(int i = serverNames.size() - 1; i >= 0; i--) { + String serverName = serverNames.get(i); + Server server = new Server(serverName); + serverUtil.fetchServerDetails(server); + if(!server.isOnline()) { + if(removeServerOnError) { + // server is offline. no point in trying to fetch it's details. + serverNames.remove(serverName); + } + continue; + } + try { + for(NetworkInterface networkInterface : server.getNetworkInterfaces()) { + ServerStats stats = fetchStats(serverName, period, networkInterface.getName()); + if(firstOnlineServerStats == null) { + firstOnlineServerStats = stats; + int rowCount = firstOnlineServerStats.getMetadata().getRowCount(); + int columnCount = firstOnlineServerStats.getMetadata().getLegend().size(); + dataCount = initDataCountArray(rowCount, columnCount); + } else { + addServerStats(stats, firstOnlineServerStats, dataCount); + } + } + + if(removeOnlineServer) { + serverNames.remove(serverName); + } + return firstOnlineServerStats; + } catch(Exception e) { + // server might be offline - continue with next one + logger.warn("Couldn't fetch stats from server [" + serverName + "]!", e); + if(removeServerOnError) { + serverNames.remove(serverName); + } + continue; + } + } + throw new GlusterRuntimeException("All servers offline!"); + } + + protected void aggregateStats(List<String> serverNames, ServerStats aggregatedStats, String period) { + if(serverNames.isEmpty()) { + return; + } + + List<ServerStats> statsList = Collections.synchronizedList(new ArrayList<ServerStats>()); + try { + List<Thread> threads = createThreads(serverNames, period, statsList); + ProcessUtil.waitForThreads(threads); + for(ServerStats stats : statsList) { + addServerStats(stats, aggregatedStats, dataCount); + } + } catch (InterruptedException e) { + String errMsg = "Exception while aggregating network statistics on servers [" + serverNames + + "] for period [" + period + "]! Error: [" + e.getMessage() + "]"; + logger.error(errMsg, e); + throw new GlusterRuntimeException(errMsg, e); + } + + averageAggregatedStats(aggregatedStats, dataCount); + } + + private <T> List<Thread> createThreads(List<String> serverNames, String period, List<ServerStats> statsList) + throws InterruptedException { + List<Thread> threads = new ArrayList<Thread>(); + for (int i = serverNames.size()-1; i >= 0 ; i--) { + Thread thread = new NetworkStatsThread(serverNames.get(i), period, statsList); + threads.add(thread); + thread.start(); + if(i >= 5 && i % 5 == 0) { + // After every 5 servers, wait for 1 second so that we don't end up with too many running threads + Thread.sleep(1000); + } + } + return threads; + } + + public class NetworkStatsThread extends Thread { + private String serverName; + private String period; + private List<ServerStats> statsList; + + public NetworkStatsThread(String serverName, String period, List<ServerStats> statsList) { + this.serverName = serverName; + this.period = period; + this.statsList = statsList; + } + + @Override + public void run() { + try { + Server server = new Server(serverName); + serverUtil.fetchServerDetails(server); + + for (NetworkInterface networkInterface : server.getNetworkInterfaces()) { + // fetch the stats and add to aggregated stats + statsList.add(fetchStats(serverName, period, networkInterface.getName())); + } + } catch(Exception e) { + // server might be offline - continue with next one + logger.warn("Couldn't fetch Network stats from server [" + serverName + "]!", e); + } + } + } + + @Override + public ServerStats fetchStats(String serverName, String period, String... args) { + ServerStats stats = super.fetchStats(serverName, period, args); + + // the data returned by rrd contains "bytes/sec". Update the stats object to represent KiB/s + for(ServerStatsRow row : stats.getRows()) { + List<Double> data = row.getUsageData(); + for (int i = 0; i < data.size(); i++) { + Double val = data.get(i); + data.set(i, val / 1024); + } + } + + return stats; + } +} diff --git a/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/utils/PasswordManager.java b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/utils/PasswordManager.java new file mode 100644 index 00000000..a44b5bf3 --- /dev/null +++ b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/utils/PasswordManager.java @@ -0,0 +1,83 @@ +/******************************************************************************* + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Gluster Management Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + *******************************************************************************/ +package org.gluster.storage.management.gateway.utils; + +import org.apache.derby.jdbc.EmbeddedDriver; +import org.gluster.storage.management.core.constants.CoreConstants; +import org.gluster.storage.management.gateway.security.UserAuthDao; +import org.springframework.jdbc.datasource.SimpleDriverDataSource; +import org.springframework.security.authentication.dao.ReflectionSaltSource; +import org.springframework.security.authentication.encoding.ShaPasswordEncoder; + + +/** + * Tool to reset password of default user gluster + */ +public class PasswordManager { + private static final int USAGE_ERR = 1; + private static final int SQL_ERR = 2; + + private void resetPassword(String username) { + try { + UserAuthDao userAuthDao = createUserAuthDao(); + ReflectionSaltSource saltSource = createSaltSource(); + + String encodedPassword = new ShaPasswordEncoder(256).encodePassword(CoreConstants.DEFAULT_PASSWORD, + saltSource.getSalt(userAuthDao.loadUserByUsername(username))); + + userAuthDao.changePassword(username, encodedPassword); + + System.out.println("Password for user [" + username + "] reset successsfully to default value of [" + + CoreConstants.DEFAULT_PASSWORD + "]." + CoreConstants.NEWLINE); + + DBUtil.shutdownDerby(); + } catch (Exception e) { + System.err.println(CoreConstants.NEWLINE + CoreConstants.NEWLINE + "Password reset for user [" + username + + "] failed! " + CoreConstants.NEWLINE + + "Make sure that the Management Gateway is not running while performing password reset." + + CoreConstants.NEWLINE); + System.exit(SQL_ERR); + } + } + + private ReflectionSaltSource createSaltSource() { + ReflectionSaltSource saltSource = new ReflectionSaltSource(); + saltSource.setUserPropertyToUse("username"); + return saltSource; + } + + private UserAuthDao createUserAuthDao() throws InstantiationException, IllegalAccessException, + ClassNotFoundException { + UserAuthDao authDao = new UserAuthDao(); + EmbeddedDriver driver = (EmbeddedDriver) Class.forName(EmbeddedDriver.class.getName()).newInstance(); + SimpleDriverDataSource dataSource = new SimpleDriverDataSource(driver, "jdbc:derby:/opt/glustermg/data", "gluster", "syst3m"); + + authDao.setDataSource(dataSource); + return authDao; + } + + public static void main(String args[]) { + if (args.length != 2 || !args[0].equals("reset")) { + System.err.println("Usage: java " + PasswordManager.class.getName() + " reset <username>\n"); + System.exit(USAGE_ERR); + } + + new PasswordManager().resetPassword(args[1]); + } +} diff --git a/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/utils/ServerUtil.java b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/utils/ServerUtil.java new file mode 100644 index 00000000..f65c5640 --- /dev/null +++ b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/utils/ServerUtil.java @@ -0,0 +1,370 @@ +/** + * ServerUtil.java + * + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Gluster Management Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + */ +package org.gluster.storage.management.gateway.utils; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import javax.servlet.ServletContext; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Unmarshaller; + +import org.apache.log4j.Logger; +import org.gluster.storage.management.core.constants.CoreConstants; +import org.gluster.storage.management.core.exceptions.ConnectionException; +import org.gluster.storage.management.core.exceptions.GlusterRuntimeException; +import org.gluster.storage.management.core.model.Server; +import org.gluster.storage.management.core.model.Status; +import org.gluster.storage.management.core.model.Server.SERVER_STATUS; +import org.gluster.storage.management.core.response.StringListResponse; +import org.gluster.storage.management.core.utils.ProcessResult; +import org.gluster.storage.management.core.utils.ProcessUtil; +import org.gluster.storage.management.gateway.services.GlusterInterfaceService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; +import org.springframework.web.context.ContextLoader; + + +@Component +public class ServerUtil { + @Autowired + ServletContext servletContext; + + @Autowired + private SshUtil sshUtil; + + @Autowired + private String appVersion; + + private static final Logger logger = Logger.getLogger(ServerUtil.class); + + private static final String SCRIPT_DIR = "scripts"; + private static final String SCRIPT_COMMAND = "python"; + private static final String REMOTE_SCRIPT_GET_DISK_FOR_DIR = "get_disk_for_dir.py"; + private static final String REMOTE_SCRIPT_GET_SERVER_DETAILS = "get_server_details.py"; + private static final String REMOTE_SCRIPT_GET_FILE_SYSTEM_TYPE = "get_filesystem_type.py"; + private static final String REMOTE_SCRIPT_BASE_DIR = "/opt/glustermg"; + private static final String REMOTE_SCRIPT_DIR_NAME = "backend"; + + public void setSshUtil(SshUtil sshUtil) { + this.sshUtil = sshUtil; + } + + public ProcessResult executeGlusterScript(boolean runInForeground, String scriptName, String...arguments) { + return executeGlusterScript(runInForeground, scriptName, Arrays.asList(arguments)); + } + + public ProcessResult executeGlusterScript(boolean runInForeground, String scriptName, List<String> arguments) { + List<String> command = new ArrayList<String>(); + + command.add(SCRIPT_COMMAND); + command.add(getScriptPath(scriptName)); + command.addAll(arguments); + return ProcessUtil.executeCommand(runInForeground, command); + } + + private String getScriptPath(String scriptName) { + return servletContext.getRealPath(SCRIPT_DIR) + CoreConstants.FILE_SEPARATOR + scriptName; + } + + private String getRemoteScriptDir() { + return REMOTE_SCRIPT_BASE_DIR + File.separator + appVersion + File.separator + REMOTE_SCRIPT_DIR_NAME; + } + + /** + * Fetch details of the given server. The server name must be populated in the object before calling this method. + * + * @param server + * Server whose details are to be fetched + */ + public void fetchServerDetails(Server server) { + try { + Server serverDetails = fetchServerDetails(server.getName()); + server.copyFrom(serverDetails); // Update the details in <Server> object + server.setDisks(serverDetails.getDisks()); + } catch (ConnectionException e) { + logger.warn("Couldn't connect to server [" + server.getName() + "]. Marking it offline!", e); + server.setStatus(SERVER_STATUS.OFFLINE); + } + } + + public boolean isServerOnline(Server server) { + // fetch latest details and check if server is still online + fetchServerDetails(server); + return server.isOnline(); + } + + public String fetchHostName(String serverName) { + Object response = fetchServerDetails(serverName); + return ((Server) response).getName(); + } + + private Server fetchServerDetails(String serverName) { + // fetch standard server details like cpu, disk, memory details + return executeScriptOnServer(serverName, REMOTE_SCRIPT_GET_SERVER_DETAILS, Server.class); + } + + /** + * Executes given script on all given servers in parallel, collects the output in objects of given class, and + * returns a list of all returned objects. + * + * @param serverNames + * @param scriptWithArgs + * @param expectedClass + * @param failOnError + * If true, an exception will be thrown as soon as the script execution fails on any of the servers. If + * false, the exception will be caught and logged. Execution on all other servers will continue. + * @return + */ + public <T> List<T> executeScriptOnServers(List<String> serverNames, String scriptWithArgs, + Class<T> expectedClass, boolean failOnError) { + List<T> result = Collections.synchronizedList(new ArrayList<T>()); + try { + List<Thread> threads = createScriptExecutionThreads(serverNames, getRemoteScriptDir() + File.separator + + scriptWithArgs, expectedClass, result, failOnError); + ProcessUtil.waitForThreads(threads); + return result; + } catch (InterruptedException e) { + String errMsg = "Exception while executing script [" + scriptWithArgs + "] on servers [" + serverNames + "]! Error: [" + e.getMessage() + "]"; + logger.error(errMsg, e); + throw new GlusterRuntimeException(errMsg, e); + } + } + + /** + * Creates threads that will run in parallel and execute the given command on each of the given servers + * + * @param serverNames + * @param commandWithArgs + * @param expectedClass + * @param result + * @param failOnError + * If true, an exception will be thrown as soon as the script execution fails on any of the servers. If + * false, the exception will be caught and logged. Execution on all other servers will continue. + * @return + * @throws InterruptedException + */ + private <T> List<Thread> createScriptExecutionThreads(List<String> serverNames, String commandWithArgs, Class<T> expectedClass, List<T> result, + boolean failOnError) + throws InterruptedException { + List<Thread> threads = new ArrayList<Thread>(); + for (int i = serverNames.size()-1; i >= 0 ; i--) { + Thread thread = new RemoteExecutionThread<T>(serverNames.get(i), commandWithArgs, expectedClass, result, failOnError); + threads.add(thread); + thread.start(); + if(i >= 5 && i % 5 == 0) { + // After every 5 servers, wait for 1 second so that we don't end up with too many running threads + Thread.sleep(1000); + } + } + return threads; + } + + + public class RemoteExecutionThread<T> extends Thread { + private String serverName; + private String commandWithArgs; + private List<T> result; + private Class<T> expectedClass; + private boolean failOnError = false; + + public RemoteExecutionThread(String serverName, String commandWithArgs, Class<T> expectedClass, List<T> result, + boolean failOnError) { + this.serverName = serverName; + this.commandWithArgs = commandWithArgs; + this.result = result; + this.expectedClass = expectedClass; + this.failOnError = failOnError; + } + + @Override + public void run() { + try { + result.add(executeOnServer(serverName, commandWithArgs, expectedClass)); + } catch(Exception e) { + String errMsg = "Couldn't execute command [" + commandWithArgs + "] on [" + serverName + "]!"; + logger.error(errMsg, e); + if(failOnError) { + throw new GlusterRuntimeException(errMsg, e); + } + } + } + } + + + /** + * Executes given script on given server. Since the remote server may contain multiple versions of backend, this + * method will invoke the script present in directory of same version as the gateway. + * + * @param serverName + * @param scriptWithArgs + * The script name followed by arguments to be passed. Note that the script name should not contain path + * as it will be automatically identified by the method. + * @param expectedClass + * Class of the object expected from script execution + * @return Output (console/error) from the script execution + * @throws GlusterRuntimeException in case the remote execution fails. + */ + public String executeScriptOnServer(String serverName, String scriptWithArgs) { + return executeOnServer(serverName, getRemoteScriptDir() + File.separator + scriptWithArgs, String.class); + } + + /** + * Executes given script on given server. Since the remote server may contain multiple versions of backend, this + * method will invoke the script present in directory of same version as the gateway. + * + * @param serverName + * @param scriptWithArgs + * The script name followed by arguments to be passed. Note that the script name should not contain path + * as it will be automatically identified by the method. + * @param expectedClass + * Class of the object expected from script execution + * @return Object of the expected class from remote execution of the command. + * @throws GlusterRuntimeException in case the remote execution fails. + */ + public <T> T executeScriptOnServer(String serverName, String scriptWithArgs, + Class<T> expectedClass) { + return executeOnServer(serverName, getRemoteScriptDir() + File.separator + scriptWithArgs, + expectedClass); + } + + /** + * Executes given command on given server + * + * @param serverName + * @param commandWithArgs + * @param expectedClass + * Class of the object expected from script execution + * @return Object of the expected class from remote execution of the command. In case the remote execution fails + * ungracefully, an object of class {@link Status} will be returned. + */ + @SuppressWarnings("unchecked") + public <T> T executeOnServer(String serverName, String commandWithArgs, + Class<T> expectedClass) { + String output = executeOnServer(serverName, commandWithArgs); + if (expectedClass == String.class) { + return (T) output; + } + + return unmarshal(expectedClass, output); + } + + public String executeOnServer(String serverName, String commandWithArgs) { + ProcessResult result = sshUtil.executeRemote(serverName, commandWithArgs); + + if (!result.isSuccess()) { + throw new GlusterRuntimeException("Command [" + commandWithArgs + "] failed on [" + serverName + + "] with error [" + result.getExitValue() + "][" + result.getOutput() + "]"); + } + return result.getOutput(); + } + + // This is the old executeOnServer that used socket communication. + // We can keep it commented for the time being. + // private String executeOnServerUsingSocket(String serverName, String commandWithArgs) { + // try { + // InetAddress address = InetAddress.getByName(serverName); + // Socket connection = new Socket(address, 50000); + // + // PrintWriter writer = new PrintWriter(connection.getOutputStream(), true); + // writer.println(commandWithArgs); + // writer.println(); // empty line means end of request + // + // InputStream inputStream = connection.getInputStream(); + // int available = inputStream.available(); + // + // StringBuffer output = new StringBuffer(); + // if( available > 0 ) { + // // This happens when PeerAgent sends complete file + // byte[] responseData = new byte[available]; + // inputStream.read(responseData); + // output.append(new String(responseData, "UTF-8")); + // } else { + // // This happens in case of normal XML response from PeerAgent + // BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8")); + // + // String line; + // while (!(line = reader.readLine()).trim().isEmpty()) { + // output.append(line + CoreConstants.NEWLINE); + // } + // } + // connection.close(); + // + // return output.toString(); + // } catch (Exception e) { + // throw new GlusterRuntimeException("Error during remote execution: [" + e.getMessage() + "]"); + // } + // } + + public void getFileFromServer(String serverName, String remoteFileName, String localDirName) { + sshUtil.getFile(serverName, remoteFileName, localDirName); + } + + /** + * Unmarshals given input string into object of given class + * + * @param expectedClass + * Class whose object is expected + * @param input + * Input string + * @return Object of given expected class + */ + public <T> T unmarshal(Class<T> expectedClass, String input) { + try { + // create JAXB context and instantiate marshaller + JAXBContext context = JAXBContext.newInstance(expectedClass); + Unmarshaller um = context.createUnmarshaller(); + return (T)um.unmarshal(new ByteArrayInputStream(input.getBytes())); + } catch (JAXBException e) { + String errMsg = "Error during unmarshalling string [" + input + "] for class [" + expectedClass.getName() + + ": [" + e.getMessage() + "]"; + logger.error(errMsg, e); + throw new GlusterRuntimeException(errMsg, e); + } + } + + /** + * @param serverName + * Server on which the directory is present + * @param brickDir + * Directory whose disk is to be fetched + * @return Status object containing the disk name, or error message in case the remote script fails. + */ + public Status getDiskForDir(String serverName, String brickDir) { + return executeScriptOnServer(serverName, REMOTE_SCRIPT_GET_DISK_FOR_DIR + " " + brickDir, Status.class); + } + + public <T> T getBean(Class<T> clazz) { + ApplicationContext ctx = ContextLoader.getCurrentWebApplicationContext(); + return ctx.getBean(clazz); + } + + public List<String> getFsTypes(String serverName) { + String output = executeScriptOnServer(serverName, REMOTE_SCRIPT_GET_FILE_SYSTEM_TYPE); + return Arrays.asList(output.trim().split(CoreConstants.NEWLINE)); + } +} diff --git a/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/utils/SshUtil.java b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/utils/SshUtil.java new file mode 100644 index 00000000..074cbd5b --- /dev/null +++ b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/utils/SshUtil.java @@ -0,0 +1,463 @@ +/******************************************************************************* + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Gluster Management Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + *******************************************************************************/ +package org.gluster.storage.management.gateway.utils; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Arrays; + +import org.apache.log4j.Logger; +import org.gluster.storage.management.core.constants.CoreConstants; +import org.gluster.storage.management.core.exceptions.ConnectionException; +import org.gluster.storage.management.core.exceptions.GlusterRuntimeException; +import org.gluster.storage.management.core.utils.FileUtil; +import org.gluster.storage.management.core.utils.LRUCache; +import org.gluster.storage.management.core.utils.ProcessResult; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import ch.ethz.ssh2.ChannelCondition; +import ch.ethz.ssh2.Connection; +import ch.ethz.ssh2.SCPClient; +import ch.ethz.ssh2.Session; +import ch.ethz.ssh2.StreamGobbler; + + +/** + * + */ +@Component +public class SshUtil { + private static final String TEMP_DIR = "/tmp/"; + public static final String SSH_AUTHORIZED_KEYS_DIR_LOCAL = "/opt/glustermg/keys/"; + public static final String SSH_AUTHORIZED_KEYS_DIR_REMOTE = "/root/.ssh/"; + private static final String SSH_AUTHORIZED_KEYS_FILE = "authorized_keys"; + private static final String SSH_AUTHORIZED_KEYS_PATH_REMOTE = SSH_AUTHORIZED_KEYS_DIR_REMOTE + SSH_AUTHORIZED_KEYS_FILE; + public static final File PRIVATE_KEY_FILE = new File(SSH_AUTHORIZED_KEYS_DIR_LOCAL + "gluster.pem"); + public static final File PUBLIC_KEY_FILE = new File(SSH_AUTHORIZED_KEYS_DIR_LOCAL + "gluster.pub"); + private LRUCache<String, Connection> sshConnCache = new LRUCache<String, Connection>(10); + + // TODO: Make user name configurable + private static final String USER_NAME = "root"; + // TODO: Make default password configurable + private static final String DEFAULT_PASSWORD = "syst3m"; + + private static final Logger logger = Logger.getLogger(SshUtil.class); + + @Autowired + private Integer sshConnectTimeout; + @Autowired + private Integer sshKexTimeout; + @Autowired + private Integer sshExecTimeout; + + public boolean hasDefaultPassword(String serverName) { + try { + getConnectionWithPassword(serverName).close(); + return true; + } catch(Exception e) { + logger.warn("Couldn't connect to [" + serverName + "] with default password!", e); + return false; + } + } + + /** + * Checks if public key of management gateway is configured on given server + * + * @param serverName + * @return true if public key is configured, else false + */ + public boolean isPublicKeyInstalled(String serverName) { + try { + getConnectionWithPubKey(serverName).close(); + return true; + } catch(ConnectionException e) { + logger.warn("Couldn't connect to [" + serverName + "] with public key!", e); + return false; + } + } + + public void getFile(String serverName, String remoteFile, String localDir) { + try { + Connection conn = getConnection(serverName); + SCPClient scpClient = new SCPClient(conn); + scpClient.get(remoteFile, localDir); + } catch (IOException e) { + throw new GlusterRuntimeException("Error while fetching file [" + remoteFile + "] from server [" + + serverName + "]", e); + } + } + + public synchronized void installPublicKey(String serverName) { + Connection conn = null; + try { + conn = getConnectionWithPassword(serverName); + } catch(Exception e) { + // authentication failed. close the connection. + conn.close(); + if (e instanceof GlusterRuntimeException) { + throw (GlusterRuntimeException) e; + } else { + throw new GlusterRuntimeException("Exception during authentication with public key on server [" + + serverName + "]", e); + } + } + SCPClient scpClient = new SCPClient(conn); + + // delete file if it exists + File localTempFile = new File(TEMP_DIR + SSH_AUTHORIZED_KEYS_FILE); + if(localTempFile.exists()) { + localTempFile.delete(); + } + + try { + // get authorized_keys from server + scpClient.get(SSH_AUTHORIZED_KEYS_PATH_REMOTE, TEMP_DIR); + } catch (IOException e) { + // file doesn't exist. it will get created. + // create the .ssh directory in case it doesn't exist + logger.info("Couldn't fetch file [" + SSH_AUTHORIZED_KEYS_PATH_REMOTE +"].", e); + logger.info("Creating /root/.ssh on [" + serverName + "] in case it doesn't exist."); + String command = "mkdir -p " + SSH_AUTHORIZED_KEYS_DIR_REMOTE; + ProcessResult result = executeCommand(conn, command); + if(!result.isSuccess()) { + String errMsg = "Command [" + command + "] failed on server [" + serverName + "] with error: " + result; + logger.error(errMsg); + throw new GlusterRuntimeException(errMsg); + } + } + + byte[] publicKeyData; + try { + publicKeyData = FileUtil.readFileAsByteArray(PUBLIC_KEY_FILE); + } catch (Exception e) { + conn.close(); + throw new GlusterRuntimeException("Couldn't load public key file [" + PUBLIC_KEY_FILE + "]", e); + } + + try { + // append it + FileOutputStream outputStream = new FileOutputStream(localTempFile, true); + outputStream.write(CoreConstants.NEWLINE.getBytes()); + outputStream.write(publicKeyData); + outputStream.close(); + } catch (Exception e) { + conn.close(); + throw new GlusterRuntimeException("Couldnt append file [" + localTempFile + "] with public key!", e); + } + + try { + scpClient.put(localTempFile.getAbsolutePath(), SSH_AUTHORIZED_KEYS_FILE, SSH_AUTHORIZED_KEYS_DIR_REMOTE, "0600"); + } catch (IOException e) { + throw new GlusterRuntimeException("Couldn't add public key to server [" + serverName + "]", e); + } finally { + conn.close(); + localTempFile.delete(); + } + + // It was decided NOT to disable password login as this may not be acceptable in a bare-metal environment + // disableSshPasswordLogin(serverName, scpClient); + } + +// private void disableSshPasswordLogin(String serverName, SCPClient scpClient) { +// ProcessResult result = executeRemote(serverName, SCRIPT_DISABLE_SSH_PASSWORD_AUTH); +// if(!result.isSuccess()) { +// throw new GlusterRuntimeException("Couldn't disable SSH password authentication on [" + serverName +// + "]. Error: " + result); +// } +// } + + private synchronized Connection getConnectionWithPassword(String serverName) { + Connection conn = createConnection(serverName); + if(!authenticateWithPassword(conn)) { + conn.close(); + throw new ConnectionException("SSH Authentication (password) failed for server [" + + conn.getHostname() + "]"); + } + return conn; + } + + private synchronized Connection getConnectionWithPubKey(String serverName) { + Connection conn = createConnection(serverName); + if(!authenticateWithPublicKey(conn)) { + conn.close(); + throw new ConnectionException("SSH Authentication (public key) failed for server [" + + conn.getHostname() + "]"); + } + return conn; + } + + private synchronized Connection getConnection(String serverName) { + Connection conn = sshConnCache.get(serverName); + if (conn != null) { + return conn; + } + + conn = createConnection(serverName); + try { + if(!authenticateWithPublicKey(conn)) { + if(!authenticateWithPassword(conn)) { + conn.close(); + throw new ConnectionException("SSH authentication failed on server [" + serverName + "]!"); + } + } + } catch(Exception e) { + // authentication failed. close the connection. + conn.close(); + if(e instanceof GlusterRuntimeException) { + throw (GlusterRuntimeException)e; + } else { + throw new GlusterRuntimeException("Exception during authentication on server [" + serverName + "]", e); + } + } + + sshConnCache.put(serverName, conn); + return conn; + } + + private boolean authenticateWithPublicKey(Connection conn) { + try { + if (!supportsPublicKeyAuthentication(conn)) { + throw new ConnectionException("Public key authentication not supported on [" + conn.getHostname() + + "]"); + } + + if (!conn.authenticateWithPublicKey(USER_NAME, PRIVATE_KEY_FILE, null)) { + return false; + } + + return true; + } catch (IOException e) { + throw new ConnectionException("Exception during SSH authentication (public key) for server [" + + conn.getHostname() + "]", e); + } + } + + private boolean authenticateWithPassword(Connection conn) { + try { + if (!supportsPasswordAuthentication(conn)) { + throw new ConnectionException("Password authentication not supported on [" + conn.getHostname() + + "]"); + } + + if (!conn.authenticateWithPassword(USER_NAME, DEFAULT_PASSWORD)) { + return false; + } + return true; + } catch (IOException e) { + throw new ConnectionException("Exception during SSH authentication (password) for server [" + + conn.getHostname() + "]", e); + } + } + + private boolean supportsPasswordAuthentication(Connection conn) throws IOException { + return Arrays.asList(conn.getRemainingAuthMethods(USER_NAME)).contains("password"); + } + + private boolean supportsPublicKeyAuthentication(Connection conn) throws IOException { + return Arrays.asList(conn.getRemainingAuthMethods(USER_NAME)).contains("publickey"); + } + + private synchronized Connection createConnection(String serverName) { + Connection conn = new Connection(serverName); + try { + conn.connect(null, sshConnectTimeout, sshKexTimeout); + } catch (IOException e) { + logger.error("Couldn't establish SSH connection with server [" + serverName + "]", e); + conn.close(); + throw new ConnectionException("Exception while creating SSH connection with server [" + serverName + "]", e); + } + return conn; + } + + private boolean wasTerminated(int condition) { + return ((condition | ChannelCondition.EXIT_SIGNAL) == condition); + } + + private boolean hasErrors(int condition, Session session) { + return (hasErrorStream(condition) || (exitedGracefully(condition) && exitedWithError(session))); + } + + private boolean timedOut(int condition) { + return (condition == ChannelCondition.TIMEOUT); + } + + private boolean exitedWithError(Session session) { + return session.getExitStatus() != ProcessResult.SUCCESS; + } + + private boolean exitedGracefully(int condition) { + return (condition | ChannelCondition.EXIT_STATUS) == condition; + } + + private boolean hasErrorStream(int condition) { + return (condition | ChannelCondition.STDERR_DATA) == condition; + } + + private ProcessResult executeCommand(Connection sshConnection, String command) { + Session session = null; + try { + session = sshConnection.openSession(); + BufferedReader stdoutReader = new BufferedReader(new InputStreamReader(new StreamGobbler( + session.getStdout()))); + BufferedReader stderrReader = new BufferedReader(new InputStreamReader(new StreamGobbler( + session.getStderr()))); + session.execCommand(command); + ProcessResult result = getResultOfExecution(session, stdoutReader, stderrReader); + return result; + } catch (Exception e) { + String errMsg = "Exception while executing command [" + command + "] on [" + sshConnection.getHostname() + + "]"; + logger.error(errMsg, e); + + // remove the connection from cache and close it + sshConnCache.remove(sshConnection.getHostname()); + sshConnection.close(); + if(e instanceof IllegalStateException || e instanceof IOException) { + // The connection is no more valid. Create and throw a connection exception. + throw new ConnectionException("Couldn't open SSH session on [" + sshConnection.getHostname() + "]!", e); + } else { + throw new GlusterRuntimeException(errMsg, e); + } + } finally { + if(session != null) { + session.close(); + } + } + } + + private ProcessResult getResultOfExecution(Session session, BufferedReader stdoutReader, BufferedReader stderrReader) { + // Wait for program to come out either + // a) gracefully with an exit status, OR + // b) because of a termination signal + // c) command takes to long to exit (timeout) + int condition = session.waitForCondition(ChannelCondition.EXIT_SIGNAL | ChannelCondition.EXIT_STATUS, + sshExecTimeout); + StringBuilder output = new StringBuilder(); + + try { + if(!timedOut(condition)) { + readFromStream(stdoutReader, output); + if (hasErrors(condition, session)) { + readFromStream(stderrReader, output); + } + } + + return prepareProcessResult(session, condition, output.toString().trim()); + } catch (IOException e) { + String errMsg = "Error while reading output stream from SSH connection!"; + logger.error(errMsg, e); + return new ProcessResult(ProcessResult.FAILURE, errMsg); + } + } + + private ProcessResult prepareProcessResult(Session session, int condition, String output) { + ProcessResult result = null; + + if (wasTerminated(condition)) { + result = new ProcessResult(ProcessResult.FAILURE, output); + } else if (timedOut(condition)) { + result = new ProcessResult(ProcessResult.FAILURE, "Command timed out!"); + } else if (hasErrors(condition, session)) { + Integer exitStatus = session.getExitStatus(); + int statusCode = (exitStatus == null ? ProcessResult.FAILURE : exitStatus); + result = new ProcessResult(statusCode, output); + } else { + result = new ProcessResult(ProcessResult.SUCCESS, output); + } + + return result; + } + + private void readFromStream(BufferedReader streamReader, StringBuilder output) throws IOException { + while (true) { + String line = streamReader.readLine(); + if (line == null) { + break; + } + output.append(line + CoreConstants.NEWLINE); + } + } + + /** + * Executes given command on remote machine using password authentication + * + * @param serverName + * @param command + * @return Result of remote execution + */ + public ProcessResult executeRemoteWithPassword(String serverName, String command) { + logger.info("Executing command [" + command + "] on server [" + serverName + "] with default password."); + Connection conn = null; + try { + conn = getConnectionWithPassword(serverName); + return executeCommand(conn, command); + } finally { + // we don't cache password based connections. hence the connection must be closed. + if(conn != null) { + conn.close(); + } + } + } + + /** + * Executes given command on remote machine using public key authentication + * + * @param serverName + * @param command + * @return Result of remote execution + */ + public ProcessResult executeRemote(String serverName, String command) { + logger.info("Executing command [" + command + "] on server [" + serverName + "]"); + return executeCommand(getConnection(serverName), command); + } + + public void cleanup() { + for (Connection conn : sshConnCache.values()) { + conn.close(); + } + } + + public Integer getSshConnectTimeout() { + return sshConnectTimeout; + } + + public void setSshConnectTimeout(Integer sshConnectTimeout) { + this.sshConnectTimeout = sshConnectTimeout; + } + + public Integer getSshKexTimeout() { + return sshKexTimeout; + } + + public void setSshKexTimeout(Integer sshKexTimeout) { + this.sshKexTimeout = sshKexTimeout; + } + + public Integer getSshExecTimeout() { + return sshExecTimeout; + } + + public void setSshExecTimeout(Integer sshExecTimeout) { + this.sshExecTimeout = sshExecTimeout; + } +} diff --git a/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/utils/StatsFactory.java b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/utils/StatsFactory.java new file mode 100644 index 00000000..f856c05a --- /dev/null +++ b/src/org.gluster.storage.management.gateway/src/org/gluster/storage/management/gateway/utils/StatsFactory.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com> + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Gluster Management Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + *******************************************************************************/ +package org.gluster.storage.management.gateway.utils; + +import java.util.List; + +import org.gluster.storage.management.core.model.ServerStats; + + +/** + * + */ +public interface StatsFactory { + public ServerStats fetchStats(String serverName, String period, String...args); + public ServerStats fetchAggregatedStats(List<String> serverName, String period); +} |