blob: f6695c8a509b45910fe3b45876d0575881744e26 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.accumulo.test.metrics.fate;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.util.List;
import org.apache.accumulo.core.Constants;
import org.apache.accumulo.fate.ReadOnlyTStore;
import org.apache.accumulo.fate.Repo;
import org.apache.accumulo.fate.ZooStore;
import org.apache.accumulo.fate.zookeeper.ZooReaderWriter;
import org.apache.accumulo.manager.Manager;
import org.apache.accumulo.manager.metrics.fate.FateMetrics;
import org.apache.accumulo.manager.tableOps.ManagerRepo;
import org.apache.accumulo.server.ServerContext;
import org.apache.accumulo.test.zookeeper.ZooKeeperTestingServer;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
import org.easymock.EasyMock;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Test FATE metrics using stubs and in-memory version of supporting infrastructure - a test
* zookeeper server is used an the FATE repos are stubs, but this should represent the metrics
* collection execution without needed to stand up a mini cluster to exercise these execution paths.
*/
public class FateMetricsTest {
public static final String INSTANCE_ID = "1234";
public static final String MOCK_ZK_ROOT = "/accumulo/" + INSTANCE_ID;
public static final String A_FAKE_SECRET = "aPasswd";
private static final Logger log = LoggerFactory.getLogger(FateMetricsTest.class);
private static ZooKeeperTestingServer szk = null;
private static ZooReaderWriter zooReaderWriter;
private ZooStore<Manager> zooStore = null;
private ZooKeeper zookeeper = null;
private ServerContext context = null;
private Manager manager;
@BeforeClass
public static void setupZk() {
// using default zookeeper port - we don't have a full configuration
szk = new ZooKeeperTestingServer();
szk.initPaths(MOCK_ZK_ROOT);
}
@AfterClass
public static void shutdownZK() throws Exception {
szk.close();
}
/**
* Instantiate a test zookeeper and setup mocks for Manager and Context. The test zookeeper is
* used create a ZooReaderWriter. The zookeeper used in tests needs to be the one from the
* zooReaderWriter, not the test server, because the zooReaderWriter sets up ACLs.
*
* @throws Exception
* any exception is a test failure.
*/
@Before
public void init() throws Exception {
zooReaderWriter = new ZooReaderWriter(szk.getConn(), 10_0000, "aPasswd");
zookeeper = zooReaderWriter.getZooKeeper();
clear(MOCK_ZK_ROOT);
zooStore = new ZooStore<>(MOCK_ZK_ROOT + Constants.ZFATE, zooReaderWriter);
manager = EasyMock.createMock(Manager.class);
context = EasyMock.createMock(ServerContext.class);
EasyMock.expect(context.getZooReaderWriter()).andReturn(zooReaderWriter).anyTimes();
EasyMock.expect(context.getZooKeeperRoot()).andReturn(MOCK_ZK_ROOT).anyTimes();
EasyMock.replay(manager, context);
}
@After
public void cleanup() throws Exception {
clear(MOCK_ZK_ROOT);
}
/**
* Validate that the expected metrics values are present in the metrics collector output
*/
@Test
public void noFates() {
FateMetrics metrics = new FateMetrics(context, 10);
metrics.overrideRefresh(0);
InMemTestCollector collector = new InMemTestCollector();
metrics.getMetrics(collector, true);
log.debug("Collector: {}", collector);
assertTrue(collector.contains("currentFateOps"));
assertTrue(collector.contains("totalFateOps"));
assertTrue(collector.contains("totalZkConnErrors"));
// Transaction STATES - defined by TStatus.
assertTrue(collector.contains("FateTxState_NEW"));
assertTrue(collector.contains("FateTxState_IN_PROGRESS"));
assertTrue(collector.contains("FateTxState_FAILED_IN_PROGRESS"));
assertTrue(collector.contains("FateTxState_FAILED"));
assertTrue(collector.contains("FateTxState_SUCCESSFUL"));
assertTrue(collector.contains("FateTxState_UNKNOWN"));
// metrics derived from operation types when see - none should have been seen.
assertFalse(collector.contains("FateTxOpType_FakeOp"));
assertEquals(0L, collector.getValue("FateTxState_IN_PROGRESS"));
assertEquals(0L, collector.getValue("currentFateOps"));
EasyMock.verify(manager);
}
/**
* Seed a fake FAKE fate that has a reserved transaction id. This sets a "new" status, but the
* repo and debug props are not yet set. Verify the the metric collection handles partial
* transaction states.
*/
@Test
public void fateNewStatus() {
long tx1Id = zooStore.create();
log.debug("ZooStore tx1 id {}", tx1Id);
FateMetrics metrics = new FateMetrics(context, 10);
metrics.overrideRefresh(0);
InMemTestCollector collector = new InMemTestCollector();
metrics.getMetrics(collector, true);
log.debug("Collector: {}", collector);
assertTrue(collector.contains("FateTxState_NEW"));
assertEquals(1L, collector.getValue("FateTxState_NEW"));
assertEquals(1L, collector.getValue("currentFateOps"));
assertTrue(collector.contains("FateTxState_IN_PROGRESS"));
assertEquals(0L, collector.getValue("FateTxState_IN_PROGRESS"));
EasyMock.verify(manager);
}
/**
* Seeds the zoo store with a "fake" repo operation with a step, and sets the prop_debug field.
* This emulates the actions performed with {@link org.apache.accumulo.fate.Fate} for what is
* expected in zookeeeper / the zoo store for an IN_PROGRESS transaction.
*
* @throws Exception
* any exception is a test failure.
*/
@Test
public void oneInProgress() throws Exception {
long tx1Id = seedTransaction();
log.debug("FATE tx: {}", prettyStat(
zookeeper.exists(MOCK_ZK_ROOT + "/fate/" + String.format("tx_%016x", tx1Id), false)));
FateMetrics metrics = new FateMetrics(context, 10);
metrics.overrideRefresh(0);
InMemTestCollector collector = new InMemTestCollector();
metrics.getMetrics(collector, true);
log.debug("Collector: {}", collector);
assertTrue(collector.contains("FateTxState_IN_PROGRESS"));
assertEquals(1L, collector.getValue("FateTxState_IN_PROGRESS"));
assertEquals(1L, collector.getValue("FateTxOpType_FakeOp"));
EasyMock.verify(manager);
}
private long seedTransaction() throws Exception {
long txId = zooStore.create();
zooStore.reserve(txId);
zooStore.setStatus(txId, ReadOnlyTStore.TStatus.IN_PROGRESS);
Repo<Manager> repo = new FakeOp();
zooStore.push(txId, repo);
Repo<Manager> step = new FakeOpStep1();
zooStore.push(txId, step);
zooStore.setProperty(txId, "debug", repo.getDescription());
zooStore.unreserve(txId, 50);
return txId;
}
/**
* builds on the "in progress" transaction - when a transaction completes, the op type metric
* should not reflect the previous operation that was "in progress".
*
* @throws Exception
* any exception is a test failure.
*/
@Test
public void typeClears() throws Exception {
long txId = seedTransaction();
zooStore.reserve(txId);
zooStore.setStatus(txId, ReadOnlyTStore.TStatus.SUCCESSFUL);
zooStore.unreserve(txId, 50);
FateMetrics metrics = new FateMetrics(context, 10);
metrics.overrideRefresh(0);
InMemTestCollector collector = new InMemTestCollector();
metrics.getMetrics(collector, true);
assertEquals(0L, collector.getValue("FateTxState_IN_PROGRESS"));
assertEquals(1L, collector.getValue("FateTxState_SUCCESSFUL"));
assertNull(collector.getValue("FateTxOpType_FakeOp"));
}
String prettyStat(final Stat stat) {
if (stat == null) {
return "{Stat:[null]}";
}
return "{Stat:[" + "czxid:" + stat.getCzxid() + ", mzxid:" + stat.getMzxid() + ", ctime: "
+ stat.getCtime() + ", mtime: " + stat.getMtime() + ", version: " + stat.getVersion()
+ ", cversion: " + stat.getCversion() + ", aversion: " + stat.getAversion()
+ ", eph owner: " + stat.getEphemeralOwner() + ", dataLength: " + stat.getDataLength()
+ ", numChildren: " + stat.getNumChildren() + ", pzxid: " + stat.getPzxid() + "}";
}
private void clear(String path) throws Exception {
log.debug("clean up - search: {}", path);
List<String> children = zookeeper.getChildren(path, false);
if (children.isEmpty()) {
log.debug("clean up - delete path: {}", path);
if (!path.equals(MOCK_ZK_ROOT)) {
zookeeper.delete(path, -1);
}
return;
}
for (String cp : children) {
clear(path + "/" + cp);
}
}
private static class FakeOp extends ManagerRepo {
private static final long serialVersionUID = -1L;
@Override
public Repo<Manager> call(long tid, Manager environment) {
return null;
}
}
private static class FakeOpStep1 extends ManagerRepo {
private static final long serialVersionUID = -1L;
@Override
public Repo<Manager> call(long tid, Manager environment) {
return null;
}
}
}