From b433b9c879e785e9ea61d0b27937babb1a82545e Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Wed, 7 May 2014 13:50:40 -0700 Subject: [PATCH] Upgrade to dropwizard 0.7. --- pom.xml | 118 +++--- .../WhisperServerConfiguration.java | 13 +- .../textsecuregcm/WhisperServerService.java | 111 +++--- .../auth/AccountAuthenticator.java | 26 +- .../auth/AuthorizationHeader.java | 6 +- .../auth/FederatedPeerAuthenticator.java | 29 +- .../auth/MultiBasicAuthProvider.java | 17 +- .../configuration/DataDogConfiguration.java | 20 - .../controllers/AccountController.java | 5 +- .../controllers/AttachmentController.java | 5 +- .../controllers/DeviceController.java | 7 +- .../controllers/DirectoryController.java | 17 +- .../controllers/FederationController.java | 5 +- .../controllers/KeysController.java | 5 +- .../controllers/MessageController.java | 5 +- .../MismatchedDevicesException.java | 1 - .../controllers/NoSuchUserException.java | 4 +- .../controllers/WebsocketController.java | 203 ++++++---- .../WebsocketControllerFactory.java | 77 +--- .../textsecuregcm/limits/RateLimiter.java | 13 +- .../textsecuregcm/metrics/CpuUsageGauge.java | 6 +- .../metrics/FreeMemoryGauge.java | 9 +- .../metrics/JsonMetricsReporter.java | 347 +++++------------- .../textsecuregcm/metrics/NetworkGauge.java | 5 +- .../metrics/NetworkReceivedGauge.java | 2 +- .../metrics/NetworkSentGauge.java | 2 +- .../providers/MemcacheHealthCheck.java | 4 +- .../providers/RedisHealthCheck.java | 3 +- .../textsecuregcm/push/APNSender.java | 16 +- .../textsecuregcm/push/GCMSender.java | 13 +- .../textsecuregcm/sms/NexmoSmsSender.java | 16 +- .../textsecuregcm/sms/TwilioSmsSender.java | 14 +- .../textsecuregcm/storage/PubSubManager.java | 1 + .../textsecuregcm/util/Constants.java | 7 + .../workers/DirectoryCommand.java | 18 +- .../controllers/AccountControllerTest.java | 33 +- .../controllers/DeviceControllerTest.java | 25 +- .../controllers/DirectoryControllerTest.java | 44 ++- .../controllers/FederatedControllerTest.java | 33 +- .../tests/controllers/KeyControllerTest.java | 52 +-- .../controllers/MessageControllerTest.java | 70 ++-- .../controllers/WebsocketControllerTest.java | 81 ++-- .../tests/entities/ClientContactTest.java | 6 +- .../tests/entities/PreKeyTest.java | 2 +- .../textsecuregcm/tests/util/JsonHelpers.java | 27 ++ .../resources/fixtures/contact.relay.json | 4 +- .../resources/fixtures/contact.relay.sms.json | 4 +- 47 files changed, 742 insertions(+), 789 deletions(-) delete mode 100644 src/main/java/org/whispersystems/textsecuregcm/configuration/DataDogConfiguration.java create mode 100644 src/main/java/org/whispersystems/textsecuregcm/util/Constants.java create mode 100644 src/test/java/org/whispersystems/textsecuregcm/tests/util/JsonHelpers.java diff --git a/pom.xml b/pom.xml index 8bea60031..aa5902685 100644 --- a/pom.xml +++ b/pom.xml @@ -11,22 +11,69 @@ TextSecureServer 0.13 + + 0.7.0 + 2.3.3 + + + + io.dropwizard + dropwizard-core + ${dropwizard.version} + + + io.dropwizard + dropwizard-jdbi + ${dropwizard.version} + + + io.dropwizard + dropwizard-auth + ${dropwizard.version} + + + io.dropwizard + dropwizard-client + ${dropwizard.version} + + + io.dropwizard + dropwizard-migrations + ${dropwizard.version} + + + io.dropwizard + dropwizard-testing + ${dropwizard.version} + + + io.dropwizard + dropwizard-metrics-graphite + ${dropwizard.version} + + + + com.sun.jersey + jersey-json + 1.18.1 + + + com.codahale.metrics + metrics-graphite + 3.0.2 + + + org.eclipse.jetty.websocket + websocket-server + 9.0.7.v20131107 + + bouncycastle bcprov-jdk16 140 - - com.yammer.dropwizard - dropwizard-core - 0.6.2 - - - com.yammer.metrics - metrics-graphite - 2.2.0 - com.google.android.gcm gcm-server @@ -66,31 +113,6 @@ jar compile - - com.yammer.dropwizard - dropwizard-jdbi - 0.6.2 - - - com.yammer.dropwizard - dropwizard-auth - 0.6.2 - - - com.yammer.dropwizard - dropwizard-client - 0.6.2 - - - com.yammer.dropwizard - dropwizard-migrations - 0.6.2 - - - com.yammer.dropwizard - dropwizard-testing - 0.6.2 - com.twilio.sdk twilio-java-sdk @@ -103,25 +125,19 @@ 9.1-901.jdbc4 - - com.sun.jersey - jersey-json - 1.17.1 - - - org.eclipse.jetty - jetty-websocket - 8.1.14.v20131031 - - - - org.coursera - metrics-datadog - 0.1.5 - + + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.api.version} + + + + diff --git a/src/main/java/org/whispersystems/textsecuregcm/WhisperServerConfiguration.java b/src/main/java/org/whispersystems/textsecuregcm/WhisperServerConfiguration.java index 61c09af19..eb43240e8 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/WhisperServerConfiguration.java +++ b/src/main/java/org/whispersystems/textsecuregcm/WhisperServerConfiguration.java @@ -17,8 +17,6 @@ package org.whispersystems.textsecuregcm; import com.fasterxml.jackson.annotation.JsonProperty; -import com.yammer.dropwizard.config.Configuration; -import com.yammer.dropwizard.db.DatabaseConfiguration; import org.whispersystems.textsecuregcm.configuration.ApnConfiguration; import org.whispersystems.textsecuregcm.configuration.FederationConfiguration; import org.whispersystems.textsecuregcm.configuration.GcmConfiguration; @@ -35,6 +33,9 @@ import org.whispersystems.textsecuregcm.configuration.WebsocketConfiguration; import javax.validation.Valid; import javax.validation.constraints.NotNull; +import io.dropwizard.Configuration; +import io.dropwizard.db.DataSourceFactory; + public class WhisperServerConfiguration extends Configuration { @NotNull @@ -74,7 +75,7 @@ public class WhisperServerConfiguration extends Configuration { @Valid @NotNull @JsonProperty - private DatabaseConfiguration database = new DatabaseConfiguration(); + private DataSourceFactory database = new DataSourceFactory(); @Valid @NotNull @@ -87,7 +88,7 @@ public class WhisperServerConfiguration extends Configuration { @Valid @JsonProperty - private MetricsConfiguration metrics = new MetricsConfiguration(); + private MetricsConfiguration viz = new MetricsConfiguration(); @Valid @JsonProperty @@ -125,7 +126,7 @@ public class WhisperServerConfiguration extends Configuration { return redis; } - public DatabaseConfiguration getDatabaseConfiguration() { + public DataSourceFactory getDataSourceFactory() { return database; } @@ -142,6 +143,6 @@ public class WhisperServerConfiguration extends Configuration { } public MetricsConfiguration getMetricsConfiguration() { - return metrics; + return viz; } } diff --git a/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java b/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java index 03485fcff..bea26462d 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java +++ b/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java @@ -16,18 +16,12 @@ */ package org.whispersystems.textsecuregcm; +import com.codahale.metrics.SharedMetricRegistries; +import com.codahale.metrics.graphite.GraphiteReporter; import com.google.common.base.Optional; -import com.yammer.dropwizard.Service; -import com.yammer.dropwizard.config.Bootstrap; -import com.yammer.dropwizard.config.Environment; -import com.yammer.dropwizard.config.HttpConfiguration; -import com.yammer.dropwizard.db.DatabaseConfiguration; -import com.yammer.dropwizard.jdbi.DBIFactory; -import com.yammer.dropwizard.migrations.MigrationsBundle; -import com.yammer.metrics.Metrics; -import com.yammer.metrics.reporting.GraphiteReporter; import net.spy.memcached.MemcachedClient; import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.eclipse.jetty.servlets.CrossOriginFilter; import org.skife.jdbi.v2.DBI; import org.whispersystems.textsecuregcm.auth.AccountAuthenticator; import org.whispersystems.textsecuregcm.auth.FederatedPeerAuthenticator; @@ -71,16 +65,28 @@ import org.whispersystems.textsecuregcm.storage.PendingDevicesManager; import org.whispersystems.textsecuregcm.storage.PubSubManager; import org.whispersystems.textsecuregcm.storage.StoredMessageManager; import org.whispersystems.textsecuregcm.storage.StoredMessages; -import org.whispersystems.textsecuregcm.util.CORSHeaderFilter; +import org.whispersystems.textsecuregcm.util.Constants; import org.whispersystems.textsecuregcm.util.UrlSigner; import org.whispersystems.textsecuregcm.workers.DirectoryCommand; +import javax.servlet.DispatcherType; +import javax.servlet.FilterRegistration; +import javax.servlet.ServletRegistration; import java.security.Security; +import java.util.EnumSet; import java.util.concurrent.TimeUnit; +import static com.codahale.metrics.MetricRegistry.name; +import io.dropwizard.Application; +import io.dropwizard.db.DataSourceFactory; +import io.dropwizard.jdbi.DBIFactory; +import io.dropwizard.metrics.graphite.GraphiteReporterFactory; +import io.dropwizard.migrations.MigrationsBundle; +import io.dropwizard.setup.Bootstrap; +import io.dropwizard.setup.Environment; import redis.clients.jedis.JedisPool; -public class WhisperServerService extends Service { +public class WhisperServerService extends Application { static { Security.addProvider(new BouncyCastleProvider()); @@ -88,24 +94,28 @@ public class WhisperServerService extends Service { @Override public void initialize(Bootstrap bootstrap) { - bootstrap.setName("whisper-server"); bootstrap.addCommand(new DirectoryCommand()); bootstrap.addBundle(new MigrationsBundle() { @Override - public DatabaseConfiguration getDatabaseConfiguration(WhisperServerConfiguration configuration) { - return configuration.getDatabaseConfiguration(); + public DataSourceFactory getDataSourceFactory(WhisperServerConfiguration configuration) { + return configuration.getDataSourceFactory(); } }); } + @Override + public String getName() { + return "whisper-server"; + } + @Override public void run(WhisperServerConfiguration config, Environment environment) throws Exception { - config.getHttpConfiguration().setConnectorType(HttpConfiguration.ConnectorType.NONBLOCKING); - + SharedMetricRegistries.add(Constants.METRICS_NAME, environment.metrics()); + DBIFactory dbiFactory = new DBIFactory(); - DBI jdbi = dbiFactory.build(environment, config.getDatabaseConfiguration(), "postgresql"); + DBI jdbi = dbiFactory.build(environment, config.getDataSourceFactory(), "postgresql"); Accounts accounts = jdbi.onDemand(Accounts.class); PendingAccounts pendingAccounts = jdbi.onDemand(PendingAccounts.class); @@ -140,44 +150,59 @@ public class WhisperServerService extends Service { KeysController keysController = new KeysController(rateLimiters, keys, accountsManager, federatedClientManager); MessageController messageController = new MessageController(rateLimiters, pushSender, accountsManager, federatedClientManager); - environment.addProvider(new MultiBasicAuthProvider<>(new FederatedPeerAuthenticator(config.getFederationConfiguration()), - FederatedPeer.class, - deviceAuthenticator, - Device.class, "WhisperServer")); + environment.jersey().register(new MultiBasicAuthProvider<>(new FederatedPeerAuthenticator(config.getFederationConfiguration()), + FederatedPeer.class, + deviceAuthenticator, + Device.class, "WhisperServer")); - environment.addResource(new AccountController(pendingAccountsManager, accountsManager, rateLimiters, smsSender)); - environment.addResource(new DeviceController(pendingDevicesManager, accountsManager, rateLimiters)); - environment.addResource(new DirectoryController(rateLimiters, directory)); - environment.addResource(new FederationController(accountsManager, attachmentController, keysController, messageController)); - environment.addResource(attachmentController); - environment.addResource(keysController); - environment.addResource(messageController); + environment.jersey().register(new AccountController(pendingAccountsManager, accountsManager, rateLimiters, smsSender)); + environment.jersey().register(new DeviceController(pendingDevicesManager, accountsManager, rateLimiters)); + environment.jersey().register(new DirectoryController(rateLimiters, directory)); + environment.jersey().register(new FederationController(accountsManager, attachmentController, keysController, messageController)); + environment.jersey().register(attachmentController); + environment.jersey().register(keysController); + environment.jersey().register(messageController); if (config.getWebsocketConfiguration().isEnabled()) { - environment.addServlet(new WebsocketControllerFactory(deviceAuthenticator, storedMessageManager, pubSubManager), - "/v1/websocket/"); - environment.addFilter(new CORSHeaderFilter(), "/*"); + WebsocketControllerFactory servlet = new WebsocketControllerFactory(deviceAuthenticator, + storedMessageManager, + pubSubManager); + + ServletRegistration.Dynamic websocket = environment.servlets().addServlet("WebSocket", servlet); + websocket.addMapping("/v1/websocket/*"); + websocket.setAsyncSupported(true); + + FilterRegistration.Dynamic filter = environment.servlets().addFilter("CORS", CrossOriginFilter.class); + filter.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true, "/*"); + filter.setInitParameter("allowedOrigins", "*"); + filter.setInitParameter("allowedHeaders", "Content-Type,Authorization,X-Requested-With,Content-Length,Accept,Origin"); + filter.setInitParameter("allowedMethods", "GET,PUT,POST,DELETE,OPTIONS"); + filter.setInitParameter("preflightMaxAge", "5184000"); + filter.setInitParameter("allowCredentials", "true"); } - environment.addHealthCheck(new RedisHealthCheck(redisClient)); - environment.addHealthCheck(new MemcacheHealthCheck(memcachedClient)); + environment.healthChecks().register("redis", new RedisHealthCheck(redisClient)); + environment.healthChecks().register("memcache", new MemcacheHealthCheck(memcachedClient)); - environment.addProvider(new IOExceptionMapper()); - environment.addProvider(new RateLimitExceededExceptionMapper()); + environment.jersey().register(new IOExceptionMapper()); + environment.jersey().register(new RateLimitExceededExceptionMapper()); - Metrics.newGauge(CpuUsageGauge.class, "cpu", new CpuUsageGauge()); - Metrics.newGauge(FreeMemoryGauge.class, "free_memory", new FreeMemoryGauge()); - Metrics.newGauge(NetworkSentGauge.class, "bytes_sent", new NetworkSentGauge()); - Metrics.newGauge(NetworkReceivedGauge.class, "bytes_received", new NetworkReceivedGauge()); + environment.metrics().register(name(CpuUsageGauge.class, "cpu"), new CpuUsageGauge()); + environment.metrics().register(name(FreeMemoryGauge.class, "free_memory"), new FreeMemoryGauge()); + environment.metrics().register(name(NetworkSentGauge.class, "bytes_sent"), new NetworkSentGauge()); + environment.metrics().register(name(NetworkReceivedGauge.class, "bytes_received"), new NetworkReceivedGauge()); if (config.getGraphiteConfiguration().isEnabled()) { - GraphiteReporter.enable(15, TimeUnit.SECONDS, - config.getGraphiteConfiguration().getHost(), - config.getGraphiteConfiguration().getPort()); + GraphiteReporterFactory graphiteReporterFactory = new GraphiteReporterFactory(); + graphiteReporterFactory.setHost(config.getGraphiteConfiguration().getHost()); + graphiteReporterFactory.setPort(config.getGraphiteConfiguration().getPort()); + + GraphiteReporter graphiteReporter = (GraphiteReporter) graphiteReporterFactory.build(environment.metrics()); + graphiteReporter.start(15, TimeUnit.SECONDS); } if (config.getMetricsConfiguration().isEnabled()) { - new JsonMetricsReporter("textsecure", Metrics.defaultRegistry(), + new JsonMetricsReporter(environment.metrics(), config.getMetricsConfiguration().getToken(), config.getMetricsConfiguration().getHost()) .start(60, TimeUnit.SECONDS); diff --git a/src/main/java/org/whispersystems/textsecuregcm/auth/AccountAuthenticator.java b/src/main/java/org/whispersystems/textsecuregcm/auth/AccountAuthenticator.java index 0ece5a49b..0e9786701 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/auth/AccountAuthenticator.java +++ b/src/main/java/org/whispersystems/textsecuregcm/auth/AccountAuthenticator.java @@ -16,29 +16,27 @@ */ package org.whispersystems.textsecuregcm.auth; +import com.codahale.metrics.Meter; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.SharedMetricRegistries; import com.google.common.base.Optional; -import com.yammer.dropwizard.auth.AuthenticationException; -import com.yammer.dropwizard.auth.Authenticator; -import com.yammer.dropwizard.auth.basic.BasicCredentials; -import com.yammer.metrics.Metrics; -import com.yammer.metrics.core.Meter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.storage.Account; -import org.whispersystems.textsecuregcm.storage.Device; import org.whispersystems.textsecuregcm.storage.AccountsManager; +import org.whispersystems.textsecuregcm.storage.Device; +import org.whispersystems.textsecuregcm.util.Constants; -import java.util.concurrent.TimeUnit; +import static com.codahale.metrics.MetricRegistry.name; +import io.dropwizard.auth.AuthenticationException; +import io.dropwizard.auth.Authenticator; +import io.dropwizard.auth.basic.BasicCredentials; public class AccountAuthenticator implements Authenticator { - private final Meter authenticationFailedMeter = Metrics.newMeter(AccountAuthenticator.class, - "authentication", "failed", - TimeUnit.MINUTES); - - private final Meter authenticationSucceededMeter = Metrics.newMeter(AccountAuthenticator.class, - "authentication", "succeeded", - TimeUnit.MINUTES); + private final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME); + private final Meter authenticationFailedMeter = metricRegistry.meter(name(getClass(), "authentication", "failed" )); + private final Meter authenticationSucceededMeter = metricRegistry.meter(name(getClass(), "authentication", "succeeded")); private final Logger logger = LoggerFactory.getLogger(AccountAuthenticator.class); diff --git a/src/main/java/org/whispersystems/textsecuregcm/auth/AuthorizationHeader.java b/src/main/java/org/whispersystems/textsecuregcm/auth/AuthorizationHeader.java index 6e5d3a8d7..c2411ec00 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/auth/AuthorizationHeader.java +++ b/src/main/java/org/whispersystems/textsecuregcm/auth/AuthorizationHeader.java @@ -25,13 +25,13 @@ import java.io.IOException; public class AuthorizationHeader { private final String number; - private final long accountId; + private final long accountId; private final String password; private AuthorizationHeader(String number, long accountId, String password) { - this.number = number; + this.number = number; this.accountId = accountId; - this.password = password; + this.password = password; } public static AuthorizationHeader fromUserAndPassword(String user, String password) throws InvalidAuthorizationHeaderException { diff --git a/src/main/java/org/whispersystems/textsecuregcm/auth/FederatedPeerAuthenticator.java b/src/main/java/org/whispersystems/textsecuregcm/auth/FederatedPeerAuthenticator.java index b4736c791..effa85b4c 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/auth/FederatedPeerAuthenticator.java +++ b/src/main/java/org/whispersystems/textsecuregcm/auth/FederatedPeerAuthenticator.java @@ -16,30 +16,35 @@ */ package org.whispersystems.textsecuregcm.auth; +import com.codahale.metrics.Meter; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.SharedMetricRegistries; import com.google.common.base.Optional; -import com.yammer.dropwizard.auth.AuthenticationException; -import com.yammer.dropwizard.auth.Authenticator; -import com.yammer.dropwizard.auth.basic.BasicCredentials; -import com.yammer.metrics.Metrics; -import com.yammer.metrics.core.Meter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.configuration.FederationConfiguration; import org.whispersystems.textsecuregcm.federation.FederatedPeer; +import org.whispersystems.textsecuregcm.util.Constants; import java.util.List; -import java.util.concurrent.TimeUnit; + +import static com.codahale.metrics.MetricRegistry.name; +import io.dropwizard.auth.AuthenticationException; +import io.dropwizard.auth.Authenticator; +import io.dropwizard.auth.basic.BasicCredentials; public class FederatedPeerAuthenticator implements Authenticator { - private final Meter authenticationFailedMeter = Metrics.newMeter(FederatedPeerAuthenticator.class, - "authentication", "failed", - TimeUnit.MINUTES); + private final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME); - private final Meter authenticationSucceededMeter = Metrics.newMeter(FederatedPeerAuthenticator.class, - "authentication", "succeeded", - TimeUnit.MINUTES); + private final Meter authenticationFailedMeter = metricRegistry.meter(name(getClass(), + "authentication", + "failed")); + + private final Meter authenticationSucceededMeter = metricRegistry.meter(name(getClass(), + "authentication", + "succeeded")); private final Logger logger = LoggerFactory.getLogger(FederatedPeerAuthenticator.class); diff --git a/src/main/java/org/whispersystems/textsecuregcm/auth/MultiBasicAuthProvider.java b/src/main/java/org/whispersystems/textsecuregcm/auth/MultiBasicAuthProvider.java index 05034aa90..713968ba2 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/auth/MultiBasicAuthProvider.java +++ b/src/main/java/org/whispersystems/textsecuregcm/auth/MultiBasicAuthProvider.java @@ -21,17 +21,14 @@ import com.sun.jersey.core.spi.component.ComponentContext; import com.sun.jersey.core.spi.component.ComponentScope; import com.sun.jersey.spi.inject.Injectable; import com.sun.jersey.spi.inject.InjectableProvider; -import com.yammer.dropwizard.auth.Auth; -import com.yammer.dropwizard.auth.Authenticator; -import com.yammer.dropwizard.auth.basic.BasicAuthProvider; -import com.yammer.dropwizard.auth.basic.BasicCredentials; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; + +import io.dropwizard.auth.Auth; +import io.dropwizard.auth.Authenticator; +import io.dropwizard.auth.basic.BasicAuthProvider; +import io.dropwizard.auth.basic.BasicCredentials; public class MultiBasicAuthProvider implements InjectableProvider { - private final Logger logger = LoggerFactory.getLogger(MultiBasicAuthProvider.class); - private final BasicAuthProvider provider1; private final BasicAuthProvider provider2; @@ -44,8 +41,8 @@ public class MultiBasicAuthProvider implements InjectableProvider clazz2, String realm) { - this.provider1 = new BasicAuthProvider(authenticator1, realm); - this.provider2 = new BasicAuthProvider(authenticator2, realm); + this.provider1 = new BasicAuthProvider<>(authenticator1, realm); + this.provider2 = new BasicAuthProvider<>(authenticator2, realm); this.clazz1 = clazz1; this.clazz2 = clazz2; } diff --git a/src/main/java/org/whispersystems/textsecuregcm/configuration/DataDogConfiguration.java b/src/main/java/org/whispersystems/textsecuregcm/configuration/DataDogConfiguration.java deleted file mode 100644 index 9fe5f9837..000000000 --- a/src/main/java/org/whispersystems/textsecuregcm/configuration/DataDogConfiguration.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.whispersystems.textsecuregcm.configuration; - -import com.fasterxml.jackson.annotation.JsonProperty; - -public class DataDogConfiguration { - - @JsonProperty - private String apiKey; - - @JsonProperty - private boolean enabled = false; - - public String getApiKey() { - return apiKey; - } - - public boolean isEnabled() { - return enabled && apiKey != null; - } -} diff --git a/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java b/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java index 9e7d6501e..1aab611fd 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java +++ b/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java @@ -16,10 +16,9 @@ */ package org.whispersystems.textsecuregcm.controllers; +import com.codahale.metrics.annotation.Timed; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Optional; -import com.yammer.dropwizard.auth.Auth; -import com.yammer.metrics.annotation.Timed; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.auth.AuthenticationCredentials; @@ -55,6 +54,8 @@ import java.io.IOException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; +import io.dropwizard.auth.Auth; + @Path("/v1/accounts") public class AccountController { diff --git a/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentController.java b/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentController.java index 5ca054464..628a050ef 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentController.java +++ b/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentController.java @@ -17,9 +17,8 @@ package org.whispersystems.textsecuregcm.controllers; import com.amazonaws.HttpMethod; +import com.codahale.metrics.annotation.Timed; import com.google.common.base.Optional; -import com.yammer.dropwizard.auth.Auth; -import com.yammer.metrics.annotation.Timed; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.entities.AttachmentDescriptor; @@ -44,6 +43,8 @@ import java.net.URL; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; +import io.dropwizard.auth.Auth; + @Path("/v1/attachments") public class AttachmentController { diff --git a/src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceController.java b/src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceController.java index 9dddc82b4..4ccda6d4a 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceController.java +++ b/src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceController.java @@ -16,10 +16,9 @@ */ package org.whispersystems.textsecuregcm.controllers; +import com.codahale.metrics.annotation.Timed; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Optional; -import com.yammer.dropwizard.auth.Auth; -import com.yammer.metrics.annotation.Timed; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.auth.AuthenticationCredentials; @@ -29,8 +28,8 @@ import org.whispersystems.textsecuregcm.entities.AccountAttributes; import org.whispersystems.textsecuregcm.entities.DeviceResponse; import org.whispersystems.textsecuregcm.limits.RateLimiters; import org.whispersystems.textsecuregcm.storage.Account; -import org.whispersystems.textsecuregcm.storage.Device; import org.whispersystems.textsecuregcm.storage.AccountsManager; +import org.whispersystems.textsecuregcm.storage.Device; import org.whispersystems.textsecuregcm.storage.PendingDevicesManager; import org.whispersystems.textsecuregcm.util.VerificationCode; @@ -48,6 +47,8 @@ import javax.ws.rs.core.Response; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; +import io.dropwizard.auth.Auth; + @Path("/v1/devices") public class DeviceController { diff --git a/src/main/java/org/whispersystems/textsecuregcm/controllers/DirectoryController.java b/src/main/java/org/whispersystems/textsecuregcm/controllers/DirectoryController.java index e7ceaf99e..785803b81 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/controllers/DirectoryController.java +++ b/src/main/java/org/whispersystems/textsecuregcm/controllers/DirectoryController.java @@ -16,11 +16,11 @@ */ package org.whispersystems.textsecuregcm.controllers; +import com.codahale.metrics.Histogram; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.SharedMetricRegistries; +import com.codahale.metrics.annotation.Timed; import com.google.common.base.Optional; -import com.yammer.dropwizard.auth.Auth; -import com.yammer.metrics.Metrics; -import com.yammer.metrics.annotation.Timed; -import com.yammer.metrics.core.Histogram; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.entities.ClientContact; @@ -30,6 +30,7 @@ import org.whispersystems.textsecuregcm.limits.RateLimiters; import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.DirectoryManager; import org.whispersystems.textsecuregcm.util.Base64; +import org.whispersystems.textsecuregcm.util.Constants; import javax.validation.Valid; import javax.ws.rs.Consumes; @@ -45,11 +46,15 @@ import java.io.IOException; import java.util.LinkedList; import java.util.List; +import static com.codahale.metrics.MetricRegistry.name; +import io.dropwizard.auth.Auth; + @Path("/v1/directory") public class DirectoryController { - private final Logger logger = LoggerFactory.getLogger(DirectoryController.class); - private final Histogram contactsHistogram = Metrics.newHistogram(DirectoryController.class, "contacts"); + private final Logger logger = LoggerFactory.getLogger(DirectoryController.class); + private final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME); + private final Histogram contactsHistogram = metricRegistry.histogram(name(getClass(), "contacts")); private final RateLimiters rateLimiters; private final DirectoryManager directory; diff --git a/src/main/java/org/whispersystems/textsecuregcm/controllers/FederationController.java b/src/main/java/org/whispersystems/textsecuregcm/controllers/FederationController.java index e9714720f..3f86bd5bc 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/controllers/FederationController.java +++ b/src/main/java/org/whispersystems/textsecuregcm/controllers/FederationController.java @@ -16,9 +16,8 @@ */ package org.whispersystems.textsecuregcm.controllers; +import com.codahale.metrics.annotation.Timed; import com.google.common.base.Optional; -import com.yammer.dropwizard.auth.Auth; -import com.yammer.metrics.annotation.Timed; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.entities.AccountCount; @@ -45,6 +44,8 @@ import java.io.IOException; import java.util.LinkedList; import java.util.List; +import io.dropwizard.auth.Auth; + @Path("/v1/federation") public class FederationController { diff --git a/src/main/java/org/whispersystems/textsecuregcm/controllers/KeysController.java b/src/main/java/org/whispersystems/textsecuregcm/controllers/KeysController.java index a13f3cf81..2fd3a3b47 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/controllers/KeysController.java +++ b/src/main/java/org/whispersystems/textsecuregcm/controllers/KeysController.java @@ -16,9 +16,8 @@ */ package org.whispersystems.textsecuregcm.controllers; +import com.codahale.metrics.annotation.Timed; import com.google.common.base.Optional; -import com.yammer.dropwizard.auth.Auth; -import com.yammer.metrics.annotation.Timed; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.entities.PreKey; @@ -47,6 +46,8 @@ import javax.ws.rs.core.Response; import java.util.LinkedList; import java.util.List; +import io.dropwizard.auth.Auth; + @Path("/v1/keys") public class KeysController { diff --git a/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java b/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java index 551437693..9bdd6aafa 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java +++ b/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java @@ -16,10 +16,9 @@ */ package org.whispersystems.textsecuregcm.controllers; +import com.codahale.metrics.annotation.Timed; import com.google.common.base.Optional; import com.google.protobuf.ByteString; -import com.yammer.dropwizard.auth.Auth; -import com.yammer.metrics.annotation.Timed; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.entities.IncomingMessage; @@ -56,6 +55,8 @@ import java.util.LinkedList; import java.util.List; import java.util.Set; +import io.dropwizard.auth.Auth; + @Path("/v1/messages") public class MessageController { diff --git a/src/main/java/org/whispersystems/textsecuregcm/controllers/MismatchedDevicesException.java b/src/main/java/org/whispersystems/textsecuregcm/controllers/MismatchedDevicesException.java index 3748b638c..44b924746 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/controllers/MismatchedDevicesException.java +++ b/src/main/java/org/whispersystems/textsecuregcm/controllers/MismatchedDevicesException.java @@ -1,7 +1,6 @@ package org.whispersystems.textsecuregcm.controllers; import java.util.List; -import java.util.Set; public class MismatchedDevicesException extends Exception { diff --git a/src/main/java/org/whispersystems/textsecuregcm/controllers/NoSuchUserException.java b/src/main/java/org/whispersystems/textsecuregcm/controllers/NoSuchUserException.java index 646ef5930..ac03eed75 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/controllers/NoSuchUserException.java +++ b/src/main/java/org/whispersystems/textsecuregcm/controllers/NoSuchUserException.java @@ -16,8 +16,6 @@ */ package org.whispersystems.textsecuregcm.controllers; -import org.whispersystems.textsecuregcm.federation.NoSuchPeerException; - import java.util.LinkedList; import java.util.List; @@ -27,7 +25,7 @@ public class NoSuchUserException extends Exception { public NoSuchUserException(String user) { super(user); - missing = new LinkedList(); + missing = new LinkedList<>(); missing.add(user); } diff --git a/src/main/java/org/whispersystems/textsecuregcm/controllers/WebsocketController.java b/src/main/java/org/whispersystems/textsecuregcm/controllers/WebsocketController.java index ea6022f39..cbc403bc7 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/controllers/WebsocketController.java +++ b/src/main/java/org/whispersystems/textsecuregcm/controllers/WebsocketController.java @@ -1,9 +1,14 @@ package org.whispersystems.textsecuregcm.controllers; import com.fasterxml.jackson.databind.ObjectMapper; -import org.eclipse.jetty.websocket.WebSocket; +import com.google.common.base.Optional; +import org.eclipse.jetty.websocket.api.CloseStatus; +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.UpgradeRequest; +import org.eclipse.jetty.websocket.api.WebSocketListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.whispersystems.textsecuregcm.auth.AccountAuthenticator; import org.whispersystems.textsecuregcm.entities.AcknowledgeWebsocketMessage; import org.whispersystems.textsecuregcm.entities.IncomingWebsocketMessage; import org.whispersystems.textsecuregcm.storage.Account; @@ -22,111 +27,94 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; -public class WebsocketController implements WebSocket.OnTextMessage, PubSubListener { +import io.dropwizard.auth.AuthenticationException; +import io.dropwizard.auth.basic.BasicCredentials; + +public class WebsocketController implements WebSocketListener, PubSubListener { private static final Logger logger = LoggerFactory.getLogger(WebsocketController.class); private static final ObjectMapper mapper = new ObjectMapper(); private static final Map pendingMessages = new HashMap<>(); + private final AccountAuthenticator accountAuthenticator; private final StoredMessageManager storedMessageManager; private final PubSubManager pubSubManager; - private final Account account; - private final Device device; + private Account account; + private Device device; + private Session session; - private Connection connection; - private long pendingMessageSequence; + private long pendingMessageSequence; - public WebsocketController(StoredMessageManager storedMessageManager, - PubSubManager pubSubManager, - Account account) + public WebsocketController(AccountAuthenticator accountAuthenticator, + StoredMessageManager storedMessageManager, + PubSubManager pubSubManager) { + this.accountAuthenticator = accountAuthenticator; this.storedMessageManager = storedMessageManager; this.pubSubManager = pubSubManager; - this.account = account; - this.device = account.getAuthenticatedDevice().get(); - } - - - @Override - public void onOpen(Connection connection) { - this.connection = connection; - pubSubManager.subscribe(new WebsocketAddress(this.account.getId(), this.device.getId()), this); - handleQueryDatabase(); } @Override - public void onClose(int i, String s) { - handleClose(); + public void onWebSocketConnect(Session session) { + try { + UpgradeRequest request = session.getUpgradeRequest(); + Map parameters = request.getParameterMap(); + String[] usernames = parameters.get("login" ); + String[] passwords = parameters.get("password"); + + if (usernames == null || usernames.length == 0 || + passwords == null || passwords.length == 0) + { + session.close(new CloseStatus(401, "Unauthorized")); + return; + } + + BasicCredentials credentials = new BasicCredentials(usernames[0], passwords[0]); + Optional account = accountAuthenticator.authenticate(credentials); + + if (!account.isPresent()) { + session.close(new CloseStatus(401, "Unauthorized")); + return; + } + + this.account = account.get(); + this.device = account.get().getAuthenticatedDevice().get(); + this.session = session; + + this.session.setIdleTimeout(10 * 60 * 1000); + this.pubSubManager.subscribe(new WebsocketAddress(this.account.getId(), + this.device.getId()), this); + + handleQueryDatabase(); + } catch (AuthenticationException e) { + try { session.close(500, "Server Error");} catch (IOException e1) {} + } catch (IOException ioe) { + logger.info("Abrupt session close."); + } } @Override - public void onMessage(String body) { + public void onWebSocketText(String body) { try { IncomingWebsocketMessage incomingMessage = mapper.readValue(body, IncomingWebsocketMessage.class); switch (incomingMessage.getType()) { - case IncomingWebsocketMessage.TYPE_ACKNOWLEDGE_MESSAGE: handleMessageAck(body); break; - case IncomingWebsocketMessage.TYPE_PING_MESSAGE: handlePing(); break; - default: handleClose(); break; + case IncomingWebsocketMessage.TYPE_ACKNOWLEDGE_MESSAGE: + handleMessageAck(body); + break; + default: + close(new CloseStatus(410, "Unknown Type")); } } catch (IOException e) { logger.debug("Parse", e); - handleClose(); + close(new CloseStatus(410, "Badly Formatted")); } } @Override - public void onPubSubMessage(PubSubMessage outgoingMessage) { - switch (outgoingMessage.getType()) { - case PubSubMessage.TYPE_DELIVER: handleDeliverOutgoingMessage(outgoingMessage.getContents()); break; - case PubSubMessage.TYPE_QUERY_DB: handleQueryDatabase(); break; - default: - logger.warn("Unknown pubsub message: " + outgoingMessage.getType()); - } - } - - private void handleDeliverOutgoingMessage(String message) { - try { - long messageSequence; - - synchronized (pendingMessages) { - messageSequence = pendingMessageSequence++; - pendingMessages.put(messageSequence, message); - } - - connection.sendMessage(mapper.writeValueAsString(new WebsocketMessage(messageSequence, message))); - } catch (IOException e) { - logger.debug("Response failed", e); - handleClose(); - } - } - - private void handleMessageAck(String message) { - try { - AcknowledgeWebsocketMessage ack = mapper.readValue(message, AcknowledgeWebsocketMessage.class); - - synchronized (pendingMessages) { - pendingMessages.remove(ack.getId()); - } - } catch (IOException e) { - logger.warn("Mapping", e); - } - } - - private void handlePing() { - try { - IncomingWebsocketMessage pongMessage = new IncomingWebsocketMessage(IncomingWebsocketMessage.TYPE_PONG_MESSAGE); - connection.sendMessage(mapper.writeValueAsString(pongMessage)); - } catch (IOException e) { - logger.warn("Pong failed", e); - handleClose(); - } - } - - private void handleClose() { + public void onWebSocketClose(int i, String s) { pubSubManager.unsubscribe(new WebsocketAddress(account.getId(), device.getId()), this); - connection.close(); List remainingMessages = new LinkedList<>(); @@ -144,6 +132,50 @@ public class WebsocketController implements WebSocket.OnTextMessage, PubSubListe storedMessageManager.storeMessages(account, device, remainingMessages); } + + @Override + public void onPubSubMessage(PubSubMessage outgoingMessage) { + switch (outgoingMessage.getType()) { + case PubSubMessage.TYPE_DELIVER: + handleDeliverOutgoingMessage(outgoingMessage.getContents()); + break; + case PubSubMessage.TYPE_QUERY_DB: + handleQueryDatabase(); + break; + default: + logger.warn("Unknown pubsub message: " + outgoingMessage.getType()); + } + } + + private void handleDeliverOutgoingMessage(String message) { + try { + long messageSequence; + + synchronized (pendingMessages) { + messageSequence = pendingMessageSequence++; + pendingMessages.put(messageSequence, message); + } + + WebsocketMessage websocketMessage = new WebsocketMessage(messageSequence, message); + session.getRemote().sendStringByFuture(mapper.writeValueAsString(websocketMessage)); + } catch (IOException e) { + logger.debug("Response failed", e); + close(null); + } + } + + private void handleMessageAck(String message) { + try { + AcknowledgeWebsocketMessage ack = mapper.readValue(message, AcknowledgeWebsocketMessage.class); + + synchronized (pendingMessages) { + pendingMessages.remove(ack.getId()); + } + } catch (IOException e) { + logger.warn("Mapping", e); + } + } + private void handleQueryDatabase() { List messages = storedMessageManager.getOutgoingMessages(account, device); @@ -152,4 +184,25 @@ public class WebsocketController implements WebSocket.OnTextMessage, PubSubListe } } + @Override + public void onWebSocketBinary(byte[] bytes, int i, int i2) { + logger.info("Received binary message!"); + } + + @Override + public void onWebSocketError(Throwable throwable) { + logger.info("onWebSocketError", throwable); + } + + + private void close(CloseStatus closeStatus) { + try { + if (this.session != null) { + if (closeStatus != null) this.session.close(closeStatus); + else this.session.close(); + } + } catch (IOException e) { + logger.info("close()", e); + } + } } diff --git a/src/main/java/org/whispersystems/textsecuregcm/controllers/WebsocketControllerFactory.java b/src/main/java/org/whispersystems/textsecuregcm/controllers/WebsocketControllerFactory.java index 825ebb2c9..c9e9201be 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/controllers/WebsocketControllerFactory.java +++ b/src/main/java/org/whispersystems/textsecuregcm/controllers/WebsocketControllerFactory.java @@ -1,23 +1,18 @@ package org.whispersystems.textsecuregcm.controllers; -import com.google.common.base.Optional; -import com.yammer.dropwizard.auth.AuthenticationException; -import com.yammer.dropwizard.auth.basic.BasicCredentials; -import org.eclipse.jetty.websocket.WebSocket; -import org.eclipse.jetty.websocket.WebSocketServlet; +import org.eclipse.jetty.websocket.api.UpgradeRequest; +import org.eclipse.jetty.websocket.api.UpgradeResponse; +import org.eclipse.jetty.websocket.servlet.WebSocketCreator; +import org.eclipse.jetty.websocket.servlet.WebSocketServlet; +import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.auth.AccountAuthenticator; -import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.PubSubManager; import org.whispersystems.textsecuregcm.storage.StoredMessageManager; -import javax.servlet.http.HttpServletRequest; -import java.util.LinkedHashMap; -import java.util.Map; - -public class WebsocketControllerFactory extends WebSocketServlet { +public class WebsocketControllerFactory extends WebSocketServlet implements WebSocketCreator { private final Logger logger = LoggerFactory.getLogger(WebsocketControllerFactory.class); @@ -25,14 +20,6 @@ public class WebsocketControllerFactory extends WebSocketServlet { private final PubSubManager pubSubManager; private final AccountAuthenticator accountAuthenticator; - private final LinkedHashMap> cache = - new LinkedHashMap>() { - @Override - protected boolean removeEldestEntry(Map.Entry> eldest) { - return size() > 10; - } - }; - public WebsocketControllerFactory(AccountAuthenticator accountAuthenticator, StoredMessageManager storedMessageManager, PubSubManager pubSubManager) @@ -43,56 +30,12 @@ public class WebsocketControllerFactory extends WebSocketServlet { } @Override - public WebSocket doWebSocketConnect(HttpServletRequest request, String s) { - try { - String username = request.getParameter("user"); - String password = request.getParameter("password"); - - if (username == null || password == null) { - return null; - } - - BasicCredentials credentials = new BasicCredentials(username, password); - - Optional account = cache.remove(credentials); - - if (account == null) { - account = accountAuthenticator.authenticate(new BasicCredentials(username, password)); - } - - if (!account.isPresent()) { - return null; - } - - return new WebsocketController(storedMessageManager, pubSubManager, account.get()); - } catch (AuthenticationException e) { - throw new AssertionError(e); - } + public void configure(WebSocketServletFactory factory) { + factory.setCreator(this); } @Override - public boolean checkOrigin(HttpServletRequest request, String origin) { - try { - String username = request.getParameter("user"); - String password = request.getParameter("password"); - - if (username == null || password == null) { - return false; - } - - BasicCredentials credentials = new BasicCredentials(username, password); - Optional account = accountAuthenticator.authenticate(credentials); - - if (!account.isPresent()) { - return false; - } - - cache.put(credentials, account); - - return true; - } catch (AuthenticationException e) { - logger.warn("Auth Failure", e); - return false; - } + public Object createWebSocket(UpgradeRequest upgradeRequest, UpgradeResponse upgradeResponse) { + return new WebsocketController(accountAuthenticator, storedMessageManager, pubSubManager); } } diff --git a/src/main/java/org/whispersystems/textsecuregcm/limits/RateLimiter.java b/src/main/java/org/whispersystems/textsecuregcm/limits/RateLimiter.java index 3309e5534..9d452b17d 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/limits/RateLimiter.java +++ b/src/main/java/org/whispersystems/textsecuregcm/limits/RateLimiter.java @@ -16,13 +16,14 @@ */ package org.whispersystems.textsecuregcm.limits; -import com.yammer.metrics.Metrics; -import com.yammer.metrics.core.Meter; +import com.codahale.metrics.Meter; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.SharedMetricRegistries; import net.spy.memcached.MemcachedClient; - import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException; +import org.whispersystems.textsecuregcm.util.Constants; -import java.util.concurrent.TimeUnit; +import static com.codahale.metrics.MetricRegistry.name; public class RateLimiter { @@ -35,7 +36,9 @@ public class RateLimiter { public RateLimiter(MemcachedClient memcachedClient, String name, int bucketSize, double leakRatePerMinute) { - this.meter = Metrics.newMeter(RateLimiter.class, name, "exceeded", TimeUnit.MINUTES); + MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME); + + this.meter = metricRegistry.meter(name(getClass(), name, "exceeded")); this.memcachedClient = memcachedClient; this.name = name; this.bucketSize = bucketSize; diff --git a/src/main/java/org/whispersystems/textsecuregcm/metrics/CpuUsageGauge.java b/src/main/java/org/whispersystems/textsecuregcm/metrics/CpuUsageGauge.java index f5ca9a7a0..77a640163 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/metrics/CpuUsageGauge.java +++ b/src/main/java/org/whispersystems/textsecuregcm/metrics/CpuUsageGauge.java @@ -1,13 +1,13 @@ package org.whispersystems.textsecuregcm.metrics; +import com.codahale.metrics.Gauge; import com.sun.management.OperatingSystemMXBean; -import com.yammer.metrics.core.Gauge; import java.lang.management.ManagementFactory; -public class CpuUsageGauge extends Gauge { +public class CpuUsageGauge implements Gauge { @Override - public Integer value() { + public Integer getValue() { OperatingSystemMXBean mbean = (com.sun.management.OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean(); diff --git a/src/main/java/org/whispersystems/textsecuregcm/metrics/FreeMemoryGauge.java b/src/main/java/org/whispersystems/textsecuregcm/metrics/FreeMemoryGauge.java index c2b959ff5..ef2232892 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/metrics/FreeMemoryGauge.java +++ b/src/main/java/org/whispersystems/textsecuregcm/metrics/FreeMemoryGauge.java @@ -1,17 +1,14 @@ package org.whispersystems.textsecuregcm.metrics; +import com.codahale.metrics.Gauge; import com.sun.management.OperatingSystemMXBean; -import com.yammer.metrics.core.Gauge; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.lang.management.ManagementFactory; -public class FreeMemoryGauge extends Gauge { +public class FreeMemoryGauge implements Gauge { @Override - public Long value() { + public Long getValue() { OperatingSystemMXBean mbean = (com.sun.management.OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean(); diff --git a/src/main/java/org/whispersystems/textsecuregcm/metrics/JsonMetricsReporter.java b/src/main/java/org/whispersystems/textsecuregcm/metrics/JsonMetricsReporter.java index 32189ed17..eb8e70d3f 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/metrics/JsonMetricsReporter.java +++ b/src/main/java/org/whispersystems/textsecuregcm/metrics/JsonMetricsReporter.java @@ -1,23 +1,17 @@ package org.whispersystems.textsecuregcm.metrics; +import com.codahale.metrics.Counter; +import com.codahale.metrics.Gauge; +import com.codahale.metrics.Histogram; +import com.codahale.metrics.Meter; +import com.codahale.metrics.MetricFilter; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.ScheduledReporter; +import com.codahale.metrics.Snapshot; +import com.codahale.metrics.Timer; import com.fasterxml.jackson.core.JsonEncoding; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; -import com.yammer.metrics.core.Clock; -import com.yammer.metrics.core.Counter; -import com.yammer.metrics.core.Gauge; -import com.yammer.metrics.core.Histogram; -import com.yammer.metrics.core.Metered; -import com.yammer.metrics.core.Metric; -import com.yammer.metrics.core.MetricName; -import com.yammer.metrics.core.MetricProcessor; -import com.yammer.metrics.core.MetricsRegistry; -import com.yammer.metrics.core.Sampling; -import com.yammer.metrics.core.Summarizable; -import com.yammer.metrics.core.Timer; -import com.yammer.metrics.core.VirtualMachineMetrics; -import com.yammer.metrics.reporting.AbstractPollingReporter; -import com.yammer.metrics.stats.Snapshot; import java.io.IOException; import java.io.OutputStream; @@ -33,292 +27,133 @@ import java.util.regex.Pattern; /** * Adapted from MetricsServlet. */ -public class JsonMetricsReporter extends AbstractPollingReporter implements MetricProcessor { - private final Clock clock = Clock.defaultClock(); - private final VirtualMachineMetrics vm = VirtualMachineMetrics.getInstance(); - private final String service; - private final MetricsRegistry registry; +public class JsonMetricsReporter extends ScheduledReporter { private final JsonFactory factory = new JsonFactory(); private final String table; private final String sunnylabsHost; private final String host; - private final boolean includeVMMetrics; - - public JsonMetricsReporter(String service, MetricsRegistry registry, String token, String sunnylabsHost) throws UnknownHostException { - this(service, registry, token, sunnylabsHost, true); - } - - public JsonMetricsReporter(String service, MetricsRegistry registry, String token, String sunnylabsHost, boolean includeVMMetrics) throws UnknownHostException { - super(registry, "jsonmetrics-reporter"); - this.service = service; - this.registry = registry; - this.table = token; + public JsonMetricsReporter(MetricRegistry registry, String token, String sunnylabsHost) + throws UnknownHostException + { + super(registry, "jsonmetrics-reporter", MetricFilter.ALL, TimeUnit.SECONDS, TimeUnit.MILLISECONDS); + this.table = token; this.sunnylabsHost = sunnylabsHost; - this.host = InetAddress.getLocalHost().getHostName(); - this.includeVMMetrics = includeVMMetrics; + this.host = InetAddress.getLocalHost().getHostName(); } @Override - public void run() { + public void report(SortedMap stringGaugeSortedMap, + SortedMap stringCounterSortedMap, + SortedMap stringHistogramSortedMap, + SortedMap stringMeterSortedMap, + SortedMap stringTimerSortedMap) + { try { - URL http = new URL("https", sunnylabsHost, 443, "/report/metrics?t=" + table + "&h=" + host); - System.out.println("Reporting started to: " + http); - HttpURLConnection urlc = (HttpURLConnection) http.openConnection(); - urlc.setDoOutput(true); - urlc.addRequestProperty("Content-Type", "application/json"); - OutputStream outputStream = urlc.getOutputStream(); - writeJson(outputStream); + URL url = new URL("https", sunnylabsHost, 443, "/report/metrics?t=" + table + "&h=" + host); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + + connection.setDoOutput(true); + connection.addRequestProperty("Content-Type", "application/json"); + + OutputStream outputStream = connection.getOutputStream(); + JsonGenerator json = factory.createGenerator(outputStream, JsonEncoding.UTF8); + + json.writeStartObject(); + + for (Map.Entry gauge : stringGaugeSortedMap.entrySet()) { + reportGauge(json, gauge.getKey(), gauge.getValue()); + } + + for (Map.Entry counter : stringCounterSortedMap.entrySet()) { + reportCounter(json, counter.getKey(), counter.getValue()); + } + + for (Map.Entry histogram : stringHistogramSortedMap.entrySet()) { + reportHistogram(json, histogram.getKey(), histogram.getValue()); + } + + for (Map.Entry meter : stringMeterSortedMap.entrySet()) { + reportMeter(json, meter.getKey(), meter.getValue()); + } + + for (Map.Entry timer : stringTimerSortedMap.entrySet()) { + reportTimer(json, timer.getKey(), timer.getValue()); + } + + json.writeEndObject(); + json.close(); + outputStream.close(); - System.out.println("Reporting complete: " + urlc.getResponseCode()); } catch (IOException e) { e.printStackTrace(); } } - static final class Context { - final boolean showFullSamples; - final JsonGenerator json; - - Context(JsonGenerator json, boolean showFullSamples) { - this.json = json; - this.showFullSamples = showFullSamples; - } - } - - public void writeJson(OutputStream out) throws IOException { - final JsonGenerator json = factory.createGenerator(out, JsonEncoding.UTF8); - json.writeStartObject(); - if (includeVMMetrics) { - writeVmMetrics(json); - } - writeRegularMetrics(json, false); - json.writeEndObject(); - json.close(); - } - - private void writeVmMetrics(JsonGenerator json) throws IOException { - json.writeFieldName(service); - json.writeStartObject(); - json.writeFieldName("jvm"); - json.writeStartObject(); - { - json.writeFieldName("vm"); - json.writeStartObject(); - { - json.writeStringField("name", vm.name()); - json.writeStringField("version", vm.version()); - } - json.writeEndObject(); - - json.writeFieldName("memory"); - json.writeStartObject(); - { - json.writeNumberField("totalInit", vm.totalInit()); - json.writeNumberField("totalUsed", vm.totalUsed()); - json.writeNumberField("totalMax", vm.totalMax()); - json.writeNumberField("totalCommitted", vm.totalCommitted()); - - json.writeNumberField("heapInit", vm.heapInit()); - json.writeNumberField("heapUsed", vm.heapUsed()); - json.writeNumberField("heapMax", vm.heapMax()); - json.writeNumberField("heapCommitted", vm.heapCommitted()); - - json.writeNumberField("heap_usage", vm.heapUsage()); - json.writeNumberField("non_heap_usage", vm.nonHeapUsage()); - json.writeFieldName("memory_pool_usages"); - json.writeStartObject(); - { - for (Map.Entry pool : vm.memoryPoolUsage().entrySet()) { - json.writeNumberField(pool.getKey(), pool.getValue()); - } - } - json.writeEndObject(); - } - json.writeEndObject(); - - final Map bufferPoolStats = vm.getBufferPoolStats(); - if (!bufferPoolStats.isEmpty()) { - json.writeFieldName("buffers"); - json.writeStartObject(); - { - json.writeFieldName("direct"); - json.writeStartObject(); - { - json.writeNumberField("count", bufferPoolStats.get("direct").getCount()); - json.writeNumberField("memoryUsed", bufferPoolStats.get("direct").getMemoryUsed()); - json.writeNumberField("totalCapacity", bufferPoolStats.get("direct").getTotalCapacity()); - } - json.writeEndObject(); - - json.writeFieldName("mapped"); - json.writeStartObject(); - { - json.writeNumberField("count", bufferPoolStats.get("mapped").getCount()); - json.writeNumberField("memoryUsed", bufferPoolStats.get("mapped").getMemoryUsed()); - json.writeNumberField("totalCapacity", bufferPoolStats.get("mapped").getTotalCapacity()); - } - json.writeEndObject(); - } - json.writeEndObject(); - } - - - json.writeNumberField("daemon_thread_count", vm.daemonThreadCount()); - json.writeNumberField("thread_count", vm.threadCount()); - json.writeNumberField("current_time", clock.time()); - json.writeNumberField("uptime", vm.uptime()); - json.writeNumberField("fd_usage", vm.fileDescriptorUsage()); - - json.writeFieldName("thread-states"); - json.writeStartObject(); - { - for (Map.Entry entry : vm.threadStatePercentages() - .entrySet()) { - json.writeNumberField(entry.getKey().toString().toLowerCase(), - entry.getValue()); - } - } - json.writeEndObject(); - - json.writeFieldName("garbage-collectors"); - json.writeStartObject(); - { - for (Map.Entry entry : vm.garbageCollectors() - .entrySet()) { - json.writeFieldName(entry.getKey()); - json.writeStartObject(); - { - final VirtualMachineMetrics.GarbageCollectorStats gc = entry.getValue(); - json.writeNumberField("runs", gc.getRuns()); - json.writeNumberField("time", gc.getTime(TimeUnit.MILLISECONDS)); - } - json.writeEndObject(); - } - } - json.writeEndObject(); - } - json.writeEndObject(); - json.writeEndObject(); - } - - public void writeRegularMetrics(JsonGenerator json, boolean showFullSamples) throws IOException { - for (Map.Entry> entry : registry.groupedMetrics().entrySet()) { - for (Map.Entry subEntry : entry.getValue().entrySet()) { - json.writeFieldName(sanitize(subEntry.getKey())); - try { - subEntry.getValue() - .processWith(this, - subEntry.getKey(), - new Context(json, showFullSamples)); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - } - - @Override - public void processHistogram(MetricName name, Histogram histogram, Context context) throws Exception { - final JsonGenerator json = context.json; - json.writeStartObject(); - { - json.writeNumberField("count", histogram.count()); - writeSummarizable(histogram, json); - writeSampling(histogram, json); - if (context.showFullSamples) { - json.writeObjectField("values", histogram.getSnapshot().getValues()); - } - histogram.clear(); - } - json.writeEndObject(); - } - - @Override - public void processCounter(MetricName name, Counter counter, Context context) throws Exception { - final JsonGenerator json = context.json; - json.writeNumber(counter.count()); - } - - @Override - public void processGauge(MetricName name, Gauge gauge, Context context) throws Exception { - final JsonGenerator json = context.json; + public void reportGauge(JsonGenerator json, String name, Gauge gauge) throws IOException { + json.writeFieldName(sanitize(name)); json.writeObject(evaluateGauge(gauge)); } - @Override - public void processMeter(MetricName name, Metered meter, Context context) throws Exception { - final JsonGenerator json = context.json; + public void reportCounter(JsonGenerator json, String name, Counter counter) throws IOException { + json.writeFieldName(sanitize(name)); + json.writeNumber(counter.getCount()); + } + + public void reportHistogram(JsonGenerator json, String name, Histogram histogram) throws IOException { + Snapshot snapshot = histogram.getSnapshot(); + json.writeFieldName(sanitize(name)); json.writeStartObject(); - { - writeMeteredFields(meter, json); - } + json.writeNumberField("count", histogram.getCount()); + writeSnapshot(json, snapshot); json.writeEndObject(); } - @Override - public void processTimer(MetricName name, Timer timer, Context context) throws Exception { - final JsonGenerator json = context.json; + public void reportMeter(JsonGenerator json, String name, Meter meter) throws IOException { + json.writeFieldName(sanitize(name)); json.writeStartObject(); - { - json.writeFieldName("duration"); - json.writeStartObject(); - { - json.writeStringField("unit", timer.durationUnit().toString().toLowerCase()); - writeSummarizable(timer, json); - writeSampling(timer, json); - if (context.showFullSamples) { - json.writeObjectField("values", timer.getSnapshot().getValues()); - } - } - json.writeEndObject(); + json.writeNumberField("count", meter.getCount()); + json.writeNumberField("mean", meter.getMeanRate()); + json.writeNumberField("m1", meter.getOneMinuteRate()); + json.writeNumberField("m5", meter.getFiveMinuteRate()); + json.writeNumberField("m15", meter.getFifteenMinuteRate()); + json.writeEndObject(); + } - json.writeFieldName("rate"); - json.writeStartObject(); - { - writeMeteredFields(timer, json); - } - json.writeEndObject(); - } + public void reportTimer(JsonGenerator json, String name, Timer timer) throws IOException { + json.writeFieldName(sanitize(name)); + json.writeStartObject(); + json.writeNumberField("count", timer.getCount()); + writeSnapshot(json, timer.getSnapshot()); json.writeEndObject(); } private static Object evaluateGauge(Gauge gauge) { try { - return gauge.value(); + return gauge.getValue(); } catch (RuntimeException e) { return "error reading gauge: " + e.getMessage(); } } - private static void writeSummarizable(Summarizable metric, JsonGenerator json) throws IOException { - json.writeNumberField("min", metric.min()); - json.writeNumberField("max", metric.max()); - json.writeNumberField("mean", metric.mean()); - } - - private static void writeSampling(Sampling metric, JsonGenerator json) throws IOException { - final Snapshot snapshot = metric.getSnapshot(); + private static void writeSnapshot(JsonGenerator json, Snapshot snapshot) throws IOException { + json.writeNumberField("max", snapshot.getMax()); + json.writeNumberField("mean", snapshot.getMean()); + json.writeNumberField("min", snapshot.getMin()); + json.writeNumberField("stddev", snapshot.getStdDev()); json.writeNumberField("median", snapshot.getMedian()); json.writeNumberField("p75", snapshot.get75thPercentile()); json.writeNumberField("p95", snapshot.get95thPercentile()); + json.writeNumberField("p98", snapshot.get98thPercentile()); json.writeNumberField("p99", snapshot.get99thPercentile()); json.writeNumberField("p999", snapshot.get999thPercentile()); } - private static void writeMeteredFields(Metered metered, JsonGenerator json) throws IOException { - json.writeNumberField("count", metered.count()); - json.writeNumberField("mean", metered.meanRate()); - json.writeNumberField("m1", metered.oneMinuteRate()); - json.writeNumberField("m5", metered.fiveMinuteRate()); - json.writeNumberField("m15", metered.fifteenMinuteRate()); - } - private static final Pattern SIMPLE_NAMES = Pattern.compile("[^a-zA-Z0-9_.\\-~]"); - private String sanitize(MetricName metricName) { - return SIMPLE_NAMES.matcher(metricName.getGroup() + "." + metricName.getName()).replaceAll("_"); + private String sanitize(String metricName) { + return SIMPLE_NAMES.matcher(metricName).replaceAll("_"); } } \ No newline at end of file diff --git a/src/main/java/org/whispersystems/textsecuregcm/metrics/NetworkGauge.java b/src/main/java/org/whispersystems/textsecuregcm/metrics/NetworkGauge.java index 08e03816f..245d33dde 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/metrics/NetworkGauge.java +++ b/src/main/java/org/whispersystems/textsecuregcm/metrics/NetworkGauge.java @@ -1,16 +1,15 @@ package org.whispersystems.textsecuregcm.metrics; -import com.yammer.metrics.core.Gauge; +import com.codahale.metrics.Gauge; import org.whispersystems.textsecuregcm.util.Pair; import java.io.BufferedReader; import java.io.File; -import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; -public abstract class NetworkGauge extends Gauge { +public abstract class NetworkGauge implements Gauge { protected Pair getSentReceived() throws IOException { File proc = new File("/proc/net/dev"); diff --git a/src/main/java/org/whispersystems/textsecuregcm/metrics/NetworkReceivedGauge.java b/src/main/java/org/whispersystems/textsecuregcm/metrics/NetworkReceivedGauge.java index 5d6437a19..647a69c99 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/metrics/NetworkReceivedGauge.java +++ b/src/main/java/org/whispersystems/textsecuregcm/metrics/NetworkReceivedGauge.java @@ -14,7 +14,7 @@ public class NetworkReceivedGauge extends NetworkGauge { private long lastReceived; @Override - public Long value() { + public Long getValue() { try { long timestamp = System.currentTimeMillis(); Pair sentAndReceived = getSentReceived(); diff --git a/src/main/java/org/whispersystems/textsecuregcm/metrics/NetworkSentGauge.java b/src/main/java/org/whispersystems/textsecuregcm/metrics/NetworkSentGauge.java index e8b1b4ef1..0e922a7b7 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/metrics/NetworkSentGauge.java +++ b/src/main/java/org/whispersystems/textsecuregcm/metrics/NetworkSentGauge.java @@ -14,7 +14,7 @@ public class NetworkSentGauge extends NetworkGauge { private long lastSent; @Override - public Long value() { + public Long getValue() { try { long timestamp = System.currentTimeMillis(); Pair sentAndReceived = getSentReceived(); diff --git a/src/main/java/org/whispersystems/textsecuregcm/providers/MemcacheHealthCheck.java b/src/main/java/org/whispersystems/textsecuregcm/providers/MemcacheHealthCheck.java index 41c407273..f6915a7e7 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/providers/MemcacheHealthCheck.java +++ b/src/main/java/org/whispersystems/textsecuregcm/providers/MemcacheHealthCheck.java @@ -16,8 +16,7 @@ */ package org.whispersystems.textsecuregcm.providers; -import com.yammer.metrics.core.HealthCheck; -import com.yammer.metrics.core.HealthCheck.Result; +import com.codahale.metrics.health.HealthCheck; import net.spy.memcached.MemcachedClient; import java.security.SecureRandom; @@ -27,7 +26,6 @@ public class MemcacheHealthCheck extends HealthCheck { private final MemcachedClient client; public MemcacheHealthCheck(MemcachedClient client) { - super("memcached"); this.client = client; } diff --git a/src/main/java/org/whispersystems/textsecuregcm/providers/RedisHealthCheck.java b/src/main/java/org/whispersystems/textsecuregcm/providers/RedisHealthCheck.java index c0a661eac..219eb86b6 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/providers/RedisHealthCheck.java +++ b/src/main/java/org/whispersystems/textsecuregcm/providers/RedisHealthCheck.java @@ -16,7 +16,7 @@ */ package org.whispersystems.textsecuregcm.providers; -import com.yammer.metrics.core.HealthCheck; +import com.codahale.metrics.health.HealthCheck; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; @@ -26,7 +26,6 @@ public class RedisHealthCheck extends HealthCheck { private final JedisPool clientPool; public RedisHealthCheck(JedisPool clientPool) { - super("redis"); this.clientPool = clientPool; } diff --git a/src/main/java/org/whispersystems/textsecuregcm/push/APNSender.java b/src/main/java/org/whispersystems/textsecuregcm/push/APNSender.java index b5aa2edfc..ff5b05640 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/push/APNSender.java +++ b/src/main/java/org/whispersystems/textsecuregcm/push/APNSender.java @@ -16,17 +16,19 @@ */ package org.whispersystems.textsecuregcm.push; +import com.codahale.metrics.Meter; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.SharedMetricRegistries; import com.google.common.base.Optional; import com.notnoop.apns.APNS; import com.notnoop.apns.ApnsService; import com.notnoop.exceptions.NetworkIOException; -import com.yammer.metrics.Metrics; -import com.yammer.metrics.core.Meter; import org.bouncycastle.openssl.PEMReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.entities.CryptoEncodingException; import org.whispersystems.textsecuregcm.entities.EncryptedOutgoingMessage; +import org.whispersystems.textsecuregcm.util.Constants; import org.whispersystems.textsecuregcm.util.Util; import java.io.ByteArrayInputStream; @@ -40,13 +42,15 @@ import java.security.NoSuchAlgorithmException; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; -import java.util.concurrent.TimeUnit; + +import static com.codahale.metrics.MetricRegistry.name; public class APNSender { - private final Meter success = Metrics.newMeter(APNSender.class, "sent", "success", TimeUnit.MINUTES); - private final Meter failure = Metrics.newMeter(APNSender.class, "sent", "failure", TimeUnit.MINUTES); - private final Logger logger = LoggerFactory.getLogger(APNSender.class); + private final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME); + private final Meter success = metricRegistry.meter(name(getClass(), "sent", "success")); + private final Meter failure = metricRegistry.meter(name(getClass(), "sent", "failure")); + private final Logger logger = LoggerFactory.getLogger(APNSender.class); private static final String MESSAGE_BODY = "m"; diff --git a/src/main/java/org/whispersystems/textsecuregcm/push/GCMSender.java b/src/main/java/org/whispersystems/textsecuregcm/push/GCMSender.java index 449eb2276..7065a8fb6 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/push/GCMSender.java +++ b/src/main/java/org/whispersystems/textsecuregcm/push/GCMSender.java @@ -16,22 +16,25 @@ */ package org.whispersystems.textsecuregcm.push; +import com.codahale.metrics.Meter; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.SharedMetricRegistries; import com.google.android.gcm.server.Constants; import com.google.android.gcm.server.Message; import com.google.android.gcm.server.Result; import com.google.android.gcm.server.Sender; -import com.yammer.metrics.Metrics; -import com.yammer.metrics.core.Meter; import org.whispersystems.textsecuregcm.entities.CryptoEncodingException; import org.whispersystems.textsecuregcm.entities.EncryptedOutgoingMessage; import java.io.IOException; -import java.util.concurrent.TimeUnit; + +import static com.codahale.metrics.MetricRegistry.name; public class GCMSender { - private final Meter success = Metrics.newMeter(GCMSender.class, "sent", "success", TimeUnit.MINUTES); - private final Meter failure = Metrics.newMeter(GCMSender.class, "sent", "failure", TimeUnit.MINUTES); + private final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(org.whispersystems.textsecuregcm.util.Constants.METRICS_NAME); + private final Meter success = metricRegistry.meter(name(getClass(), "sent", "success")); + private final Meter failure = metricRegistry.meter(name(getClass(), "sent", "failure")); private final Sender sender; diff --git a/src/main/java/org/whispersystems/textsecuregcm/sms/NexmoSmsSender.java b/src/main/java/org/whispersystems/textsecuregcm/sms/NexmoSmsSender.java index fab421abd..58e34a060 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/sms/NexmoSmsSender.java +++ b/src/main/java/org/whispersystems/textsecuregcm/sms/NexmoSmsSender.java @@ -16,11 +16,13 @@ */ package org.whispersystems.textsecuregcm.sms; -import com.yammer.metrics.Metrics; -import com.yammer.metrics.core.Meter; +import com.codahale.metrics.Meter; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.SharedMetricRegistries; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.configuration.NexmoConfiguration; +import org.whispersystems.textsecuregcm.util.Constants; import java.io.BufferedReader; import java.io.IOException; @@ -28,13 +30,15 @@ import java.io.InputStreamReader; import java.net.URL; import java.net.URLConnection; import java.net.URLEncoder; -import java.util.concurrent.TimeUnit; + +import static com.codahale.metrics.MetricRegistry.name; public class NexmoSmsSender { - private final Meter smsMeter = Metrics.newMeter(NexmoSmsSender.class, "sms", "delivered", TimeUnit.MINUTES); - private final Meter voxMeter = Metrics.newMeter(NexmoSmsSender.class, "vox", "delivered", TimeUnit.MINUTES); - private final Logger logger = LoggerFactory.getLogger(NexmoSmsSender.class); + private final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME); + private final Meter smsMeter = metricRegistry.meter(name(getClass(), "sms", "delivered")); + private final Meter voxMeter = metricRegistry.meter(name(getClass(), "vox", "delivered")); + private final Logger logger = LoggerFactory.getLogger(NexmoSmsSender.class); private static final String NEXMO_SMS_URL = "https://rest.nexmo.com/sms/json?api_key=%s&api_secret=%s&from=%s&to=%s&text=%s"; diff --git a/src/main/java/org/whispersystems/textsecuregcm/sms/TwilioSmsSender.java b/src/main/java/org/whispersystems/textsecuregcm/sms/TwilioSmsSender.java index f538c89fc..07c281be9 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/sms/TwilioSmsSender.java +++ b/src/main/java/org/whispersystems/textsecuregcm/sms/TwilioSmsSender.java @@ -16,22 +16,25 @@ */ package org.whispersystems.textsecuregcm.sms; +import com.codahale.metrics.Meter; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.SharedMetricRegistries; import com.twilio.sdk.TwilioRestClient; import com.twilio.sdk.TwilioRestException; import com.twilio.sdk.resource.factory.CallFactory; import com.twilio.sdk.resource.factory.MessageFactory; -import com.yammer.metrics.Metrics; -import com.yammer.metrics.core.Meter; import org.apache.http.NameValuePair; import org.apache.http.message.BasicNameValuePair; import org.whispersystems.textsecuregcm.configuration.TwilioConfiguration; +import org.whispersystems.textsecuregcm.util.Constants; import java.io.IOException; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.concurrent.TimeUnit; + +import static com.codahale.metrics.MetricRegistry.name; public class TwilioSmsSender { @@ -40,8 +43,9 @@ public class TwilioSmsSender { " " + SmsSender.VOX_VERIFICATION_TEXT + "%s\n" + ""; - private final Meter smsMeter = Metrics.newMeter(TwilioSmsSender.class, "sms", "delivered", TimeUnit.MINUTES); - private final Meter voxMeter = Metrics.newMeter(TwilioSmsSender.class, "vox", "delivered", TimeUnit.MINUTES); + private final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME); + private final Meter smsMeter = metricRegistry.meter(name(getClass(), "sms", "delivered")); + private final Meter voxMeter = metricRegistry.meter(name(getClass(), "vox", "delivered")); private final String accountId; private final String accountToken; diff --git a/src/main/java/org/whispersystems/textsecuregcm/storage/PubSubManager.java b/src/main/java/org/whispersystems/textsecuregcm/storage/PubSubManager.java index b9e10b7e8..471d892ce 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/storage/PubSubManager.java +++ b/src/main/java/org/whispersystems/textsecuregcm/storage/PubSubManager.java @@ -134,6 +134,7 @@ public class PubSubManager { public void onSubscribe(String channel, int count) { try { WebsocketAddress address = new WebsocketAddress(channel); + if (address.getAccountId() == 0 && address.getDeviceId() == 0) { synchronized (PubSubManager.this) { subscribed = true; diff --git a/src/main/java/org/whispersystems/textsecuregcm/util/Constants.java b/src/main/java/org/whispersystems/textsecuregcm/util/Constants.java new file mode 100644 index 000000000..86021e884 --- /dev/null +++ b/src/main/java/org/whispersystems/textsecuregcm/util/Constants.java @@ -0,0 +1,7 @@ +package org.whispersystems.textsecuregcm.util; + +public class Constants { + + public static final String METRICS_NAME = "textsecure"; + +} diff --git a/src/main/java/org/whispersystems/textsecuregcm/workers/DirectoryCommand.java b/src/main/java/org/whispersystems/textsecuregcm/workers/DirectoryCommand.java index d06d76318..fcc8dd0fe 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/workers/DirectoryCommand.java +++ b/src/main/java/org/whispersystems/textsecuregcm/workers/DirectoryCommand.java @@ -16,13 +16,6 @@ */ package org.whispersystems.textsecuregcm.workers; -import com.yammer.dropwizard.cli.ConfiguredCommand; -import com.yammer.dropwizard.config.Bootstrap; -import com.yammer.dropwizard.db.DatabaseConfiguration; -import com.yammer.dropwizard.jdbi.ImmutableListContainerFactory; -import com.yammer.dropwizard.jdbi.ImmutableSetContainerFactory; -import com.yammer.dropwizard.jdbi.OptionalContainerFactory; -import com.yammer.dropwizard.jdbi.args.OptionalArgumentFactory; import net.sourceforge.argparse4j.inf.Namespace; import net.spy.memcached.MemcachedClient; import org.skife.jdbi.v2.DBI; @@ -36,6 +29,13 @@ import org.whispersystems.textsecuregcm.storage.Accounts; import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.DirectoryManager; +import io.dropwizard.cli.ConfiguredCommand; +import io.dropwizard.db.DataSourceFactory; +import io.dropwizard.jdbi.ImmutableListContainerFactory; +import io.dropwizard.jdbi.ImmutableSetContainerFactory; +import io.dropwizard.jdbi.OptionalContainerFactory; +import io.dropwizard.jdbi.args.OptionalArgumentFactory; +import io.dropwizard.setup.Bootstrap; import redis.clients.jedis.JedisPool; public class DirectoryCommand extends ConfiguredCommand { @@ -53,8 +53,8 @@ public class DirectoryCommand extends ConfiguredCommand>() { @Override public List answer(InvocationOnMock invocationOnMock) throws Throwable { - List query = (List) invocationOnMock.getArguments()[0]; + List query = (List) invocationOnMock.getArguments()[0]; List response = new LinkedList<>(query); response.remove(0); return response; } }); - - addResource(new DirectoryController(rateLimiters, directoryManager)); } @Test @@ -58,14 +65,13 @@ public class DirectoryControllerTest extends ResourceTest { expectedResponse.remove(0); ClientResponse response = - client().resource("/v1/directory/tokens/") - .entity(new ClientContactTokens(tokens)) - .type(MediaType.APPLICATION_JSON_TYPE) - .header("Authorization", - AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, - AuthHelper.VALID_PASSWORD)) - .put(ClientResponse.class); - + resources.client().resource("/v1/directory/tokens/") + .entity(new ClientContactTokens(tokens)) + .type(MediaType.APPLICATION_JSON_TYPE) + .header("Authorization", + AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, + AuthHelper.VALID_PASSWORD)) + .put(ClientResponse.class); assertThat(response.getStatus()).isEqualTo(200); diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/FederatedControllerTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/FederatedControllerTest.java index c00d396a1..d04674a06 100644 --- a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/FederatedControllerTest.java +++ b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/FederatedControllerTest.java @@ -4,7 +4,8 @@ package org.whispersystems.textsecuregcm.tests.controllers; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Optional; import com.sun.jersey.api.client.ClientResponse; -import com.yammer.dropwizard.testing.ResourceTest; +import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.whispersystems.textsecuregcm.controllers.FederationController; import org.whispersystems.textsecuregcm.controllers.MessageController; @@ -23,17 +24,16 @@ import javax.ws.rs.core.MediaType; import java.util.LinkedList; import java.util.List; -import static com.yammer.dropwizard.testing.JsonHelpers.jsonFixture; +import io.dropwizard.testing.junit.ResourceTestRule; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; +import static org.whispersystems.textsecuregcm.tests.util.JsonHelpers.jsonFixture; -public class FederatedControllerTest extends ResourceTest { +public class FederatedControllerTest { private static final String SINGLE_DEVICE_RECIPIENT = "+14151111111"; private static final String MULTI_DEVICE_RECIPIENT = "+14152222222"; @@ -46,10 +46,20 @@ public class FederatedControllerTest extends ResourceTest { private final ObjectMapper mapper = new ObjectMapper(); - @Override - protected void setUpResources() throws Exception { - addProvider(AuthHelper.getAuthenticator()); + private final MessageController messageController = new MessageController(rateLimiters, pushSender, accountsManager, federatedClientManager); + @Rule + public final ResourceTestRule resources = ResourceTestRule.builder() + .addProvider(AuthHelper.getAuthenticator()) + .addResource(new FederationController(accountsManager, + null, null, + messageController)) + .build(); + + + + @Before + public void setup() throws Exception { List singleDeviceList = new LinkedList() {{ add(new Device(1, "foo", "bar", "baz", "isgcm", null, false, 111)); }}; @@ -66,15 +76,12 @@ public class FederatedControllerTest extends ResourceTest { when(accountsManager.get(eq(MULTI_DEVICE_RECIPIENT))).thenReturn(Optional.of(multiDeviceAccount)); when(rateLimiters.getMessagesLimiter()).thenReturn(rateLimiter); - - MessageController messageController = new MessageController(rateLimiters, pushSender, accountsManager, federatedClientManager); - addResource(new FederationController(accountsManager, null, null, messageController)); } @Test public void testSingleDeviceCurrent() throws Exception { ClientResponse response = - client().resource(String.format("/v1/federation/messages/+14152223333/1/%s", SINGLE_DEVICE_RECIPIENT)) + resources.client().resource(String.format("/v1/federation/messages/+14152223333/1/%s", SINGLE_DEVICE_RECIPIENT)) .header("Authorization", AuthHelper.getAuthHeader("cyanogen", "foofoo")) .entity(mapper.readValue(jsonFixture("fixtures/current_message_single_device.json"), IncomingMessageList.class)) .type(MediaType.APPLICATION_JSON_TYPE) diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/KeyControllerTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/KeyControllerTest.java index c40d5def0..e6edabd97 100644 --- a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/KeyControllerTest.java +++ b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/KeyControllerTest.java @@ -2,7 +2,8 @@ package org.whispersystems.textsecuregcm.tests.controllers; import com.google.common.base.Optional; import com.sun.jersey.api.client.ClientResponse; -import com.yammer.dropwizard.testing.ResourceTest; +import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.invocation.InvocationOnMock; @@ -24,16 +25,17 @@ import javax.ws.rs.core.MediaType; import java.util.LinkedList; import java.util.List; +import io.dropwizard.testing.junit.ResourceTestRule; import static org.fest.assertions.api.Assertions.assertThat; import static org.mockito.Mockito.*; -public class KeyControllerTest extends ResourceTest { +public class KeyControllerTest { - private final String EXISTS_NUMBER = "+14152222222"; - private final String NOT_EXISTS_NUMBER = "+14152222220"; + private static final String EXISTS_NUMBER = "+14152222222"; + private static String NOT_EXISTS_NUMBER = "+14152222220"; - private final int SAMPLE_REGISTRATION_ID = 999; - private final int SAMPLE_REGISTRATION_ID2 = 1002; + private static int SAMPLE_REGISTRATION_ID = 999; + private static int SAMPLE_REGISTRATION_ID2 = 1002; private final PreKey SAMPLE_KEY = new PreKey(1, EXISTS_NUMBER, Device.MASTER_ID, 1234, "test1", "test2", false); private final PreKey SAMPLE_KEY2 = new PreKey(2, EXISTS_NUMBER, 2, 5667, "test3", "test4,", false ); @@ -42,14 +44,17 @@ public class KeyControllerTest extends ResourceTest { private final AccountsManager accounts = mock(AccountsManager.class); private final Account existsAccount = mock(Account.class ); + private RateLimiters rateLimiters = mock(RateLimiters.class); + private RateLimiter rateLimiter = mock(RateLimiter.class ); - @Override - protected void setUpResources() { - addProvider(AuthHelper.getAuthenticator()); - - RateLimiters rateLimiters = mock(RateLimiters.class); - RateLimiter rateLimiter = mock(RateLimiter.class ); + @Rule + public final ResourceTestRule resources = ResourceTestRule.builder() + .addProvider(AuthHelper.getAuthenticator()) + .addResource(new KeysController(rateLimiters, keys, accounts, null)) + .build(); + @Before + public void setup() { Device sampleDevice = mock(Device.class ); Device sampleDevice2 = mock(Device.class); Device sampleDevice3 = mock(Device.class); @@ -94,15 +99,12 @@ public class KeyControllerTest extends ResourceTest { }); when(keys.getCount(eq(AuthHelper.VALID_NUMBER), eq(1L))).thenReturn(5); - when(AuthHelper.VALID_ACCOUNT.getIdentityKey()).thenReturn(null); - - addResource(new KeysController(rateLimiters, keys, accounts, null)); } @Test public void validKeyStatusTest() throws Exception { - PreKeyStatus result = client().resource("/v1/keys") + PreKeyStatus result = resources.client().resource("/v1/keys") .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD)) .get(PreKeyStatus.class); @@ -114,7 +116,7 @@ public class KeyControllerTest extends ResourceTest { @Test public void validLegacyRequestTest() throws Exception { - PreKey result = client().resource(String.format("/v1/keys/%s", EXISTS_NUMBER)) + PreKey result = resources.client().resource(String.format("/v1/keys/%s", EXISTS_NUMBER)) .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD)) .get(PreKey.class); @@ -131,7 +133,7 @@ public class KeyControllerTest extends ResourceTest { @Test public void validMultiRequestTest() throws Exception { - UnstructuredPreKeyList results = client().resource(String.format("/v1/keys/%s/*", EXISTS_NUMBER)) + UnstructuredPreKeyList results = resources.client().resource(String.format("/v1/keys/%s/*", EXISTS_NUMBER)) .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD)) .get(UnstructuredPreKeyList.class); @@ -163,27 +165,27 @@ public class KeyControllerTest extends ResourceTest { @Test public void invalidRequestTest() throws Exception { - ClientResponse response = client().resource(String.format("/v1/keys/%s", NOT_EXISTS_NUMBER)) + ClientResponse response = resources.client().resource(String.format("/v1/keys/%s", NOT_EXISTS_NUMBER)) .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD)) .get(ClientResponse.class); - assertThat(response.getClientResponseStatus().getStatusCode()).isEqualTo(404); + assertThat(response.getStatusInfo().getStatusCode()).isEqualTo(404); } @Test public void unauthorizedRequestTest() throws Exception { ClientResponse response = - client().resource(String.format("/v1/keys/%s", NOT_EXISTS_NUMBER)) + resources.client().resource(String.format("/v1/keys/%s", NOT_EXISTS_NUMBER)) .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.INVALID_PASSWORD)) .get(ClientResponse.class); - assertThat(response.getClientResponseStatus().getStatusCode()).isEqualTo(401); + assertThat(response.getStatusInfo().getStatusCode()).isEqualTo(401); response = - client().resource(String.format("/v1/keys/%s", NOT_EXISTS_NUMBER)) + resources.client().resource(String.format("/v1/keys/%s", NOT_EXISTS_NUMBER)) .get(ClientResponse.class); - assertThat(response.getClientResponseStatus().getStatusCode()).isEqualTo(401); + assertThat(response.getStatusInfo().getStatusCode()).isEqualTo(401); } @Test @@ -200,7 +202,7 @@ public class KeyControllerTest extends ResourceTest { preKeyList.setLastResortKey(lastResortKey); ClientResponse response = - client().resource("/v1/keys") + resources.client().resource("/v1/keys") .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD)) .type(MediaType.APPLICATION_JSON_TYPE) .put(ClientResponse.class, preKeyList); diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/MessageControllerTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/MessageControllerTest.java index e16cf632c..97f434a50 100644 --- a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/MessageControllerTest.java +++ b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/MessageControllerTest.java @@ -3,10 +3,11 @@ package org.whispersystems.textsecuregcm.tests.controllers; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Optional; import com.sun.jersey.api.client.ClientResponse; -import com.yammer.dropwizard.testing.ResourceTest; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; import org.whispersystems.textsecuregcm.controllers.MessageController; -import org.whispersystems.textsecuregcm.entities.AccountAttributes; import org.whispersystems.textsecuregcm.entities.IncomingMessageList; import org.whispersystems.textsecuregcm.entities.MessageProtos; import org.whispersystems.textsecuregcm.entities.MismatchedDevices; @@ -24,35 +25,39 @@ import javax.ws.rs.core.MediaType; import java.util.LinkedList; import java.util.List; -import static com.yammer.dropwizard.testing.JsonHelpers.asJson; -import static com.yammer.dropwizard.testing.JsonHelpers.jsonFixture; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.is; +import io.dropwizard.testing.junit.ResourceTestRule; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; - import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.*; +import static org.whispersystems.textsecuregcm.tests.util.JsonHelpers.asJson; +import static org.whispersystems.textsecuregcm.tests.util.JsonHelpers.jsonFixture; -public class MessageControllerTest extends ResourceTest { +public class MessageControllerTest { private static final String SINGLE_DEVICE_RECIPIENT = "+14151111111"; private static final String MULTI_DEVICE_RECIPIENT = "+14152222222"; - private PushSender pushSender = mock(PushSender.class ); - private FederatedClientManager federatedClientManager = mock(FederatedClientManager.class); - private AccountsManager accountsManager = mock(AccountsManager.class ); - private RateLimiters rateLimiters = mock(RateLimiters.class ); - private RateLimiter rateLimiter = mock(RateLimiter.class ); + private final PushSender pushSender = mock(PushSender.class ); + private final FederatedClientManager federatedClientManager = mock(FederatedClientManager.class); + private final AccountsManager accountsManager = mock(AccountsManager.class ); + private final RateLimiters rateLimiters = mock(RateLimiters.class ); + private final RateLimiter rateLimiter = mock(RateLimiter.class ); - private final ObjectMapper mapper = new ObjectMapper(); + private final ObjectMapper mapper = new ObjectMapper(); - @Override - protected void setUpResources() throws Exception { - addProvider(AuthHelper.getAuthenticator()); + @Rule + public final ResourceTestRule resources = ResourceTestRule.builder() + .addProvider(AuthHelper.getAuthenticator()) + .addResource(new MessageController(rateLimiters, pushSender, accountsManager, + federatedClientManager)) + .build(); + + @Before + public void setup() throws Exception { List singleDeviceList = new LinkedList() {{ add(new Device(1, "foo", "bar", "baz", "isgcm", null, false, 111)); }}; @@ -69,15 +74,12 @@ public class MessageControllerTest extends ResourceTest { when(accountsManager.get(eq(MULTI_DEVICE_RECIPIENT))).thenReturn(Optional.of(multiDeviceAccount)); when(rateLimiters.getMessagesLimiter()).thenReturn(rateLimiter); - - addResource(new MessageController(rateLimiters, pushSender, accountsManager, - federatedClientManager)); } @Test - public void testSingleDeviceLegacy() throws Exception { + public synchronized void testSingleDeviceLegacy() throws Exception { ClientResponse response = - client().resource("/v1/messages/") + resources.client().resource("/v1/messages/") .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD)) .entity(mapper.readValue(jsonFixture("fixtures/legacy_message_single_device.json"), IncomingMessageList.class)) .type(MediaType.APPLICATION_JSON_TYPE) @@ -85,13 +87,13 @@ public class MessageControllerTest extends ResourceTest { assertThat("Good Response", response.getStatus(), is(equalTo(200))); - verify(pushSender).sendMessage(any(Account.class), any(Device.class), any(MessageProtos.OutgoingMessageSignal.class)); + verify(pushSender, times(1)).sendMessage(any(Account.class), any(Device.class), any(MessageProtos.OutgoingMessageSignal.class)); } @Test - public void testSingleDeviceCurrent() throws Exception { + public synchronized void testSingleDeviceCurrent() throws Exception { ClientResponse response = - client().resource(String.format("/v1/messages/%s", SINGLE_DEVICE_RECIPIENT)) + resources.client().resource(String.format("/v1/messages/%s", SINGLE_DEVICE_RECIPIENT)) .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD)) .entity(mapper.readValue(jsonFixture("fixtures/current_message_single_device.json"), IncomingMessageList.class)) .type(MediaType.APPLICATION_JSON_TYPE) @@ -99,13 +101,13 @@ public class MessageControllerTest extends ResourceTest { assertThat("Good Response", response.getStatus(), is(equalTo(204))); - verify(pushSender).sendMessage(any(Account.class), any(Device.class), any(MessageProtos.OutgoingMessageSignal.class)); + verify(pushSender, times(1)).sendMessage(any(Account.class), any(Device.class), any(MessageProtos.OutgoingMessageSignal.class)); } @Test - public void testMultiDeviceMissing() throws Exception { + public synchronized void testMultiDeviceMissing() throws Exception { ClientResponse response = - client().resource(String.format("/v1/messages/%s", MULTI_DEVICE_RECIPIENT)) + resources.client().resource(String.format("/v1/messages/%s", MULTI_DEVICE_RECIPIENT)) .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD)) .entity(mapper.readValue(jsonFixture("fixtures/current_message_single_device.json"), IncomingMessageList.class)) .type(MediaType.APPLICATION_JSON_TYPE) @@ -121,9 +123,9 @@ public class MessageControllerTest extends ResourceTest { } @Test - public void testMultiDeviceExtra() throws Exception { + public synchronized void testMultiDeviceExtra() throws Exception { ClientResponse response = - client().resource(String.format("/v1/messages/%s", MULTI_DEVICE_RECIPIENT)) + resources.client().resource(String.format("/v1/messages/%s", MULTI_DEVICE_RECIPIENT)) .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD)) .entity(mapper.readValue(jsonFixture("fixtures/current_message_extra_device.json"), IncomingMessageList.class)) .type(MediaType.APPLICATION_JSON_TYPE) @@ -139,9 +141,9 @@ public class MessageControllerTest extends ResourceTest { } @Test - public void testMultiDevice() throws Exception { + public synchronized void testMultiDevice() throws Exception { ClientResponse response = - client().resource(String.format("/v1/messages/%s", MULTI_DEVICE_RECIPIENT)) + resources.client().resource(String.format("/v1/messages/%s", MULTI_DEVICE_RECIPIENT)) .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD)) .entity(mapper.readValue(jsonFixture("fixtures/current_message_multi_device.json"), IncomingMessageList.class)) .type(MediaType.APPLICATION_JSON_TYPE) @@ -153,9 +155,9 @@ public class MessageControllerTest extends ResourceTest { } @Test - public void testRegistrationIdMismatch() throws Exception { + public synchronized void testRegistrationIdMismatch() throws Exception { ClientResponse response = - client().resource(String.format("/v1/messages/%s", MULTI_DEVICE_RECIPIENT)) + resources.client().resource(String.format("/v1/messages/%s", MULTI_DEVICE_RECIPIENT)) .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD)) .entity(mapper.readValue(jsonFixture("fixtures/current_message_registration_id.json"), IncomingMessageList.class)) .type(MediaType.APPLICATION_JSON_TYPE) diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/WebsocketControllerTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/WebsocketControllerTest.java index 331a548bf..fe81dc6b8 100644 --- a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/WebsocketControllerTest.java +++ b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/WebsocketControllerTest.java @@ -2,8 +2,10 @@ package org.whispersystems.textsecuregcm.tests.controllers; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Optional; -import com.yammer.dropwizard.auth.basic.BasicCredentials; -import org.eclipse.jetty.websocket.WebSocket; +import org.eclipse.jetty.websocket.api.CloseStatus; +import org.eclipse.jetty.websocket.api.RemoteEndpoint; +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.UpgradeRequest; import org.junit.Test; import org.whispersystems.textsecuregcm.auth.AccountAuthenticator; import org.whispersystems.textsecuregcm.controllers.WebsocketController; @@ -15,12 +17,11 @@ import org.whispersystems.textsecuregcm.storage.PubSubManager; import org.whispersystems.textsecuregcm.storage.StoredMessageManager; import org.whispersystems.textsecuregcm.websocket.WebsocketAddress; -import javax.servlet.http.HttpServletRequest; - +import java.util.HashMap; import java.util.LinkedList; import java.util.List; -import static org.fest.assertions.api.Assertions.assertThat; +import io.dropwizard.auth.basic.BasicCredentials; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.*; @@ -34,13 +35,13 @@ public class WebsocketControllerTest { private static final String VALID_PASSWORD = "secure"; private static final String INVALID_PASSWORD = "insecure"; - private final StoredMessageManager storedMessageManager = mock(StoredMessageManager.class); - private final AccountAuthenticator accountAuthenticator = mock(AccountAuthenticator.class); - private final PubSubManager pubSubManager = mock(PubSubManager.class ); - private final Account account = mock(Account.class ); - private final Device device = mock(Device.class ); - private final HttpServletRequest request = mock(HttpServletRequest.class ); - private final WebSocket.Connection connection = mock(WebSocket.Connection.class); + private static final StoredMessageManager storedMessageManager = mock(StoredMessageManager.class); + private static final AccountAuthenticator accountAuthenticator = mock(AccountAuthenticator.class); + private static final PubSubManager pubSubManager = mock(PubSubManager.class ); + private static final Account account = mock(Account.class ); + private static final Device device = mock(Device.class ); + private static final UpgradeRequest upgradeRequest = mock(UpgradeRequest.class ); + private static final Session session = mock(Session.class ); @Test public void testCredentials() throws Exception { @@ -50,23 +51,35 @@ public class WebsocketControllerTest { when(accountAuthenticator.authenticate(eq(new BasicCredentials(INVALID_USER, INVALID_PASSWORD)))) .thenReturn(Optional.absent()); - WebsocketControllerFactory factory = new WebsocketControllerFactory(accountAuthenticator, - storedMessageManager, - pubSubManager); + when(session.getUpgradeRequest()).thenReturn(upgradeRequest); - when(request.getParameter(eq("user"))).thenReturn(VALID_USER); - when(request.getParameter(eq("password"))).thenReturn(VALID_PASSWORD); + WebsocketController controller = new WebsocketController(accountAuthenticator, storedMessageManager, pubSubManager); - assertThat(factory.checkOrigin(request, "foobar")).isEqualTo(true); + when(upgradeRequest.getParameterMap()).thenReturn(new HashMap() {{ + put("login", new String[] {VALID_USER}); + put("password", new String[] {VALID_PASSWORD}); + }}); - when(request.getParameter(eq("user"))).thenReturn(INVALID_USER); - when(request.getParameter(eq("password"))).thenReturn(INVALID_PASSWORD); + controller.onWebSocketConnect(session); - assertThat(factory.checkOrigin(request, "foobar")).isEqualTo(false); + verify(session, never()).close(); + verify(session, never()).close(any(CloseStatus.class)); + verify(session, never()).close(anyInt(), anyString()); + + when(upgradeRequest.getParameterMap()).thenReturn(new HashMap() {{ + put("login", new String[] {INVALID_USER}); + put("password", new String[] {INVALID_PASSWORD}); + }}); + + controller.onWebSocketConnect(session); + + verify(session).close(any(CloseStatus.class)); } @Test public void testOpen() throws Exception { + RemoteEndpoint remote = mock(RemoteEndpoint.class); + List outgoingMessages = new LinkedList() {{ add("first"); add("second"); @@ -76,29 +89,29 @@ public class WebsocketControllerTest { when(device.getId()).thenReturn(2L); when(account.getId()).thenReturn(31337L); when(account.getAuthenticatedDevice()).thenReturn(Optional.of(device)); + when(session.getRemote()).thenReturn(remote); + when(session.getUpgradeRequest()).thenReturn(upgradeRequest); - when(request.getParameter(eq("user"))).thenReturn(VALID_USER); - when(request.getParameter(eq("password"))).thenReturn(VALID_PASSWORD); + when(upgradeRequest.getParameterMap()).thenReturn(new HashMap() {{ + put("login", new String[] {VALID_USER}); + put("password", new String[] {VALID_PASSWORD}); + }}); when(accountAuthenticator.authenticate(eq(new BasicCredentials(VALID_USER, VALID_PASSWORD)))) .thenReturn(Optional.of(account)); when(storedMessageManager.getOutgoingMessages(eq(account), eq(device))).thenReturn(outgoingMessages); - WebsocketControllerFactory factory = new WebsocketControllerFactory(accountAuthenticator, - storedMessageManager, - pubSubManager); + WebsocketControllerFactory factory = new WebsocketControllerFactory(accountAuthenticator, storedMessageManager, pubSubManager); + WebsocketController controller = (WebsocketController) factory.createWebSocket(null, null); - assertThat(factory.checkOrigin(request, "foobar")).isEqualTo(true); + controller.onWebSocketConnect(session); - WebsocketController socket = (WebsocketController)factory.doWebSocketConnect(request, "foo"); - socket.onOpen(connection); + verify(pubSubManager).subscribe(eq(new WebsocketAddress(31337L, 2L)), eq((controller))); + verify(remote, times(3)).sendStringByFuture(anyString()); - verify(pubSubManager).subscribe(eq(new WebsocketAddress(31337L, 2L)), eq((socket))); - verify(connection, times(3)).sendMessage(anyString()); - - socket.onMessage(mapper.writeValueAsString(new AcknowledgeWebsocketMessage(1))); - socket.onClose(1000, "Closed"); + controller.onWebSocketText(mapper.writeValueAsString(new AcknowledgeWebsocketMessage(1))); + controller.onWebSocketClose(1000, "Closed"); List pending = new LinkedList() {{ add("first"); diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/entities/ClientContactTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/entities/ClientContactTest.java index 67144edb5..d1bf807d4 100644 --- a/src/test/java/org/whispersystems/textsecuregcm/tests/entities/ClientContactTest.java +++ b/src/test/java/org/whispersystems/textsecuregcm/tests/entities/ClientContactTest.java @@ -1,13 +1,16 @@ package org.whispersystems.textsecuregcm.tests.entities; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Test; import org.whispersystems.textsecuregcm.entities.ClientContact; import org.whispersystems.textsecuregcm.util.Util; -import static com.yammer.dropwizard.testing.JsonHelpers.*; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import static org.whispersystems.textsecuregcm.tests.util.JsonHelpers.asJson; +import static org.whispersystems.textsecuregcm.tests.util.JsonHelpers.fromJson; +import static org.whispersystems.textsecuregcm.tests.util.JsonHelpers.jsonFixture; public class ClientContactTest { @@ -41,4 +44,5 @@ public class ClientContactTest { is(contact)); } + } diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/entities/PreKeyTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/entities/PreKeyTest.java index 0c8a256ec..f4bd4bdd0 100644 --- a/src/test/java/org/whispersystems/textsecuregcm/tests/entities/PreKeyTest.java +++ b/src/test/java/org/whispersystems/textsecuregcm/tests/entities/PreKeyTest.java @@ -5,10 +5,10 @@ import org.whispersystems.textsecuregcm.entities.ClientContact; import org.whispersystems.textsecuregcm.entities.PreKey; import org.whispersystems.textsecuregcm.util.Util; -import static com.yammer.dropwizard.testing.JsonHelpers.*; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import static org.whispersystems.textsecuregcm.tests.util.JsonHelpers.*; public class PreKeyTest { diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/util/JsonHelpers.java b/src/test/java/org/whispersystems/textsecuregcm/tests/util/JsonHelpers.java new file mode 100644 index 000000000..fd3bded24 --- /dev/null +++ b/src/test/java/org/whispersystems/textsecuregcm/tests/util/JsonHelpers.java @@ -0,0 +1,27 @@ +package org.whispersystems.textsecuregcm.tests.util; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; + +import static io.dropwizard.testing.FixtureHelpers.fixture; + +public class JsonHelpers { + + private static final ObjectMapper objectMapper = new ObjectMapper(); + + public static String asJson(Object object) throws JsonProcessingException { + return objectMapper.writeValueAsString(object); + } + + public static T fromJson(String value, Class clazz) throws IOException { + return objectMapper.readValue(value, clazz); + } + + public static String jsonFixture(String filename) throws IOException { + return objectMapper.writeValueAsString(objectMapper.readValue(fixture(filename), JsonNode.class)); + } +} diff --git a/src/test/resources/fixtures/contact.relay.json b/src/test/resources/fixtures/contact.relay.json index 5661661c3..de8c7991f 100644 --- a/src/test/resources/fixtures/contact.relay.json +++ b/src/test/resources/fixtures/contact.relay.json @@ -1,4 +1,4 @@ { - "token" : "BQVVHxMt5zAFXA", - "relay" : "whisper" + "relay" : "whisper", + "token" : "BQVVHxMt5zAFXA" } \ No newline at end of file diff --git a/src/test/resources/fixtures/contact.relay.sms.json b/src/test/resources/fixtures/contact.relay.sms.json index 4ec2d626e..4afe626c4 100644 --- a/src/test/resources/fixtures/contact.relay.sms.json +++ b/src/test/resources/fixtures/contact.relay.sms.json @@ -1,5 +1,5 @@ { - "token" : "BQVVHxMt5zAFXA", "relay" : "whisper", - "supportsSms" : true + "supportsSms" : true, + "token" : "BQVVHxMt5zAFXA" } \ No newline at end of file