diff --git a/src/main/java/com/netflix/simianarmy/basic/BasicCalendar.java b/src/main/java/com/netflix/simianarmy/basic/BasicCalendar.java index cfb0c801..5791ce5f 100644 --- a/src/main/java/com/netflix/simianarmy/basic/BasicCalendar.java +++ b/src/main/java/com/netflix/simianarmy/basic/BasicCalendar.java @@ -50,7 +50,7 @@ public class BasicCalendar implements MonkeyCalendar { private final TimeZone tz; /** The holidays. */ - private final Set holidays = new TreeSet(); + protected final Set holidays = new TreeSet(); /** The cfg. */ private MonkeyConfiguration cfg; @@ -83,7 +83,7 @@ public BasicCalendar(int open, int close, TimeZone timezone) { closeHour = close; tz = timezone; } - + /** * Instantiates a new basic calendar. * @@ -235,7 +235,7 @@ protected void loadHolidays(int year) { * the day * @return the day of the year */ - private int dayOfYear(int year, int month, int day) { + protected int dayOfYear(int year, int month, int day) { Calendar holiday = now(); holiday.set(Calendar.YEAR, year); holiday.set(Calendar.MONTH, month); @@ -256,7 +256,7 @@ private int dayOfYear(int year, int month, int day) { * the week in month * @return the day of the year */ - private int dayOfYear(int year, int month, int dayOfWeek, int weekInMonth) { + protected int dayOfYear(int year, int month, int dayOfWeek, int weekInMonth) { Calendar holiday = now(); holiday.set(Calendar.YEAR, year); holiday.set(Calendar.MONTH, month); @@ -276,7 +276,7 @@ private int dayOfYear(int year, int month, int dayOfWeek, int weekInMonth) { * the day * @return the day of the year adjusted to the closest workday */ - private int workDayInYear(int year, int month, int day) { + protected int workDayInYear(int year, int month, int day) { Calendar holiday = now(); holiday.set(Calendar.YEAR, year); holiday.set(Calendar.MONTH, month); @@ -308,8 +308,7 @@ public Date getBusinessDay(Date date, int n) { private boolean isWeekend(Calendar calendar) { int dow = calendar.get(Calendar.DAY_OF_WEEK); - return dow == Calendar.SATURDAY - || dow == Calendar.SUNDAY; + return dow == Calendar.SATURDAY || dow == Calendar.SUNDAY; } } diff --git a/src/main/java/com/netflix/simianarmy/basic/BasicSimianArmyContext.java b/src/main/java/com/netflix/simianarmy/basic/BasicSimianArmyContext.java index 712e4d5d..aa6f76f6 100644 --- a/src/main/java/com/netflix/simianarmy/basic/BasicSimianArmyContext.java +++ b/src/main/java/com/netflix/simianarmy/basic/BasicSimianArmyContext.java @@ -123,19 +123,18 @@ protected BasicSimianArmyContext(String... configFiles) { } config = new BasicConfiguration(properties); - calendar = new BasicCalendar(config); account = config.getStr("simianarmy.client.aws.accountKey"); secret = config.getStr("simianarmy.client.aws.secretKey"); accountName = config.getStrOrElse("simianarmy.client.aws.accountName", "Default"); - + String defaultRegion = "us-east-1"; Region currentRegion = Regions.getCurrentRegion(); - + if (currentRegion != null) { defaultRegion = currentRegion.getName(); } - + region = config.getStrOrElse("simianarmy.client.aws.region", defaultRegion); GLOBAL_OWNER_TAGKEY = config.getStrOrElse("simianarmy.tags.owner", "owner"); @@ -165,6 +164,8 @@ protected BasicSimianArmyContext(String... configFiles) { createClient(); + createCalendar(); + createScheduler(); createRecorder(); @@ -224,6 +225,17 @@ private void createRecorder() { } } + @SuppressWarnings("unchecked") + private void createCalendar() { + @SuppressWarnings("rawtypes") + Class calendarClass = loadClientClass("simianarmy.calendar.class"); + if (calendarClass == null || calendarClass.equals(BasicCalendar.class)) { + setCalendar(new BasicCalendar(config)); + } else { + setCalendar((MonkeyCalendar) factory(calendarClass)); + } + } + /** * Create the specific client with region taken from properties. * Override to provide your own client. diff --git a/src/main/java/com/netflix/simianarmy/basic/calendars/BavarianCalendar.java b/src/main/java/com/netflix/simianarmy/basic/calendars/BavarianCalendar.java new file mode 100644 index 00000000..99f666e7 --- /dev/null +++ b/src/main/java/com/netflix/simianarmy/basic/calendars/BavarianCalendar.java @@ -0,0 +1,157 @@ +/* + * + * Copyright 2012 Netflix, 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.netflix.simianarmy.basic.calendars; + +import com.netflix.simianarmy.MonkeyConfiguration; +import com.netflix.simianarmy.basic.BasicCalendar; + +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collection; + +// CHECKSTYLE IGNORE MagicNumberCheck +/** + * The Class BavarianCalendar. + */ +public class BavarianCalendar extends BasicCalendar +{ + /** + * Instantiates a new basic calendar. + * + * @param cfg the monkey configuration + */ + public BavarianCalendar(MonkeyConfiguration cfg) + { + super(cfg); + } + + /** {@inheritDoc} */ + @Override + protected void loadHolidays(int year) { + holidays.clear(); + + // these aren't all strictly holidays, but days when engineers will likely + // not be in the office to respond to rampaging monkeys + + // first of all, we need easter sunday doy, + // because ome other holidays calculated from it + int easter = westernEasterDayOfYear(year); + + // new year + holidays.addAll(getHolidayWithBridgeDays(year, dayOfYear(year, Calendar.JANUARY, 1))); + + // epiphanie + holidays.addAll(getHolidayWithBridgeDays(year, dayOfYear(year, Calendar.JANUARY, 6))); + + // good friday, always friday, don't need to check if it's bridge day + holidays.add(easter - 2); + + // easter monday, always monday, don't need to check if it's bridge day + holidays.add(easter + 1); + + // labor day + holidays.addAll(getHolidayWithBridgeDays(year, dayOfYear(year, Calendar.MAY, 1))); + + // ascension day + holidays.addAll(getHolidayWithBridgeDays(year, easter + 39)); + + // whit monday, always monday, don't need to check if it's bridge day + holidays.add(easter + 50); + + // corpus christi + holidays.add(westernEasterDayOfYear(year) + 60); + + // assumption day + holidays.addAll(getHolidayWithBridgeDays(year, dayOfYear(year, Calendar.AUGUST, 15))); + + // german unity day + holidays.addAll(getHolidayWithBridgeDays(year, dayOfYear(year, Calendar.OCTOBER, 3))); + + // all saints + holidays.addAll(getHolidayWithBridgeDays(year, dayOfYear(year, Calendar.NOVEMBER, 1))); + + // monkey goes on christmas vacations between christmas and new year! + holidays.addAll(getHolidayWithBridgeDays(year, dayOfYear(year, Calendar.DECEMBER, 24))); + holidays.add(dayOfYear(year, Calendar.DECEMBER, 25)); + holidays.add(dayOfYear(year, Calendar.DECEMBER, 26)); + holidays.add(dayOfYear(year, Calendar.DECEMBER, 27)); + holidays.add(dayOfYear(year, Calendar.DECEMBER, 28)); + holidays.add(dayOfYear(year, Calendar.DECEMBER, 29)); + holidays.add(dayOfYear(year, Calendar.DECEMBER, 30)); + holidays.add(dayOfYear(year, Calendar.DECEMBER, 31)); + + // mark the holiday set with the year, so on Jan 1 it will automatically + // recalculate the holidays for next year + holidays.add(year); + } + + /** + * Returns collection of holidays, including Monday or Friday + * if given holiday is Thuesday or Thursday. + * + * The behaviour to take Monday as day off if official holiday is Thuesday + * and to take Friday as day off if official holiday is Thursday + * is specific to [at least] Germany. + * We call it, literally, "bridge day". + * + * @param dayOfYear holiday day of year + */ + private Collection getHolidayWithBridgeDays(int year, int dayOfYear) { + Calendar holiday = now(); + holiday.set(Calendar.YEAR, year); + holiday.set(Calendar.DAY_OF_YEAR, dayOfYear); + int dow = holiday.get(Calendar.DAY_OF_WEEK); + int mon = holiday.get(Calendar.MONTH); + int dom = holiday.get(Calendar.DAY_OF_MONTH); + + // We don't want to include Monday if Thuesday is January 1. + if (dow == Calendar.TUESDAY && dayOfYear != 1) + return Arrays.asList(dayOfYear, dayOfYear - 1); + + // We don't want to include Friday if Thursday is December 31. + if (dow == Calendar.THURSDAY && (mon != Calendar.DECEMBER || dom != 31)) + return Arrays.asList(dayOfYear, dayOfYear + 1); + + return Arrays.asList(dayOfYear); + } + + /** + * Western easter sunday in year. + * + * @param year + * the year + * @return the day of the year of western easter sunday + */ + protected int westernEasterDayOfYear(int year) { + int a = year % 19, + b = year / 100, + c = year % 100, + d = b / 4, + e = b % 4, + g = (8 * b + 13) / 25, + h = (19 * a + b - d - g + 15) % 30, + j = c / 4, + k = c % 4, + m = (a + 11 * h) / 319, + r = (2 * e + 2 * j - k - h + m + 32) % 7; + int oneBasedMonth = (h - m + r + 90) / 25; + int dayOfYear = (h - m + r + oneBasedMonth + 19) % 32; + return dayOfYear(year, oneBasedMonth - 1, dayOfYear); + } + +} diff --git a/src/test/java/com/netflix/simianarmy/basic/TestBasicCalendar.java b/src/test/java/com/netflix/simianarmy/basic/TestBasicCalendar.java index 18df4c91..23d2143b 100644 --- a/src/test/java/com/netflix/simianarmy/basic/TestBasicCalendar.java +++ b/src/test/java/com/netflix/simianarmy/basic/TestBasicCalendar.java @@ -204,7 +204,7 @@ public void testGetBusinessDayWihHolidayNextYear() { Assert.assertEquals(businessDay.get(Calendar.YEAR), 2013); Assert.assertEquals(businessDay.get(Calendar.MONTH), Calendar.JANUARY); Assert.assertEquals(businessDay.get(Calendar.DAY_OF_MONTH), 2); - Assert.assertEquals(businessDay.get(Calendar.HOUR_OF_DAY), - hour); + Assert.assertEquals(businessDay.get(Calendar.HOUR_OF_DAY), hour); } + } diff --git a/src/test/java/com/netflix/simianarmy/basic/calendar/TestBavarianCalendar.java b/src/test/java/com/netflix/simianarmy/basic/calendar/TestBavarianCalendar.java new file mode 100644 index 00000000..a54cd5a4 --- /dev/null +++ b/src/test/java/com/netflix/simianarmy/basic/calendar/TestBavarianCalendar.java @@ -0,0 +1,153 @@ +/* + * + * Copyright 2012 Netflix, 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. + * + */ +// CHECKSTYLE IGNORE Javadoc +package com.netflix.simianarmy.basic.calendar; + +import com.netflix.simianarmy.basic.BasicConfiguration; +import com.netflix.simianarmy.basic.calendars.BavarianCalendar; +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.util.Calendar; +import java.util.Properties; + +// CHECKSTYLE IGNORE MagicNumberCheck + +public class TestBavarianCalendar extends BavarianCalendar { + private static final Properties PROPS = new Properties(); + private static final BasicConfiguration CFG = new BasicConfiguration(PROPS); + + public TestBavarianCalendar() { + super(CFG); + } + + private Calendar now = super.now(); + + @Override + public Calendar now() { + return (Calendar) now.clone(); + } + + private void setNow(Calendar now) { + this.now = now; + } + + @DataProvider + public Object[][] easterDataProvider() { + return new Object[][] { + {1996, Calendar.APRIL, 7}, + {1997, Calendar.MARCH, 30}, + {1998, Calendar.APRIL, 12}, + {1999, Calendar.APRIL, 4}, + {2000, Calendar.APRIL, 23}, + {2001, Calendar.APRIL, 15}, + {2002, Calendar.MARCH, 31}, + {2003, Calendar.APRIL, 20}, + {2004, Calendar.APRIL, 11}, + {2005, Calendar.MARCH, 27}, + {2006, Calendar.APRIL, 16}, + {2007, Calendar.APRIL, 8}, + {2008, Calendar.MARCH, 23}, + {2009, Calendar.APRIL, 12}, + {2010, Calendar.APRIL, 4}, + {2011, Calendar.APRIL, 24}, + {2012, Calendar.APRIL, 8}, + {2013, Calendar.MARCH, 31}, + {2014, Calendar.APRIL, 20}, + {2015, Calendar.APRIL, 5}, + {2016, Calendar.MARCH, 27}, + {2017, Calendar.APRIL, 16}, + {2018, Calendar.APRIL, 1}, + {2019, Calendar.APRIL, 21}, + {2020, Calendar.APRIL, 12}, + {2021, Calendar.APRIL, 4}, + {2022, Calendar.APRIL, 17}, + {2023, Calendar.APRIL, 9}, + {2024, Calendar.MARCH, 31}, + {2025, Calendar.APRIL, 20}, + {2026, Calendar.APRIL, 5}, + {2027, Calendar.MARCH, 28}, + {2028, Calendar.APRIL, 16}, + {2029, Calendar.APRIL, 1}, + {2030, Calendar.APRIL, 21}, + {2031, Calendar.APRIL, 13}, + {2032, Calendar.MARCH, 28}, + {2033, Calendar.APRIL, 17}, + {2034, Calendar.APRIL, 9}, + {2035, Calendar.MARCH, 25}, + {2036, Calendar.APRIL, 13}, + }; + } + + @Test(dataProvider = "easterDataProvider") + public void testEaster(int year, int month, int dayOfMonth) { + Assert.assertEquals(dayOfYear(year, month, dayOfMonth), westernEasterDayOfYear(year)); + } + + @DataProvider + public Object[][] holidayDataProvider() { + return new Object[][] { + {2016, Calendar.JANUARY, 1}, // new year + {2016, Calendar.JANUARY, 6}, // epiphanie + {2016, Calendar.MARCH, 25}, // good friday + {2016, Calendar.MARCH, 28}, // easter monday + {2016, Calendar.MAY, 1}, // labor day + {2016, Calendar.MAY, 5}, // ascension day + {2016, Calendar.MAY, 6}, // friday after ascension day + {2016, Calendar.MAY, 16}, // whit monday + {2016, Calendar.MAY, 26}, // corpus christi + {2016, Calendar.AUGUST, 15}, // assumption day + {2016, Calendar.OCTOBER, 3}, // german unity day + {2016, Calendar.DECEMBER, 24}, // christmas holidays + {2016, Calendar.DECEMBER, 25}, + {2016, Calendar.DECEMBER, 26}, + {2016, Calendar.DECEMBER, 27}, + {2016, Calendar.DECEMBER, 28}, + {2016, Calendar.DECEMBER, 29}, + {2016, Calendar.DECEMBER, 30}, + {2016, Calendar.DECEMBER, 31}, + // now, "bridge days" + {2015, Calendar.JANUARY, 2}, // friday after new year + {2015, Calendar.JANUARY, 5}, // monday before epiphanie + {2011, Calendar.JANUARY, 7}, // friday after epiphanie + {2012, Calendar.APRIL, 30}, // monday before labor day + {2014, Calendar.MAY, 2}, // friday after labor day + {2006, Calendar.AUGUST, 14}, // monday before assumption day + {2013, Calendar.AUGUST, 16}, // friday after assumption day + {2006, Calendar.OCTOBER, 2}, // monday before german unity day + {2013, Calendar.OCTOBER, 4}, // friday after german unity day + {2011, Calendar.OCTOBER, 31}, // monday before all saints + {2012, Calendar.NOVEMBER, 2}, // friday after all saints + {2013, Calendar.DECEMBER, 23} // monday before christas eve + }; + } + + @Test(dataProvider = "holidayDataProvider") + public void testHolidays(int year, int month, int dayOfMonth) { + Calendar test = Calendar.getInstance(); + test.set(Calendar.YEAR, year); + test.set(Calendar.MONTH, month); + test.set(Calendar.DAY_OF_MONTH, dayOfMonth); + test.set(Calendar.HOUR_OF_DAY, 10); + setNow(test); + + Assert.assertTrue(isHoliday(test), test.getTime().toString() + " is a holiday?"); + } + +}