Java tutorial
/* * Copyright 2013 Rackspace * * 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.rackspacecloud.blueflood.cache; import com.netflix.astyanax.model.ColumnFamily; import com.rackspacecloud.blueflood.internal.Account; import com.rackspacecloud.blueflood.internal.AccountMapEntry; import com.rackspacecloud.blueflood.internal.AccountTest; import com.rackspacecloud.blueflood.internal.InternalAPI; import com.rackspacecloud.blueflood.io.AstyanaxIO; import com.rackspacecloud.blueflood.rollup.Granularity; import com.rackspacecloud.blueflood.types.Locator; import com.rackspacecloud.blueflood.utils.TimeValue; import com.yammer.metrics.Metrics; import org.apache.http.client.HttpResponseException; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import javax.management.MBeanServer; import javax.management.ObjectName; import java.io.IOException; import java.lang.management.ManagementFactory; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; public class TtlCacheTest { // ids in cache are: ackVCKg1rk and acAAAAAAAA private TtlCache twoSecondCache; private AtomicLong loadCount; private AtomicLong buildCount; @Before public void setupCache() { loadCount = new AtomicLong(0); buildCount = new AtomicLong(0); twoSecondCache = new TtlCache("Test", new TimeValue(2, TimeUnit.SECONDS), 5, new InternalAPI() { public Account fetchAccount(String tenantId) throws IOException { loadCount.incrementAndGet(); if (IOException.class.getName().equals(tenantId)) throw new IOException("Error retrieving account from internal API"); else if (HttpResponseException.class.getName().equals(tenantId)) throw new HttpResponseException(404, "That account does not exist"); else return Account.fromJSON(AccountTest.JSON_ACCOUNTS.get(tenantId)); } @Override public List<AccountMapEntry> listAccountMapEntries() throws IOException { throw new RuntimeException("Not implemented for this test"); } }) { @Override protected Map<ColumnFamily<Locator, Long>, TimeValue> buildTtlMap(Account acct) { buildCount.incrementAndGet(); return super.buildTtlMap(acct); } }; } @After public void deregister() throws Exception { final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); final String name = String.format(TtlCache.class.getPackage().getName() + ":type=%s,scope=%s,name=Stats", TtlCache.class.getSimpleName(), "Test"); final ObjectName nameObj = new ObjectName(name); mbs.unregisterMBean(nameObj); // involves a little unsafe knowledge of TtlCache internals. Metrics.defaultRegistry().removeMetric(TtlCache.class, "Load Errors", "Test"); Metrics.defaultRegistry().removeMetric(TtlCache.class, "Http Errors", "Test"); } private void warmCache() { for (int i = 0; i < 100; i++) { for (Granularity gran : Granularity.granularities()) { twoSecondCache.getTtl("ackVCKg1rk", AstyanaxIO.getColumnFamilyMapper().get(gran.name())); twoSecondCache.getTtl("acAAAAAAAA", AstyanaxIO.getColumnFamilyMapper().get(gran.name())); } } } @Test public void testCacheDoesNotReloadOrRebuild() { warmCache(); Assert.assertEquals(2, loadCount.get()); Assert.assertEquals(2, buildCount.get()); warmCache(); Assert.assertEquals(2, loadCount.get()); Assert.assertEquals(2, buildCount.get()); } @Test public void testCacheReloadAfterExpiry() throws Exception { warmCache(); Assert.assertEquals(2, loadCount.get()); Assert.assertEquals(2, buildCount.get()); Thread.sleep(3000); warmCache(); Assert.assertEquals(4, loadCount.get()); Assert.assertEquals(4, buildCount.get()); warmCache(); } @Test public void testDefaultsAreNotNormallyUsed() { warmCache(); for (String acctId : new String[] { "ackVCKg1rk", "acAAAAAAAA" }) for (Granularity gran : Granularity.granularities()) { ColumnFamily<Locator, Long> CF = AstyanaxIO.getColumnFamilyMapper().get(gran.name()); Assert.assertFalse( TtlCache.SAFETY_TTLS.get(CF).toSeconds() == twoSecondCache.getTtl(acctId, CF).toSeconds()); } } @Test public void testDefaultsUsedOnAPIError() { warmCache(); for (Map.Entry<ColumnFamily<Locator, Long>, TimeValue> entry : TtlCache.SAFETY_TTLS.entrySet()) { Assert.assertEquals(entry.getValue(), twoSecondCache.getTtl(IOException.class.getName(), entry.getKey())); } } @Test public void testDefaultUsedOnHttpError() { warmCache(); for (Map.Entry<ColumnFamily<Locator, Long>, TimeValue> entry : TtlCache.SAFETY_TTLS.entrySet()) { Assert.assertEquals(entry.getValue(), twoSecondCache.getTtl(HttpResponseException.class.getName(), entry.getKey())); } } @Test public void testIOErrorTriggersSafetyMode() { warmCache(); for (int i = 0; i < (int) twoSecondCache.getSafetyThreshold() + 10; i++) { twoSecondCache.getTtl(IOException.class.getName(), AstyanaxIO.getColumnFamilyMapper().get(Granularity.FULL)); } forceMeterTick("generalErrorMeter"); Assert.assertTrue(twoSecondCache.isSafetyMode()); } @Test public void testHttpErrorDoesNotTriggerSafetyMode() { warmCache(); for (int i = 0; i < (int) twoSecondCache.getSafetyThreshold() + 1; i++) { twoSecondCache.getTtl(HttpResponseException.class.getName(), AstyanaxIO.getColumnFamilyMapper().get(Granularity.FULL)); } forceMeterTick("generalErrorMeter"); Assert.assertFalse(twoSecondCache.isSafetyMode()); } // normally this is handled by a thread in the yammer library. The point here is to force it. private void forceMeterTick(String fieldName) { try { Field generalErrorMeterField = TtlCache.class.getDeclaredField(fieldName); generalErrorMeterField.setAccessible(true); Object generalErrorMeter = generalErrorMeterField.get(twoSecondCache); Method tick = generalErrorMeter.getClass().getDeclaredMethod("tick"); tick.setAccessible(true); tick.invoke(generalErrorMeter); } catch (Exception ex) { throw new RuntimeException(ex); } } }