Mercurial > hg > thermostat-ng > web-gateway
changeset 167:8822a33e3823
Add System Info API to web gateway
See bug: http://icedtea.classpath.org/bugzilla/show_bug.cgi?id=3377
Reviewed-by: sgehwolf
Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2017-June/023421.html
Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2017-May/023313.html
line wrap: on
line diff
--- a/distribution/pom.xml Wed May 31 18:48:25 2017 -0400 +++ b/distribution/pom.xml Wed May 31 18:56:32 2017 -0400 @@ -213,6 +213,13 @@ </artifactItem> <artifactItem> <groupId>com.redhat.thermostat</groupId> + <artifactId>thermostat-web-gateway-service-systems</artifactId> + <version>${project.version}</version> + <type>war</type> + <overWrite>false</overWrite> + </artifactItem> + <artifactItem> + <groupId>com.redhat.thermostat</groupId> <artifactId>thermostat-web-gateway-service-white-pages</artifactId> <version>${project.version}</version> <type>war</type> @@ -253,6 +260,13 @@ </dependency> <dependency> <groupId>com.redhat.thermostat</groupId> + <artifactId>thermostat-web-gateway-service-systems</artifactId> + <version>${project.version}</version> + <type>war</type> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>com.redhat.thermostat</groupId> <artifactId>thermostat-web-gateway-service-commands</artifactId> <version>${project.version}</version> <type>war</type>
--- a/distribution/src/etc/services.properties Wed May 31 18:48:25 2017 -0400 +++ b/distribution/src/etc/services.properties Wed May 31 18:56:32 2017 -0400 @@ -2,4 +2,5 @@ /jvm-gc = thermostat-web-gateway-service-jvm-gc-@project.version@.war /jvm-memory = thermostat-web-gateway-service-jvm-memory-@project.version@.war /jvms = thermostat-web-gateway-service-jvms-@project.version@.war +/systems = thermostat-web-gateway-service-systems-@project.version@.war /white-pages = thermostat-web-gateway-service-white-pages-@project.version@.war
--- a/services/pom.xml Wed May 31 18:48:25 2017 -0400 +++ b/services/pom.xml Wed May 31 18:56:32 2017 -0400 @@ -55,6 +55,7 @@ <module>jvm-gc</module> <module>jvms</module> <module>jvm-memory</module> + <module>systems</module> <module>white-pages</module> </modules>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/services/systems/pom.xml Wed May 31 18:56:32 2017 -0400 @@ -0,0 +1,97 @@ +<!-- + + Copyright 2012-2017 Red Hat, Inc. + + This file is part of Thermostat. + + Thermostat is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 2, or (at your + option) any later version. + + Thermostat is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Thermostat; see the file COPYING. If not see + <http://www.gnu.org/licenses/>. + + Linking this code with other modules is making a combined work + based on this code. Thus, the terms and conditions of the GNU + General Public License cover the whole combination. + + As a special exception, the copyright holders of this code give + you permission to link this code with independent modules to + produce an executable, regardless of the license terms of these + independent modules, and to copy and distribute the resulting + executable under terms of your choice, provided that you also + meet, for each linked independent module, the terms and conditions + of the license of that module. An independent module is a module + which is not derived from or based on this code. If you modify + this code, you may extend this exception to your version of the + library, but you are not obligated to do so. If you do not wish + to do so, delete this exception statement from your version. + +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <artifactId>thermostat-web-gateway-services</artifactId> + <groupId>com.redhat.thermostat</groupId> + <version>1.99.12-SNAPSHOT</version> + </parent> + + <artifactId>thermostat-web-gateway-service-systems</artifactId> + + <packaging>war</packaging> + + <name>Thermostat Web Gateway System Info Service</name> + + <properties> + <com.redhat.thermostat.gateway.SERVICE_NAME>systems</com.redhat.thermostat.gateway.SERVICE_NAME> + </properties> + + <build> + <plugins> + <plugin> + <artifactId>maven-war-plugin</artifactId> + <configuration> + <webResources> + <resource> + <directory>src/main/webapp</directory> + <filtering>true</filtering> + </resource> + </webResources> + </configuration> + </plugin> + </plugins> + </build> + + <dependencies> + <!-- Thermostat Web Gateway Dependencies --> + <dependency> + <groupId>com.redhat.thermostat</groupId> + <artifactId>thermostat-web-gateway-common-mongodb</artifactId> + <version>${project.version}</version> + </dependency> + + <!-- Servlet API deps --> + <dependency> + <groupId>javax.servlet</groupId> + <artifactId>javax.servlet-api</artifactId> + <version>${javax.servlet.version}</version> + <scope>provided</scope> + </dependency> + + <!-- JAX-RS Dependencies --> + <dependency> + <groupId>javax.ws.rs</groupId> + <artifactId>javax.ws.rs-api</artifactId> + <version>${javax-rs-api.version}</version> + <scope>provided</scope> + </dependency> + </dependencies> +</project>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/services/systems/src/main/java/com/redhat/thermostat/service/systems/http/Parameters.java Wed May 31 18:56:32 2017 -0400 @@ -0,0 +1,51 @@ +/* + * Copyright 2012-2017 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.service.systems.http; + +class Parameters { + static final String SYSTEM_ID = "systemId"; + + static final String SORT = "sort"; + static final String QUERY = "query"; + static final String OFFSET = "offset"; + static final String LIMIT = "limit"; + static final String INCLUDE = "include"; + static final String EXCLUDE = "exclude"; + + static final String TIMESTAMP = "timeStamp"; + static final String ALIVE_ONLY = "aliveOnly"; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/services/systems/src/main/java/com/redhat/thermostat/service/systems/http/SwaggerSpecResourceHandler.java Wed May 31 18:56:32 2017 -0400 @@ -0,0 +1,59 @@ +/* + * Copyright 2012-2017 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.service.systems.http; + +import java.io.IOException; + +import javax.ws.rs.GET; +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 com.redhat.thermostat.gateway.common.core.servlet.BasicResourceHandler; + +@Path("doc/{fileName: .+\\.yaml}") +@Produces(MediaType.TEXT_PLAIN) +public class SwaggerSpecResourceHandler extends BasicResourceHandler { + + @GET + public Response getFileAsPlainText(@PathParam("fileName") String fileName) throws IOException { + return getFileAsResponse(SwaggerSpecResourceHandler.class.getClassLoader(), fileName); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/services/systems/src/main/java/com/redhat/thermostat/service/systems/http/SystemsHttpHandler.java Wed May 31 18:56:32 2017 -0400 @@ -0,0 +1,153 @@ +/* + * Copyright 2012-2017 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.service.systems.http; + +import javax.servlet.ServletContext; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.DefaultValue; +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.Context; +import javax.ws.rs.core.Response; + + +import com.mongodb.DBObject; +import com.redhat.thermostat.gateway.common.mongodb.ThermostatMongoStorage; +import com.redhat.thermostat.gateway.common.mongodb.servlet.ServletContextConstants; +import com.redhat.thermostat.service.systems.mongo.MongoStorageHandler; + +@Path("/") +public class SystemsHttpHandler { + private final MongoStorageHandler mongoStorageHandler = new MongoStorageHandler(); + private final String collectionName = "system-info"; + + @GET + @Consumes({ "application/json" }) + @Produces({ "application/json", "text/html; charset=utf-8" }) + public Response getSystemInfoAll(@QueryParam(Parameters.LIMIT) @DefaultValue("1") Integer limit, + @QueryParam(Parameters.OFFSET) @DefaultValue("0") Integer offset, + @QueryParam(Parameters.SORT) String sort, + @QueryParam(Parameters.QUERY) String queries, + @QueryParam(Parameters.INCLUDE) String includes, + @QueryParam(Parameters.EXCLUDE) String excludes, + @Context ServletContext context + ) { + try { + ThermostatMongoStorage storage = (ThermostatMongoStorage) context.getAttribute(ServletContextConstants.MONGODB_CLIENT_ATTRIBUTE); + String message = mongoStorageHandler.getSystemInfos(storage.getDatabase().getCollection(collectionName), limit, offset, sort, queries, includes, excludes); + return Response.status(Response.Status.OK).entity(message).build(); + } catch (Exception e) { + return Response.status(Response.Status.BAD_REQUEST).build(); + } + } + + @GET + @Path("/systems/{" + Parameters.SYSTEM_ID +"}") + @Consumes({ "application/json" }) + @Produces({ "application/json", "text/html; charset=utf-8" }) + public Response getSystemInfo(@PathParam(Parameters.SYSTEM_ID) String systemId, + @QueryParam(Parameters.LIMIT) @DefaultValue("1") Integer limit, + @QueryParam(Parameters.OFFSET) @DefaultValue("0") Integer offset, + @QueryParam(Parameters.SORT) String sort, + @QueryParam(Parameters.INCLUDE) String includes, + @QueryParam(Parameters.EXCLUDE) String excludes, + @Context ServletContext context + ) { + try { + ThermostatMongoStorage storage = (ThermostatMongoStorage) context.getAttribute(ServletContextConstants.MONGODB_CLIENT_ATTRIBUTE); + String message = mongoStorageHandler.getSystemInfo(storage.getDatabase().getCollection(collectionName), systemId, limit, offset, sort, includes, excludes); + return Response.status(Response.Status.OK).entity(message).build(); + } catch (Exception e) { + return Response.status(Response.Status.BAD_REQUEST).build(); + } + } + + @PUT + @Path("/systems/{" + Parameters.SYSTEM_ID +"}") + @Consumes({ "application/json" }) + @Produces({ "application/json", "text/html; charset=utf-8" }) + public Response putSystemInfo(String body, + @PathParam(Parameters.SYSTEM_ID) String systemId, + @QueryParam(Parameters.QUERY) String queries, + @Context ServletContext context) { + try { + ThermostatMongoStorage storage = (ThermostatMongoStorage) context.getAttribute(ServletContextConstants.MONGODB_CLIENT_ATTRIBUTE); + mongoStorageHandler.updateSystemInfo(storage.getDatabase().getCollection(collectionName), body, systemId, queries); + return Response.status(Response.Status.OK).build(); + } catch (Exception e) { + return Response.status(Response.Status.BAD_REQUEST).build(); + } + } + + @POST + @Path("/systems/{" + Parameters.SYSTEM_ID +"}") + @Consumes({ "application/json" }) + @Produces({ "application/json", "text/html; charset=utf-8" }) + public Response postSystemInfo(String body, + @PathParam(Parameters.SYSTEM_ID) String systemId, + @Context ServletContext context) { + try { + ThermostatMongoStorage storage = (ThermostatMongoStorage) context.getAttribute(ServletContextConstants.MONGODB_CLIENT_ATTRIBUTE); + mongoStorageHandler.addSystemInfos(storage.getDatabase().getCollection(collectionName, DBObject.class), body, systemId); + return Response.status(Response.Status.OK).build(); + } catch (Exception e) { + return Response.status(Response.Status.BAD_REQUEST).build(); + } + } + + @DELETE + @Path("/systems/{" + Parameters.SYSTEM_ID +"}") + @Consumes({ "application/json" }) + @Produces({ "application/json", "text/html; charset=utf-8" }) + public Response deleteSystemInfo(@PathParam(Parameters.SYSTEM_ID) String systemId, + @QueryParam(Parameters.QUERY) String queries, + @Context ServletContext context) { + try { + ThermostatMongoStorage storage = (ThermostatMongoStorage) context.getAttribute(ServletContextConstants.MONGODB_CLIENT_ATTRIBUTE); + mongoStorageHandler.deleteSystemInfo(storage.getDatabase().getCollection(collectionName), systemId); + return Response.status(Response.Status.OK).build(); + } catch (Exception e) { + return Response.status(Response.Status.BAD_REQUEST).build(); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/services/systems/src/main/java/com/redhat/thermostat/service/systems/mongo/Fields.java Wed May 31 18:56:32 2017 -0400 @@ -0,0 +1,48 @@ +/* + * Copyright 2012-2017 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.service.systems.mongo; + +class Fields { + static final String SYSTEM_ID = "systemId"; + + static final String SET = "set"; + static final String LAST_UPDATED = "lastUpdated"; + static final String STOP_TIME = "stopTime"; + + static final String RESPONSE = "response"; + static final String SYSTEMS = "systems"; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/services/systems/src/main/java/com/redhat/thermostat/service/systems/mongo/MongoStorageHandler.java Wed May 31 18:56:32 2017 -0400 @@ -0,0 +1,173 @@ +/* + * Copyright 2012-2017 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.service.systems.mongo; + +import com.mongodb.BasicDBObject; +import com.mongodb.Block; +import com.mongodb.CursorType; +import com.mongodb.DBObject; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import com.mongodb.util.JSON; +import com.redhat.thermostat.gateway.common.mongodb.filters.MongoRequestFilters; +import com.redhat.thermostat.gateway.common.mongodb.filters.MongoSortFilters; +import com.redhat.thermostat.gateway.common.mongodb.response.MongoResponseBuilder; +import org.bson.Document; +import org.bson.conversions.Bson; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static com.mongodb.client.model.Filters.and; +import static com.mongodb.client.model.Filters.eq; +import static com.mongodb.client.model.Filters.lt; +import static com.mongodb.client.model.Filters.or; +import static com.mongodb.client.model.Projections.exclude; +import static com.mongodb.client.model.Projections.excludeId; +import static com.mongodb.client.model.Projections.fields; +import static com.mongodb.client.model.Projections.include; + +public class MongoStorageHandler { + + private final MongoResponseBuilder mongoResponseBuilder = new MongoResponseBuilder(); + + public String getSystemInfos(MongoCollection<Document> collection, Integer limit, Integer offset, String sort, String queries, String includes, String excludes) { + FindIterable<Document> documents; + if (queries != null) { + List<String> queriesList = Arrays.asList(queries.split(",")); + final Bson query = MongoRequestFilters.buildQueriesFilter(queriesList); + documents = collection.find(query); + } else { + documents = collection.find(); + } + + documents = buildProjection(documents, includes, excludes); + + final Bson sortObject = MongoSortFilters.createSortObject(sort); + documents = documents.sort(sortObject).limit(limit).skip(offset).batchSize(limit).cursorType(CursorType.NonTailable); + + return mongoResponseBuilder.buildGetResponseString(documents); + } + + public String getSystemInfo(MongoCollection<Document> collection, String systemId, Integer limit, Integer offset, String sort, String includes, String excludes) { + Bson query = eq(Fields.SYSTEM_ID, systemId); + FindIterable<Document> documents = collection.find(query); + + documents = buildProjection(documents, includes, excludes); + + final Bson sortObject = MongoSortFilters.createSortObject(sort); + documents = documents.sort(sortObject).limit(limit).skip(offset).batchSize(limit + offset).cursorType(CursorType.NonTailable); + + return mongoResponseBuilder.buildGetResponseString(documents); + } + + private FindIterable<Document> buildProjection(FindIterable<Document> documents, String includes, String excludes) { + if (excludes != null) { + List<String> excludesList = Arrays.asList(excludes.split(",")); + documents = documents.projection(fields(exclude(excludesList), excludeId())); + } else if (includes != null) { + List<String> includesList = Arrays.asList(includes.split(",")); + documents = documents.projection(fields(include(includesList), excludeId())); + } else { + documents = documents.projection(excludeId()); + } + + return documents; + } + + public void addSystemInfos(MongoCollection<DBObject> collection, String body, String systemId) { + if (body.length() > 0) { + List<DBObject> inputList = (List<DBObject>) JSON.parse(body); + for (DBObject o : inputList) { + o.put(Fields.SYSTEM_ID, systemId); + } + collection.insertMany(inputList); + } + } + + public void deleteSystemInfo(MongoCollection<Document> collection, String systemId) { + Bson query = eq(Fields.SYSTEM_ID, systemId); + deleteDocuments(collection, query); + } + + private void deleteDocuments(MongoCollection<Document> collection, Bson query) { + collection.deleteMany(query); + } + + public void updateSystemInfo(MongoCollection<Document> collection, String body, String systemId, String queries) { + Bson baseQuery = eq(Fields.SYSTEM_ID, systemId); + + BasicDBObject inputObject = (BasicDBObject) JSON.parse(body); + BasicDBObject setObject = (BasicDBObject) inputObject.get(Fields.SET); + if (setObject.containsField(Fields.SYSTEM_ID)) { + throw new UnsupportedOperationException("Updating " + Fields.SYSTEM_ID + " fields is not allowed"); + } + + final List<String> queriesList; + if (queries != null) { + queriesList = Arrays.asList(queries.split(",")); + } else { + queriesList = Collections.emptyList(); + } + + final Bson fields = new Document("$set", setObject); + + collection.updateMany(and(baseQuery, MongoRequestFilters.buildQueriesFilter(queriesList)), fields); + } + + public void updateTimestamps(MongoCollection<Document> collection, String body, String systemId, Long timeStamp) { + final Bson filter; + if (body != null && body.length() > 0) { + List<String> systems = (List<String>) JSON.parse(body); + List<Bson> systemFilters = new ArrayList<>(); + for (String id : systems) { + systemFilters.add(eq(Fields.SYSTEM_ID, id)); + } + filter = or(eq(Fields.SYSTEM_ID, systemId), or(systemFilters)); + } else { + filter = eq(Fields.SYSTEM_ID, systemId); + } + + String setDocument = "{ \"$set\" : { \"" + Fields.LAST_UPDATED + "\":" + timeStamp + " } }"; + final Bson update = Document.parse(setDocument); + collection.updateMany(filter, update); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/services/systems/src/main/resources/systems-swagger.yaml Wed May 31 18:56:32 2017 -0400 @@ -0,0 +1,192 @@ +swagger: '2.0' +info: + version: 0.0.1 + title: Thermostat Web Gateway System Information API + license: + name: GPL v2 with Classpath Exception + url: 'http://www.gnu.org/licenses' +consumes: + - application/json +produces: + - application/json + - text/html; charset=utf-8 +basePath: /systems/0.0.1 +paths: + /: + get: + description: Get information for all systems. + parameters: + - $ref: '#/parameters/limit' + - $ref: '#/parameters/offset' + - $ref: '#/parameters/sort' + - $ref: '#/parameters/include' + - $ref: '#/parameters/exclude' + - $ref: '#/parameters/query' + - $ref: '#/parameters/alive' + responses: + '200': + description: OK + schema: + $ref: '#/definitions/systems-get-response' + /systems/{systemId}: + parameters: + - $ref: '#/parameters/system-id' + get: + description: Get information for system {systemId}. + parameters: + - $ref: '#/parameters/limit' + - $ref: '#/parameters/offset' + - $ref: '#/parameters/sort' + - $ref: '#/parameters/include' + - $ref: '#/parameters/exclude' + - $ref: '#/parameters/query' + - $ref: '#/parameters/alive' + responses: + '200': + description: OK + schema: + $ref: '#/definitions/systems-get-response' + put: + description: Update information for system {systemId}. + parameters: + - $ref: '#/parameters/systems-put-body' + - $ref: '#/parameters/query' + responses: + '200': + description: OK + post: + description: Add information for system {systemId} + parameters: + - $ref: '#/parameters/system-info-array' + responses: + '200': + description: OK + delete: + description: Delete information for system ID {systemId}. + parameters: + - $ref: '#/parameters/query' + responses: + '200': + description: OK +definitions: + systems-get-response: + type: object + properties: + response: + $ref: '#/definitions/system-info-array' + system-info-array: + type: array + items: + $ref: '#/definitions/system-info' + system-info: + type: object + properties: + systemId: + type: string + agentId: + type: string + hostname: + type: string + osName: + type: string + osKernel: + type: string + osArch: + type: string + cpuCount: + type: integer + cpuModel: + type: string + totalMemory: + type: integer + format: int64 + timeCreated: + type: integer + format: int64 + lastUpdated: + type: integer + format: int64 + systems-put-body: + type: object + properties: + "set": + type: object +parameters: + system-id: + name: systemId + in: path + required: true + type: string + system-info-array: + name: system-info-array + in: body + description: The system information + required: true + schema: + $ref: '#/definitions/system-info-array' + systems-put-body: + name: body + in: body + description: >- + The JSON object containing a 'set' object. This contains single item JSON + objects that specify the field to replace and the JSON value to replace with. + Must not include 'systemId' field. Example { "set" : { + "field" : "value", "field2":{"object":"item"} } + required: true + schema: + $ref: '#/definitions/systems-put-body' + alive: + name: alive + in: query + description: Whether to return only systems that are currently running + type: boolean + required: false + default: true + limit: + name: limit + in: query + description: Limit of items to return. Example '1' + type: integer + required: false + default: 1 + offset: + name: offset + in: query + description: Offset of items to return. Example '0' + type: integer + required: true + default: 0 + sort: + name: sort + in: query + description: Sort string. Comma separated list of fields prefixed with '+' for ascending or '-' for descending. Example '?sort=+a,-b' Fields use dot notation for embedded documents. Example 'outer.inner' refers to field inner contained in field outer. + type: string + required: false + query: + name: query + in: query + description: Query string. Comma separated list of key, comparator, value pairs. Comparator supports '==', '<=', '>=', '<', '>', '!='. Example '?query=a==b,c!=d'. Keys are fields in documents and use dot notation for embedded documents. Example 'outer.inner' refers to field inner contained in field outer. + type: string + required: false + include: + name: include + in: query + description: >- + Inclusion string. Comma separated list of fields to include in the + response. Example '?include=a,b' Fields use dot notation for embedded + documents. Example 'outer.inner' refers to field inner contained in field + outer. Cannot be used in combination with 'exclude' parameter Overriden by + 'exclude' parameter + type: string + required: false + exclude: + name: exclude + in: query + description: >- + Exclusion string. Comma separated list of fields to exclude in the + response. Example '?exclude=a,b' Fields use dot notation for embedded + documents. Example 'outer.inner' refers to field inner contained in field + outer. Cannot be used in combination with 'include' parameter; takes + precedence over 'include' parameter + type: string + required: false
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/services/systems/src/main/webapp/WEB-INF/web.xml Wed May 31 18:56:32 2017 -0400 @@ -0,0 +1,68 @@ +<!-- + + Copyright 2012-2017 Red Hat, Inc. + + This file is part of Thermostat. + + Thermostat is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 2, or (at your + option) any later version. + + Thermostat is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Thermostat; see the file COPYING. If not see + <http://www.gnu.org/licenses/>. + + Linking this code with other modules is making a combined work + based on this code. Thus, the terms and conditions of the GNU + General Public License cover the whole combination. + + As a special exception, the copyright holders of this code give + you permission to link this code with independent modules to + produce an executable, regardless of the license terms of these + independent modules, and to copy and distribute the resulting + executable under terms of your choice, provided that you also + meet, for each linked independent module, the terms and conditions + of the license of that module. An independent module is a module + which is not derived from or based on this code. If you modify + this code, you may extend this exception to your version of the + library, but you are not obligated to do so. If you do not wish + to do so, delete this exception statement from your version. + +--> +<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee + http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" + version="3.1"> + <servlet> + <servlet-name>SystemInfoServlet</servlet-name> + <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class> + <init-param> + <param-name> + jersey.config.server.provider.packages + </param-name> + <param-value> + com.redhat.thermostat.service.systems, + </param-value> + </init-param> + </servlet> + <servlet-mapping> + <servlet-name>SystemInfoServlet</servlet-name> + <url-pattern>/0.0.1/*</url-pattern> + </servlet-mapping> + <!-- Service configuration --> + <context-param> + <param-name>com.redhat.thermostat.gateway.SERVICE_NAME</param-name> + <param-value>@com.redhat.thermostat.gateway.SERVICE_NAME@</param-value> + </context-param> + <!-- Listener for setting up the storage connection --> + <listener> + <listener-class>com.redhat.thermostat.gateway.common.mongodb.servlet.StorageConnectionSettingListener</listener-class> + </listener> +</web-app> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/integration-tests/src/test/java/com/redhat/thermostat/gateway/service/systems/SystemInfoIntegrationTest.java Wed May 31 18:56:32 2017 -0400 @@ -0,0 +1,291 @@ +/* + * Copyright 2012-2017 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.gateway.service.systems; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.redhat.thermostat.gateway.tests.integration.IntegrationTest; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.client.util.StringContentProvider; +import org.eclipse.jetty.http.HttpMethod; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class SystemInfoIntegrationTest extends IntegrationTest { + + private static final String collectionName = "system-info"; + private static final String serviceURL = baseUrl + "/systems/0.0.1"; + private static final int HTTP_200_OK = 200; + private static final String CPU_STRING1 = "Intel"; + private static final String CPU_STRING2 = "AMD"; + private static final String AGENT_ID = getRandomSystemId(); + private static final String HOSTNAME = getRandomSystemId(); + private static final String systemInfoJSON = + "{\n" + + " \"agentId\": \"" + AGENT_ID + "\",\n" + + " \"hostname\": \"" + HOSTNAME + "\",\n" + + " \"osName\": \"Windows 10\",\n" + + " \"osKernel\": \"10.0\",\n" + + " \"cpuModel\": \"" + CPU_STRING1 + "\",\n" + + " \"cpuCount\": 4,\n" + + " \"totalMemory\": {\n" + + " \"$numberLong\": \"12566220800\"\n" + + " }\n" + + "}"; + + private static class TinyHostInfo { + TinyHostInfo(String sytehId, String agentId, String hostName, String cpuModel) { + this.systemId = sytehId; + this.agentId = agentId; + this.hostName = hostName; + this.cpuModel = cpuModel; + } + String hostName; + String agentId; + String systemId; + String cpuModel; + } + + public SystemInfoIntegrationTest() { + super(serviceURL); + } + + @Before + public void beforeIntegrationTest() { + mongodTestUtil.dropCollection(collectionName); + } + + @Test + public void testGetAll() throws InterruptedException, TimeoutException, ExecutionException { + final String systemid1 = getRandomSystemId(); + postSystemInfo(systemid1); + + final String systemid2 = getRandomSystemId(); + postSystemInfo(systemid2); + + ContentResponse response = client.newRequest(serviceURL).method(HttpMethod.GET).send(); + assertEquals(HTTP_200_OK, response.getStatus()); + final List<TinyHostInfo> list = parseHostInfo(response, null); + assertEquals(1, list.size()); + + ContentResponse response2 = client.newRequest(serviceURL + "?limit=99").method(HttpMethod.GET).send(); + assertEquals(HTTP_200_OK, response2.getStatus()); + final List<TinyHostInfo> list2 = parseHostInfo(response2, null); + assertEquals(2, list2.size()); + + ContentResponse response3 = client.newRequest(serviceURL + "?limit=0").method(HttpMethod.GET).send(); + assertEquals(HTTP_200_OK, response3.getStatus()); + final List<TinyHostInfo> list3 = parseHostInfo(response3, null); + assertEquals(2, list3.size()); + } + + @Test + public void testGetAllEmpty() throws InterruptedException, TimeoutException, ExecutionException { + ContentResponse response = client.newRequest(serviceURL).method(HttpMethod.GET).send(); + assertEquals(HTTP_200_OK, response.getStatus()); + final List<TinyHostInfo> list = parseHostInfo(response, null); + assertTrue(list.isEmpty()); + } + + @Test + public void testGetUnknown() throws InterruptedException, TimeoutException, ExecutionException { + final String systemid = getRandomSystemId(); + getUnknownSystemInfo(systemid); + } + + @Test + public void testCreateOne() throws InterruptedException, TimeoutException, ExecutionException { + + final String systemid = getRandomSystemId(); + postSystemInfo(systemid); + getKnownSystemInfo(systemid); + } + + @Test + public void testPut() throws InterruptedException, TimeoutException, ExecutionException { + + final String systemid = getRandomSystemId(); + + // create it + postSystemInfo(systemid); + + // retrieve it + final ContentResponse response1 = getKnownSystemInfo(systemid); + final List<TinyHostInfo> list1 = parseHostInfo(response1, systemid); + assertEquals(1, list1.size()); + assertEquals(CPU_STRING1, list1.get(0).cpuModel); + + // modify it + putSystemInfo(systemid, CPU_STRING2); + + // ensure it was changed + final ContentResponse response2 = getKnownSystemInfo(systemid); + final List<TinyHostInfo> list2 = parseHostInfo(response2, systemid); + assertEquals(1, list2.size()); + assertEquals(CPU_STRING2, list2.get(0).cpuModel); + } + + @Test + public void testDeleteUnknown() throws InterruptedException, TimeoutException, ExecutionException { + final String systemid = getRandomSystemId(); + + // delete it + deleteSystemInfo(systemid); + } + + @Test + public void testDeleteOne() throws InterruptedException, ExecutionException, TimeoutException { + final String systemid = getRandomSystemId(); + + // create the new record + postSystemInfo(systemid); + + // check that it's there + getKnownSystemInfo(systemid); + + // delete it + deleteSystemInfo(systemid); + + // check that it's not there + getUnknownSystemInfo(systemid); + } + + private ContentResponse postSystemInfo(final String systemid) throws InterruptedException, ExecutionException, TimeoutException { + final Request request = client.newRequest(serviceURL + "/systems/" + systemid); + request.header("Content-Type", "application/json"); + request.content(new StringContentProvider("[" + createSystemInfoJSON(systemid) + "]")); + ContentResponse response = request.method(HttpMethod.POST).send(); + assertEquals(HTTP_200_OK, response.getStatus()); + final String expected = ""; + assertEquals(expected, response.getContentAsString()); + return response; + } + + private List<TinyHostInfo> parseHostInfo(ContentResponse contentResponse, final String expectedSystemId) { + + JsonParser parser = new JsonParser(); + JsonObject json = (JsonObject) parser.parse(contentResponse.getContentAsString()); + JsonElement response = json.get("response"); + + JsonArray allData = response.getAsJsonArray(); + List<TinyHostInfo> result = new ArrayList<>(); + + for (JsonElement entry : allData) { + + json = (JsonObject) parser.parse(entry.toString()); + + assertTrue(json.has("systemId")); + assertTrue(json.has("agentId")); + assertTrue(json.has("hostname")); + assertTrue(json.has("cpuModel")); + + final String systemId = json.get("systemId").getAsString(); + final String agentId = json.get("agentId").getAsString(); + final String hostName = json.get("hostname").getAsString(); + final String cpuModel = json.get("cpuModel").getAsString(); + + assertEquals(AGENT_ID, agentId); + assertEquals(HOSTNAME, hostName); + if (expectedSystemId != null) { + assertEquals(expectedSystemId, systemId); + } + + TinyHostInfo hi = new TinyHostInfo(systemId, agentId, hostName, cpuModel); + + result.add(hi); + } + return result; + } + + private ContentResponse putSystemInfo(final String systemid, final String cpuid) throws InterruptedException, ExecutionException, TimeoutException { + final Request request = client.newRequest(serviceURL + "/systems/" + systemid); + request.header("Content-Type", "application/json"); + final String contentStr = createSystemInfoJSON(systemid).replace(CPU_STRING1, cpuid); + request.content(new StringContentProvider("{ \"set\" : " +contentStr + "}")); + ContentResponse response = request.method(HttpMethod.PUT).send(); + assertEquals(HTTP_200_OK, response.getStatus()); + final String expected = ""; + assertEquals(expected, response.getContentAsString()); + return response; + } + + private ContentResponse getUnknownSystemInfo(final String systemid) throws InterruptedException, ExecutionException, TimeoutException { + ContentResponse response = getSystemInfo(systemid); + assertTrue(parseHostInfo(response, systemid).isEmpty()); + return response; + } + + private ContentResponse getKnownSystemInfo(final String systemid) throws InterruptedException, ExecutionException, TimeoutException { + ContentResponse response = getSystemInfo(systemid); + assertEquals(1, parseHostInfo(response, systemid).size()); + return response; + } + + private ContentResponse getSystemInfo(final String systemid) throws InterruptedException, ExecutionException, TimeoutException { + ContentResponse response = client.newRequest(serviceURL + "/systems/" + systemid).method(HttpMethod.GET).send(); + assertEquals(HTTP_200_OK, response.getStatus()); + return response; + } + + private ContentResponse deleteSystemInfo(final String systemid) throws InterruptedException, ExecutionException, TimeoutException { + ContentResponse response = client.newRequest(serviceURL + "/systems/" + systemid).method(HttpMethod.DELETE).send(); + assertEquals(HTTP_200_OK, response.getStatus()); + final String expected = ""; + assertEquals(expected, response.getContentAsString()); + return response; + } + + private static String getRandomSystemId() { + return UUID.randomUUID().toString(); + } + + private String createSystemInfoJSON(final String systemid) { + return systemInfoJSON; + } +}