google-nomulus/javatests/google/registry/monitoring/whitebox/VerifyEntityIntegrityActionTest.java
shikhman f76bc70f91 Preserve test logs and test summary output for Kokoro CI runs
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=135494972
2016-10-14 16:57:43 -04:00

328 lines
13 KiB
Java

// Copyright 2016 The Nomulus Authors. All Rights Reserved.
//
// 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 google.registry.monitoring.whitebox;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.testing.DatastoreHelper.createTld;
import static google.registry.testing.DatastoreHelper.deleteResource;
import static google.registry.testing.DatastoreHelper.newContactResource;
import static google.registry.testing.DatastoreHelper.newDomainResource;
import static google.registry.testing.DatastoreHelper.persistActiveContact;
import static google.registry.testing.DatastoreHelper.persistActiveDomain;
import static google.registry.testing.DatastoreHelper.persistActiveHost;
import static google.registry.testing.DatastoreHelper.persistDomainAsDeleted;
import static google.registry.testing.DatastoreHelper.persistResource;
import static google.registry.testing.DatastoreHelper.persistSimpleResource;
import static org.joda.time.DateTimeZone.UTC;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import com.google.api.client.util.Data;
import com.google.api.services.bigquery.Bigquery;
import com.google.api.services.bigquery.model.TableDataInsertAllRequest;
import com.google.api.services.bigquery.model.TableDataInsertAllRequest.Rows;
import com.google.api.services.bigquery.model.TableDataInsertAllResponse;
import com.google.common.base.Optional;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
import google.registry.bigquery.BigqueryFactory;
import google.registry.mapreduce.MapreduceRunner;
import google.registry.model.contact.ContactResource;
import google.registry.model.domain.DomainResource;
import google.registry.model.host.HostResource;
import google.registry.model.index.EppResourceIndex;
import google.registry.model.index.ForeignKeyIndex;
import google.registry.model.index.ForeignKeyIndex.ForeignKeyContactIndex;
import google.registry.model.index.ForeignKeyIndex.ForeignKeyDomainIndex;
import google.registry.model.index.ForeignKeyIndex.ForeignKeyHostIndex;
import google.registry.testing.FakeClock;
import google.registry.testing.FakeResponse;
import google.registry.testing.FakeSleeper;
import google.registry.testing.InjectRule;
import google.registry.testing.mapreduce.MapreduceTestCase;
import google.registry.util.Retrier;
import java.util.Map;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
/** Unit tests for {@link VerifyEntityIntegrityAction}. */
@RunWith(MockitoJUnitRunner.class)
public class VerifyEntityIntegrityActionTest
extends MapreduceTestCase<VerifyEntityIntegrityAction> {
@Rule
public final InjectRule inject = new InjectRule();
private VerifyEntityIntegrityStreamer integrity;
private ArgumentCaptor<TableDataInsertAllRequest> rowsCaptor;
private final DateTime now = DateTime.parse("2012-01-02T03:04:05Z");
@Mock
private Bigquery bigquery;
@Mock
private Bigquery.Tabledata bigqueryTableData;
@Mock
private Bigquery.Tabledata.InsertAll bigqueryInsertAll;
@Mock
private BigqueryFactory bigqueryFactory;
@Mock
private VerifyEntityIntegrityStreamerFactory streamerFactory;
@Before
public void before() throws Exception {
createTld("tld");
action = new VerifyEntityIntegrityAction();
action.mrRunner = new MapreduceRunner(Optional.of(2), Optional.of(2));
action.response = new FakeResponse();
WhiteboxComponent component = mock(WhiteboxComponent.class);
inject.setStaticField(VerifyEntityIntegrityAction.class, "component", component);
integrity =
new VerifyEntityIntegrityStreamer(
"project-id",
bigqueryFactory,
new Retrier(new FakeSleeper(new FakeClock()), 1),
Suppliers.ofInstance("rowid"),
now);
when(bigqueryFactory.create(anyString(), anyString(), anyString())).thenReturn(bigquery);
when(component.verifyEntityIntegrityStreamerFactory()).thenReturn(streamerFactory);
when(streamerFactory.create(any(DateTime.class))).thenReturn(integrity);
when(bigquery.tabledata()).thenReturn(bigqueryTableData);
rowsCaptor = ArgumentCaptor.forClass(TableDataInsertAllRequest.class);
when(bigqueryTableData.insertAll(anyString(), anyString(), anyString(), rowsCaptor.capture()))
.thenReturn(bigqueryInsertAll);
when(bigqueryInsertAll.execute()).thenReturn(new TableDataInsertAllResponse());
}
private void runMapreduce() throws Exception {
action.run();
executeTasksUntilEmpty("mapreduce");
}
@Test
public void test_singleDomain_noBadInvariants() throws Exception {
persistActiveDomain("ninetails.tld");
runMapreduce();
verifyZeroInteractions(bigquery);
}
@Test
public void test_lotsOfData_noBadInvariants() throws Exception {
createTld("march");
ContactResource contact = persistActiveContact("longbottom");
persistResource(newDomainResource("ninetails.tld", contact));
persistResource(newDomainResource("tentails.tld", contact));
persistDomainAsDeleted(newDomainResource("long.march", contact), now.minusMonths(4));
persistResource(
newDomainResource("long.march", contact)
.asBuilder()
.setCreationTimeForTest(now.minusMonths(3))
.build());
DateTime now = DateTime.now(UTC);
persistResource(
newContactResource("ricketycricket")
.asBuilder()
.setCreationTimeForTest(now.minusDays(10))
.setDeletionTime(now.minusDays(9))
.build());
persistResource(
newContactResource("ricketycricket")
.asBuilder()
.setCreationTimeForTest(now.minusDays(7))
.setDeletionTime(now.minusDays(6))
.build());
persistResource(
newContactResource("ricketycricket")
.asBuilder()
.setCreationTimeForTest(now.minusDays(4))
.setDeletionTime(now.minusDays(3))
.build());
persistResource(
newContactResource("ricketycricket")
.asBuilder()
.setCreationTimeForTest(now.minusDays(1))
.build());
persistActiveHost("ns9001.example.net");
runMapreduce();
verifyZeroInteractions(bigquery);
}
@Test
public void test_missingFki() throws Exception {
persistActiveDomain("ninetails.tld");
ForeignKeyIndex<DomainResource> fki =
ForeignKeyIndex.load(DomainResource.class, "ninetails.tld", DateTime.now(DateTimeZone.UTC));
deleteResource(fki);
runMapreduce();
assertIntegrityErrors(IntegrityError.create(
"ninetails.tld", "DomainBase", "Missing foreign key index for EppResource"));
}
@Test
public void test_missingEppResourceIndex() throws Exception {
Key<ContactResource> cooperKey = Key.create(persistActiveContact("cooper"));
deleteResource(EppResourceIndex.create(cooperKey));
runMapreduce();
assertIntegrityErrors(IntegrityError.create(
Data.NULL_STRING, cooperKey, "Missing EPP resource index for EPP resource"));
}
@Test
public void test_referencesToHostsThatDontExist() throws Exception {
Key<HostResource> missingHost1 = Key.create(HostResource.class, "DEADBEEF-ROID");
Key<HostResource> missingHost2 = Key.create(HostResource.class, "123ABC-ROID");
Key<HostResource> missingHost3 = Key.create(HostResource.class, "FADDACA-ROID");
DomainResource domain =
persistResource(
newDomainResource("blah.tld")
.asBuilder()
.setNameservers(ImmutableSet.of(missingHost1, missingHost2, missingHost3))
.build());
Key<DomainResource> domainKey = Key.create(domain);
runMapreduce();
assertIntegrityErrors(
IntegrityError.create(domainKey, missingHost1, "Target entity does not exist"),
IntegrityError.create(domainKey, missingHost2, "Target entity does not exist"),
IntegrityError.create(domainKey, missingHost3, "Target entity does not exist"));
}
@Test
public void test_overlappingActivePeriods() throws Exception {
ContactResource contact123 = persistActiveContact("contact123");
// These two have overlapping active periods because they will have both been created at
// START_OF_TIME.
DomainResource domain1 =
persistDomainAsDeleted(newDomainResource("penny.tld", contact123), now.minusYears(2));
DomainResource domain2 = persistActiveDomain("penny.tld");
runMapreduce();
assertIntegrityErrors(
IntegrityError.create(
ForeignKeyDomainIndex.createKey(domain2),
Key.create(domain1),
"Found inactive resource deleted more recently than when active resource was created"));
}
@Test
public void test_multipleActiveContactsWithSameContactId() throws Exception {
ContactResource contact1 = persistActiveContact("dupeid");
ContactResource contact2 = persistActiveContact("dupeid");
runMapreduce();
assertIntegrityErrors(
IntegrityError.create(
Key.create(ForeignKeyContactIndex.class, "dupeid"),
Key.create(contact1),
"Multiple active EppResources with same foreign key"),
IntegrityError.create(
Key.create(ForeignKeyContactIndex.class, "dupeid"),
Key.create(contact2),
"Multiple active EppResources with same foreign key"));
}
@Test
public void test_deletedFkiWithOldHostnameIsntAnError() throws Exception {
HostResource hostLosing = persistActiveHost("losing.example.tld");
persistResource(
hostLosing.asBuilder().setFullyQualifiedHostName("gaining.example.tld").build());
persistSimpleResource(
ForeignKeyHostIndex.create(hostLosing, DateTime.parse("2010-01-01T00:00:00Z")));
// The old FKI with a primary key of "losing.example.tld" still exists, and it points to the
// resource which has a hostname of "gaining.example.tld", but this isn't an error because the
// FKI is soft-deleted.
runMapreduce();
verifyZeroInteractions(bigquery);
}
@Test
public void test_fkiWithDifferentHostnameDeletedMoreRecentlyIsAnError() throws Exception {
HostResource hostLosing = persistActiveHost("losing.example.tld");
HostResource hostGaining = persistResource(
hostLosing
.asBuilder()
.setFullyQualifiedHostName("gaining.example.tld")
.setDeletionTime(DateTime.parse("2013-01-01T00:00:00Z"))
.build());
ForeignKeyIndex<?> fki = persistSimpleResource(
ForeignKeyHostIndex.create(hostLosing, DateTime.parse("2014-01-01T00:00:00Z")));
// The old FKI is pointing to the old name but has a deletion time more recent than the deleted
// host, so it should have the same foreign key.
runMapreduce();
assertIntegrityErrors(
IntegrityError.create(
Key.create(fki),
Key.create(hostGaining),
"Foreign key index points to EppResource with different foreign key"));
}
/** Encapsulates the data representing a single integrity error. */
private static class IntegrityError {
String source;
String target;
String message;
static IntegrityError create(Object source, Object target, String message) {
IntegrityError instance = new IntegrityError();
instance.source = source.toString();
instance.target = target.toString();
instance.message = message;
return instance;
}
/**
* Returns a Map representing the JSON blob corresponding to the BigQuery output for this
* integrity violation at the given scan time.
*/
Map<String, Object> toMap(DateTime scanTime) {
return new ImmutableMap.Builder<String, Object>()
.put("scanTime", new com.google.api.client.util.DateTime(scanTime.toDate()))
.put("source", source)
.put("target", target)
.put("message", message)
.build();
}
private IntegrityError() {}
}
/** Asserts that the given integrity errors, and no others, were logged to BigQuery. */
private void assertIntegrityErrors(IntegrityError... errors) {
ImmutableList.Builder<Rows> expected = new ImmutableList.Builder<>();
for (IntegrityError error : errors) {
expected.add(new Rows().setInsertId("rowid").setJson(error.toMap(now)));
}
ImmutableList.Builder<Rows> allRows = new ImmutableList.Builder<>();
for (TableDataInsertAllRequest req : rowsCaptor.getAllValues()) {
allRows.addAll(req.getRows());
}
assertThat(allRows.build()).containsExactlyElementsIn(expected.build());
}
}