http://git-wip-us.apache.org/repos/asf/metron/blob/e22479e6/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/config/HBaseConfigTest.java ---------------------------------------------------------------------- diff --git a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/config/HBaseConfigTest.java b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/config/HBaseConfigTest.java new file mode 100644 index 0000000..835aa8d --- /dev/null +++ b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/config/HBaseConfigTest.java @@ -0,0 +1,69 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.metron.rest.config; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.HBaseConfiguration; +import org.apache.metron.hbase.HTableProvider; +import org.apache.metron.rest.service.GlobalConfigService; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import java.util.HashMap; + +import static org.apache.metron.hbase.client.UserSettingsClient.USER_SETTINGS_HBASE_CF; +import static org.apache.metron.hbase.client.UserSettingsClient.USER_SETTINGS_HBASE_TABLE; +import static org.mockito.Mockito.*; +import static org.powermock.api.mockito.PowerMockito.mockStatic; +import static org.powermock.api.mockito.PowerMockito.whenNew; + +@RunWith(PowerMockRunner.class) +@PrepareForTest({HTableProvider.class, HBaseConfiguration.class, HBaseConfig.class}) +public class HBaseConfigTest { + + private GlobalConfigService globalConfigService; + private HBaseConfig hBaseConfig; + + @Before + public void setUp() throws Exception { + globalConfigService = mock(GlobalConfigService.class); + hBaseConfig = new HBaseConfig(globalConfigService); + mockStatic(HBaseConfiguration.class); + } + + @Test + public void userSettingsTableShouldBeReturnedFromGlobalConfigByDefault() throws Exception { + when(globalConfigService.get()).thenReturn(new HashMap<String, Object>() {{ + put(USER_SETTINGS_HBASE_TABLE, "global_config_user_settings_table"); + put(USER_SETTINGS_HBASE_CF, "global_config_user_settings_cf"); + }}); + HTableProvider htableProvider = mock(HTableProvider.class); + whenNew(HTableProvider.class).withNoArguments().thenReturn(htableProvider); + Configuration configuration = mock(Configuration.class); + when(HBaseConfiguration.create()).thenReturn(configuration); + + hBaseConfig.userSettingsClient(); + verify(htableProvider).getTable(configuration, "global_config_user_settings_table"); + verifyZeroInteractions(htableProvider); + } + + +}
http://git-wip-us.apache.org/repos/asf/metron/blob/e22479e6/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/config/TestConfig.java ---------------------------------------------------------------------- diff --git a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/config/TestConfig.java b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/config/TestConfig.java index 1150189..008f3fc 100644 --- a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/config/TestConfig.java +++ b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/config/TestConfig.java @@ -17,14 +17,6 @@ */ package org.apache.metron.rest.config; -import static org.apache.metron.rest.MetronRestConstants.TEST_PROFILE; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; import kafka.admin.AdminUtils$; import kafka.utils.ZKStringSerializer$; import kafka.utils.ZkUtils; @@ -34,15 +26,18 @@ import org.apache.curator.RetryPolicy; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; import org.apache.curator.retry.ExponentialBackoffRetry; +import org.apache.hadoop.hbase.util.Bytes; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.metron.common.configuration.ConfigurationsUtils; import org.apache.metron.common.zookeeper.ConfigurationsCache; import org.apache.metron.common.zookeeper.ZKConfigurationsCache; +import org.apache.metron.hbase.client.UserSettingsClient; import org.apache.metron.hbase.mock.MockHBaseTableProvider; import org.apache.metron.integration.ComponentRunner; import org.apache.metron.integration.UnableToStartException; import org.apache.metron.integration.components.KafkaComponent; import org.apache.metron.integration.components.ZKServerComponent; +import org.apache.metron.rest.RestException; import org.apache.metron.rest.mock.MockStormCLIClientWrapper; import org.apache.metron.rest.mock.MockStormRestTemplate; import org.apache.metron.rest.service.impl.StormCLIWrapper; @@ -53,6 +48,16 @@ import org.springframework.kafka.core.ConsumerFactory; import org.springframework.kafka.core.DefaultKafkaConsumerFactory; import org.springframework.web.client.RestTemplate; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import static org.apache.metron.rest.MetronRestConstants.TEST_PROFILE; + @Configuration @Profile(TEST_PROFILE) public class TestConfig { @@ -175,4 +180,9 @@ public class TestConfig { return AdminUtils$.MODULE$; } + + @Bean() + public UserSettingsClient userSettingsClient() throws RestException, IOException { + return new UserSettingsClient(new MockHBaseTableProvider().addToCache("user_settings", "cf"), Bytes.toBytes("cf")); + } } http://git-wip-us.apache.org/repos/asf/metron/blob/e22479e6/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertControllerIntegrationTest.java ---------------------------------------------------------------------- diff --git a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertControllerIntegrationTest.java b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertControllerIntegrationTest.java deleted file mode 100644 index c3a4ac4..0000000 --- a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertControllerIntegrationTest.java +++ /dev/null @@ -1,345 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.metron.rest.controller; - -import static org.apache.metron.rest.MetronRestConstants.TEST_PROFILE; -import static org.hamcrest.Matchers.hasSize; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; -import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -import org.adrianwalker.multilinestring.Multiline; -import org.apache.metron.integration.ComponentRunner; -import org.apache.metron.integration.UnableToStartException; -import org.apache.metron.integration.components.KafkaComponent; -import org.apache.metron.rest.model.AlertProfile; -import org.apache.metron.rest.service.AlertService; -import org.apache.metron.rest.service.AlertsProfileService; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.MediaType; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.context.WebApplicationContext; - -@RunWith(SpringRunner.class) -@SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT) -@ActiveProfiles(TEST_PROFILE) -public class AlertControllerIntegrationTest { - - /** - [ - { - "is_alert": true, - "field": "value1" - }, - { - "is_alert": true, - "field": "value2" - } - ] - */ - @Multiline - public static String alerts; - - /** - * { - * "tableColumns": ["user1_field"], - * "savedSearches": [ - * { - * "name": "user1 search 1", - * "searchRequest": { - * "from": 0, - * "indices": ["bro"], - * "query": "*", - * "size": 5 - * } - * }, - * { - * "name": "user1 search 2", - * "searchRequest": { - * "from": 10, - * "indices": ["snort"], - * "query": "*", - * "size": 10 - * } - * } - * ] - * } - */ - @Multiline - public static String user1ProfileJson; - - - /** - * { - * "tableColumns": ["user2_field"], - * "savedSearches": [ - * { - * "name": "user2 search 1", - * "searchRequest": { - * "from": 0, - * "indices": ["bro", "snort"], - * "query": "ip_src_addr:192.168.1.1", - * "size": 100 - * } - * } - * ] - * } - */ - @Multiline - public static String user2ProfileJson; - - // A bug in Spring and/or Kafka forced us to move into a component that is spun up and down per test-case - // Given the large spinup time of components, please avoid this pattern until we upgrade Spring. - // See: https://issues.apache.org/jira/browse/METRON-1009 - @Autowired - private KafkaComponent kafkaWithZKComponent; - private ComponentRunner runner; - - @Autowired - private WebApplicationContext wac; - - @Autowired - private AlertService alertService; - - private MockMvc mockMvc; - - private String alertUrl = "/api/v1/alert"; - private String user1 = "user1"; - private String user2 = "user2"; - private String admin = "admin"; - private String password = "password"; - - @Before - public void setup() throws Exception { - for (AlertProfile alertsProfile : alertService.findAllProfiles()) { - alertService.deleteProfile(alertsProfile.getId()); - } - this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).apply(springSecurity()).build(); - } - - @Test - public void testSecurity() throws Exception { - this.mockMvc.perform(post(alertUrl + "/escalate").with(csrf()).contentType(MediaType.parseMediaType("application/json;charset=UTF-8")).content(alerts)) - .andExpect(status().isUnauthorized()); - this.mockMvc.perform(get(alertUrl + "/profile")) - .andExpect(status().isUnauthorized()); - this.mockMvc.perform(get(alertUrl + "/profile/all")) - .andExpect(status().isUnauthorized()); - this.mockMvc.perform(post(alertUrl + "/profile").with(csrf()) - .contentType(MediaType.parseMediaType("application/json;charset=UTF-8")) - .content(user1ProfileJson)) - .andExpect(status().isUnauthorized()); - this.mockMvc.perform(get(alertUrl + "/profile/all").with(httpBasic(user1, password)).with(csrf())) - .andExpect(status().isForbidden()); - this.mockMvc.perform(delete(alertUrl + "/profile/user1").with(httpBasic(user1, password)).with(csrf())) - .andExpect(status().isForbidden()); - } - - @Test - public void escalateShouldEscalateAlerts() throws Exception { - startKafka(); - this.mockMvc.perform(post(alertUrl + "/escalate").with(httpBasic(user1,password)).with(csrf()).contentType(MediaType.parseMediaType("application/json;charset=UTF-8")).content(alerts)) - .andExpect(status().isOk()); - stopKafka(); - } - - @Test - public void testAlertProfiles() throws Exception { - emptyProfileShouldReturnNotFound(); - alertsProfilesShouldBeCreatedOrUpdated(); - alertsProfilesShouldBeProperlyDeleted(); - } - - /** Ensures a 404 is returned when an alerts profile cannot be found. In the case of an admin getting - * all profiles, an empty list should be returned. This tests depends on the alertsProfileRepository - * being empty. - * - * @throws Exception - */ - private void emptyProfileShouldReturnNotFound() throws Exception { - - // user1 should get a 404 because an alerts profile has not been created - this.mockMvc.perform(get(alertUrl + "/profile").with(httpBasic(user1, password))) - .andExpect(status().isNotFound()); - - // user2 should get a 404 because an alerts profile has not been created - this.mockMvc.perform(get(alertUrl + "/profile").with(httpBasic(user2, password))) - .andExpect(status().isNotFound()); - - // getting all alerts profiles should return an empty list - this.mockMvc.perform(get(alertUrl + "/profile/all").with(httpBasic(admin, password))) - .andExpect(status().isOk()) - .andExpect( - content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) - .andExpect(jsonPath("$.*", hasSize(0))); - } - - /** Ensures users can update their profiles independently of other users. When user1 updates an - * alerts profile, alerts profile for user2 should not be affected. Tests that an initial update - * returns a 201 status and subsequent updates return 200 statuses. A call to get all alerts profiles - * by an admin user should also work properly. This tests depends on the alertsProfileRepository - * being empty initially. - * - * @throws Exception - */ - private void alertsProfilesShouldBeCreatedOrUpdated() throws Exception { - - // user1 creates their alerts profile - this.mockMvc.perform(post(alertUrl + "/profile").with(httpBasic(user1, password)).with(csrf()) - .contentType(MediaType.parseMediaType("application/json;charset=UTF-8")) - .content(user1ProfileJson)) - .andExpect(status().isCreated()) - .andExpect(content().json(user1ProfileJson)); - - // user1 updates their alerts profile - this.mockMvc.perform(post(alertUrl + "/profile").with(httpBasic(user1, password)).with(csrf()) - .contentType(MediaType.parseMediaType("application/json;charset=UTF-8")) - .content(user1ProfileJson)) - .andExpect(status().isOk()) - .andExpect(content().json(user1ProfileJson)); - - // user1 gets their alerts profile - this.mockMvc.perform(get(alertUrl + "/profile").with(httpBasic(user1, password))) - .andExpect(status().isOk()) - .andExpect( - content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) - .andExpect(content().json(user1ProfileJson)); - - // user2 alerts profile should still be empty - this.mockMvc.perform(get(alertUrl + "/profile").with(httpBasic(user2, password))) - .andExpect(status().isNotFound()); - - // getting all alerts profiles should only return user1's - this.mockMvc.perform(get(alertUrl + "/profile/all").with(httpBasic(admin, password))) - .andExpect(status().isOk()) - .andExpect( - content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) - .andExpect(content().json("[" + user1ProfileJson + "]")); - - // user2 creates their alerts profile - this.mockMvc.perform(post(alertUrl + "/profile").with(httpBasic(user2, password)).with(csrf()) - .contentType(MediaType.parseMediaType("application/json;charset=UTF-8")) - .content(user2ProfileJson)) - .andExpect(status().isCreated()) - .andExpect(content().json(user2ProfileJson)); - - // user2 updates their alerts profile - this.mockMvc.perform(get(alertUrl + "/profile").with(httpBasic(user1, password))) - .andExpect(status().isOk()) - .andExpect( - content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) - .andExpect(content().json(user1ProfileJson)); - - // user2 gets their alerts profile - this.mockMvc.perform(get(alertUrl + "/profile").with(httpBasic(user2, password))) - .andExpect(status().isOk()) - .andExpect( - content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) - .andExpect(content().json(user2ProfileJson)); - - // getting all alerts profiles should return both - this.mockMvc.perform(get(alertUrl + "/profile/all").with(httpBasic(admin, password))) - .andExpect(status().isOk()) - .andExpect( - content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) - .andExpect(content().json("[" + user1ProfileJson + "," + user2ProfileJson + "]")); - } - - /** Ensures users can delete their profiles independently of other users. When user1 deletes an - * alerts profile, alerts profile for user2 should not be deleted. This tests depends on alerts - * profiles existing for user1 and user2. - * - * @throws Exception - */ - private void alertsProfilesShouldBeProperlyDeleted() throws Exception { - - // user1 deletes their profile - this.mockMvc.perform(delete(alertUrl + "/profile/user1").with(httpBasic(admin, password))) - .andExpect(status().isOk()); - - // user1 should get a 404 when trying to delete an alerts profile that doesn't exist - this.mockMvc.perform(delete(alertUrl + "/profile/user1").with(httpBasic(admin, password))) - .andExpect(status().isNotFound()); - - // user1 should get a 404 when trying to retrieve their alerts profile - this.mockMvc.perform(get(alertUrl + "/profile").with(httpBasic(user1, password))) - .andExpect(status().isNotFound()); - - // user2's alerts profile should still exist - this.mockMvc.perform(get(alertUrl + "/profile").with(httpBasic(user2, password))) - .andExpect(status().isOk()) - .andExpect( - content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) - .andExpect(content().json(user2ProfileJson)); - - // getting all alerts profiles should only return user2's - this.mockMvc.perform(get(alertUrl + "/profile/all").with(httpBasic(admin, password))) - .andExpect(status().isOk()) - .andExpect( - content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) - .andExpect(content().json("[" + user2ProfileJson + "]")); - - // user2 deletes their profile - this.mockMvc.perform(delete(alertUrl + "/profile/user2").with(httpBasic(admin, password))) - .andExpect(status().isOk()); - - // user2 should get a 404 when trying to delete an alerts profile that doesn't exist - this.mockMvc.perform(get(alertUrl + "/profile").with(httpBasic(user1, password))) - .andExpect(status().isNotFound()); - - // user2 should get a 404 when trying to retrieve their alerts profile - this.mockMvc.perform(get(alertUrl + "/profile").with(httpBasic(user2, password))) - .andExpect(status().isNotFound()); - - // getting all alerts profiles should return an empty list - this.mockMvc.perform(get(alertUrl + "/profile/all").with(httpBasic(admin, password))) - .andExpect(status().isOk()) - .andExpect( - content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) - .andExpect(jsonPath("$.*", hasSize(0))); - } - - private void startKafka() { - runner = new ComponentRunner.Builder() - .withComponent("kafka", kafkaWithZKComponent) - .withCustomShutdownOrder(new String[]{"kafka"}) - .build(); - try { - runner.start(); - } catch (UnableToStartException e) { - e.printStackTrace(); - } - } - - private void stopKafka() { - runner.stop(); - } -} http://git-wip-us.apache.org/repos/asf/metron/blob/e22479e6/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertsUIControllerIntegrationTest.java ---------------------------------------------------------------------- diff --git a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertsUIControllerIntegrationTest.java b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertsUIControllerIntegrationTest.java new file mode 100644 index 0000000..49863d6 --- /dev/null +++ b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/AlertsUIControllerIntegrationTest.java @@ -0,0 +1,340 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.metron.rest.controller; + +import static org.apache.metron.rest.MetronRestConstants.TEST_PROFILE; +import static org.hamcrest.Matchers.hasSize; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.adrianwalker.multilinestring.Multiline; +import org.apache.metron.integration.ComponentRunner; +import org.apache.metron.integration.UnableToStartException; +import org.apache.metron.integration.components.KafkaComponent; +import org.apache.metron.rest.service.AlertsUIService; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@ActiveProfiles(TEST_PROFILE) +public class AlertsUIControllerIntegrationTest { + + /** + * [ + * { + * "is_alert": true, + * "field": "value1" + * }, + * { + * "is_alert": true, + * "field": "value2" + * } + * ] + */ + @Multiline + public static String alerts; + + /** + * { + * "tableColumns": ["user1_field"], + * "savedSearches": [ + * { + * "name": "user1 search 1", + * "searchRequest": { + * "from": 0, + * "indices": ["bro"], + * "query": "*", + * "size": 5 + * } + * }, + * { + * "name": "user1 search 2", + * "searchRequest": { + * "from": 10, + * "indices": ["snort"], + * "query": "*", + * "size": 10 + * } + * } + * ] + * } + */ + @Multiline + public static String user1AlertUserSettingsJson; + + + /** + * { + * "tableColumns": ["user2_field"], + * "savedSearches": [ + * { + * "name": "user2 search 1", + * "searchRequest": { + * "from": 0, + * "indices": ["bro", "snort"], + * "query": "ip_src_addr:192.168.1.1", + * "size": 100 + * } + * } + * ] + * } + */ + @Multiline + public static String user2AlertUserSettingsJson; + + // A bug in Spring and/or Kafka forced us to move into a component that is spun up and down per test-case + // Given the large spinup time of components, please avoid this pattern until we upgrade Spring. + // See: https://issues.apache.org/jira/browse/METRON-1009 + @Autowired + private KafkaComponent kafkaWithZKComponent; + private ComponentRunner runner; + + @Autowired + private WebApplicationContext wac; + + private MockMvc mockMvc; + + @Autowired + private AlertsUIService alertsUIService; + + private String alertUrl = "/api/v1/alerts/ui"; + private String user1 = "user1"; + private String user2 = "user2"; + private String admin = "admin"; + private String password = "password"; + + @Before + public void setup() throws Exception { + for (String user : alertsUIService.findAllAlertsUIUserSettings().keySet()) { + alertsUIService.deleteAlertsUIUserSettings(user); + } + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).apply(springSecurity()).build(); + } + + @Test + public void testSecurity() throws Exception { + this.mockMvc.perform(post(alertUrl + "/escalate").with(csrf()).contentType(MediaType.parseMediaType("application/json;charset=UTF-8")).content(alerts)) + .andExpect(status().isUnauthorized()); + this.mockMvc.perform(get(alertUrl + "/settings")) + .andExpect(status().isUnauthorized()); + this.mockMvc.perform(get(alertUrl + "/settings/all")) + .andExpect(status().isUnauthorized()); + this.mockMvc.perform(post(alertUrl + "/settings").with(csrf()) + .contentType(MediaType.parseMediaType("application/json;charset=UTF-8")) + .content(user1AlertUserSettingsJson)) + .andExpect(status().isUnauthorized()); + this.mockMvc.perform(get(alertUrl + "/settings/all").with(httpBasic(user1, password)).with(csrf())) + .andExpect(status().isForbidden()); + this.mockMvc.perform(delete(alertUrl + "/settings/user1").with(httpBasic(user1, password)).with(csrf())) + .andExpect(status().isForbidden()); + } + + @Test + public void escalateShouldEscalateAlerts() throws Exception { + startKafka(); + this.mockMvc.perform(post(alertUrl + "/escalate").with(httpBasic(user1, password)).with(csrf()).contentType(MediaType.parseMediaType("application/json;charset=UTF-8")).content(alerts)) + .andExpect(status().isOk()); + stopKafka(); + } + + @Test + public void testAlertProfiles() throws Exception { + emptyProfileShouldReturnNotFound(); + alertsProfilesShouldBeCreatedOrUpdated(); + alertsProfilesShouldBeProperlyDeleted(); + } + + /** Ensures a 404 is returned when an alerts profile cannot be found. In the case of an admin getting + * all profiles, an empty list should be returned. This tests depends on the alertsProfileRepository + * being empty. + * + * @throws Exception + */ + private void emptyProfileShouldReturnNotFound() throws Exception { + + // user1 should get a 404 because an alerts profile has not been created + this.mockMvc.perform(get(alertUrl + "/settings").with(httpBasic(user1, password))) + .andExpect(status().isNotFound()); + + // user2 should get a 404 because an alerts profile has not been created + this.mockMvc.perform(get(alertUrl + "/settings").with(httpBasic(user2, password))) + .andExpect(status().isNotFound()); + + // getting all alerts profiles should return an empty list + this.mockMvc.perform(get(alertUrl + "/settings/all").with(httpBasic(admin, password))) + .andExpect(status().isOk()) + .andExpect( + content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) + .andExpect(jsonPath("$.*", hasSize(0))); + } + + /** Ensures users can update their profiles independently of other users. When user1 updates an + * alerts profile, alerts profile for user2 should not be affected. Tests that an initial update + * returns a 201 status and subsequent updates return 200 statuses. A call to get all alerts profiles + * by an admin user should also work properly. This tests depends on the alertsProfileRepository + * being empty initially. + * + * @throws Exception + */ + private void alertsProfilesShouldBeCreatedOrUpdated() throws Exception { + + // user1 creates their alerts profile + this.mockMvc.perform(post(alertUrl + "/settings").with(httpBasic(user1, password)).with(csrf()) + .contentType(MediaType.parseMediaType("application/json;charset=UTF-8")) + .content(user1AlertUserSettingsJson)) + .andExpect(status().isCreated()); + + // user1 updates their alerts profile + this.mockMvc.perform(post(alertUrl + "/settings").with(httpBasic(user1, password)).with(csrf()) + .contentType(MediaType.parseMediaType("application/json;charset=UTF-8")) + .content(user1AlertUserSettingsJson)) + .andExpect(status().isOk()); + + // user1 gets their alerts profile + this.mockMvc.perform(get(alertUrl + "/settings").with(httpBasic(user1, password))) + .andExpect(status().isOk()) + .andExpect( + content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) + .andExpect(content().json(user1AlertUserSettingsJson)); + + // user2 alerts profile should still be empty + this.mockMvc.perform(get(alertUrl + "/settings").with(httpBasic(user2, password))) + .andExpect(status().isNotFound()); + + // getting all alerts profiles should only return user1's + this.mockMvc.perform(get(alertUrl + "/settings/all").with(httpBasic(admin, password))) + .andExpect(status().isOk()) + .andExpect( + content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) + .andExpect(content().json("{\"" + user1 + "\": " + user1AlertUserSettingsJson + "}")); + + // user2 creates their alerts profile + this.mockMvc.perform(post(alertUrl + "/settings").with(httpBasic(user2, password)).with(csrf()) + .contentType(MediaType.parseMediaType("application/json;charset=UTF-8")) + .content(user2AlertUserSettingsJson)) + .andExpect(status().isCreated()); + + // user2 updates their alerts profile + this.mockMvc.perform(get(alertUrl + "/settings").with(httpBasic(user1, password))) + .andExpect(status().isOk()) + .andExpect( + content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) + .andExpect(content().json(user1AlertUserSettingsJson)); + + // user2 gets their alerts profile + this.mockMvc.perform(get(alertUrl + "/settings").with(httpBasic(user2, password))) + .andExpect(status().isOk()) + .andExpect( + content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) + .andExpect(content().json(user2AlertUserSettingsJson)); + + // getting all alerts profiles should return both + this.mockMvc.perform(get(alertUrl + "/settings/all").with(httpBasic(admin, password))) + .andExpect(status().isOk()) + .andExpect( + content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) + .andExpect(content().json("{\"" + user1 + "\": " + user1AlertUserSettingsJson + ",\"" + user2 + "\": " + user2AlertUserSettingsJson + "}")); + } + + /** Ensures users can delete their profiles independently of other users. When user1 deletes an + * alerts profile, alerts profile for user2 should not be deleted. This tests depends on alerts + * profiles existing for user1 and user2. + * + * @throws Exception + */ + private void alertsProfilesShouldBeProperlyDeleted() throws Exception { + + // user1 deletes their profile + this.mockMvc.perform(delete(alertUrl + "/settings/user1").with(httpBasic(admin, password))) + .andExpect(status().isOk()); + + // user1 should get a 404 when trying to delete an alerts profile that doesn't exist + this.mockMvc.perform(delete(alertUrl + "/settings/user1").with(httpBasic(admin, password))) + .andExpect(status().isNotFound()); + + // user1 should get a 404 when trying to retrieve their alerts profile + this.mockMvc.perform(get(alertUrl + "/settings").with(httpBasic(user1, password))) + .andExpect(status().isNotFound()); + + // user2's alerts profile should still exist + this.mockMvc.perform(get(alertUrl + "/settings").with(httpBasic(user2, password))) + .andExpect(status().isOk()) + .andExpect( + content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) + .andExpect(content().json(user2AlertUserSettingsJson)); + + // getting all alerts profiles should only return user2's + this.mockMvc.perform(get(alertUrl + "/settings/all").with(httpBasic(admin, password))) + .andExpect(status().isOk()) + .andExpect( + content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) + .andExpect(content().json("{\"" + user2 + "\": " + user2AlertUserSettingsJson + "}")); + + // user2 deletes their profile + this.mockMvc.perform(delete(alertUrl + "/settings/user2").with(httpBasic(admin, password))) + .andExpect(status().isOk()); + + // user2 should get a 404 when trying to delete an alerts profile that doesn't exist + this.mockMvc.perform(get(alertUrl + "/settings").with(httpBasic(user1, password))) + .andExpect(status().isNotFound()); + + // user2 should get a 404 when trying to retrieve their alerts profile + this.mockMvc.perform(get(alertUrl + "/settings").with(httpBasic(user2, password))) + .andExpect(status().isNotFound()); + + // getting all alerts profiles should return an empty list + this.mockMvc.perform(get(alertUrl + "/settings/all").with(httpBasic(admin, password))) + .andExpect(status().isOk()) + .andExpect( + content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) + .andExpect(jsonPath("$.*", hasSize(0))); + } + + private void startKafka() { + runner = new ComponentRunner.Builder() + .withComponent("kafka", kafkaWithZKComponent) + .withCustomShutdownOrder(new String[]{"kafka"}) + .build(); + try { + runner.start(); + } catch (UnableToStartException e) { + e.printStackTrace(); + } + } + + private void stopKafka() { + runner.stop(); + } +} http://git-wip-us.apache.org/repos/asf/metron/blob/e22479e6/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/SearchControllerIntegrationTest.java ---------------------------------------------------------------------- diff --git a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/SearchControllerIntegrationTest.java b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/SearchControllerIntegrationTest.java index d8758cd..a55132c 100644 --- a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/SearchControllerIntegrationTest.java +++ b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/SearchControllerIntegrationTest.java @@ -29,12 +29,14 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.google.common.collect.ImmutableMap; + import java.util.HashMap; import java.util.Map; import org.adrianwalker.multilinestring.Multiline; import org.apache.metron.indexing.dao.InMemoryDao; import org.apache.metron.indexing.dao.SearchIntegrationTest; import org.apache.metron.indexing.dao.search.FieldType; +import org.apache.metron.rest.service.AlertsUIService; import org.apache.metron.rest.service.SensorIndexingConfigService; import org.json.simple.parser.ParseException; import org.junit.After; @@ -66,16 +68,28 @@ public class SearchControllerIntegrationTest extends DaoControllerTest { * "field": "timestamp", * "sortOrder": "desc" * } - * ] + * ], + * "facetFields": [] * } */ @Multiline public static String defaultQuery; + /** + * { + * "facetFields": ["ip_src_port"] + * } + */ + @Multiline + public static String alertProfile; + @Autowired private SensorIndexingConfigService sensorIndexingConfigService; @Autowired + private AlertsUIService alertsUIService; + + @Autowired private WebApplicationContext wac; private MockMvc mockMvc; @@ -93,6 +107,7 @@ public class SearchControllerIntegrationTest extends DaoControllerTest { ); loadTestData(testData); loadColumnTypes(); + loadFacetCounts(); } @After @@ -107,7 +122,7 @@ public class SearchControllerIntegrationTest extends DaoControllerTest { } @Test - public void testDefaultQuery() throws Exception { + public void testSearchWithDefaults() throws Exception { sensorIndexingConfigService.save("bro", new HashMap<String, Object>() {{ put("index", "bro"); }}); @@ -126,12 +141,40 @@ public class SearchControllerIntegrationTest extends DaoControllerTest { .andExpect(jsonPath("$.results[3].source.timestamp").value(2)) .andExpect(jsonPath("$.results[4].source.source:type").value("bro")) .andExpect(jsonPath("$.results[4].source.timestamp").value(1)) + .andExpect(jsonPath("$.facetCounts.*", hasSize(1))) + .andExpect(jsonPath("$.facetCounts.ip_src_addr.*", hasSize(2))) + .andExpect(jsonPath("$.facetCounts.ip_src_addr['192.168.1.1']").value(3)) + .andExpect(jsonPath("$.facetCounts.ip_src_addr['192.168.1.2']").value(1)) ); sensorIndexingConfigService.delete("bro"); } @Test + public void testSearchWithAlertProfileFacetFields() throws Exception { + assertEventually(() -> this.mockMvc.perform( + post("/api/v1/alerts/ui/settings").with(httpBasic(user, password)).with(csrf()) + .contentType(MediaType.parseMediaType("application/json;charset=UTF-8")) + .content(alertProfile)) + .andExpect(status().isOk()) + ); + + assertEventually(() -> this.mockMvc.perform( + post(searchUrl + "/search").with(httpBasic(user, password)).with(csrf()) + .contentType(MediaType.parseMediaType("application/json;charset=UTF-8")) + .content(defaultQuery)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.parseMediaType("application/json;charset=UTF-8"))) + .andExpect(jsonPath("$.facetCounts.*", hasSize(1))) + .andExpect(jsonPath("$.facetCounts.ip_src_port.*", hasSize(2))) + .andExpect(jsonPath("$.facetCounts.ip_src_port['8010']").value(1)) + .andExpect(jsonPath("$.facetCounts.ip_src_port['8009']").value(2)) + ); + + alertsUIService.deleteAlertsUIUserSettings(user); + } + + @Test public void testColumnMetadataUsingDefaultIndices() throws Exception { // Setup the default indices of bro and snort sensorIndexingConfigService.save("bro", new HashMap<String, Object>() {{ @@ -314,4 +357,18 @@ public class SearchControllerIntegrationTest extends DaoControllerTest { columnTypes.put("snort", snortTypes); InMemoryDao.setColumnMetadata(columnTypes); } + + private void loadFacetCounts() { + Map<String, Map<String, Long>> facetCounts = new HashMap<>(); + Map<String, Long> ipSrcAddrCounts = new HashMap<>(); + ipSrcAddrCounts.put("192.168.1.1", 3L); + ipSrcAddrCounts.put("192.168.1.2", 1L); + Map<String, Long> ipSrcPortCounts = new HashMap<>(); + ipSrcPortCounts.put("8010", 1L); + ipSrcPortCounts.put("8009", 2L); + facetCounts.put("ip_src_addr", ipSrcAddrCounts); + facetCounts.put("ip_src_port", ipSrcPortCounts); + InMemoryDao.setFacetCounts(facetCounts); + } + } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/metron/blob/e22479e6/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/UserControllerIntegrationTest.java ---------------------------------------------------------------------- diff --git a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/UserControllerIntegrationTest.java b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/UserControllerIntegrationTest.java index 9bdd439..b1e432e 100644 --- a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/UserControllerIntegrationTest.java +++ b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/controller/UserControllerIntegrationTest.java @@ -36,30 +36,34 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @RunWith(SpringRunner.class) -@SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @ActiveProfiles(TEST_PROFILE) public class UserControllerIntegrationTest { - private MockMvc mockMvc; + private MockMvc mockMvc; - @Autowired - private WebApplicationContext wac; + @Autowired + private WebApplicationContext wac; - private String user = "user"; - private String password = "password"; + private String userUrl = "/api/v1/user"; + private String user1 = "user1"; + private String password = "password"; - @Before - public void setup() throws Exception { - this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).apply(springSecurity()).build(); - } + @Before + public void setup() throws Exception { + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).apply(springSecurity()).build(); + } - @Test - public void test() throws Exception { - this.mockMvc.perform(get("/api/v1/user")) - .andExpect(status().isUnauthorized()); + @Test + public void testSecurity() throws Exception { + this.mockMvc.perform(get(userUrl)) + .andExpect(status().isUnauthorized()); + } - this.mockMvc.perform(get("/api/v1/user").with(httpBasic(user,password))) - .andExpect(status().isOk()) - .andExpect(content().string(user)); - } + @Test + public void testGetUser() throws Exception { + this.mockMvc.perform(get(userUrl).with(httpBasic(user1, password))) + .andExpect(status().isOk()) + .andExpect(content().string(user1)); + } } http://git-wip-us.apache.org/repos/asf/metron/blob/e22479e6/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/AlertServiceImplTest.java ---------------------------------------------------------------------- diff --git a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/AlertServiceImplTest.java b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/AlertServiceImplTest.java deleted file mode 100644 index 8bed6b3..0000000 --- a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/AlertServiceImplTest.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.metron.rest.service.impl; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; -import static org.mockito.Mockito.when; -import static org.powermock.api.mockito.PowerMockito.mock; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import org.apache.metron.rest.MetronRestConstants; -import org.apache.metron.rest.model.AlertProfile; -import org.apache.metron.rest.repository.AlertProfileRepository; -import org.apache.metron.rest.service.AlertService; -import org.apache.metron.rest.service.KafkaService; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mockito; -import org.springframework.core.env.Environment; -import org.springframework.dao.EmptyResultDataAccessException; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; - -@SuppressWarnings("unchecked") -public class AlertServiceImplTest { - - private KafkaService kafkaService; - private Environment environment; - private AlertProfileRepository alertProfileRepository; - private AlertService alertService; - private String testUser = "user1"; - - @SuppressWarnings("unchecked") - @Before - public void setUp() throws Exception { - kafkaService = mock(KafkaService.class); - environment = mock(Environment.class); - alertProfileRepository = Mockito.mock(AlertProfileRepository.class); - alertService = new AlertServiceImpl(kafkaService, environment, alertProfileRepository); - - Authentication authentication = Mockito.mock(Authentication.class); - UserDetails userDetails = Mockito.mock(UserDetails.class); - when(authentication.getPrincipal()).thenReturn(userDetails); - when(userDetails.getUsername()).thenReturn(testUser); - SecurityContextHolder.getContext().setAuthentication(authentication); - } - - @Test - public void produceMessageShouldProperlyProduceMessage() throws Exception { - String escalationTopic = "escalation"; - final Map<String, Object> message1 = new HashMap<>(); - message1.put("field", "value1"); - final Map<String, Object> message2 = new HashMap<>(); - message2.put("field", "value2"); - List<Map<String, Object>> messages = Arrays.asList(message1, message2); - when(environment.getProperty(MetronRestConstants.KAFKA_TOPICS_ESCALATION_PROPERTY)).thenReturn(escalationTopic); - - alertService.escalateAlerts(messages); - - String expectedMessage1 = "{\"field\":\"value1\"}"; - String expectedMessage2 = "{\"field\":\"value2\"}"; - verify(kafkaService).produceMessage("escalation", expectedMessage1); - verify(kafkaService).produceMessage("escalation", expectedMessage2); - verifyZeroInteractions(kafkaService); - } - - @Test - public void getShouldProperlyReturnActiveProfile() throws Exception { - AlertProfile alertsProfile = new AlertProfile(); - alertsProfile.setId(testUser); - when(alertProfileRepository.findOne(testUser)).thenReturn(alertsProfile); - - AlertProfile expectedAlertsProfile = new AlertProfile(); - expectedAlertsProfile.setId(testUser); - assertEquals(expectedAlertsProfile, alertService.getProfile()); - verify(alertProfileRepository, times(1)).findOne(testUser); - verifyNoMoreInteractions(alertProfileRepository); - } - - @Test - public void findAllShouldProperlyReturnActiveProfiles() throws Exception { - AlertProfile alertsProfile1 = new AlertProfile(); - alertsProfile1.setId(testUser); - AlertProfile alertsProfile2 = new AlertProfile(); - alertsProfile2.setId(testUser); - when(alertProfileRepository.findAll()) - .thenReturn(Arrays.asList(alertsProfile1, alertsProfile2)); - - AlertProfile expectedAlertsProfile1 = new AlertProfile(); - expectedAlertsProfile1.setId(testUser); - AlertProfile expectedAlertsProfile2 = new AlertProfile(); - expectedAlertsProfile2.setId(testUser); - Iterator<AlertProfile> actualAlertsProfiles = alertService.findAllProfiles().iterator(); - assertEquals(expectedAlertsProfile1, actualAlertsProfiles.next()); - assertEquals(expectedAlertsProfile2, actualAlertsProfiles.next()); - assertFalse(actualAlertsProfiles.hasNext()); - verify(alertProfileRepository, times(1)).findAll(); - verifyNoMoreInteractions(alertProfileRepository); - } - - @Test - public void saveShouldProperlySaveActiveProfile() throws Exception { - AlertProfile savedAlertsProfile = new AlertProfile(); - savedAlertsProfile.setId(testUser); - when(alertProfileRepository.save(savedAlertsProfile)).thenReturn(savedAlertsProfile); - - AlertProfile expectedAlertsProfile = new AlertProfile(); - expectedAlertsProfile.setId(testUser); - AlertProfile alertsProfile = new AlertProfile(); - assertEquals(expectedAlertsProfile, alertService.saveProfile(alertsProfile)); - - verify(alertProfileRepository, times(1)).save(savedAlertsProfile); - verifyNoMoreInteractions(alertProfileRepository); - } - - @Test - public void deleteShouldProperlyDeleteActiveProfile() throws Exception { - assertTrue(alertService.deleteProfile(testUser)); - - doThrow(new EmptyResultDataAccessException(1)).when(alertProfileRepository).delete(testUser); - assertFalse(alertService.deleteProfile(testUser)); - - verify(alertProfileRepository, times(2)).delete(testUser); - verifyNoMoreInteractions(alertProfileRepository); - } -} http://git-wip-us.apache.org/repos/asf/metron/blob/e22479e6/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/AlertsUIServiceImplTest.java ---------------------------------------------------------------------- diff --git a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/AlertsUIServiceImplTest.java b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/AlertsUIServiceImplTest.java new file mode 100644 index 0000000..dc52712 --- /dev/null +++ b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/AlertsUIServiceImplTest.java @@ -0,0 +1,180 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.metron.rest.service.impl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; +import static org.powermock.api.mockito.PowerMockito.mock; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.adrianwalker.multilinestring.Multiline; +import org.apache.metron.rest.MetronRestConstants; +import org.apache.metron.rest.model.AlertsUIUserSettings; +import org.apache.metron.hbase.client.UserSettingsClient; +import org.apache.metron.rest.service.AlertsUIService; +import org.apache.metron.rest.service.KafkaService; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.core.env.Environment; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; + +@SuppressWarnings("unchecked") +public class AlertsUIServiceImplTest { + + public static ThreadLocal<ObjectMapper> _mapper = ThreadLocal.withInitial(() -> + new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL)); + + /** + * { + * "tableColumns": ["user1_field"] + * } + */ + @Multiline + public static String user1AlertUserSettings; + + /** + * { + * "tableColumns": ["user2_field"] + * } + */ + @Multiline + public static String user2AlertUserSettings; + + private KafkaService kafkaService; + private Environment environment; + private UserSettingsClient userSettingsClient; + private AlertsUIService alertsUIService; + private String user1 = "user1"; + private String user2 = "user2"; + + @SuppressWarnings("unchecked") + @Before + public void setUp() throws Exception { + kafkaService = mock(KafkaService.class); + environment = mock(Environment.class); + userSettingsClient = mock(UserSettingsClient.class); + alertsUIService = new AlertsUIServiceImpl(kafkaService, environment, userSettingsClient); + + // assume user1 is logged in for tests + Authentication authentication = Mockito.mock(Authentication.class); + UserDetails userDetails = Mockito.mock(UserDetails.class); + when(authentication.getPrincipal()).thenReturn(userDetails); + when(userDetails.getUsername()).thenReturn(user1); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + + @Test + public void produceMessageShouldProperlyProduceMessage() throws Exception { + String escalationTopic = "escalation"; + final Map<String, Object> message1 = new HashMap<>(); + message1.put("field", "value1"); + final Map<String, Object> message2 = new HashMap<>(); + message2.put("field", "value2"); + List<Map<String, Object>> messages = Arrays.asList(message1, message2); + when(environment.getProperty(MetronRestConstants.KAFKA_TOPICS_ESCALATION_PROPERTY)).thenReturn(escalationTopic); + + alertsUIService.escalateAlerts(messages); + + String expectedMessage1 = "{\"field\":\"value1\"}"; + String expectedMessage2 = "{\"field\":\"value2\"}"; + verify(kafkaService).produceMessage("escalation", expectedMessage1); + verify(kafkaService).produceMessage("escalation", expectedMessage2); + verifyZeroInteractions(kafkaService); + } + + @Test + public void getShouldProperlyReturnActiveProfile() throws Exception { + when(userSettingsClient.findOne(user1, AlertsUIServiceImpl.ALERT_USER_SETTING_TYPE)).thenReturn(Optional.of(user1AlertUserSettings)); + + AlertsUIUserSettings expectedAlertsUIUserSettings = new AlertsUIUserSettings(); + expectedAlertsUIUserSettings.setTableColumns(Collections.singletonList("user1_field")); + assertEquals(expectedAlertsUIUserSettings, alertsUIService.getAlertsUIUserSettings().get()); + verify(userSettingsClient, times(1)).findOne(user1, AlertsUIServiceImpl.ALERT_USER_SETTING_TYPE); + verifyNoMoreInteractions(userSettingsClient); + } + + @Test + public void findAllShouldProperlyReturnActiveProfiles() throws Exception { + AlertsUIUserSettings alertsProfile1 = new AlertsUIUserSettings(); + alertsProfile1.setUser(user1); + AlertsUIUserSettings alertsProfile2 = new AlertsUIUserSettings(); + alertsProfile2.setUser(user1); + when(userSettingsClient.findAll(AlertsUIServiceImpl.ALERT_USER_SETTING_TYPE)) + .thenReturn(new HashMap<String, Optional<String>>() {{ + put(user1, Optional.of(user1AlertUserSettings)); + put(user2, Optional.of(user2AlertUserSettings)); + }}); + + AlertsUIUserSettings expectedAlertsUIUserSettings1 = new AlertsUIUserSettings(); + expectedAlertsUIUserSettings1.setTableColumns(Collections.singletonList("user1_field")); + AlertsUIUserSettings expectedAlertsUIUserSettings2 = new AlertsUIUserSettings(); + expectedAlertsUIUserSettings2.setTableColumns(Collections.singletonList("user2_field")); + Map<String, AlertsUIUserSettings> actualAlertsProfiles = alertsUIService.findAllAlertsUIUserSettings(); + assertEquals(2, actualAlertsProfiles.size()); + assertEquals(expectedAlertsUIUserSettings1, actualAlertsProfiles.get(user1)); + assertEquals(expectedAlertsUIUserSettings2, actualAlertsProfiles.get(user2)); + + verify(userSettingsClient, times(1)).findAll(AlertsUIServiceImpl.ALERT_USER_SETTING_TYPE); + verifyNoMoreInteractions(userSettingsClient); + } + + @Test + public void saveShouldProperlySaveActiveProfile() throws Exception { + AlertsUIUserSettings alertsUIUserSettings = new AlertsUIUserSettings(); + alertsUIUserSettings.setTableColumns(Collections.singletonList("user1_field")); + + alertsUIService.saveAlertsUIUserSettings(alertsUIUserSettings); + + String expectedAlertUserSettings = _mapper.get().writeValueAsString(alertsUIUserSettings); + verify(userSettingsClient, times(1)) + .save(user1, AlertsUIServiceImpl.ALERT_USER_SETTING_TYPE, expectedAlertUserSettings); + verifyNoMoreInteractions(userSettingsClient); + } + + @Test + public void deleteShouldProperlyDeleteActiveProfile() throws Exception { + assertTrue(alertsUIService.deleteAlertsUIUserSettings(user1)); + + doThrow(new IOException()).when(userSettingsClient).delete(user1, AlertsUIServiceImpl.ALERT_USER_SETTING_TYPE); + assertFalse(alertsUIService.deleteAlertsUIUserSettings(user1)); + + verify(userSettingsClient, times(2)).delete(user1, AlertsUIServiceImpl.ALERT_USER_SETTING_TYPE); + verifyNoMoreInteractions(userSettingsClient); + } +} http://git-wip-us.apache.org/repos/asf/metron/blob/e22479e6/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/SearchServiceImplTest.java ---------------------------------------------------------------------- diff --git a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/SearchServiceImplTest.java b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/SearchServiceImplTest.java index 1d8f182..82ca0e9 100644 --- a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/SearchServiceImplTest.java +++ b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/service/impl/SearchServiceImplTest.java @@ -18,6 +18,7 @@ package org.apache.metron.rest.service.impl; import static org.apache.metron.rest.MetronRestConstants.INDEX_WRITER_NAME; +import static org.apache.metron.rest.MetronRestConstants.SEARCH_FACET_FIELDS_SPRING_PROPERTY; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; @@ -27,10 +28,14 @@ import static org.mockito.Mockito.when; import java.util.ArrayList; import java.util.Arrays; +import java.util.Optional; + import org.apache.metron.indexing.dao.IndexDao; import org.apache.metron.indexing.dao.search.InvalidSearchException; import org.apache.metron.indexing.dao.search.SearchRequest; import org.apache.metron.rest.RestException; +import org.apache.metron.rest.model.AlertsUIUserSettings; +import org.apache.metron.rest.service.AlertsUIService; import org.apache.metron.rest.service.SearchService; import org.apache.metron.rest.service.SensorIndexingConfigService; import org.junit.Before; @@ -47,6 +52,7 @@ public class SearchServiceImplTest { IndexDao dao; Environment environment; SensorIndexingConfigService sensorIndexingConfigService; + AlertsUIService alertsUIService; SearchService searchService; @Before @@ -54,7 +60,8 @@ public class SearchServiceImplTest { dao = mock(IndexDao.class); environment = mock(Environment.class); sensorIndexingConfigService = mock(SensorIndexingConfigService.class); - searchService = new SearchServiceImpl(dao, environment, sensorIndexingConfigService); + alertsUIService = mock(AlertsUIService.class); + searchService = new SearchServiceImpl(dao, environment, sensorIndexingConfigService, alertsUIService); } @@ -74,13 +81,63 @@ public class SearchServiceImplTest { } @Test + public void searchShouldProperlySearchWithEmptyDefaultFacetFields() throws Exception { + when(environment.getProperty(SEARCH_FACET_FIELDS_SPRING_PROPERTY, String.class, "")) + .thenReturn(""); + + SearchRequest searchRequest = new SearchRequest(); + searchRequest.setIndices(Arrays.asList("bro", "snort", "metaalert")); + searchService.search(searchRequest); + + SearchRequest expectedSearchRequest = new SearchRequest(); + expectedSearchRequest.setIndices(Arrays.asList("bro", "snort", "metaalert")); + verify(dao).search(eq(expectedSearchRequest)); + } + + @Test + public void searchShouldProperlySearchDefaultFacetFields() throws Exception { + when(environment.getProperty(SEARCH_FACET_FIELDS_SPRING_PROPERTY, String.class, "")) + .thenReturn("source:type,ip_src_addr"); + when(alertsUIService.getAlertsUIUserSettings()).thenReturn(Optional.empty()); + + SearchRequest searchRequest = new SearchRequest(); + searchRequest.setIndices(Arrays.asList("bro", "snort", "metaalert")); + searchRequest.setFacetFields(new ArrayList<>()); + searchService.search(searchRequest); + + SearchRequest expectedSearchRequest = new SearchRequest(); + expectedSearchRequest.setIndices(Arrays.asList("bro", "snort", "metaalert")); + expectedSearchRequest.setFacetFields(Arrays.asList("source:type", "ip_src_addr")); + verify(dao).search(eq(expectedSearchRequest)); + } + + @Test + public void searchShouldProperlySearchWithUserSettingsFacetFields() throws Exception { + AlertsUIUserSettings alertsUIUserSettings = new AlertsUIUserSettings(); + alertsUIUserSettings.setFacetFields(Arrays.asList("source:type", "ip_dst_addr")); + when(alertsUIService.getAlertsUIUserSettings()).thenReturn(Optional.of(alertsUIUserSettings)); + + SearchRequest searchRequest = new SearchRequest(); + searchRequest.setIndices(Arrays.asList("bro", "snort", "metaalert")); + searchRequest.setFacetFields(new ArrayList<>()); + searchService.search(searchRequest); + + SearchRequest expectedSearchRequest = new SearchRequest(); + expectedSearchRequest.setIndices(Arrays.asList("bro", "snort", "metaalert")); + expectedSearchRequest.setFacetFields(Arrays.asList("source:type", "ip_dst_addr")); + verify(dao).search(eq(expectedSearchRequest)); + } + + @Test public void searchShouldProperlySearch() throws Exception { SearchRequest searchRequest = new SearchRequest(); searchRequest.setIndices(Arrays.asList("bro")); + searchRequest.setFacetFields(Arrays.asList("ip_src_addr")); searchService.search(searchRequest); SearchRequest expectedSearchRequest = new SearchRequest(); expectedSearchRequest.setIndices(Arrays.asList("bro")); + expectedSearchRequest.setFacetFields(Arrays.asList("ip_src_addr")); verify(dao).search(eq(expectedSearchRequest)); verifyNoMoreInteractions(dao); @@ -94,6 +151,7 @@ public class SearchServiceImplTest { SearchRequest searchRequest = new SearchRequest(); searchRequest.setIndices(Arrays.asList("bro")); + searchRequest.setFacetFields(Arrays.asList("ip_src_addr")); searchService.search(searchRequest); } http://git-wip-us.apache.org/repos/asf/metron/blob/e22479e6/metron-platform/metron-elasticsearch/src/main/java/org/apache/metron/elasticsearch/dao/ElasticsearchDao.java ---------------------------------------------------------------------- diff --git a/metron-platform/metron-elasticsearch/src/main/java/org/apache/metron/elasticsearch/dao/ElasticsearchDao.java b/metron-platform/metron-elasticsearch/src/main/java/org/apache/metron/elasticsearch/dao/ElasticsearchDao.java index 650462e..9bb109d 100644 --- a/metron-platform/metron-elasticsearch/src/main/java/org/apache/metron/elasticsearch/dao/ElasticsearchDao.java +++ b/metron-platform/metron-elasticsearch/src/main/java/org/apache/metron/elasticsearch/dao/ElasticsearchDao.java @@ -37,7 +37,6 @@ import org.apache.metron.indexing.dao.search.SearchResult; import org.apache.metron.indexing.dao.search.SortField; import org.apache.metron.indexing.dao.search.SortOrder; import org.apache.metron.indexing.dao.update.Document; -import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest; import org.elasticsearch.action.bulk.BulkRequestBuilder; import org.elasticsearch.action.bulk.BulkResponse; import org.elasticsearch.action.index.IndexRequest; @@ -45,8 +44,6 @@ import org.elasticsearch.action.index.IndexResponse; import org.elasticsearch.action.search.SearchRequestBuilder; import org.elasticsearch.action.support.replication.ReplicationResponse.ShardInfo; import org.elasticsearch.client.transport.TransportClient; -import org.elasticsearch.cluster.metadata.MappingMetaData; -import org.elasticsearch.common.collect.ImmutableOpenMap; import org.elasticsearch.index.mapper.LegacyIpFieldMapper; import org.elasticsearch.index.query.IdsQueryBuilder; import org.elasticsearch.index.query.QueryBuilder; @@ -194,7 +191,7 @@ public class ElasticsearchDao implements IndexDao { .from(searchRequest.getFrom()) .query(queryBuilder) .trackScores(true); - Optional<List<String>> fields = searchRequest.getFields(); + List<String> fields = searchRequest.getFields(); // column metadata needed to understand the type of each sort field Map<String, FieldType> meta; try { @@ -227,18 +224,18 @@ public class ElasticsearchDao implements IndexDao { } // handle search fields - if (fields.isPresent()) { + if (fields != null) { searchBuilder.fetchSource("*", null); } else { searchBuilder.fetchSource(true); } - Optional<List<String>> facetFields = searchRequest.getFacetFields(); + List<String> facetFields = searchRequest.getFacetFields(); // handle facet fields - if (searchRequest.getFacetFields().isPresent()) { + if (facetFields != null) { // https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/_bucket_aggregations.html - for(String field : searchRequest.getFacetFields().get()) { + for(String field : facetFields) { String name = getFacetAggregationName(field); TermsAggregationBuilder terms = AggregationBuilders.terms( name).field(field); // new TermsBuilder(name).field(field); @@ -282,8 +279,8 @@ public class ElasticsearchDao implements IndexDao { searchResponse.setResults(results); // handle facet fields - if (searchRequest.getFacetFields().isPresent()) { - List<String> facetFields = searchRequest.getFacetFields().get(); + if (searchRequest.getFacetFields() != null) { + List<String> facetFields = searchRequest.getFacetFields(); Map<String, FieldType> commonColumnMetadata; try { commonColumnMetadata = getColumnMetadata(searchRequest.getIndices()); @@ -674,14 +671,14 @@ public class ElasticsearchDao implements IndexDao { return searchResultGroups; } - private SearchResult getSearchResult(SearchHit searchHit, Optional<List<String>> fields) { + private SearchResult getSearchResult(SearchHit searchHit, List<String> fields) { SearchResult searchResult = new SearchResult(); searchResult.setId(searchHit.getId()); Map<String, Object> source; - if (fields.isPresent()) { + if (fields != null) { Map<String, Object> resultSourceAsMap = searchHit.getSourceAsMap(); source = new HashMap<>(); - fields.get().forEach(field -> { + fields.forEach(field -> { source.put(field, resultSourceAsMap.get(field)); }); } else { http://git-wip-us.apache.org/repos/asf/metron/blob/e22479e6/metron-platform/metron-hbase/src/main/java/org/apache/metron/hbase/client/UserSettingsClient.java ---------------------------------------------------------------------- diff --git a/metron-platform/metron-hbase/src/main/java/org/apache/metron/hbase/client/UserSettingsClient.java b/metron-platform/metron-hbase/src/main/java/org/apache/metron/hbase/client/UserSettingsClient.java new file mode 100644 index 0000000..f492497 --- /dev/null +++ b/metron-platform/metron-hbase/src/main/java/org/apache/metron/hbase/client/UserSettingsClient.java @@ -0,0 +1,175 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.metron.hbase.client; + +import org.apache.hadoop.hbase.HBaseConfiguration; +import org.apache.hadoop.hbase.client.Delete; +import org.apache.hadoop.hbase.client.Get; +import org.apache.hadoop.hbase.client.HTableInterface; +import org.apache.hadoop.hbase.client.Put; +import org.apache.hadoop.hbase.client.Result; +import org.apache.hadoop.hbase.client.ResultScanner; +import org.apache.hadoop.hbase.client.Scan; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.metron.hbase.TableProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.lang.invoke.MethodHandles; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.NavigableMap; +import java.util.Optional; +import java.util.function.Supplier; + +public class UserSettingsClient { + + private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + public static String USER_SETTINGS_HBASE_TABLE = "user.settings.hbase.table"; + public static String USER_SETTINGS_HBASE_CF = "user.settings.hbase.cf"; + + private HTableInterface userSettingsTable; + private byte[] cf; + private Supplier<Map<String, Object>> globalConfigSupplier; + private TableProvider tableProvider; + + public UserSettingsClient() { + } + + public UserSettingsClient(Supplier<Map<String, Object>> globalConfigSupplier, TableProvider tableProvider) { + this.globalConfigSupplier = globalConfigSupplier; + this.tableProvider = tableProvider; + } + + public UserSettingsClient(HTableInterface userSettingsTable, byte[] cf) { + this.userSettingsTable = userSettingsTable; + this.cf = cf; + } + + public synchronized void init(Supplier<Map<String, Object>> globalConfigSupplier, TableProvider tableProvider) { + if (this.userSettingsTable == null) { + Map<String, Object> globalConfig = globalConfigSupplier.get(); + if(globalConfig == null) { + throw new IllegalStateException("Cannot find the global config."); + } + String table = (String)globalConfig.get(USER_SETTINGS_HBASE_TABLE); + String cf = (String) globalConfigSupplier.get().get(USER_SETTINGS_HBASE_CF); + if(table == null || cf == null) { + throw new IllegalStateException("You must configure " + USER_SETTINGS_HBASE_TABLE + " and " + USER_SETTINGS_HBASE_CF + " in the global config."); + } + try { + userSettingsTable = tableProvider.getTable(HBaseConfiguration.create(), table); + this.cf = cf.getBytes(); + } catch (IOException e) { + throw new IllegalStateException("Unable to initialize HBaseDao: " + e.getMessage(), e); + } + + } + } + + public HTableInterface getTableInterface() { + if(userSettingsTable == null) { + init(globalConfigSupplier, tableProvider); + } + return userSettingsTable; + } + + public Map<String, String> findOne(String user) throws IOException { + Result result = getResult(user); + return getAllUserSettings(result); + } + + public Optional<String> findOne(String user, String type) throws IOException { + Result result = getResult(user); + return getUserSettings(result, type); + } + + public Map<String, Map<String, String>> findAll() throws IOException { + Scan scan = new Scan(); + ResultScanner results = getTableInterface().getScanner(scan); + Map<String, Map<String, String>> allUserSettings = new HashMap<>(); + for (Result result : results) { + allUserSettings.put(new String(result.getRow()), getAllUserSettings(result)); + } + return allUserSettings; + } + + public Map<String, Optional<String>> findAll(String type) throws IOException { + Scan scan = new Scan(); + ResultScanner results = getTableInterface().getScanner(scan); + Map<String, Optional<String>> allUserSettings = new HashMap<>(); + for (Result result : results) { + allUserSettings.put(new String(result.getRow()), getUserSettings(result, type)); + } + return allUserSettings; + } + + public void save(String user, String type, String userSettings) throws IOException { + byte[] rowKey = Bytes.toBytes(user); + Put put = new Put(rowKey); + put.addColumn(cf, Bytes.toBytes(type), Bytes.toBytes(userSettings)); + getTableInterface().put(put); + } + + public void delete(String user) throws IOException { + Delete delete = new Delete(Bytes.toBytes(user)); + getTableInterface().delete(delete); + } + + public void delete(String user, String type) throws IOException { + Delete delete = new Delete(Bytes.toBytes(user)); + delete.addColumn(cf, Bytes.toBytes(type)); + getTableInterface().delete(delete); + } + + private Result getResult(String user) throws IOException { + byte[] rowKey = Bytes.toBytes(user); + Get get = new Get(rowKey); + get.addFamily(cf); + return getTableInterface().get(get); + } + + private Optional<String> getUserSettings(Result result, String type) throws IOException { + Optional<String> userSettings = Optional.empty(); + if (result != null) { + byte[] value = result.getValue(cf, Bytes.toBytes(type)); + if (value != null) { + userSettings = Optional.of(new String(value, StandardCharsets.UTF_8)); + } + } + return userSettings; + } + + public Map<String, String> getAllUserSettings(Result result) { + if (result == null) { + return new HashMap<>(); + } + NavigableMap<byte[], byte[]> columns = result.getFamilyMap(cf); + if(columns == null || columns.size() == 0) { + return new HashMap<>(); + } + Map<String, String> userSettingsMap = new HashMap<>(); + for(Map.Entry<byte[], byte[]> column: columns.entrySet()) { + userSettingsMap.put(new String(column.getKey(), StandardCharsets.UTF_8), new String(column.getValue(), StandardCharsets.UTF_8)); + } + return userSettingsMap; + } +} http://git-wip-us.apache.org/repos/asf/metron/blob/e22479e6/metron-platform/metron-hbase/src/test/java/org/apache/metron/hbase/client/UserSettingsClientTest.java ---------------------------------------------------------------------- diff --git a/metron-platform/metron-hbase/src/test/java/org/apache/metron/hbase/client/UserSettingsClientTest.java b/metron-platform/metron-hbase/src/test/java/org/apache/metron/hbase/client/UserSettingsClientTest.java new file mode 100644 index 0000000..55a28ad --- /dev/null +++ b/metron-platform/metron-hbase/src/test/java/org/apache/metron/hbase/client/UserSettingsClientTest.java @@ -0,0 +1,101 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.metron.hbase.client; + + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.hadoop.hbase.client.Get; +import org.apache.hadoop.hbase.client.HTableInterface; +import org.apache.hadoop.hbase.client.Result; +import org.apache.hadoop.hbase.client.ResultScanner; +import org.apache.hadoop.hbase.client.Scan; +import org.apache.hadoop.hbase.util.Bytes; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.function.Supplier; + +import static org.apache.metron.hbase.client.UserSettingsClient.USER_SETTINGS_HBASE_CF; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class UserSettingsClientTest { + + @Rule + public final ExpectedException exception = ExpectedException.none(); + + private static ThreadLocal<ObjectMapper> _mapper = ThreadLocal.withInitial(() -> + new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL)); + + private HTableInterface userSettingsTable; + private Supplier<Map<String, Object>> globalConfigSupplier; + private UserSettingsClient userSettingsClient; + private static byte[] cf = Bytes.toBytes("cf"); + + @Before + public void setUp() throws Exception { + userSettingsTable = mock(HTableInterface.class); + globalConfigSupplier = () -> new HashMap<String, Object>() {{ + put(USER_SETTINGS_HBASE_CF, "cf"); + }}; + } + + @Test + public void shouldFindOne() throws Exception { + Result result = mock(Result.class); + when(result.getValue(cf, Bytes.toBytes("type"))).thenReturn("userSettings1String".getBytes()); + Get get = new Get("user1".getBytes()); + get.addFamily(cf); + when(userSettingsTable.get(get)).thenReturn(result); + + UserSettingsClient userSettingsClient = new UserSettingsClient(userSettingsTable, cf); + Assert.assertEquals("userSettings1String", userSettingsClient.findOne("user1", "type").get()); + assertFalse(userSettingsClient.findOne("missingUser", "type").isPresent()); + } + + @Test + public void shouldFindAll() throws Exception { + ResultScanner resultScanner = mock(ResultScanner.class); + Result result1 = mock(Result.class); + Result result2 = mock(Result.class); + when(result1.getRow()).thenReturn("user1".getBytes()); + when(result2.getRow()).thenReturn("user2".getBytes()); + when(result1.getValue(cf, Bytes.toBytes("type"))).thenReturn("userSettings1String".getBytes()); + when(result2.getValue(cf, Bytes.toBytes("type"))).thenReturn("userSettings2String".getBytes()); + when(resultScanner.iterator()).thenReturn(Arrays.asList(result1, result2).iterator()); + when(userSettingsTable.getScanner(any(Scan.class))).thenReturn(resultScanner); + + UserSettingsClient userSettingsClient = new UserSettingsClient(userSettingsTable, cf); + assertEquals(new HashMap<String, Optional<String>>() {{ + put("user1", Optional.of("userSettings1String")); + put("user2", Optional.of("userSettings2String")); + }}, userSettingsClient.findAll("type")); + } + +} http://git-wip-us.apache.org/repos/asf/metron/blob/e22479e6/metron-platform/metron-hbase/src/test/java/org/apache/metron/hbase/mock/MockHTable.java ---------------------------------------------------------------------- diff --git a/metron-platform/metron-hbase/src/test/java/org/apache/metron/hbase/mock/MockHTable.java b/metron-platform/metron-hbase/src/test/java/org/apache/metron/hbase/mock/MockHTable.java index e75b533..484d0e8 100644 --- a/metron-platform/metron-hbase/src/test/java/org/apache/metron/hbase/mock/MockHTable.java +++ b/metron-platform/metron-hbase/src/test/java/org/apache/metron/hbase/mock/MockHTable.java @@ -534,7 +534,12 @@ public class MockHTable implements HTableInterface { @Override public void delete(Delete delete) throws IOException { - throw new UnsupportedOperationException(); + byte[] row = delete.getRow(); + if (data.containsKey(row)) { + data.remove(row); + } else { + throw new IOException(); + } } @Override http://git-wip-us.apache.org/repos/asf/metron/blob/e22479e6/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/search/SearchRequest.java ---------------------------------------------------------------------- diff --git a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/search/SearchRequest.java b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/search/SearchRequest.java index f4b6196..26c566c 100644 --- a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/search/SearchRequest.java +++ b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/search/SearchRequest.java @@ -19,8 +19,8 @@ package org.apache.metron.indexing.dao.search; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; -import java.util.Optional; public class SearchRequest { @@ -38,7 +38,6 @@ public class SearchRequest { defaultSortField.setSortOrder(SortOrder.DESC.toString()); sort = new ArrayList<>(); sort.add(defaultSortField); - facetFields = new ArrayList<>(); } /** @@ -101,16 +100,16 @@ public class SearchRequest { this.sort = sort; } - public Optional<List<String>> getFields() { - return fields == null || fields.size() == 0 ? Optional.empty() : Optional.of(fields); + public List<String> getFields() { + return fields; } public void setFields(List<String> fields) { this.fields = fields; } - public Optional<List<String>> getFacetFields() { - return facetFields == null || facetFields.size() == 0 ? Optional.empty() : Optional.of(facetFields); + public List<String> getFacetFields() { + return facetFields; } public void setFacetFields(List<String> facetFields) { @@ -128,10 +127,11 @@ public class SearchRequest { SearchRequest that = (SearchRequest) o; - return indices != null ? indices.equals(that.indices) : that.indices == null && + return (indices != null ? indices.equals(that.indices) : that.indices == null) && (query != null ? query.equals(that.query) : that.query == null) && size == that.size && from == that.from && (sort != null ? sort.equals(that.sort) : that.sort == null) && + (fields != null ? fields.equals(that.fields) : that.fields == null) && (facetFields != null ? facetFields.equals(that.facetFields) : that.facetFields == null); } @@ -142,6 +142,7 @@ public class SearchRequest { result = 31 * result + getSize(); result = 31 * result + getFrom(); result = 31 * result + (sort != null ? sort.hashCode() : 0); + result = 31 * result + (fields != null ? fields.hashCode() : 0); result = 31 * result + (facetFields != null ? facetFields.hashCode() : 0); return result; } http://git-wip-us.apache.org/repos/asf/metron/blob/e22479e6/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/search/SortField.java ---------------------------------------------------------------------- diff --git a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/search/SortField.java b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/search/SortField.java index a3473fc..4dd9e82 100644 --- a/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/search/SortField.java +++ b/metron-platform/metron-indexing/src/main/java/org/apache/metron/indexing/dao/search/SortField.java @@ -36,4 +36,19 @@ public class SortField { public void setSortOrder(String sortOrder) { this.sortOrder = SortOrder.fromString(sortOrder); } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + SortField that = (SortField) o; + + return (field != null ? field.equals(that.field) : that.field == null) && + (sortOrder != null ? sortOrder.equals(that.sortOrder) : that.sortOrder == null); + } }