mirror of
https://github.com/google/nomulus.git
synced 2025-05-07 15:28:22 +02:00
This change replaces all Ref objects in the code with Key objects. These are stored in datastore as the same object (raw datastore keys), so this is not a model change. Our best practices doc says to use Keys not Refs because: * The .get() method obscures what's actually going on - Much harder to visually audit the code for datastore loads - Hard to distinguish Ref<T> get()'s from Optional get()'s and Supplier get()'s * Implicit ofy().load() offers much less control - Antipattern for ultimate goal of making Ofy injectable - Can't control cache use or batch loading without making ofy() explicit anyway * Serialization behavior is surprising and could be quite dangerous/incorrect - Can lead to serialization errors. If it actually worked "as intended", it would lead to a Ref<> on a serialized object being replaced upon deserialization with a stale copy of the old value, which could potentially break all kinds of transactional expectations * Having both Ref<T> and Key<T> introduces extra boilerplate everywhere - E.g. helper methods all need to have Ref and Key overloads, or you need to call .key() to get the Key<T> for every Ref<T> you want to pass in - Creating a Ref<T> is more cumbersome, since it doesn't have all the create() overloads that Key<T> has, only create(Key<T>) and create(Entity) - no way to create directly from kind+ID/name, raw Key, websafe key string, etc. (Note that Refs are treated specially by Objectify's @Load method and Keys are not; we don't use that feature, but it is the one advantage Refs have over Keys.) The direct impetus for this change is that I am trying to audit our use of memcache, and the implicit .get() calls to datastore were making that very hard. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=131965491
221 lines
10 KiB
Java
221 lines
10 KiB
Java
// Copyright 2016 The Domain Registry 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.flows.async;
|
|
|
|
import static com.google.common.truth.Truth.assertThat;
|
|
import static google.registry.model.EppResourceUtils.loadByUniqueId;
|
|
import static google.registry.testing.DatastoreHelper.assertNoBillingEvents;
|
|
import static google.registry.testing.DatastoreHelper.createTld;
|
|
import static google.registry.testing.DatastoreHelper.getOnlyHistoryEntryOfType;
|
|
import static google.registry.testing.DatastoreHelper.newDomainResource;
|
|
import static google.registry.testing.DatastoreHelper.newHostResource;
|
|
import static google.registry.testing.DatastoreHelper.persistActiveHost;
|
|
import static google.registry.testing.DatastoreHelper.persistResource;
|
|
import static google.registry.testing.HostResourceSubject.assertAboutHosts;
|
|
import static google.registry.testing.TaskQueueHelper.assertDnsTasksEnqueued;
|
|
import static google.registry.util.DateTimeUtils.END_OF_TIME;
|
|
|
|
import com.google.common.collect.ImmutableSet;
|
|
import com.googlecode.objectify.Key;
|
|
import google.registry.model.domain.DomainResource;
|
|
import google.registry.model.eppcommon.StatusValue;
|
|
import google.registry.model.host.HostResource;
|
|
import google.registry.model.reporting.HistoryEntry;
|
|
import google.registry.request.HttpException.BadRequestException;
|
|
import org.junit.Before;
|
|
import org.junit.Test;
|
|
import org.junit.runner.RunWith;
|
|
import org.junit.runners.JUnit4;
|
|
|
|
/** Unit tests for {@link DeleteHostResourceAction}. */
|
|
@RunWith(JUnit4.class)
|
|
public class DeleteHostResourceActionTest
|
|
extends DeleteEppResourceActionTestCase<DeleteHostResourceAction> {
|
|
|
|
HostResource hostUnused;
|
|
|
|
@Before
|
|
public void setup() throws Exception {
|
|
setupDeleteEppResourceAction(new DeleteHostResourceAction());
|
|
hostUnused = persistActiveHost("ns2.example.tld");
|
|
}
|
|
|
|
@Test
|
|
public void testSuccess_host_referencedByActiveDomain_doesNotGetDeleted() throws Exception {
|
|
hostUsed = persistResource(
|
|
hostUsed.asBuilder()
|
|
.addStatusValue(StatusValue.PENDING_DELETE)
|
|
.build());
|
|
runMapreduceWithKeyParam(Key.create(hostUsed).getString());
|
|
hostUsed = loadByUniqueId(HostResource.class, "ns1.example.tld", now);
|
|
assertAboutHosts().that(hostUsed).doesNotHaveStatusValue(StatusValue.PENDING_DELETE)
|
|
.and().hasDeletionTime(END_OF_TIME);
|
|
domain = loadByUniqueId(DomainResource.class, "example.tld", now);
|
|
assertThat(domain.getNameservers()).contains(Key.create(hostUsed));
|
|
HistoryEntry historyEntry =
|
|
getOnlyHistoryEntryOfType(hostUsed, HistoryEntry.Type.HOST_DELETE_FAILURE);
|
|
assertPollMessageFor(
|
|
historyEntry,
|
|
"TheRegistrar",
|
|
"Can't delete host ns1.example.tld because it is referenced by a domain.");
|
|
}
|
|
|
|
@Test
|
|
public void testSuccess_host_notReferenced_getsDeleted() throws Exception {
|
|
hostUnused = persistResource(
|
|
hostUnused.asBuilder()
|
|
.addStatusValue(StatusValue.PENDING_DELETE)
|
|
.build());
|
|
runMapreduceWithKeyParam(Key.create(hostUnused).getString());
|
|
assertThat(loadByUniqueId(HostResource.class, "ns2.example.tld", now)).isNull();
|
|
HostResource hostBeforeDeletion =
|
|
loadByUniqueId(HostResource.class, "ns2.example.tld", now.minusDays(1));
|
|
assertAboutHosts().that(hostBeforeDeletion).hasDeletionTime(now)
|
|
.and().hasExactlyStatusValues(StatusValue.OK)
|
|
// Note that there will be another history entry of HOST_PENDING_DELETE, but this is
|
|
// added by the flow and not the mapreduce itself.
|
|
.and().hasOnlyOneHistoryEntryWhich().hasType(HistoryEntry.Type.HOST_DELETE);
|
|
HistoryEntry historyEntry =
|
|
getOnlyHistoryEntryOfType(hostBeforeDeletion, HistoryEntry.Type.HOST_DELETE);
|
|
assertPollMessageFor(historyEntry, "TheRegistrar", "Deleted host ns2.example.tld.");
|
|
}
|
|
|
|
@Test
|
|
public void testSuccess_host_referencedByDeletedDomain_getsDeleted() throws Exception {
|
|
hostUsed = persistResource(
|
|
hostUsed.asBuilder()
|
|
.addStatusValue(StatusValue.PENDING_DELETE)
|
|
.build());
|
|
domain = persistResource(
|
|
domain.asBuilder()
|
|
.setDeletionTime(now.minusDays(3))
|
|
.build());
|
|
runMapreduceWithKeyParam(Key.create(hostUsed).getString());
|
|
assertThat(loadByUniqueId(HostResource.class, "ns1.example.tld", now)).isNull();
|
|
HostResource hostBeforeDeletion =
|
|
loadByUniqueId(HostResource.class, "ns1.example.tld", now.minusDays(1));
|
|
assertAboutHosts().that(hostBeforeDeletion).hasDeletionTime(now)
|
|
.and().hasExactlyStatusValues(StatusValue.OK)
|
|
// Note that there will be another history entry of HOST_PENDING_DELETE, but this is
|
|
// added by the flow and not the mapreduce itself.
|
|
.and().hasOnlyOneHistoryEntryWhich().hasType(HistoryEntry.Type.HOST_DELETE);
|
|
HistoryEntry historyEntry =
|
|
getOnlyHistoryEntryOfType(hostBeforeDeletion, HistoryEntry.Type.HOST_DELETE);
|
|
assertPollMessageFor(historyEntry, "TheRegistrar", "Deleted host ns1.example.tld.");
|
|
}
|
|
|
|
@Test
|
|
public void testSuccess_subordinateHost_getsDeleted() throws Exception {
|
|
domain = persistResource(
|
|
newDomainResource("example.tld").asBuilder()
|
|
.setSubordinateHosts(ImmutableSet.of("ns2.example.tld"))
|
|
.build());
|
|
persistResource(
|
|
hostUnused.asBuilder()
|
|
.addStatusValue(StatusValue.PENDING_DELETE)
|
|
.setSuperordinateDomain(Key.create(domain))
|
|
.build());
|
|
runMapreduceWithKeyParam(Key.create(hostUnused).getString());
|
|
// Check that the host is deleted as of now.
|
|
assertThat(loadByUniqueId(HostResource.class, "ns2.example.tld", clock.nowUtc()))
|
|
.isNull();
|
|
assertNoBillingEvents();
|
|
assertThat(loadByUniqueId(DomainResource.class, "example.tld", clock.nowUtc())
|
|
.getSubordinateHosts())
|
|
.isEmpty();
|
|
assertDnsTasksEnqueued("ns2.example.tld");
|
|
HostResource hostBeforeDeletion =
|
|
loadByUniqueId(HostResource.class, "ns2.example.tld", now.minusDays(1));
|
|
assertAboutHosts().that(hostBeforeDeletion).hasDeletionTime(now)
|
|
.and().hasExactlyStatusValues(StatusValue.OK)
|
|
.and().hasOnlyOneHistoryEntryWhich().hasType(HistoryEntry.Type.HOST_DELETE);
|
|
HistoryEntry historyEntry =
|
|
getOnlyHistoryEntryOfType(hostBeforeDeletion, HistoryEntry.Type.HOST_DELETE);
|
|
assertPollMessageFor(historyEntry, "TheRegistrar", "Deleted host ns2.example.tld.");
|
|
}
|
|
|
|
@Test
|
|
public void testFailure_notPendingDelete() throws Exception {
|
|
thrown.expect(
|
|
IllegalStateException.class, "Resource ns2.example.tld is not set as PENDING_DELETE");
|
|
runMapreduceWithKeyParam(Key.create(hostUnused).getString());
|
|
assertThat(
|
|
loadByUniqueId(HostResource.class, "ns2.example.tld", now)).isEqualTo(hostUnused);
|
|
}
|
|
|
|
@Test
|
|
public void testSuccess_notRequestedByOwner_doesNotGetDeleted() throws Exception {
|
|
hostUnused = persistResource(
|
|
hostUnused.asBuilder()
|
|
.addStatusValue(StatusValue.PENDING_DELETE)
|
|
.build());
|
|
Key<HostResource> key = Key.create(hostUnused);
|
|
runMapreduceWithParams(key.getString(), "OtherRegistrar", false);
|
|
hostUnused = loadByUniqueId(HostResource.class, "ns2.example.tld", now);
|
|
assertAboutHosts().that(hostUnused).doesNotHaveStatusValue(StatusValue.PENDING_DELETE)
|
|
.and().hasDeletionTime(END_OF_TIME);
|
|
domain = loadByUniqueId(DomainResource.class, "example.tld", now);
|
|
HistoryEntry historyEntry =
|
|
getOnlyHistoryEntryOfType(hostUnused, HistoryEntry.Type.HOST_DELETE_FAILURE);
|
|
assertPollMessageFor(
|
|
historyEntry,
|
|
"OtherRegistrar",
|
|
"Can't delete host ns2.example.tld because it was transferred prior to deletion.");
|
|
}
|
|
|
|
@Test
|
|
public void testSuccess_notRequestedByOwner_isSuperuser_getsDeleted() throws Exception {
|
|
hostUnused = persistResource(
|
|
hostUnused.asBuilder()
|
|
.addStatusValue(StatusValue.PENDING_DELETE)
|
|
.build());
|
|
Key<HostResource> key = Key.create(hostUnused);
|
|
runMapreduceWithParams(key.getString(), "OtherRegistrar", true);
|
|
assertThat(loadByUniqueId(HostResource.class, "ns2.example.tld", now)).isNull();
|
|
HostResource hostBeforeDeletion =
|
|
loadByUniqueId(HostResource.class, "ns2.example.tld", now.minusDays(1));
|
|
assertAboutHosts().that(hostBeforeDeletion).hasDeletionTime(now)
|
|
.and().hasExactlyStatusValues(StatusValue.OK)
|
|
// Note that there will be another history entry of HOST_PENDING_DELETE, but this is
|
|
// added by the flow and not the mapreduce itself.
|
|
.and().hasOnlyOneHistoryEntryWhich().hasType(HistoryEntry.Type.HOST_DELETE);
|
|
HistoryEntry historyEntry =
|
|
getOnlyHistoryEntryOfType(hostBeforeDeletion, HistoryEntry.Type.HOST_DELETE);
|
|
assertPollMessageFor(historyEntry, "OtherRegistrar", "Deleted host ns2.example.tld.");
|
|
}
|
|
|
|
@Test
|
|
public void testFailure_targetResourceDoesntExist() throws Exception {
|
|
createTld("tld");
|
|
HostResource notPersisted = newHostResource("ns1.example.tld");
|
|
thrown.expect(
|
|
BadRequestException.class,
|
|
"Could not load resource for key: Key<?>(HostResource(\"7-ROID\"))");
|
|
runMapreduceWithKeyParam(Key.create(notPersisted).getString());
|
|
}
|
|
|
|
@Test
|
|
public void testFailure_hostAlreadyDeleted() throws Exception {
|
|
HostResource hostDeleted = persistResource(
|
|
newHostResource("ns3.example.tld").asBuilder()
|
|
.setCreationTimeForTest(clock.nowUtc().minusDays(2))
|
|
.setDeletionTime(clock.nowUtc().minusDays(1))
|
|
.build());
|
|
thrown.expect(
|
|
IllegalStateException.class,
|
|
"Resource ns3.example.tld is already deleted.");
|
|
runMapreduceWithKeyParam(Key.create(hostDeleted).getString());
|
|
}
|
|
}
|