Skip to content
This repository was archived by the owner on Mar 4, 2021. It is now read-only.

Commit b05e618

Browse files
authored
Merge pull request #268 from ebukoski/master
Add option to use RDS for resource tracking
2 parents 0866a9a + 2018650 commit b05e618

15 files changed

+1782
-72
lines changed

build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ dependencies {
4848
compile 'org.apache.jclouds.api:ec2:1.9.0'
4949
compile 'org.apache.jclouds.provider:aws-ec2:1.9.0'
5050
compile 'com.netflix.servo:servo-core:0.9.4'
51+
compile 'org.springframework:spring-jdbc:4.2.5.RELEASE'
52+
compile 'com.zaxxer:HikariCP:2.4.7'
5153

5254
testCompile 'org.testng:testng:6.3.1'
5355
testCompile 'org.mockito:mockito-core:1.8.5'
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
/*
2+
*
3+
* Copyright 2012 Netflix, Inc.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*/
18+
package com.netflix.simianarmy.aws;
19+
20+
import com.amazonaws.AmazonClientException;
21+
import com.fasterxml.jackson.core.JsonProcessingException;
22+
import com.fasterxml.jackson.core.type.TypeReference;
23+
import com.fasterxml.jackson.databind.ObjectMapper;
24+
import com.netflix.simianarmy.EventType;
25+
import com.netflix.simianarmy.MonkeyRecorder;
26+
import com.netflix.simianarmy.MonkeyType;
27+
import com.netflix.simianarmy.basic.BasicRecorderEvent;
28+
import com.zaxxer.hikari.HikariDataSource;
29+
import org.slf4j.Logger;
30+
import org.slf4j.LoggerFactory;
31+
import org.springframework.jdbc.core.JdbcTemplate;
32+
import org.springframework.jdbc.core.RowMapper;
33+
34+
import java.io.IOException;
35+
import java.sql.ResultSet;
36+
import java.sql.SQLException;
37+
import java.util.ArrayList;
38+
import java.util.Date;
39+
import java.util.List;
40+
import java.util.Map;
41+
42+
/**
43+
* The Class RDSRecorder. Records events to and fetched events from a RDS table (default SIMIAN_ARMY)
44+
*/
45+
@SuppressWarnings("serial")
46+
public class RDSRecorder implements MonkeyRecorder {
47+
/** The Constant LOGGER. */
48+
private static final Logger LOGGER = LoggerFactory.getLogger(RDSRecorder.class);
49+
50+
private final String region;
51+
52+
/** The table. */
53+
private final String table;
54+
55+
/** the jdbcTemplate */
56+
JdbcTemplate jdbcTemplate = null;
57+
58+
public static final String FIELD_ID = "eventId";
59+
public static final String FIELD_EVENT_TIME = "eventTime";
60+
public static final String FIELD_MONKEY_TYPE = "monkeyType";
61+
public static final String FIELD_EVENT_TYPE = "eventType";
62+
public static final String FIELD_REGION = "region";
63+
public static final String FIELD_DATA_JSON = "dataJson";
64+
65+
/**
66+
* Instantiates a new RDS recorder.
67+
*
68+
*/
69+
public RDSRecorder(String dbDriver, String dbUser,
70+
String dbPass, String dbUrl, String dbTable, String region) {
71+
HikariDataSource dataSource = new HikariDataSource();
72+
dataSource.setDriverClassName(dbDriver);
73+
dataSource.setJdbcUrl(dbUrl);
74+
dataSource.setUsername(dbUser);
75+
dataSource.setPassword(dbPass);
76+
this.jdbcTemplate = new JdbcTemplate(dataSource);
77+
this.table = dbTable;
78+
this.region = region;
79+
}
80+
81+
/**
82+
* Instantiates a new RDS recorder. This constructor is intended
83+
* for unit testing.
84+
*
85+
*/
86+
public RDSRecorder(JdbcTemplate jdbcTemplate, String table, String region) {
87+
this.jdbcTemplate = jdbcTemplate;
88+
this.table = table;
89+
this.region = region;
90+
}
91+
92+
public JdbcTemplate getJdbcTemplate() {
93+
return jdbcTemplate;
94+
}
95+
96+
/** {@inheritDoc} */
97+
@Override
98+
public Event newEvent(MonkeyType monkeyType, EventType eventType, String reg, String id) {
99+
return new BasicRecorderEvent(monkeyType, eventType, reg, id);
100+
}
101+
102+
/** {@inheritDoc} */
103+
@Override
104+
public void recordEvent(Event evt) {
105+
String evtTime = String.valueOf(evt.eventTime().getTime());
106+
String name = String.format("%s-%s-%s-%s", evt.monkeyType().name(), evt.id(), region, evtTime);
107+
String json;
108+
try {
109+
json = new ObjectMapper().writeValueAsString(evt.fields());
110+
} catch (JsonProcessingException e) {
111+
LOGGER.error("ERROR generating JSON when saving resource " + name, e);
112+
return;
113+
}
114+
115+
LOGGER.debug(String.format("Saving event %s to RDS table %s", name, table));
116+
StringBuilder sb = new StringBuilder();
117+
sb.append("insert into ").append(table);
118+
sb.append(" (");
119+
sb.append(FIELD_ID).append(",");
120+
sb.append(FIELD_EVENT_TIME).append(",");
121+
sb.append(FIELD_MONKEY_TYPE).append(",");
122+
sb.append(FIELD_EVENT_TYPE).append(",");
123+
sb.append(FIELD_REGION).append(",");
124+
sb.append(FIELD_DATA_JSON).append(") values (?,?,?,?,?,?)");
125+
126+
LOGGER.debug(String.format("Insert statement is '%s'", sb));
127+
int updated = this.jdbcTemplate.update(sb.toString(),
128+
evt.id(),
129+
evt.eventTime().getTime(),
130+
SimpleDBRecorder.enumToValue(evt.monkeyType()),
131+
SimpleDBRecorder.enumToValue(evt.eventType()),
132+
evt.region(),
133+
json);
134+
LOGGER.debug(String.format("%d rows inserted", updated));
135+
}
136+
137+
/** {@inheritDoc} */
138+
@Override
139+
public List<Event> findEvents(Map<String, String> query, Date after) {
140+
return findEvents(null, null, query, after);
141+
}
142+
143+
/** {@inheritDoc} */
144+
@Override
145+
public List<Event> findEvents(MonkeyType monkeyType, Map<String, String> query, Date after) {
146+
return findEvents(monkeyType, null, query, after);
147+
}
148+
149+
/** {@inheritDoc} */
150+
@Override
151+
public List<Event> findEvents(MonkeyType monkeyType, EventType eventType, Map<String, String> query, Date after) {
152+
ArrayList<Object> args = new ArrayList<>();
153+
StringBuilder sqlquery = new StringBuilder(
154+
String.format("select * from %s where region = ?", table, region));
155+
args.add(table);
156+
157+
if (monkeyType != null) {
158+
sqlquery.append(String.format(" and %s = ?", FIELD_MONKEY_TYPE));
159+
args.add(SimpleDBRecorder.enumToValue(monkeyType));
160+
}
161+
162+
if (eventType != null) {
163+
sqlquery.append(String.format(" and %s = ?", FIELD_EVENT_TYPE));
164+
args.add(SimpleDBRecorder.enumToValue(eventType));
165+
}
166+
167+
for (Map.Entry<String, String> pair : query.entrySet()) {
168+
sqlquery.append(String.format(" and %s like ?", FIELD_DATA_JSON));
169+
args.add((String.format("%s: \"%s\"", pair.getKey(), pair.getValue())));
170+
}
171+
sqlquery.append(String.format(" and %s > ? order by %s desc", FIELD_EVENT_TIME, FIELD_EVENT_TIME));
172+
args.add(new Long(after.getTime()));
173+
174+
LOGGER.debug(String.format("Query is '%s'", sqlquery));
175+
List<Event> events = jdbcTemplate.query(sqlquery.toString(), args.toArray(), new RowMapper<Event>() {
176+
public Event mapRow(ResultSet rs, int rowNum) throws SQLException {
177+
return mapEvent(rs);
178+
}
179+
});
180+
return events;
181+
}
182+
183+
private Event mapEvent(ResultSet rs) throws SQLException {
184+
String json = rs.getString("dataJson");
185+
ObjectMapper mapper = new ObjectMapper();
186+
Event event = null;
187+
try {
188+
String id = rs.getString(FIELD_ID);
189+
MonkeyType monkeyType = SimpleDBRecorder.valueToEnum(MonkeyType.class, rs.getString(FIELD_MONKEY_TYPE));
190+
EventType eventType = SimpleDBRecorder.valueToEnum(EventType.class, rs.getString(FIELD_EVENT_TYPE));
191+
String region = rs.getString(FIELD_REGION);
192+
long time = rs.getLong(FIELD_EVENT_TIME);
193+
event = new BasicRecorderEvent(monkeyType, eventType, region, id, time);
194+
195+
TypeReference<Map<String,String>> typeRef = new TypeReference<Map<String,String>>() {};
196+
Map<String, String> map = mapper.readValue(json, typeRef);
197+
for(String key : map.keySet()) {
198+
event.addField(key, map.get(key));
199+
}
200+
201+
}catch(IOException ie) {
202+
LOGGER.error("Error parsing resource from json", ie);
203+
}
204+
return event;
205+
}
206+
207+
208+
/**
209+
* Creates the RDS table, if it does not already exist.
210+
*/
211+
public void init() {
212+
try {
213+
if (this.region == null || this.region.equals("region-null")) {
214+
// This is a mock with an invalid region; avoid a slow timeout
215+
LOGGER.debug("Region=null; skipping RDS table creation");
216+
return;
217+
}
218+
219+
LOGGER.info("Creating RDS table: {}", table);
220+
String sql = String.format("create table if not exists %s ("
221+
+ " %s varchar(255),"
222+
+ " %s BIGINT,"
223+
+ " %s varchar(255),"
224+
+ " %s varchar(255),"
225+
+ " %s varchar(255),"
226+
+ " %s varchar(4096) )",
227+
table,
228+
FIELD_ID,
229+
FIELD_EVENT_TIME,
230+
FIELD_MONKEY_TYPE,
231+
FIELD_EVENT_TYPE,
232+
FIELD_REGION,
233+
FIELD_DATA_JSON);
234+
LOGGER.debug("Create SQL is: '{}'", sql);
235+
jdbcTemplate.execute(sql);
236+
237+
} catch (AmazonClientException e) {
238+
LOGGER.warn("Error while trying to auto-create RDS table", e);
239+
}
240+
}
241+
}

src/main/java/com/netflix/simianarmy/aws/SimpleDBRecorder.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ protected AmazonSimpleDB sdbClient() {
123123
* the e
124124
* @return the string
125125
*/
126-
private static String enumToValue(NamedType e) {
126+
public static String enumToValue(NamedType e) {
127127
return String.format("%s|%s", e.name(), e.getClass().getName());
128128
}
129129

@@ -134,7 +134,7 @@ private static String enumToValue(NamedType e) {
134134
* the value
135135
* @return the enum
136136
*/
137-
private static <T extends NamedType> T valueToEnum(
137+
public static <T extends NamedType> T valueToEnum(
138138
Class<T> type, String value) {
139139
// parts = [enum value, enum class type]
140140
String[] parts = value.split("\\|", 2);

0 commit comments

Comments
 (0)