Skip to content

Commit 345ad95

Browse files
committed
Merge pull request Netflix#195 from koendc/untagged
New rule: UntaggedRule
2 parents 82773f0 + 1f0f595 commit 345ad95

4 files changed

Lines changed: 306 additions & 0 deletions

File tree

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
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+
19+
package com.netflix.simianarmy.aws.janitor.rule.generic;
20+
21+
import java.util.Date;
22+
import java.util.Set;
23+
24+
import org.apache.commons.lang.StringUtils;
25+
import org.apache.commons.lang.Validate;
26+
import org.joda.time.DateTime;
27+
import org.slf4j.Logger;
28+
import org.slf4j.LoggerFactory;
29+
30+
import com.netflix.simianarmy.MonkeyCalendar;
31+
import com.netflix.simianarmy.Resource;
32+
import com.netflix.simianarmy.aws.AWSResource;
33+
import com.netflix.simianarmy.janitor.Rule;
34+
35+
/**
36+
* The rule for checking the orphaned instances that do not belong to any ASGs and
37+
* launched for certain days.
38+
*/
39+
public class UntaggedRule implements Rule {
40+
41+
/** The Constant LOGGER. */
42+
private static final Logger LOGGER = LoggerFactory.getLogger(UntaggedRule.class);
43+
44+
private static final String TERMINATION_REASON = "This resource is missing the required tags";
45+
46+
private final MonkeyCalendar calendar;
47+
48+
private final Set<String> tagNames;
49+
50+
private final int retentionDaysWithOwner;
51+
52+
private final int retentionDaysWithoutOwner;
53+
54+
55+
/**
56+
* Constructor for UntaggedInstanceRule.
57+
*
58+
* @param calendar
59+
* The calendar used to calculate the termination time
60+
* @param tagNames
61+
* Set of tags that needs to be set
62+
*/
63+
public UntaggedRule(MonkeyCalendar calendar, Set<String> tagNames, int retentionDaysWithOwner, int retentionDaysWithoutOwner) {
64+
Validate.notNull(calendar);
65+
Validate.notNull(tagNames);
66+
this.calendar = calendar;
67+
this.tagNames = tagNames;
68+
this.retentionDaysWithOwner = retentionDaysWithOwner;
69+
this.retentionDaysWithoutOwner = retentionDaysWithoutOwner;
70+
}
71+
72+
@Override
73+
public boolean isValid(Resource resource) {
74+
Validate.notNull(resource);
75+
for (String tagName : this.tagNames) {
76+
if (((AWSResource) resource).getTag(tagName) == null) {
77+
String terminationReason = String.format("does not have the required tag %s", resource.getId(),
78+
tagName);
79+
LOGGER.error(String.format("The resource %s %s", resource.getId(), terminationReason));
80+
DateTime now = new DateTime(calendar.now().getTimeInMillis());
81+
if (resource.getExpectedTerminationTime() == null) {
82+
int retentionDays = retentionDaysWithoutOwner;
83+
if (resource.getOwnerEmail() != null) {
84+
retentionDays = retentionDaysWithOwner;
85+
}
86+
Date terminationTime = calendar.getBusinessDay(new Date(now.getMillis()), retentionDays);
87+
resource.setExpectedTerminationTime(terminationTime);
88+
resource.setTerminationReason(terminationReason);
89+
}
90+
return false;
91+
} else {
92+
LOGGER.debug(String.format("The resource %s has the required tag %s", resource.getId(), tagName));
93+
}
94+
}
95+
LOGGER.info(String.format("The resource %s has all required tags", resource.getId()));
96+
return true;
97+
}
98+
}

src/main/java/com/netflix/simianarmy/basic/janitor/BasicJanitorMonkeyContext.java

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import com.netflix.simianarmy.aws.janitor.rule.asg.DummyASGInstanceValidator;
4949
import com.netflix.simianarmy.aws.janitor.rule.asg.OldEmptyASGRule;
5050
import com.netflix.simianarmy.aws.janitor.rule.asg.SuspendedASGRule;
51+
import com.netflix.simianarmy.aws.janitor.rule.generic.UntaggedRule;
5152
import com.netflix.simianarmy.aws.janitor.rule.instance.OrphanedInstanceRule;
5253
import com.netflix.simianarmy.aws.janitor.rule.launchconfig.OldUnusedLaunchConfigRule;
5354
import com.netflix.simianarmy.aws.janitor.rule.snapshot.NoGeneratedAMIRule;
@@ -192,6 +193,15 @@ private ASGJanitor getASGJanitor() {
192193
instanceValidator
193194
));
194195
}
196+
if (configuration().getBoolOrElse("simianarmy.janitor.rule.untaggedRule.enabled", false)) {
197+
ruleEngine.addRule(new UntaggedRule(monkeyCalendar, getPropertySet("simianarmy.janitor.rule.untaggedRule.requiredTags"),
198+
(int) configuration().getNumOrElse(
199+
"simianarmy.janitor.rule.untaggedRule.retentionDaysWithOwner", 3),
200+
(int) configuration().getNumOrElse(
201+
"simianarmy.janitor.rule.untaggedRule.retentionDaysWithoutOwner",
202+
8)));
203+
}
204+
195205
JanitorCrawler crawler;
196206
if (configuration().getBoolOrElse("simianarmy.janitor.edda.enabled", false)) {
197207
crawler = new EddaASGJanitorCrawler(createEddaClient(), awsClient().region());
@@ -219,6 +229,15 @@ private InstanceJanitor getInstanceJanitor() {
219229
"simianarmy.janitor.rule.orphanedInstanceRule.opsworks.parentage",
220230
false)));
221231
}
232+
if (configuration().getBoolOrElse("simianarmy.janitor.rule.untaggedRule.enabled", false)) {
233+
ruleEngine.addRule(new UntaggedRule(monkeyCalendar, getPropertySet("simianarmy.janitor.rule.untaggedRule.requiredTags"),
234+
(int) configuration().getNumOrElse(
235+
"simianarmy.janitor.rule.untaggedRule.retentionDaysWithOwner", 3),
236+
(int) configuration().getNumOrElse(
237+
"simianarmy.janitor.rule.untaggedRule.retentionDaysWithoutOwner",
238+
8)));
239+
}
240+
222241
JanitorCrawler instanceCrawler;
223242
if (configuration().getBoolOrElse("simianarmy.janitor.edda.enabled", false)) {
224243
instanceCrawler = new EddaInstanceJanitorCrawler(createEddaClient(), awsClient().region());
@@ -246,6 +265,15 @@ && configuration().getBoolOrElse("simianarmy.janitor.rule.deleteOnTerminationRul
246265
"simianarmy.janitor.rule.deleteOnTerminationRule.retentionDays", 3)));
247266
}
248267
}
268+
if (configuration().getBoolOrElse("simianarmy.janitor.rule.untaggedRule.enabled", false)) {
269+
ruleEngine.addRule(new UntaggedRule(monkeyCalendar, getPropertySet("simianarmy.janitor.rule.untaggedRule.requiredTags"),
270+
(int) configuration().getNumOrElse(
271+
"simianarmy.janitor.rule.untaggedRule.retentionDaysWithOwner", 3),
272+
(int) configuration().getNumOrElse(
273+
"simianarmy.janitor.rule.untaggedRule.retentionDaysWithoutOwner",
274+
8)));
275+
}
276+
249277
JanitorCrawler volumeCrawler;
250278
if (configuration().getBoolOrElse("simianarmy.janitor.edda.enabled", false)) {
251279
volumeCrawler = new EddaEBSVolumeJanitorCrawler(createEddaClient(), awsClient().region());
@@ -267,6 +295,15 @@ private EBSSnapshotJanitor getEBSSnapshotJanitor() {
267295
(int) configuration().getNumOrElse(
268296
"simianarmy.janitor.rule.noGeneratedAMIRule.retentionDays", 7)));
269297
}
298+
if (configuration().getBoolOrElse("simianarmy.janitor.rule.untaggedRule.enabled", false)) {
299+
ruleEngine.addRule(new UntaggedRule(monkeyCalendar, getPropertySet("simianarmy.janitor.rule.untaggedRule.requiredTags"),
300+
(int) configuration().getNumOrElse(
301+
"simianarmy.janitor.rule.untaggedRule.retentionDaysWithOwner", 3),
302+
(int) configuration().getNumOrElse(
303+
"simianarmy.janitor.rule.untaggedRule.retentionDaysWithoutOwner",
304+
8)));
305+
}
306+
270307
JanitorCrawler snapshotCrawler;
271308
if (configuration().getBoolOrElse("simianarmy.janitor.edda.enabled", false)) {
272309
snapshotCrawler = new EddaEBSSnapshotJanitorCrawler(
@@ -290,6 +327,15 @@ private LaunchConfigJanitor getLaunchConfigJanitor() {
290327
(int) configuration().getNumOrElse(
291328
"simianarmy.janitor.rule.oldUnusedLaunchConfigRule.retentionDays", 3)));
292329
}
330+
if (configuration().getBoolOrElse("simianarmy.janitor.rule.untaggedRule.enabled", false)) {
331+
ruleEngine.addRule(new UntaggedRule(monkeyCalendar, getPropertySet("simianarmy.janitor.rule.untaggedRule.requiredTags"),
332+
(int) configuration().getNumOrElse(
333+
"simianarmy.janitor.rule.untaggedRule.retentionDaysWithOwner", 3),
334+
(int) configuration().getNumOrElse(
335+
"simianarmy.janitor.rule.untaggedRule.retentionDaysWithoutOwner",
336+
8)));
337+
}
338+
293339
JanitorCrawler crawler;
294340
if (configuration().getBoolOrElse("simianarmy.janitor.edda.enabled", false)) {
295341
crawler = new EddaLaunchConfigJanitorCrawler(
@@ -322,6 +368,14 @@ private ImageJanitor getImageJanitor() {
322368
(int) configuration().getNumOrElse(
323369
"simianarmy.janitor.rule.unusedImageRule.lastReferenceDaysThreshold", 45)));
324370
}
371+
if (configuration().getBoolOrElse("simianarmy.janitor.rule.untaggedRule.enabled", false)) {
372+
ruleEngine.addRule(new UntaggedRule(monkeyCalendar, getPropertySet("simianarmy.janitor.rule.untaggedRule.requiredTags"),
373+
(int) configuration().getNumOrElse(
374+
"simianarmy.janitor.rule.untaggedRule.retentionDaysWithOwner", 3),
375+
(int) configuration().getNumOrElse(
376+
"simianarmy.janitor.rule.untaggedRule.retentionDaysWithoutOwner",
377+
8)));
378+
}
325379

326380
BasicJanitorContext janitorCtx = new BasicJanitorContext(
327381
monkeyRegion, ruleEngine, crawler, janitorResourceTracker,
@@ -347,6 +401,17 @@ private Set<String> getEnabledResourceSet() {
347401
return enabledResourceSet;
348402
}
349403

404+
private Set<String> getPropertySet(String property) {
405+
Set<String> propertyValueSet = new HashSet<String>();
406+
String propertyValue = configuration().getStr(property);
407+
if (StringUtils.isNotBlank(propertyValue)) {
408+
for (String propertyValueItem : propertyValue.split(",")) {
409+
propertyValueSet.add(propertyValueItem.trim());
410+
}
411+
}
412+
return propertyValueSet;
413+
}
414+
350415
public JanitorEmailNotifier.Context getJanitorEmailNotifierContext() {
351416
return new JanitorEmailNotifier.Context() {
352417
@Override

src/main/resources/janitor.properties

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,18 @@ simianarmy.janitor.rule.orphanedInstanceRule.retentionDaysWithoutOwner = 8
6262
# If true, don't consider members of an OpsWorks stack as orphans
6363
simianarmy.janitor.rule.orphanedInstanceRule.opsworks.parentage = false
6464

65+
# The following properties are used by the Janitor rule for cleaning up untagged resources,
66+
# i.e. instances that are missing any required tags
67+
simianarmy.janitor.rule.untaggedRule.enabled = true
68+
# List of tags that are required for each resource
69+
simianarmy.janitor.rule.untaggedRule.requiredTags = owner, purpose, project
70+
# The number of business days the resource is kept after a notification is sent for the deletion
71+
# when the resource has an owner.
72+
simianarmy.janitor.rule.untaggedRule.retentionDaysWithOwner = 2
73+
# The number of business days the resource is kept after a notification is sent for the deletion
74+
# when the resource has no owner.
75+
simianarmy.janitor.rule.untaggedRule.retentionDaysWithoutOwner = 2
76+
6577
# The following properties are used by the Janitor rule for cleaning up volumes that have been
6678
# detached from instances for certain days.
6779
simianarmy.janitor.rule.oldDetachedVolumeRule.enabled = true
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// CHECKSTYLE IGNORE Javadoc
2+
// CHECKSTYLE IGNORE MagicNumberCheck
3+
/*
4+
*
5+
* Copyright 2012 Netflix, Inc.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*
19+
*/
20+
21+
package com.netflix.simianarmy.aws.janitor.rule.generic;
22+
23+
import java.util.Date;
24+
import java.util.HashSet;
25+
import java.util.Set;
26+
27+
import org.joda.time.DateTime;
28+
import org.testng.Assert;
29+
import org.testng.annotations.Test;
30+
31+
import com.netflix.simianarmy.Resource;
32+
import com.netflix.simianarmy.TestUtils;
33+
import com.netflix.simianarmy.aws.AWSResource;
34+
import com.netflix.simianarmy.aws.AWSResourceType;
35+
import com.netflix.simianarmy.aws.janitor.crawler.InstanceJanitorCrawler;
36+
import com.netflix.simianarmy.aws.janitor.rule.TestMonkeyCalendar;
37+
38+
public class TestUntaggedRule {
39+
40+
@Test
41+
public void testUntaggedInstanceWithOwner() {
42+
DateTime now = DateTime.now();
43+
Resource resource = new AWSResource().withId("i-12345678").withResourceType(AWSResourceType.INSTANCE)
44+
.withOwnerEmail("owner@foo.com");
45+
resource.setTag("tag1", "value1");
46+
((AWSResource) resource).setAWSResourceState("running");
47+
Set<String> tags = new HashSet<String>();
48+
tags.add("tag1");
49+
tags.add("tag2");
50+
int retentionDaysWithOwner = 4;
51+
int retentionDaysWithoutOwner = 8;
52+
UntaggedRule rule = new UntaggedRule(new TestMonkeyCalendar(), tags, retentionDaysWithOwner, retentionDaysWithoutOwner);
53+
Assert.assertFalse(rule.isValid(resource));
54+
TestUtils.verifyTerminationTimeRough(resource, retentionDaysWithOwner, now);
55+
}
56+
57+
@Test
58+
public void testUntaggedInstanceWithoutOwner() {
59+
DateTime now = DateTime.now();
60+
Resource resource = new AWSResource().withId("i-12345678").withResourceType(AWSResourceType.INSTANCE);
61+
resource.setTag("tag1", "value1");
62+
((AWSResource) resource).setAWSResourceState("running");
63+
Set<String> tags = new HashSet<String>();
64+
tags.add("tag1");
65+
tags.add("tag2");
66+
int retentionDaysWithOwner = 4;
67+
int retentionDaysWithoutOwner = 8;
68+
UntaggedRule rule = new UntaggedRule(new TestMonkeyCalendar(), tags, retentionDaysWithOwner, retentionDaysWithoutOwner);
69+
Assert.assertFalse(rule.isValid(resource));
70+
TestUtils.verifyTerminationTimeRough(resource, retentionDaysWithoutOwner, now);
71+
}
72+
73+
@Test
74+
public void testTaggedInstance() {
75+
Resource resource = new AWSResource().withId("i-12345678").withResourceType(AWSResourceType.INSTANCE);
76+
resource.setTag("tag1", "value1");
77+
resource.setTag("tag2", "value2");
78+
((AWSResource) resource).setAWSResourceState("running");
79+
Set<String> tags = new HashSet<String>();
80+
tags.add("tag1");
81+
tags.add("tag2");
82+
int retentionDaysWithOwner = 4;
83+
int retentionDaysWithoutOwner = 8;
84+
UntaggedRule rule = new UntaggedRule(new TestMonkeyCalendar(), tags, retentionDaysWithOwner, retentionDaysWithoutOwner);
85+
Assert.assertTrue(rule.isValid(resource));
86+
}
87+
88+
@Test
89+
public void testUntaggedResource() {
90+
DateTime now = DateTime.now();
91+
Resource imageResource = new AWSResource().withId("ami-123123").withResourceType(AWSResourceType.IMAGE);
92+
Resource asgResource = new AWSResource().withId("my-cool-asg").withResourceType(AWSResourceType.ASG);
93+
Resource ebsSnapshotResource = new AWSResource().withId("snap-123123").withResourceType(AWSResourceType.EBS_SNAPSHOT);
94+
Resource lauchConfigurationResource = new AWSResource().withId("my-cool-launch-configuration").withResourceType(AWSResourceType.LAUNCH_CONFIG);
95+
Set<String> tags = new HashSet<String>();
96+
tags.add("tag1");
97+
tags.add("tag2");
98+
int retentionDaysWithOwner = 4;
99+
int retentionDaysWithoutOwner = 8;
100+
UntaggedRule rule = new UntaggedRule(new TestMonkeyCalendar(), tags, retentionDaysWithOwner, retentionDaysWithoutOwner);
101+
Assert.assertFalse(rule.isValid(imageResource));
102+
Assert.assertFalse(rule.isValid(asgResource));
103+
Assert.assertFalse(rule.isValid(ebsSnapshotResource));
104+
Assert.assertFalse(rule.isValid(lauchConfigurationResource));
105+
TestUtils.verifyTerminationTimeRough(imageResource, retentionDaysWithoutOwner, now);
106+
TestUtils.verifyTerminationTimeRough(asgResource, retentionDaysWithoutOwner, now);
107+
TestUtils.verifyTerminationTimeRough(ebsSnapshotResource, retentionDaysWithoutOwner, now);
108+
TestUtils.verifyTerminationTimeRough(lauchConfigurationResource, retentionDaysWithoutOwner, now);
109+
}
110+
111+
@Test
112+
public void testResourceWithExpectedTerminationTimeSet() {
113+
DateTime now = DateTime.now();
114+
Date oldTermDate = new Date(now.plusDays(10).getMillis());
115+
String oldTermReason = "Foo";
116+
Resource resource = new AWSResource().withId("i-12345678").withResourceType(AWSResourceType.INSTANCE)
117+
.withExpectedTerminationTime(oldTermDate)
118+
.withTerminationReason(oldTermReason);
119+
((AWSResource) resource).setAWSResourceState("running");
120+
Set<String> tags = new HashSet<String>();
121+
tags.add("tag1");
122+
tags.add("tag2");
123+
int retentionDaysWithOwner = 4;
124+
int retentionDaysWithoutOwner = 8;
125+
UntaggedRule rule = new UntaggedRule(new TestMonkeyCalendar(), tags, retentionDaysWithOwner, retentionDaysWithoutOwner);
126+
Assert.assertFalse(rule.isValid(resource));
127+
Assert.assertEquals(oldTermDate, resource.getExpectedTerminationTime());
128+
Assert.assertEquals(oldTermReason, resource.getTerminationReason());
129+
}
130+
131+
}

0 commit comments

Comments
 (0)