/*
* Copyright 2012 Nodeable Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.streamreduce.storm.bolts;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import backtype.storm.task.OutputCollector;
import backtype.storm.tuple.Tuple;
import backtype.storm.tuple.Values;
import com.google.common.collect.ImmutableSet;
import com.streamreduce.ProviderIdConstants;
import com.streamreduce.core.event.EventId;
import com.streamreduce.storm.MockOutputCollector;
import org.bson.types.ObjectId;
import org.mockito.Mockito;
/**
* Class all tests will extend if the bolt being tested extends {@link AbstractMetricsBolt}.
*/
public abstract class AbstractMetricsBoltTest {
protected MockOutputCollector outputCollector;
/**
* Returns an instance of the bolt being tested.
*
* @return the bolt
*/
public abstract AbstractMetricsBolt getBolt();
/**
* Takes a list of mock events, executes them using the bolt specified and returns the calculated metrics.
*
* @param events the events
*
* @return the map of calculated events
*/
protected Map<String, Float> processEvents(List<Map<String, Object>> events) {
Map<String, Float> metricCounts = new HashMap<>();
AbstractMetricsBolt bolt = getBolt();
outputCollector = new MockOutputCollector();
// Prepare the bolt so that it uses our mock output collector
bolt.prepare(null, null, new OutputCollector(outputCollector));
// Emit all events
for (Map<String, Object> event : events) {
Tuple tuple = Mockito.mock(Tuple.class);
Mockito.when(tuple.getValue(0)).thenReturn(event);
bolt.execute(tuple);
}
// Turn all emitted metrics into the metrics map
for (Values metric : outputCollector.getEmittedValues()) {
String accountId = metric.get(0).toString();
String metricId = metric.get(6).toString();
Float metricValue = (Float)metric.get(5);
String mapKey = accountId + "." + metricId;
Float mapValue = metricCounts.containsKey(mapKey) ? metricCounts.get(mapKey) : 0f;
metricCounts.put(mapKey, mapValue += metricValue);
}
return metricCounts;
}
/**
* Helper to create a full cycle of CRUD events for a target type.
*
* @param targetType the target type
*
* @return map where each CRUD event is the key and the object for said event is the value
*/
protected Map<EventId, Map<String, Object>> createCRUDEvents(String targetType) {
Map<String, Object> createEvent = createBaseEventMock(EventId.CREATE, targetType, null);
Map<String, Object> readEvent = createBaseEventMock(EventId.READ, createEvent.get("accountId").toString(),
createEvent.get("userId").toString(),
createEvent.get("targetId").toString(), targetType, null);
Map<String, Object> updateEvent = createBaseEventMock(EventId.UPDATE, createEvent.get("accountId").toString(),
createEvent.get("userId").toString(),
createEvent.get("targetId").toString(), targetType, null);
Map<String, Object> deleteEvent = createBaseEventMock(EventId.DELETE, createEvent.get("accountId").toString(),
createEvent.get("userId").toString(),
createEvent.get("targetId").toString(), targetType, null);
Map<EventId, Map<String, Object>> crudEvents = new HashMap<>();
crudEvents.put(EventId.CREATE, createEvent);
crudEvents.put(EventId.READ, readEvent);
crudEvents.put(EventId.UPDATE, updateEvent);
crudEvents.put(EventId.DELETE, deleteEvent);
return crudEvents;
}
/**
* Returns a {@link com.mongodb.BasicDBObject} that has the default structure required to mirror
* an {@link com.streamreduce.core.model.Event}.
*
* @param eventId the event id
* @param targetType the event's target type
* @param metadata the event's metadata
*
* @return the base event
*/
protected Map<String, Object> createBaseEventMock(EventId eventId, String targetType,
Map<String, Object> metadata) {
return createBaseEventMock(eventId, null, null, null, targetType, metadata);
}
/**
* Returns a {@link com.mongodb.BasicDBObject} that has the default structure required to mirror
* an {@link com.streamreduce.core.model.Event}.
*
* @param eventId the {@link EventId} of the event
* @param accountId the account id of the event
* @param userId the user id of the event
* @param targetId the target id of the event
* @param targetType the target type of the event
*
* @return the base event
*/
protected Map<String, Object> createBaseEventMock(EventId eventId, String accountId, String userId,
String targetId, String targetType,
Map<String, Object> metadata) {
Map<String, Object> baseEvent = new HashMap<>();
baseEvent.put("_id", new ObjectId().toString());
baseEvent.put("timestamp", new Date().getTime());
baseEvent.put("eventId", eventId);
baseEvent.put("accountId", accountId != null ? accountId : new ObjectId().toString());
baseEvent.put("userId", userId != null ? userId : new ObjectId().toString());
baseEvent.put("targetId", targetId != null ? targetId : new ObjectId().toString());
if (metadata == null) {
metadata = new HashMap<>();
}
if (targetType != null) {
metadata.put("targetType", targetType);
}
baseEvent.put("metadata", metadata);
return baseEvent;
}
/**
* Helper to create a full cycle of CRUD events for a connection/inventory item and an ACTIVITY event.
*
* @param targetType the target's type
* @param providerType the target's provider type
* @param providerId the target's provider id
* @param payload the target's activity payload
*
* @return map where each CRUD event is the key and the object for said event is the value
*/
protected Map<EventId, Map<String, Object>> createConnectionOrInventoryItemEvents(String targetType,
String providerType,
String providerId,
Map<String, Object> payload) {
Map<EventId, Map<String, Object>> allEvents = createCRUDEvents(targetType);
Map<String, Object> metadata = new HashMap<>();
metadata.put("targetProviderId", providerId);
metadata.put("targetProviderType", providerType);
metadata.put("targetHashtags", ImmutableSet.of('#' + providerId, '#' + providerType));
if (targetType.equals("InventoryItem")) {
metadata.put("targetConnectionId", new ObjectId().toString());
if (providerId.equals(ProviderIdConstants.AWS_PROVIDER_ID)) {
metadata.put("targetISO3166Code", "US-CA");
metadata.put("targetRegion", "us-west-1");
metadata.put("targetZone", "us-west-1a");
metadata.put("targetOS", "AMZN_LINUX");
}
}
for (Map.Entry<EventId, Map<String, Object>> crudEvent : allEvents.entrySet()) {
Map<String, Object> eventMetadata = (Map<String, Object>)crudEvent.getValue().get("metadata");
eventMetadata.putAll(metadata);
// Since the hashtag handling in the AbstractMetricsBolt requires a database entry to handle things
// properly, just remove hashtags from the update event so that an update doesn't produce any unnecessary
// hashtag-related metrics.
if (crudEvent.getKey() == EventId.UPDATE) {
eventMetadata.remove("targetHashtags");
}
crudEvent.getValue().put("metadata", eventMetadata);
}
Map<String, Object> createEvent = allEvents.get(EventId.CREATE);
Map<String, Object> activityEvent = createBaseEventMock(EventId.ACTIVITY,
createEvent.get("accountId").toString(),
createEvent.get("userId").toString(),
createEvent.get("targetId").toString(),
targetType,
(Map<String, Object>)createEvent.get("metadata"));
Map<String, Object> activityMetadata = (Map<String, Object>)activityEvent.get("metadata");
if (payload != null) {
activityMetadata.put("payload", payload);
}
activityEvent.put("metadata", activityMetadata);
allEvents.put(EventId.ACTIVITY, activityEvent);
return allEvents;
}
}