mirror of
https://github.com/google/nomulus
synced 2026-06-09 16:33:02 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f770f6a46d | |||
| e071f5579c | |||
| 6080cd2f7a | |||
| 90f583910e | |||
| a85bf5c30a | |||
| dcfe939c38 | |||
| ae61922318 |
@@ -12,7 +12,7 @@ Nomulus is an open source, scalable, cloud-based service for operating
|
||||
[top-level domains](https://en.wikipedia.org/wiki/Top-level_domain) (TLDs). It
|
||||
is the authoritative source for the TLDs that it runs, meaning that it is
|
||||
responsible for tracking domain name ownership and handling registrations,
|
||||
renewals, availability checks, and WHOIS requests. End-user registrants (i.e.,
|
||||
renewals, availability checks, and RDAP requests. End-user registrants (i.e.,
|
||||
people or companies that want to register a domain name) use an intermediate
|
||||
domain name registrar acting on their behalf to interact with the registry.
|
||||
|
||||
@@ -97,7 +97,7 @@ Nomulus has the following capabilities:
|
||||
for details), and an implementation based on
|
||||
[Google Cloud Secret Manager](https://cloud.google.com/security/products/secret-manager) is
|
||||
available.
|
||||
* **TPC Proxy**: Nomulus is built on top of the [Jetty](https://jetty.org/)
|
||||
* **TCP Proxy**: Nomulus is built on top of the [Jetty](https://jetty.org/)
|
||||
container that implements the [Jakarta Servlet](https://jakarta.ee/specifications/servlet/)
|
||||
specification and only serves HTTP/S traffic. A proxy to translate raw TCP traffic (e.g., EPP)
|
||||
to and from HTTP is provided.
|
||||
|
||||
@@ -9,15 +9,14 @@ expected to change.
|
||||
|
||||
## Deployment
|
||||
|
||||
The webapp is deployed with the nomulus default service war to GKE.
|
||||
During nomulus default service war build task, gradle script triggers the
|
||||
following:
|
||||
The webapp is deployed as part of the default Nomulus GKE service image.
|
||||
During the image build task, the Gradle script triggers the following:
|
||||
|
||||
1) Console webapp build script `buildConsoleWebapp`, which installs
|
||||
dependencies, assembles a compiled ts -> js, minified, optimized static
|
||||
artifact (html, css, js)
|
||||
2) Artifact assembled in step 1 then gets copied to core project web artifact
|
||||
location, so that it can be deployed with the rest of the core webapp
|
||||
2) Artifact assembled in step 1 then gets copied to the jetty webapp resource
|
||||
location, so that it can be staged inside the default GKE service container.
|
||||
|
||||
## Development server
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
<queue>
|
||||
<name>dns-refresh</name>
|
||||
<max-dispatches-per-second>100</max-dispatches-per-second>
|
||||
<max-retry-duration>82800s</max-retry-duration>
|
||||
</queue>
|
||||
|
||||
<!-- Queue for publishing DNS updates in batches. -->
|
||||
@@ -29,6 +30,7 @@
|
||||
<min-backoff>30s</min-backoff>
|
||||
<max-backoff>1800s</max-backoff>
|
||||
<max-doublings>0</max-doublings>
|
||||
<max-retry-duration>82800s</max-retry-duration>
|
||||
</queue>
|
||||
|
||||
<!-- Queue for uploading RDE deposits to the escrow provider. -->
|
||||
@@ -59,6 +61,7 @@
|
||||
<queue>
|
||||
<name>async-host-rename</name>
|
||||
<max-dispatches-per-second>1</max-dispatches-per-second>
|
||||
<max-retry-duration>82800s</max-retry-duration>
|
||||
</queue>
|
||||
|
||||
<!-- Queue for tasks that wait for a Beam pipeline to complete (i.e. Spec11 and invoicing). -->
|
||||
@@ -110,11 +113,12 @@
|
||||
<max-attempts>3</max-attempts>
|
||||
</queue>
|
||||
|
||||
<!-- <!– Queue for async actions that should be run at some point in the future. –>-->
|
||||
<!-- Queue for async actions that should be run at some point in the future.-->
|
||||
<queue>
|
||||
<name>async-actions</name>
|
||||
<max-dispatches-per-second>1</max-dispatches-per-second>
|
||||
<max-concurrent-dispatches>5</max-concurrent-dispatches>
|
||||
<max-retry-duration>82800s</max-retry-duration>
|
||||
</queue>
|
||||
|
||||
</entries>
|
||||
|
||||
-10
@@ -266,16 +266,6 @@
|
||||
<schedule>0 15 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/task/wipeOutContactHistoryPii]]></url>
|
||||
<name>wipeOutContactHistoryPii</name>
|
||||
<description>
|
||||
This job runs weekly to wipe out PII fields of ContactHistory entities
|
||||
that have been in the database for a certain period of time.
|
||||
</description>
|
||||
<schedule>0 15 * * 1</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/task/bsaDownload]]></url>
|
||||
<name>bsaDownload</name>
|
||||
|
||||
-10
@@ -155,16 +155,6 @@
|
||||
<schedule>*/1 * * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/task/wipeOutContactHistoryPii]]></url>
|
||||
<name>wipeOutContactHistoryPii</name>
|
||||
<description>
|
||||
This job runs weekly to wipe out PII fields of ContactHistory entities
|
||||
that have been in the database for a certain period of time.
|
||||
</description>
|
||||
<schedule>0 15 * * 1</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/task/bsaDownload]]></url>
|
||||
<name>bsaDownload</name>
|
||||
|
||||
@@ -118,7 +118,9 @@ public class PasswordResetRequest extends ImmutableObject implements Buildable {
|
||||
checkArgumentNotNull(getInstance().requester, "Requester must be specified");
|
||||
checkArgumentNotNull(getInstance().destinationEmail, "Destination email must be specified");
|
||||
checkArgumentNotNull(getInstance().registrarId, "Registrar ID must be specified");
|
||||
getInstance().verificationCode = UUID.randomUUID().toString();
|
||||
if (getInstance().verificationCode == null) {
|
||||
getInstance().verificationCode = UUID.randomUUID().toString();
|
||||
}
|
||||
return super.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ import jakarta.inject.Qualifier;
|
||||
import jakarta.inject.Singleton;
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Supplier;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@@ -88,8 +89,8 @@ public class AuthModule {
|
||||
TokenVerifier provideIapTokenVerifier(
|
||||
@Config("projectIdNumber") long projectIdNumber,
|
||||
@Named("backendServiceIdMap") Supplier<ImmutableMap<String, Long>> backendServiceIdMap) {
|
||||
com.google.auth.oauth2.TokenVerifier.Builder tokenVerifierBuilder =
|
||||
com.google.auth.oauth2.TokenVerifier.newBuilder().setIssuer(IAP_ISSUER_URL);
|
||||
ConcurrentHashMap<String, com.google.auth.oauth2.TokenVerifier> tokenVerifiers =
|
||||
new ConcurrentHashMap<>();
|
||||
return (String service, String token) -> {
|
||||
Long backendServiceId = backendServiceIdMap.get().get(service);
|
||||
checkNotNull(
|
||||
@@ -98,7 +99,15 @@ public class AuthModule {
|
||||
service,
|
||||
backendServiceIdMap);
|
||||
String audience = String.format(IAP_AUDIENCE_FORMAT, projectIdNumber, backendServiceId);
|
||||
return tokenVerifierBuilder.setAudience(audience).build().verify(token);
|
||||
com.google.auth.oauth2.TokenVerifier verifier =
|
||||
tokenVerifiers.computeIfAbsent(
|
||||
audience,
|
||||
aud ->
|
||||
com.google.auth.oauth2.TokenVerifier.newBuilder()
|
||||
.setIssuer(IAP_ISSUER_URL)
|
||||
.setAudience(aud)
|
||||
.build());
|
||||
return verifier.verify(token);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -283,15 +283,15 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
|
||||
private JsonObject generateExpectedJsonForTwoDomainsNsReply() {
|
||||
return jsonFileBuilder()
|
||||
.addDomain("cat.example", "F-EXAMPLE")
|
||||
.addDomain("cat.lol", "6-LOL")
|
||||
.addDomain("cat.example", domainCatExample.getRepoId())
|
||||
.addDomain("cat.lol", domainCatLol.getRepoId())
|
||||
.load("rdap_domains_two.json");
|
||||
}
|
||||
|
||||
private JsonObject generateExpectedJsonForTwoDomainsCatStarReplySql() {
|
||||
return jsonFileBuilder()
|
||||
.addDomain("cat.lol", "6-LOL")
|
||||
.addDomain("cat2.lol", "B-LOL")
|
||||
.addDomain("cat.lol", domainCatLol.getRepoId())
|
||||
.addDomain("cat2.lol", domainCatLol2.getRepoId())
|
||||
.load("rdap_domains_two.json");
|
||||
}
|
||||
|
||||
@@ -306,7 +306,7 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
minusMonths(clock.now(), 6)));
|
||||
}
|
||||
|
||||
private void createManyDomainsAndHosts(
|
||||
private ImmutableList<Domain> createManyDomainsAndHosts(
|
||||
int numActiveDomains, int numTotalDomainsPerActiveDomain, int numHosts) {
|
||||
ImmutableSet.Builder<VKey<Host>> hostKeysBuilder = new ImmutableSet.Builder<>();
|
||||
ImmutableSet.Builder<String> subordinateHostnamesBuilder = new ImmutableSet.Builder<>();
|
||||
@@ -340,7 +340,7 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
}
|
||||
domainsBuilder.add(builder.build());
|
||||
}
|
||||
persistResources(domainsBuilder.build());
|
||||
return persistResources(domainsBuilder.build());
|
||||
}
|
||||
|
||||
private void checkNumberOfDomainsInResult(JsonObject obj, int expected) {
|
||||
@@ -353,10 +353,10 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
requestType,
|
||||
queryString,
|
||||
jsonFileBuilder()
|
||||
.addDomain("cat.lol", "6-LOL")
|
||||
.addDomain("cat.lol", domainCatLol.getRepoId())
|
||||
.addRegistrar("Yes Virginia <script>")
|
||||
.addNameserver("ns1.cat.lol", "2-ROID")
|
||||
.addNameserver("ns2.cat.lol", "4-ROID")
|
||||
.addNameserver("ns1.cat.lol", hostNs1CatLol.getRepoId())
|
||||
.addNameserver("ns2.cat.lol", hostNs2CatLol.getRepoId())
|
||||
.setNextQuery(queryString)
|
||||
.load(filename));
|
||||
}
|
||||
@@ -367,10 +367,10 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
requestType,
|
||||
queryString,
|
||||
jsonFileBuilder()
|
||||
.addDomain("cat2.lol", "B-LOL")
|
||||
.addDomain("cat2.lol", domainCatLol2.getRepoId())
|
||||
.addRegistrar("Yes Virginia <script>")
|
||||
.addNameserver("ns1.cat.example", "7-ROID")
|
||||
.addNameserver("ns2.dog.lol", "9-ROID")
|
||||
.addNameserver("ns1.cat.example", hostNameToHostMap.get("ns1.cat.example").getRepoId())
|
||||
.addNameserver("ns2.dog.lol", hostNameToHostMap.get("ns2.dog.lol").getRepoId())
|
||||
.setNextQuery(queryString)
|
||||
.load(filename));
|
||||
}
|
||||
@@ -679,10 +679,11 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
RequestType.NAME,
|
||||
"cat.example",
|
||||
jsonFileBuilder()
|
||||
.addDomain("cat.example", "F-EXAMPLE")
|
||||
.addDomain("cat.example", domainCatExample.getRepoId())
|
||||
.addRegistrar("St. John Chrysostom")
|
||||
.addNameserver("ns1.cat.lol", "2-ROID")
|
||||
.addNameserver("ns2.external.tld", "D-ROID")
|
||||
.addNameserver("ns1.cat.lol", hostNs1CatLol.getRepoId())
|
||||
.addNameserver(
|
||||
"ns2.external.tld", hostNameToHostMap.get("ns2.external.tld").getRepoId())
|
||||
.load("rdap_domain.json"));
|
||||
verifyMetrics(SearchType.BY_DOMAIN_NAME, Optional.of(1L));
|
||||
}
|
||||
@@ -693,10 +694,10 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
RequestType.NAME,
|
||||
"cat.みんな",
|
||||
jsonFileBuilder()
|
||||
.addDomain("cat.みんな", "15-Q9JYB4C")
|
||||
.addDomain("cat.みんな", domainIdn.getRepoId())
|
||||
.addRegistrar("みんな")
|
||||
.addNameserver("ns1.cat.みんな", "11-ROID")
|
||||
.addNameserver("ns2.cat.みんな", "13-ROID")
|
||||
.addNameserver("ns1.cat.みんな", hostNameToHostMap.get("ns1.cat.xn--q9jyb4c").getRepoId())
|
||||
.addNameserver("ns2.cat.みんな", hostNameToHostMap.get("ns2.cat.xn--q9jyb4c").getRepoId())
|
||||
.load("rdap_domain_unicode_with_unicode_nameservers.json"));
|
||||
// The unicode gets translated to ASCII before getting parsed into a search pattern.
|
||||
metricPrefixLength = 15;
|
||||
@@ -709,10 +710,10 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
RequestType.NAME,
|
||||
"cat.xn--q9jyb4c",
|
||||
jsonFileBuilder()
|
||||
.addDomain("cat.みんな", "15-Q9JYB4C")
|
||||
.addDomain("cat.みんな", domainIdn.getRepoId())
|
||||
.addRegistrar("みんな")
|
||||
.addNameserver("ns1.cat.みんな", "11-ROID")
|
||||
.addNameserver("ns2.cat.みんな", "13-ROID")
|
||||
.addNameserver("ns1.cat.みんな", hostNameToHostMap.get("ns1.cat.xn--q9jyb4c").getRepoId())
|
||||
.addNameserver("ns2.cat.みんな", hostNameToHostMap.get("ns2.cat.xn--q9jyb4c").getRepoId())
|
||||
.load("rdap_domain_unicode_with_unicode_nameservers.json"));
|
||||
verifyMetrics(SearchType.BY_DOMAIN_NAME, Optional.of(1L));
|
||||
}
|
||||
@@ -723,10 +724,10 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
RequestType.NAME,
|
||||
"cat.1.test",
|
||||
jsonFileBuilder()
|
||||
.addDomain("cat.1.test", "1B-1TEST")
|
||||
.addDomain("cat.1.test", domainMultipart.getRepoId())
|
||||
.addRegistrar("1.test")
|
||||
.addNameserver("ns1.cat.1.test", "17-ROID")
|
||||
.addNameserver("ns2.cat.2.test", "19-ROID")
|
||||
.addNameserver("ns1.cat.1.test", hostNameToHostMap.get("ns1.cat.1.test").getRepoId())
|
||||
.addNameserver("ns2.cat.2.test", hostNameToHostMap.get("ns2.cat.2.test").getRepoId())
|
||||
.load("rdap_domain.json"));
|
||||
verifyMetrics(SearchType.BY_DOMAIN_NAME, Optional.of(1L));
|
||||
}
|
||||
@@ -737,10 +738,10 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
RequestType.NAME,
|
||||
"ca*.1.test",
|
||||
jsonFileBuilder()
|
||||
.addDomain("cat.1.test", "1B-1TEST")
|
||||
.addDomain("cat.1.test", domainMultipart.getRepoId())
|
||||
.addRegistrar("1.test")
|
||||
.addNameserver("ns1.cat.1.test", "17-ROID")
|
||||
.addNameserver("ns2.cat.2.test", "19-ROID")
|
||||
.addNameserver("ns1.cat.1.test", hostNameToHostMap.get("ns1.cat.1.test").getRepoId())
|
||||
.addNameserver("ns2.cat.2.test", hostNameToHostMap.get("ns2.cat.2.test").getRepoId())
|
||||
.load("rdap_domain.json"));
|
||||
verifyMetrics(SearchType.BY_DOMAIN_NAME, Optional.of(1L));
|
||||
}
|
||||
@@ -813,10 +814,10 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
.that(generateActualJson(RequestType.NAME, "cat.*"))
|
||||
.isEqualTo(
|
||||
jsonFileBuilder()
|
||||
.addDomain("cat.1.test", "1B-1TEST")
|
||||
.addDomain("cat.example", "F-EXAMPLE")
|
||||
.addDomain("cat.lol", "6-LOL")
|
||||
.addDomain("cat.みんな", "15-Q9JYB4C")
|
||||
.addDomain("cat.1.test", domainMultipart.getRepoId())
|
||||
.addDomain("cat.example", domainCatExample.getRepoId())
|
||||
.addDomain("cat.lol", domainCatLol.getRepoId())
|
||||
.addDomain("cat.みんな", domainIdn.getRepoId())
|
||||
.load("rdap_domains_four_with_one_unicode.json"));
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
verifyMetrics(SearchType.BY_DOMAIN_NAME, Optional.of(4L));
|
||||
@@ -851,10 +852,10 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
.that(generateActualJson(RequestType.NAME, "cat*"))
|
||||
.isEqualTo(
|
||||
jsonFileBuilder()
|
||||
.addDomain("cat.1.test", "1B-1TEST")
|
||||
.addDomain("cat.example", "F-EXAMPLE")
|
||||
.addDomain("cat.lol", "6-LOL")
|
||||
.addDomain("cat.みんな", "15-Q9JYB4C")
|
||||
.addDomain("cat.1.test", domainMultipart.getRepoId())
|
||||
.addDomain("cat.example", domainCatExample.getRepoId())
|
||||
.addDomain("cat.lol", domainCatLol.getRepoId())
|
||||
.addDomain("cat.みんな", domainIdn.getRepoId())
|
||||
.setNextQuery("name=cat*&cursor=Y2F0LnhuLS1xOWp5YjRj")
|
||||
.load("rdap_domains_four_with_one_unicode_truncated.json"));
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
@@ -970,15 +971,15 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
// This is not exactly desired behavior, but expected: There are enough domains to fill a full
|
||||
// result set, but there are so many deleted domains that we run out of patience before we work
|
||||
// our way through all of them.
|
||||
createManyDomainsAndHosts(4, 50, 2);
|
||||
ImmutableList<Domain> domains = createManyDomainsAndHosts(4, 50, 2);
|
||||
rememberWildcardType("domain*.lol");
|
||||
assertAboutJson()
|
||||
.that(generateActualJson(RequestType.NAME, "domain*.lol"))
|
||||
.isEqualTo(
|
||||
jsonFileBuilder()
|
||||
.addDomain("domain100.lol", "8E-LOL")
|
||||
.addDomain("domain150.lol", "5C-LOL")
|
||||
.addDomain("domain200.lol", "2A-LOL")
|
||||
.addDomain("domain100.lol", domains.get(100).getRepoId())
|
||||
.addDomain("domain150.lol", domains.get(50).getRepoId())
|
||||
.addDomain("domain200.lol", domains.get(0).getRepoId())
|
||||
.addDomain("domainunused.lol", "unused-LOL")
|
||||
.load("rdap_incomplete_domain_result_set.json"));
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
@@ -990,28 +991,28 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
|
||||
@Test
|
||||
void testDomainMatch_nontruncatedResultsSet() {
|
||||
createManyDomainsAndHosts(4, 1, 2);
|
||||
ImmutableList<Domain> domains = createManyDomainsAndHosts(4, 1, 2);
|
||||
runSuccessfulTestWithFourDomains(
|
||||
RequestType.NAME,
|
||||
"domain*.lol",
|
||||
"2D-LOL",
|
||||
"2C-LOL",
|
||||
"2B-LOL",
|
||||
"2A-LOL",
|
||||
domains.get(3).getRepoId(),
|
||||
domains.get(2).getRepoId(),
|
||||
domains.get(1).getRepoId(),
|
||||
domains.get(0).getRepoId(),
|
||||
"rdap_nontruncated_domains.json");
|
||||
verifyMetrics(SearchType.BY_DOMAIN_NAME, Optional.of(4L));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDomainMatch_truncatedResultsSet() {
|
||||
createManyDomainsAndHosts(5, 1, 2);
|
||||
ImmutableList<Domain> domains = createManyDomainsAndHosts(5, 1, 2);
|
||||
runSuccessfulTestWithFourDomains(
|
||||
RequestType.NAME,
|
||||
"domain*.lol",
|
||||
"2E-LOL",
|
||||
"2D-LOL",
|
||||
"2C-LOL",
|
||||
"2B-LOL",
|
||||
domains.get(4).getRepoId(),
|
||||
domains.get(3).getRepoId(),
|
||||
domains.get(2).getRepoId(),
|
||||
domains.get(1).getRepoId(),
|
||||
"name=domain*.lol&cursor=ZG9tYWluNC5sb2w%3D",
|
||||
"rdap_domains_four_truncated.json");
|
||||
verifyMetrics(SearchType.BY_DOMAIN_NAME, Optional.of(5L), IncompletenessWarningType.TRUNCATED);
|
||||
@@ -1019,16 +1020,16 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
|
||||
@Test
|
||||
void testDomainMatch_tldSearchOrderedProperly_sql() {
|
||||
createManyDomainsAndHosts(4, 1, 2);
|
||||
ImmutableList<Domain> domains = createManyDomainsAndHosts(4, 1, 2);
|
||||
rememberWildcardType("*.lol");
|
||||
assertAboutJson()
|
||||
.that(generateActualJson(RequestType.NAME, "*.lol"))
|
||||
.isEqualTo(
|
||||
jsonFileBuilder()
|
||||
.addDomain("cat.lol", "6-LOL")
|
||||
.addDomain("cat2.lol", "B-LOL")
|
||||
.addDomain("domain1.lol", "2D-LOL")
|
||||
.addDomain("domain2.lol", "2C-LOL")
|
||||
.addDomain("cat.lol", domainCatLol.getRepoId())
|
||||
.addDomain("cat2.lol", domainCatLol2.getRepoId())
|
||||
.addDomain("domain1.lol", domains.get(3).getRepoId())
|
||||
.addDomain("domain2.lol", domains.get(2).getRepoId())
|
||||
.setNextQuery("name=*.lol&cursor=ZG9tYWluMi5sb2w%3D")
|
||||
.load("rdap_domains_four_truncated.json"));
|
||||
verifyMetrics(SearchType.BY_DOMAIN_NAME, Optional.of(5L), IncompletenessWarningType.TRUNCATED);
|
||||
@@ -1038,14 +1039,14 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
void testDomainMatch_reallyTruncatedResultsSet() {
|
||||
// Don't use 10 or more domains for this test, because domain10.lol will come before
|
||||
// domain2.lol, and you'll get the wrong domains in the result set.
|
||||
createManyDomainsAndHosts(9, 1, 2);
|
||||
ImmutableList<Domain> domains = createManyDomainsAndHosts(9, 1, 2);
|
||||
runSuccessfulTestWithFourDomains(
|
||||
RequestType.NAME,
|
||||
"domain*.lol",
|
||||
"32-LOL",
|
||||
"31-LOL",
|
||||
"30-LOL",
|
||||
"2F-LOL",
|
||||
domains.get(8).getRepoId(),
|
||||
domains.get(7).getRepoId(),
|
||||
domains.get(6).getRepoId(),
|
||||
domains.get(5).getRepoId(),
|
||||
"name=domain*.lol&cursor=ZG9tYWluNC5sb2w%3D",
|
||||
"rdap_domains_four_truncated.json");
|
||||
verifyMetrics(SearchType.BY_DOMAIN_NAME, Optional.of(5L), IncompletenessWarningType.TRUNCATED);
|
||||
@@ -1053,16 +1054,16 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
|
||||
@Test
|
||||
void testDomainMatch_truncatedResultsAfterMultipleChunks_sql() {
|
||||
createManyDomainsAndHosts(5, 6, 2);
|
||||
ImmutableList<Domain> domains = createManyDomainsAndHosts(5, 6, 2);
|
||||
rememberWildcardType("domain*.lol");
|
||||
assertAboutJson()
|
||||
.that(generateActualJson(RequestType.NAME, "domain*.lol"))
|
||||
.isEqualTo(
|
||||
jsonFileBuilder()
|
||||
.addDomain("domain12.lol", "3C-LOL")
|
||||
.addDomain("domain18.lol", "36-LOL")
|
||||
.addDomain("domain24.lol", "30-LOL")
|
||||
.addDomain("domain30.lol", "2A-LOL")
|
||||
.addDomain("domain12.lol", domains.get(18).getRepoId())
|
||||
.addDomain("domain18.lol", domains.get(12).getRepoId())
|
||||
.addDomain("domain24.lol", domains.get(6).getRepoId())
|
||||
.addDomain("domain30.lol", domains.get(0).getRepoId())
|
||||
.setNextQuery("name=domain*.lol&cursor=ZG9tYWluMzAubG9s")
|
||||
.load("rdap_domains_four_truncated.json"));
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
@@ -1261,10 +1262,10 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
RequestType.NS_LDH_NAME,
|
||||
"ns1.cat.xn--q9jyb4c",
|
||||
jsonFileBuilder()
|
||||
.addDomain("cat.みんな", "15-Q9JYB4C")
|
||||
.addDomain("cat.みんな", domainIdn.getRepoId())
|
||||
.addRegistrar("みんな")
|
||||
.addNameserver("ns1.cat.みんな", "11-ROID")
|
||||
.addNameserver("ns2.cat.みんな", "13-ROID")
|
||||
.addNameserver("ns1.cat.みんな", hostNameToHostMap.get("ns1.cat.xn--q9jyb4c").getRepoId())
|
||||
.addNameserver("ns2.cat.みんな", hostNameToHostMap.get("ns2.cat.xn--q9jyb4c").getRepoId())
|
||||
.load("rdap_domain_unicode_with_unicode_nameservers.json"));
|
||||
verifyMetrics(SearchType.BY_NAMESERVER_NAME, 1, 1);
|
||||
}
|
||||
@@ -1275,10 +1276,10 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
RequestType.NS_LDH_NAME,
|
||||
"ns1.cat.1.test",
|
||||
jsonFileBuilder()
|
||||
.addDomain("cat.1.test", "1B-1TEST")
|
||||
.addDomain("cat.1.test", domainMultipart.getRepoId())
|
||||
.addRegistrar("1.test")
|
||||
.addNameserver("ns1.cat.1.test", "17-ROID")
|
||||
.addNameserver("ns2.cat.2.test", "19-ROID")
|
||||
.addNameserver("ns1.cat.1.test", hostNameToHostMap.get("ns1.cat.1.test").getRepoId())
|
||||
.addNameserver("ns2.cat.2.test", hostNameToHostMap.get("ns2.cat.2.test").getRepoId())
|
||||
.load("rdap_domain.json"));
|
||||
verifyMetrics(SearchType.BY_NAMESERVER_NAME, 1, 1);
|
||||
}
|
||||
@@ -1289,10 +1290,10 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
RequestType.NS_LDH_NAME,
|
||||
"ns*.cat.1.test",
|
||||
jsonFileBuilder()
|
||||
.addDomain("cat.1.test", "1B-1TEST")
|
||||
.addDomain("cat.1.test", domainMultipart.getRepoId())
|
||||
.addRegistrar("1.test")
|
||||
.addNameserver("ns1.cat.1.test", "17-ROID")
|
||||
.addNameserver("ns2.cat.2.test", "19-ROID")
|
||||
.addNameserver("ns1.cat.1.test", hostNameToHostMap.get("ns1.cat.1.test").getRepoId())
|
||||
.addNameserver("ns2.cat.2.test", hostNameToHostMap.get("ns2.cat.2.test").getRepoId())
|
||||
.load("rdap_domain.json"));
|
||||
verifyMetrics(SearchType.BY_NAMESERVER_NAME, 1, 1);
|
||||
}
|
||||
@@ -1430,28 +1431,28 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
|
||||
@Test
|
||||
void testNameserverMatch_nontruncatedResultsSet() {
|
||||
createManyDomainsAndHosts(4, 1, 2);
|
||||
ImmutableList<Domain> domains = createManyDomainsAndHosts(4, 1, 2);
|
||||
runSuccessfulTestWithFourDomains(
|
||||
RequestType.NS_LDH_NAME,
|
||||
"ns1.domain1.lol",
|
||||
"2D-LOL",
|
||||
"2C-LOL",
|
||||
"2B-LOL",
|
||||
"2A-LOL",
|
||||
domains.get(3).getRepoId(),
|
||||
domains.get(2).getRepoId(),
|
||||
domains.get(1).getRepoId(),
|
||||
domains.get(0).getRepoId(),
|
||||
"rdap_nontruncated_domains.json");
|
||||
verifyMetrics(SearchType.BY_NAMESERVER_NAME, Optional.of(4L), Optional.of(1L));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNameserverMatch_truncatedResultsSet() {
|
||||
createManyDomainsAndHosts(5, 1, 2);
|
||||
ImmutableList<Domain> domains = createManyDomainsAndHosts(5, 1, 2);
|
||||
runSuccessfulTestWithFourDomains(
|
||||
RequestType.NS_LDH_NAME,
|
||||
"ns1.domain1.lol",
|
||||
"2E-LOL",
|
||||
"2D-LOL",
|
||||
"2C-LOL",
|
||||
"2B-LOL",
|
||||
domains.get(4).getRepoId(),
|
||||
domains.get(3).getRepoId(),
|
||||
domains.get(2).getRepoId(),
|
||||
domains.get(1).getRepoId(),
|
||||
"nsLdhName=ns1.domain1.lol&cursor=ZG9tYWluNC5sb2w%3D",
|
||||
"rdap_domains_four_truncated.json");
|
||||
verifyMetrics(
|
||||
@@ -1463,14 +1464,14 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
|
||||
@Test
|
||||
void testNameserverMatch_reallyTruncatedResultsSet() {
|
||||
createManyDomainsAndHosts(9, 1, 2);
|
||||
ImmutableList<Domain> domains = createManyDomainsAndHosts(9, 1, 2);
|
||||
runSuccessfulTestWithFourDomains(
|
||||
RequestType.NS_LDH_NAME,
|
||||
"ns1.domain1.lol",
|
||||
"32-LOL",
|
||||
"31-LOL",
|
||||
"30-LOL",
|
||||
"2F-LOL",
|
||||
domains.get(8).getRepoId(),
|
||||
domains.get(7).getRepoId(),
|
||||
domains.get(6).getRepoId(),
|
||||
domains.get(5).getRepoId(),
|
||||
"nsLdhName=ns1.domain1.lol&cursor=ZG9tYWluNC5sb2w%3D",
|
||||
"rdap_domains_four_truncated.json");
|
||||
verifyMetrics(
|
||||
@@ -1484,16 +1485,16 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
void testNameserverMatch_duplicatesNotTruncated() {
|
||||
// 36 nameservers for each of 4 domains; these should translate into two fetches, which should
|
||||
// not trigger the truncation warning because all the domains will be duplicates.
|
||||
createManyDomainsAndHosts(4, 1, 36);
|
||||
ImmutableList<Domain> domains = createManyDomainsAndHosts(4, 1, 36);
|
||||
rememberWildcardType("ns*.domain1.lol");
|
||||
assertAboutJson()
|
||||
.that(generateActualJson(RequestType.NS_LDH_NAME, "ns*.domain1.lol"))
|
||||
.isEqualTo(
|
||||
jsonFileBuilder()
|
||||
.addDomain("domain1.lol", "71-LOL")
|
||||
.addDomain("domain2.lol", "70-LOL")
|
||||
.addDomain("domain3.lol", "6F-LOL")
|
||||
.addDomain("domain4.lol", "6E-LOL")
|
||||
.addDomain("domain1.lol", domains.get(3).getRepoId())
|
||||
.addDomain("domain2.lol", domains.get(2).getRepoId())
|
||||
.addDomain("domain3.lol", domains.get(1).getRepoId())
|
||||
.addDomain("domain4.lol", domains.get(0).getRepoId())
|
||||
.load("rdap_nontruncated_domains.json"));
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
verifyMetrics(SearchType.BY_NAMESERVER_NAME, Optional.of(4L), Optional.of(36L));
|
||||
@@ -1501,14 +1502,14 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
|
||||
@Test
|
||||
void testNameserverMatch_incompleteResultsSet() {
|
||||
createManyDomainsAndHosts(2, 1, 41);
|
||||
ImmutableList<Domain> domains = createManyDomainsAndHosts(2, 1, 41);
|
||||
rememberWildcardType("ns*.domain1.lol");
|
||||
assertAboutJson()
|
||||
.that(generateActualJson(RequestType.NS_LDH_NAME, "ns*.domain1.lol"))
|
||||
.isEqualTo(
|
||||
jsonFileBuilder()
|
||||
.addDomain("domain1.lol", "79-LOL")
|
||||
.addDomain("domain2.lol", "78-LOL")
|
||||
.addDomain("domain1.lol", domains.get(1).getRepoId())
|
||||
.addDomain("domain2.lol", domains.get(0).getRepoId())
|
||||
.load("rdap_incomplete_domains.json"));
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
verifyMetrics(
|
||||
@@ -1641,10 +1642,10 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
.isEqualTo(
|
||||
wrapInSearchReply(
|
||||
jsonFileBuilder()
|
||||
.addDomain("cat.lol", "6-LOL")
|
||||
.addDomain("cat.lol", domainCatLol.getRepoId())
|
||||
.addRegistrar("Yes Virginia <script>")
|
||||
.addNameserver("ns1.cat.lol", "2-ROID")
|
||||
.addNameserver("ns2.cat.lol", "4-ROID")
|
||||
.addNameserver("ns1.cat.lol", hostNs1CatLol.getRepoId())
|
||||
.addNameserver("ns2.cat.lol", hostNs2CatLol.getRepoId())
|
||||
.load("rdap_domain.json")));
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
verifyMetrics(SearchType.BY_NAMESERVER_ADDRESS, 1, 1);
|
||||
@@ -1667,28 +1668,28 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
|
||||
@Test
|
||||
void testAddressMatch_nontruncatedResultsSet() {
|
||||
createManyDomainsAndHosts(4, 1, 2);
|
||||
ImmutableList<Domain> domains = createManyDomainsAndHosts(4, 1, 2);
|
||||
runSuccessfulTestWithFourDomains(
|
||||
RequestType.NS_IP,
|
||||
"5.5.5.1",
|
||||
"2D-LOL",
|
||||
"2C-LOL",
|
||||
"2B-LOL",
|
||||
"2A-LOL",
|
||||
domains.get(3).getRepoId(),
|
||||
domains.get(2).getRepoId(),
|
||||
domains.get(1).getRepoId(),
|
||||
domains.get(0).getRepoId(),
|
||||
"rdap_nontruncated_domains.json");
|
||||
verifyMetrics(SearchType.BY_NAMESERVER_ADDRESS, 4, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddressMatch_truncatedResultsSet() {
|
||||
createManyDomainsAndHosts(5, 1, 2);
|
||||
ImmutableList<Domain> domains = createManyDomainsAndHosts(5, 1, 2);
|
||||
runSuccessfulTestWithFourDomains(
|
||||
RequestType.NS_IP,
|
||||
"5.5.5.1",
|
||||
"2E-LOL",
|
||||
"2D-LOL",
|
||||
"2C-LOL",
|
||||
"2B-LOL",
|
||||
domains.get(4).getRepoId(),
|
||||
domains.get(3).getRepoId(),
|
||||
domains.get(2).getRepoId(),
|
||||
domains.get(1).getRepoId(),
|
||||
"nsIp=5.5.5.1&cursor=ZG9tYWluNC5sb2w%3D",
|
||||
"rdap_domains_four_truncated.json");
|
||||
verifyMetrics(
|
||||
@@ -1700,14 +1701,14 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
|
||||
|
||||
@Test
|
||||
void testAddressMatch_reallyTruncatedResultsSet() {
|
||||
createManyDomainsAndHosts(9, 1, 2);
|
||||
ImmutableList<Domain> domains = createManyDomainsAndHosts(9, 1, 2);
|
||||
runSuccessfulTestWithFourDomains(
|
||||
RequestType.NS_IP,
|
||||
"5.5.5.1",
|
||||
"32-LOL",
|
||||
"31-LOL",
|
||||
"30-LOL",
|
||||
"2F-LOL",
|
||||
domains.get(8).getRepoId(),
|
||||
domains.get(7).getRepoId(),
|
||||
domains.get(6).getRepoId(),
|
||||
domains.get(5).getRepoId(),
|
||||
"nsIp=5.5.5.1&cursor=ZG9tYWluNC5sb2w%3D",
|
||||
"rdap_domains_four_truncated.json");
|
||||
verifyMetrics(
|
||||
|
||||
+10
@@ -85,6 +85,16 @@ public class PasswordResetVerifyActionTest extends ConsoleActionBaseTestCase {
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_post_replay() throws Exception {
|
||||
createAction("POST", verificationCode, "newPassword1").run();
|
||||
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
|
||||
|
||||
// Attempting to reuse the same code should fail
|
||||
createAction("POST", verificationCode, "newPassword2").run();
|
||||
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_BAD_REQUEST);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_get_invalidVerificationCode() throws Exception {
|
||||
createAction("GET", "invalid", null).run();
|
||||
|
||||
@@ -12,7 +12,7 @@ for more information on using webdriver.
|
||||
2. Missing golden images
|
||||
* If you added a new test using screenshot comparison, you have to generate
|
||||
the golden image for that test in advance and copy it to
|
||||
[goldens/](https://github.com/google/nomulus/tree/master/core/src/test/java/google/registry/webdriver/goldens)
|
||||
[goldens/](https://github.com/google/nomulus/tree/master/core/src/test/resources/google/registry/webdriver/goldens)
|
||||
folder. There
|
||||
is an auxiliary Gradle build task to help with this, and here are some examples:
|
||||
```shell
|
||||
|
||||
+69
-4
@@ -7,11 +7,11 @@ utilities.
|
||||
|
||||
The following links are the ER diagrams generated from the current SQL schema:
|
||||
|
||||
* [Full ER diagram](https://storage.googleapis.com/domain-registry-dev-er-diagram/full_er_diagram.html):
|
||||
* [Full ER diagram](https://storage.googleapis.com/domain-registry-dev-er-diagram/full_er_diagram.html):
|
||||
shows all columns, foreign keys and indexes.
|
||||
|
||||
* [Brief ER diagram](https://storage.googleapis.com/domain-registry-dev-er-diagram/brief_er_diagram.html):
|
||||
shows only significant columns, such as primary and foreign key columns, and
|
||||
* [Brief ER diagram](https://storage.googleapis.com/domain-registry-dev-er-diagram/brief_er_diagram.html):
|
||||
shows only significant columns, such as primary and foreign key columns, and
|
||||
columns that are part of unique indexes.
|
||||
|
||||
### Database roles and privileges
|
||||
@@ -55,7 +55,7 @@ following steps:
|
||||
one. The generated SQL file from the previous step should help. New create
|
||||
table statements can be used as is, whereas alter table statements should be
|
||||
written to change any existing tables.
|
||||
|
||||
|
||||
If an incremental file changes more than one schema element (table, index,
|
||||
or sequence), it MAY hit deadlocks when applied on sandbox/production where
|
||||
it'll be competing against live traffic that may also be locking said
|
||||
@@ -132,6 +132,71 @@ update the Java to no longer contain the old column, wait for a deployment, and
|
||||
then remove the old column. A rename operation requires the most complicated
|
||||
series of steps to complete, as it is effectively an add followed by a remove.
|
||||
|
||||
### Refactoring Column Types (Expand and Contract)
|
||||
|
||||
When refactoring a column (such as changing its type, e.g., from a basic `boolean` to an `hstore` timed transition map), you must be extremely careful to avoid breaking running servers during rolling deployments.
|
||||
|
||||
Because the database schema change is deployed *before* the new server code is running on all instances, the database must remain compatible with both the old and new Java code at all times. This is achieved using the **Expand and Contract** pattern:
|
||||
|
||||
* **Case A: The old column is NOT mapped in Java.**
|
||||
If the old column exists in the database but was never mapped as a field in any ORM entity (i.e., it is "dead weight" in the schema), you can safely drop it immediately in the first PR. The running servers do not know it exists, so dropping it will not cause any errors.
|
||||
* **Case B: The old column IS mapped in Java.**
|
||||
If the old column is actively mapped in Java, you **cannot** drop it in the first PR. Doing so will immediately crash the running servers. Instead, you must perform a three-step migration across separate releases:
|
||||
1. **First PR (DB-only):** Add the new column as nullable, migrate data from the old column, and keep the old column.
|
||||
2. **Second PR (Java-only):** Update the Java ORM classes to map only the new column and ignore the old one. Wait for this to be fully deployed to 100% of production instances.
|
||||
3. **Third PR (DB-only Cleanup):** Create a new Flyway migration to safely drop the old column from the database.
|
||||
|
||||
### Writing Safe NOT NULL Migrations for Transition Maps
|
||||
|
||||
Nomulus avoids database-level `DEFAULT` constraints on timed transition properties (like `create_billing_cost_transitions` or `expiry_access_period_transitions`) in the long run to ensure that the application layer explicitly manages the data and fails fast if it fails to initialize a field.
|
||||
|
||||
However, during a Two-PR deployment, the database schema change (PR 1) is live in production *before* the new Java code (PR 2) is deployed. If you add a new column as `NOT NULL` with no default in the first PR, any new inserts from the running old Java code (which doesn't know about the column) will immediately fail with a constraint violation, causing production write downtime.
|
||||
|
||||
To satisfy both the `NOT NULL` requirement (for consistency) and backward compatibility, you must use the **Temporary Database Default** pattern across three phases:
|
||||
|
||||
1. **PR 1 (DB Schema Change):**
|
||||
* Add the column as `NOT NULL` with a temporary database-level `DEFAULT` value matching the initial state. This allows the old Java code to continue inserting rows (the database will automatically apply the default value for the missing column).
|
||||
* Leave a `TODO` comment in the Flyway SQL migration script to remind developers to drop the default in a subsequent release.
|
||||
* **PostgreSQL hstore Cast:** When writing transition map updates or defaults in SQL, **PostgreSQL requires an explicit `::hstore` cast** on string literals, otherwise the migration will fail with a type mismatch:
|
||||
```sql
|
||||
ALTER TABLE "Tld" ADD COLUMN expiry_access_period_transitions hstore
|
||||
DEFAULT '"1970-01-01T00:00:00.000Z"=>"DISABLED"'::hstore NOT NULL;
|
||||
```
|
||||
|
||||
2. **PR 2 (Java Implementation):**
|
||||
* Deploy the Java changes that map the column and ensure the application layer always explicitly sets the value (e.g., in the entity builder or via Java-level defaults).
|
||||
* Leave a `TODO` comment in the Java code near the `@Column` annotation referencing the plan to drop the DB default.
|
||||
|
||||
3. **PR 3 (DB Cleanup - Contract Phase):**
|
||||
* Once the new Java code is fully deployed to 100% of production instances, create a new subsequent Flyway migration to safely drop the temporary database-level default constraint:
|
||||
```sql
|
||||
-- Drop the temporary default constraint to restore the fail-fast invariant in Java
|
||||
ALTER TABLE "Tld" ALTER COLUMN expiry_access_period_transitions DROP DEFAULT;
|
||||
```
|
||||
* Remove the `TODO` comments from the SQL and Java files.
|
||||
|
||||
### Recommended Git Workflow for Two-PR Splits
|
||||
|
||||
To cleanly manage a two-PR split where the second branch depends on the first, use the following chained branch workflow:
|
||||
|
||||
1. Implement and verify all your changes (both DB and Java) on a single implementation branch (e.g., `feature-impl`). Commit all changes in a single commit.
|
||||
2. Create the database-only branch off `master`:
|
||||
```shell
|
||||
$ git checkout master
|
||||
$ git checkout -b feature-db
|
||||
```
|
||||
3. Checkout only the database-related files from your implementation branch and commit them:
|
||||
```shell
|
||||
$ git checkout feature-impl -- db/src/main/resources/sql/flyway/ db/src/main/resources/sql/flyway.txt db/src/main/resources/sql/schema/nomulus.golden.sql db/src/main/resources/sql/er_diagram/
|
||||
$ git commit -m "Implement database schema for feature"
|
||||
```
|
||||
4. Switch back to your implementation branch and rebase it against the database branch:
|
||||
```shell
|
||||
$ git checkout feature-impl
|
||||
$ git rebase feature-db
|
||||
```
|
||||
Git will automatically detect that the database changes are already present in the parent branch and will skip them, leaving your implementation commit containing **only** the Java changes, tests, and the ORM-generated `db-schema.sql.generated`!
|
||||
|
||||
### Summary of Schema Tests
|
||||
|
||||
#### The Golden Schema Test
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -220,3 +220,4 @@ V219__domain_history_package_token_idx.sql
|
||||
V220__domain_package_token_idx.sql
|
||||
V221__remove_contact_history.sql
|
||||
V222__remove_contact.sql
|
||||
V223__tld_change_xap_enabled_to_transitions.sql
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
-- Copyright 2026 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.
|
||||
|
||||
-- Drop the old unused boolean column. Since it was never mapped in Tld.java
|
||||
-- on master, no running servers map or use it, making it completely safe to drop.
|
||||
ALTER TABLE "Tld" DROP COLUMN expiry_access_period_enabled;
|
||||
|
||||
-- Add the new transitions column as NOT NULL with a temporary DEFAULT value to
|
||||
-- ensure backward compatibility with the running servers (old Java code) during
|
||||
-- the transition phase of the deployment.
|
||||
-- TODO(mcilwain): Drop this DEFAULT constraint in a subsequent schema release
|
||||
-- once the Java code mapping this column has been fully deployed.
|
||||
ALTER TABLE "Tld" ADD COLUMN expiry_access_period_transitions hstore
|
||||
DEFAULT '"1970-01-01T00:00:00.000Z"=>"DISABLED"'::hstore NOT NULL;
|
||||
@@ -1205,7 +1205,7 @@ CREATE TABLE public."Tld" (
|
||||
breakglass_mode boolean DEFAULT false NOT NULL,
|
||||
bsa_enroll_start_time timestamp with time zone,
|
||||
create_billing_cost_transitions public.hstore NOT NULL,
|
||||
expiry_access_period_enabled boolean DEFAULT false NOT NULL
|
||||
expiry_access_period_transitions public.hstore DEFAULT '"1970-01-01T00:00:00.000Z"=>"DISABLED"'::public.hstore NOT NULL
|
||||
);
|
||||
|
||||
|
||||
|
||||
+69
-8
@@ -102,6 +102,14 @@ Here are the task queues in use by the system:
|
||||
run infrequently, such as exporting reserved terms.
|
||||
* `sheet` -- Queue for tasks to sync registrar updates to a Google Sheets
|
||||
spreadsheet, done by `SyncRegistrarsSheetAction`.
|
||||
* `async-actions` -- Queue for general asynchronous actions that should be run at
|
||||
some point in the future.
|
||||
* `async-host-rename` -- Queue for tasks that trigger domain DNS updates upon
|
||||
host renaming.
|
||||
* `beam-reporting` -- Queue for tasks that wait for a Beam pipeline (such as
|
||||
Spec11 reporting or invoicing) to complete.
|
||||
* `console-user-group-update` -- Queue for tasks that update membership in the
|
||||
Google Groups for console users.
|
||||
|
||||
### Scheduled cron jobs
|
||||
|
||||
@@ -113,13 +121,65 @@ minute (in the case of syncing DNS updates) or as infrequently as once per month
|
||||
are more tasks that run in Production than in other environments because tasks
|
||||
like uploading RDE dumps are only done for the live system.
|
||||
|
||||
Here are the primary cron tasks configured in the production environment:
|
||||
|
||||
* **`rdeStaging`** (`/_dr/task/rdeStaging`) -- Generates a full RDE escrow
|
||||
deposit as a single large XML document and streams it to Google Cloud
|
||||
Storage daily.
|
||||
* **`rdeUpload`** (`/_dr/task/rdeUpload`) -- Uploads already-generated RDE
|
||||
files from GCS to the escrow provider (e.g. Iron Mountain) via SFTP.
|
||||
* **`rdeReport`** (`/_dr/task/rdeReport`) -- Uploads RDE reports to ICANN.
|
||||
* **`tmchDnl`**, **`tmchSmdrl`**, **`tmchCrl`** (`/_dr/task/tmchDnl`,
|
||||
`tmchSmdrl`, `tmchCrl`) -- Download the latest Domain Name Label list,
|
||||
Signed Mark Revocation List, and Certificate Revocation List from MarksDB
|
||||
and update the registry database.
|
||||
* **`syncGroupMembers`** (`/_dr/task/syncGroupMembers`) -- Syncs registrar
|
||||
contact changes from the database to Google Groups.
|
||||
* **`syncRegistrarsSheet`** (`/_dr/task/syncRegistrarsSheet`) -- Synchronizes
|
||||
registrar entities to a Google Sheets spreadsheet for business visibility.
|
||||
* **`updateRegistrarRdapBaseUrls`** (`/_dr/task/updateRegistrarRdapBaseUrls`)
|
||||
-- Reloads all registrar RDAP base URLs from ICANN.
|
||||
* **`exportDomainLists`** (`/_dr/task/exportDomainLists`) -- Exports active
|
||||
domain lists to GCS and Google Drive.
|
||||
* **`expandBillingRecurrences`** (`/_dr/task/expandBillingRecurrences`) --
|
||||
Generates synthetic one-time billing events from recurring billing setup.
|
||||
* **`deleteExpiredDomains`** (`/_dr/task/deleteExpiredDomains`) -- Deletes
|
||||
domains that are past their auto-renew end date daily.
|
||||
* **`sendExpiringCertificateNotificationEmail`**
|
||||
(`/_dr/task/sendExpiringCertificateNotificationEmail`) -- Notifies
|
||||
registrars of upcoming SSL certificate expirations.
|
||||
* **`nordnUploadSunrise`**, **`nordnUploadClaims`** (`/_dr/task/nordnUpload`)
|
||||
-- Upload LORDN Sunrise/Claims CSV files to MarksDB.
|
||||
* **`deleteProberData`** (`/_dr/task/deleteProberData`) -- Daily cleanup of
|
||||
test data generated by probers.
|
||||
* **`exportReservedTerms`**, **`exportPremiumTerms`**
|
||||
(`/_dr/task/exportReservedTerms`, `exportPremiumTerms`) -- Export reserved
|
||||
and premium terms to Google Drive.
|
||||
* **`readDnsRefreshRequests`** (`/_dr/task/readDnsRefreshRequests`) -- Reads
|
||||
DNS refresh requests from the database and batches them to the publish queue
|
||||
every minute.
|
||||
* **`icannReportingStaging`**, **`icannReportingUpload`**
|
||||
(`/_dr/task/icannReportingStaging`, `icannReportingUpload`) -- Stage monthly
|
||||
ICANN activity/transaction reports and upload them.
|
||||
* **`generateInvoices`** (`/_dr/task/generateInvoices`) -- Starts Dataflow
|
||||
templates to generate monthly billing invoices.
|
||||
* **`generateSpec11`** (`/_dr/task/generateSpec11`) -- Starts Dataflow
|
||||
templates to generate daily Spec11 anti-abuse reports.
|
||||
* **`bsaDownload`**, **`bsaRefresh`**, **`bsaValidate`**,
|
||||
**`uploadBsaUnavailableNames`** -- Download block lists, refresh registered
|
||||
names, validate data, and upload unavailable names for the Brand Safety
|
||||
Alliance (BSA) service.
|
||||
* **`triggerMosApiServiceState`** (`/_dr/task/triggerMosApiServiceState`) --
|
||||
Fetches the service state from MosAPI and triggers metrics status for all
|
||||
TLDs every 5 minutes.
|
||||
|
||||
Most cron tasks use the `TldFanoutAction` which is accessed via the
|
||||
`/_dr/cron/fanout` URL path. This action fans out a given cron task for each TLD
|
||||
that exists in the registry system, using the queue that is specified in the XML
|
||||
entry. Because some tasks may be computationally intensive and could risk
|
||||
spiking system latency if all start executing immediately at the same time,
|
||||
spiking system latency if all start executing immediately at the same time,
|
||||
there is a `jitterSeconds` parameter that spreads out tasks over the given
|
||||
number of seconds. This is used with DNS updates and commit log deletion.
|
||||
number of seconds. This is used with DNS updates.
|
||||
|
||||
The reason the `TldFanoutAction` exists is that a lot of tasks need to be done
|
||||
separately for each TLD, such as RDE exports and NORDN uploads. It's simpler to
|
||||
@@ -130,8 +190,9 @@ failures that a raw cron task does not. This is why there are some tasks that do
|
||||
not fan out across TLDs that still use `TldFanoutAction` -- it's so that the
|
||||
tasks retry in the face of transient errors.
|
||||
|
||||
The full list of URL parameters to `TldFanoutAction` that can be specified in
|
||||
cron.xml is:
|
||||
The full list of URL parameters to `TldFanoutAction` that can be specified in the
|
||||
Cloud Scheduler configuration files (such as
|
||||
`cloud-scheduler-tasks-production.xml`) is:
|
||||
|
||||
* `endpoint` -- The path of the action that should be executed
|
||||
* `queue` -- The cron queue to enqueue tasks in.
|
||||
@@ -156,7 +217,7 @@ which includes a separate database and separate bulk storage in Cloud Storage.
|
||||
Each environment is thus completely independent.
|
||||
|
||||
The different environments are specified in `RegistryEnvironment`. Most
|
||||
correspond to a separate App Engine app except for `UNITTEST` and `LOCAL`, which
|
||||
correspond to a separate GCP project except for `UNITTEST` and `LOCAL`, which
|
||||
by their nature do not use real environments running in the cloud. The
|
||||
recommended project naming scheme that has the best possible compatibility with
|
||||
the codebase and thus requires the least configuration is to pick a name for the
|
||||
@@ -185,14 +246,14 @@ real to not-real, is:
|
||||
the entire system down until it is completed) without affecting the QA
|
||||
environment.
|
||||
* `ALPHA` -- The developers' playground. Experimental builds are routinely
|
||||
pushed here in order to test them on a real app running on App Engine. You
|
||||
pushed here in order to test them on a real GKE cluster running in GCP. You
|
||||
may end up wanting multiple environments like Alpha if you regularly
|
||||
experience contention (i.e. developers being blocked from testing their code
|
||||
on Alpha because others are already using it).
|
||||
* `LOCAL` -- A fake environment that is used when running the app locally on a
|
||||
simulated App Engine instance.
|
||||
simulated instance.
|
||||
* `UNITTEST` -- A fake environment that is used in unit tests, where
|
||||
everything in the App Engine stack is simulated or mocked.
|
||||
everything in the cloud stack is simulated or mocked.
|
||||
|
||||
## Release process
|
||||
|
||||
|
||||
@@ -185,12 +185,13 @@ architecture -- an `Authorization` HTTP header of the form "Bearer: XXXX".
|
||||
|
||||
### Configuration
|
||||
|
||||
The `auth` block of the configuration requires two fields: *
|
||||
`allowedServiceAccountEmails` is the list of service accounts that should be
|
||||
allowed to run tasks when internally authenticated. This will likely include
|
||||
whatever service account runs Nomulus in Google Kubernetes Engine, as well as
|
||||
the Cloud Scheduler service account. * `oauthClientId` is the OAuth client ID
|
||||
associated with IAP. This is retrievable from the
|
||||
[Clients page](https://pantheon.corp.google.com/auth/clients) of GCP after
|
||||
enabling the Identity-Aware Proxy. It should look something like
|
||||
`someNumbers-someNumbersAndLetters.apps.googleusercontent.com`
|
||||
The `auth` block of the configuration requires two fields:
|
||||
|
||||
* `allowedServiceAccountEmails` is the list of service accounts that should be
|
||||
allowed to run tasks when internally authenticated. This will likely include
|
||||
whatever service account runs Nomulus in Google Kubernetes Engine, as well
|
||||
as the Cloud Scheduler service account.
|
||||
* `oauthClientId` is the OAuth client ID associated with IAP. This is retrievable
|
||||
from the [Clients page](https://pantheon.corp.google.com/auth/clients) of GCP
|
||||
after enabling the Identity-Aware Proxy. It should look something like
|
||||
`someNumbers-someNumbersAndLetters.apps.googleusercontent.com`
|
||||
|
||||
+12
-5
@@ -20,12 +20,13 @@ versions stored in the various `gradle.lockfile` files. To update these
|
||||
versions, run any Gradle command (e.g. `./gradlew build`) with the
|
||||
`--write-locks` argument.
|
||||
|
||||
### Generating WAR archives for deployment
|
||||
### Generating Docker images for deployment
|
||||
|
||||
The `jetty` project is the main entry point for building the Nomulus WAR files,
|
||||
and one can use the `war` gradle task to build the base WAR file. The various
|
||||
deployment/release files use Docker to deploy this, in a system that is too
|
||||
Google-specialized to replicate directly here.
|
||||
The `jetty` project is the main entry point for building the Nomulus Docker
|
||||
images. You can use the `./gradlew :jetty:buildNomulusImage` task to build the
|
||||
image locally, which contains the compiled WAR files and Angular assets staged
|
||||
inside a Jetty base image. You can use `./gradlew :jetty:pushNomulusImage` to
|
||||
push this image to your GCR/Artifact Registry repository.
|
||||
|
||||
## Subprojects
|
||||
|
||||
@@ -68,6 +69,12 @@ The following cursor types are defined:
|
||||
events into one-time `BillingEvent`s.
|
||||
* **`SYNC_REGISTRAR_SHEET`** - Tracks the last time the registrar spreadsheet
|
||||
was successfully synced.
|
||||
* **`ICANN_UPLOAD_TX`** - Tracks monthly uploads of ICANN transaction reports.
|
||||
* **`ICANN_UPLOAD_ACTIVITY`** - Tracks monthly uploads of ICANN activity reports.
|
||||
* **`REMOTE_CACHE_DOMAIN_SYNC`** - Tracks the reflection of domain changes in
|
||||
the remote cache.
|
||||
* **`REMOTE_CACHE_HOST_SYNC`** - Tracks the reflection of host changes in
|
||||
the remote cache.
|
||||
|
||||
All `Cursor` entities in the database contain a `DateTime` that represents the
|
||||
next timestamp at which an operation should resume processing and a `CursorType`
|
||||
|
||||
+25
-24
@@ -117,16 +117,19 @@ For the Nomulus tool OAuth configuration, do the following steps:
|
||||
`registryTool` section. This will make the `nomulus` tool use this
|
||||
credential to authenticate itself to the system.
|
||||
|
||||
For IAP configuration, do the following steps: * **Create the IAP client ID:**
|
||||
Follow similar steps from above to create an additional OAuth client ID, but
|
||||
using an application type of "Web application". Note the client ID and secret. *
|
||||
**Enable IAP for your HTTPS load balancer:** On the
|
||||
[IAP page](https://pantheon.corp.google.com/security/iap), enable IAP for all of
|
||||
the backend services that all use the same HTTPS load balancer. * **Use a custom
|
||||
OAuth configuration:** For the backend services, under the "Settings" section
|
||||
(in the three-dot menu) enable custom OAuth and insert the client ID and secret
|
||||
that we just created * **Save the client ID:** In the configuration file, save
|
||||
the client ID as `oauthClientId` in the `auth` section
|
||||
For IAP configuration, do the following steps:
|
||||
|
||||
* **Create the IAP client ID:** Follow similar steps from above to create an
|
||||
additional OAuth client ID, but using an application type of "Web
|
||||
application". Note the client ID and secret.
|
||||
* **Enable IAP for your HTTPS load balancer:** On the
|
||||
[IAP page](https://pantheon.corp.google.com/security/iap), enable IAP for
|
||||
all of the backend services that all use the same HTTPS load balancer.
|
||||
* **Use a custom OAuth configuration:** For the backend services, under the
|
||||
"Settings" section (in the three-dot menu), enable custom OAuth and
|
||||
insert the client ID and secret that we just created.
|
||||
* **Save the client ID:** In the configuration file, save the client ID as
|
||||
`oauthClientId` in the `auth` section.
|
||||
|
||||
Once these steps are taken, the `nomulus` tool and IAP will both use client IDs
|
||||
which the server is configured to accept, and authentication should succeed.
|
||||
@@ -171,9 +174,8 @@ To create or update TLDs, we use
|
||||
configure_tld` command. Because the TLDs are stored as data in the running
|
||||
system, they do not require code pushes to update.
|
||||
|
||||
[app-engine-config]: https://cloud.google.com/appengine/docs/java/configuration-files
|
||||
[default-config]: https://github.com/google/nomulus/blob/master/java/google/registry/config/files/default-config.yaml
|
||||
[registry-config]: https://github.com/google/nomulus/blob/master/java/google/registry/config/RegistryConfig.java
|
||||
[default-config]: https://github.com/google/nomulus/blob/master/core/src/main/java/google/registry/config/files/default-config.yaml
|
||||
[registry-config]: https://github.com/google/nomulus/blob/master/core/src/main/java/google/registry/config/RegistryConfig.java
|
||||
|
||||
## Cloud SQL Configuration
|
||||
|
||||
@@ -244,9 +246,9 @@ something similar. However, for purposes of this exercise we will push the
|
||||
schema from the build system.
|
||||
|
||||
First, download the
|
||||
[Cloud SQL Proxy](https://cloud.google.com/sql/docs/mysql/sql-proxy). This will
|
||||
allow you to connect to your database from a local workstation without a lot of
|
||||
additional configuration.
|
||||
[Cloud SQL Auth Proxy](https://cloud.google.com/sql/docs/postgres/sql-proxy).
|
||||
This will allow you to connect to your database from a local workstation without
|
||||
a lot of additional configuration.
|
||||
|
||||
Create a service account for use with the proxy:
|
||||
|
||||
@@ -277,12 +279,11 @@ Now start the proxy:
|
||||
|
||||
```
|
||||
$ PORT=3306 # Use a different value for this if you like.
|
||||
$ ./cloud_sql_proxy -credential_file=sql-admin.json \
|
||||
-instances=$PROJECT_ID:nomulus=tcp:$PORT
|
||||
2020/07/01 12:11:20 current FDs rlimit set to 32768, wanted limit is 8500. Nothing to do here.
|
||||
2020/07/01 12:11:20 using credential file for authentication; email=sql-proxy@pproject-id.iam.gserviceaccount.com
|
||||
2020/07/01 12:11:20 Listening on 127.0.0.1:3306 for project-id:nomulus
|
||||
2020/07/01 12:11:20 Ready for new connections
|
||||
$ ./cloud-sql-proxy --credentials-file=sql-admin.json --port=$PORT \
|
||||
$PROJECT_ID:us-central1:nomulus
|
||||
2026/06/16 12:11:20 Authorizing with credentials file: sql-admin.json
|
||||
2026/06/16 12:11:20 Listening on 127.0.0.1:3306 for project-id:us-central1:nomulus
|
||||
2026/06/16 12:11:20 The proxy has started successfully and is ready for new connections!
|
||||
```
|
||||
|
||||
Finally, upload the new database schema:
|
||||
@@ -366,8 +367,8 @@ $ nomulus -e $ENV update_keyring_secret --keyname TOOLS_CLOUD_SQL_PASSWORD \
|
||||
Use get_keyring_secret command to verify the data you put in:
|
||||
|
||||
```
|
||||
$ nomulus -e alpha -e alpha get_keyring_secret --keyname CLOUD_SQL_PASSWORD
|
||||
$ nomulus -e alpha get_keyring_secret --keyname CLOUD_SQL_PASSWORD
|
||||
[your password]
|
||||
$ nomulus -e alpha -e alpha get_keyring_secret --keyname CLOUD_SQL_PASSWORD
|
||||
$ nomulus -e alpha get_keyring_secret --keyname TOOLS_CLOUD_SQL_PASSWORD
|
||||
[your password]
|
||||
```
|
||||
|
||||
@@ -21,16 +21,19 @@ it'll never be created for real on the Internet at large. Then,
|
||||
[example template](https://github.com/google/nomulus/blob/master/core/src/test/resources/google/registry/tools/tld.yaml)
|
||||
as a guide.
|
||||
|
||||
The fields you'll want to change from the template: * `driveFolderId` should be
|
||||
null * `roidSuffix` should be `EXAMPLE` -- this is the suffix that will be used
|
||||
for repository ids of domains on the TLD. This suffix must be all uppercase and
|
||||
a maximum of eight ASCII characters and can be set to the upper-case equivalent
|
||||
of our TLD name (if it is 8 characters or fewer), such as "EXAMPLE." You can
|
||||
also abbreviate the upper-case TLD name down to 8 characters. Refer to the
|
||||
[gTLD Registry Advisory: Correction of non-compliant ROIDs][roids] for further
|
||||
information. * `tldStr` should be `example` * `tldType` should be `TEST`, which
|
||||
identifies that the TLD is for testing purposes, whereas `REAL` would identify
|
||||
the TLD as a live TLD
|
||||
The fields you'll want to change from the template:
|
||||
|
||||
* `driveFolderId` should be null.
|
||||
* `roidSuffix` should be `EXAMPLE` -- this is the suffix that will be used
|
||||
for repository ids of domains on the TLD. This suffix must be all uppercase and
|
||||
a maximum of eight ASCII characters and can be set to the upper-case equivalent
|
||||
of our TLD name (if it is 8 characters or fewer), such as "EXAMPLE." You can
|
||||
also abbreviate the upper-case TLD name down to 8 characters. Refer to the
|
||||
[gTLD Registry Advisory: Correction of non-compliant ROIDs][roids] for further
|
||||
information.
|
||||
* `tldStr` should be `example`.
|
||||
* `tldType` should be `TEST`, which identifies that the TLD is for testing purposes,
|
||||
whereas `REAL` would identify the TLD as a live TLD.
|
||||
|
||||
```shell
|
||||
$ nomulus -e alpha configure_tld --input=example.yaml
|
||||
|
||||
@@ -34,6 +34,10 @@ providing the test project as an argument, e.g.
|
||||
./gradlew deployNomulus -Penvironment=alpha
|
||||
```
|
||||
|
||||
Note: Deploying to GCP requires Docker to be running locally (to build the
|
||||
Nomulus container image) and `gcloud` credentials to be configured with access
|
||||
to the target GCP project.
|
||||
|
||||
### Notable Issues
|
||||
|
||||
Test suites (RdeTestSuite and TmchTestSuite) are ignored to avoid duplicate
|
||||
|
||||
+12
-7
@@ -6,9 +6,9 @@ This document covers the steps necessary to download, build, and deploy Nomulus.
|
||||
|
||||
You will need the following programs installed on your local machine:
|
||||
|
||||
* A recent version of the [Java 21 JDK][java-jdk21].
|
||||
* A recent version of the [Java 25 JDK][java-jdk25].
|
||||
* The [Google Cloud CLI](https://docs.cloud.google.com/sdk/docs/install-sdk)
|
||||
(configure an alias to the `gcloud`utility, because you'll use it a lot)
|
||||
(configure an alias to the `gcloud` utility, because you'll use it a lot)
|
||||
* [Git](https://git-scm.com/) version control system.
|
||||
* Docker (confirm with `docker info` no permission issues, use `sudo groupadd
|
||||
docker` for sudoless docker).
|
||||
@@ -63,7 +63,7 @@ while.
|
||||
## Create and configure a GCP project
|
||||
|
||||
First,
|
||||
[create an application](https://cloud.google.com/appengine/docs/java/quickstart)
|
||||
[create a project][create-project]
|
||||
on Google Cloud Platform. Make sure to choose a good Project ID, as it will be
|
||||
used repeatedly in a large number of places. If your company is named Acme, then
|
||||
a good Project ID for your production environment would be "acme-registry". Keep
|
||||
@@ -123,10 +123,14 @@ $ gcloud container clusters create proxy-cluster \
|
||||
--num-nodes=3 \
|
||||
--enable-ip-alias
|
||||
```
|
||||
Then create an artifact repository:
|
||||
|
||||
Then create an artifact repository: `shell $ gcloud artifacts repositories
|
||||
create nomulus-repo \ --repository-format=docker \ --location=$REGION \
|
||||
--description="Nomulus Docker images"`
|
||||
```shell
|
||||
$ gcloud artifacts repositories create nomulus-repo \
|
||||
--repository-format=docker \
|
||||
--location=$REGION \
|
||||
--description="Nomulus Docker images"
|
||||
```
|
||||
|
||||
See the files and documentation in the `release/` folder for more information on
|
||||
the release process. You will likely need to customize the internal build
|
||||
@@ -141,7 +145,8 @@ can rebuild and start using the `nomulus` tool to create test entities in your
|
||||
newly deployed system. See the [first steps tutorial](./first-steps-tutorial.md)
|
||||
for more information.
|
||||
|
||||
[java-jdk21]: https://www.oracle.com/java/technologies/javase-downloads.html
|
||||
[java-jdk25]: https://www.oracle.com/java/technologies/javase-downloads.html
|
||||
[create-project]: https://cloud.google.com/resource-manager/docs/creating-managing-projects
|
||||
|
||||
## Deploy the Beam Pipelines
|
||||
|
||||
|
||||
@@ -9,9 +9,10 @@ useful for doing web UI development (i.e. the registrar console). It allows you
|
||||
to update Typescript, HTML, and CSS and see the changes simply by refreshing the
|
||||
relevant page in your browser.
|
||||
|
||||
In order to serve content locally, there are two services that must be run: *
|
||||
the `RegistryTestServer` to serve as the backing server * the Angular service to
|
||||
provide the UI files
|
||||
In order to serve content locally, there are two services that must be run:
|
||||
|
||||
* The `RegistryTestServer` to serve as the backing server.
|
||||
* The Angular service to provide the UI files.
|
||||
|
||||
In order to do this in one step, from the `console-webapp` folder, run:
|
||||
|
||||
@@ -24,7 +25,7 @@ This will start both the `RegistryTestServer` and the Angular testing service.
|
||||
Any changes to Typescript/HTML/CSS files will be recompiled and available on
|
||||
page reload.
|
||||
|
||||
One it is running, you can interact with the console by going to
|
||||
Once it is running, you can interact with the console by going to
|
||||
`http://localhost:4200` to view the registrar console in a web browser. The
|
||||
server will continue running until you terminate the process.
|
||||
|
||||
|
||||
@@ -10,22 +10,37 @@ instrument internal state within the Nomulus internal environment. This is
|
||||
broadly called white-box monitoring. EPP, DNS, and RDAP are instrumented. The
|
||||
metrics monitored are as follows:
|
||||
|
||||
* `/custom/dns/publish_domain_requests` -- A count of publish domain requests,
|
||||
described by the target TLD and the return status code from the underlying
|
||||
DNS implementation.
|
||||
* `/custom/dns/publish_host_requests` -- A count of publish host requests,
|
||||
described by the target TLD and the return status code from the underlying
|
||||
DNS implementation.
|
||||
* `/custom/epp/requests` -- A count of EPP requests, described by command
|
||||
name, client id, and return status code.
|
||||
* `/custom/epp/processing_time` -- A [Distribution][distribution] representing
|
||||
the processing time for EPP requests, described by command name, client id,
|
||||
* `/dns/publish_domain_requests` -- A count of publish domain requests,
|
||||
described by the target TLD and the publish status.
|
||||
* `/dns/publish_host_requests` -- A count of publish host requests,
|
||||
described by the target TLD and the publish status.
|
||||
* `/epp/requests` -- A count of EPP requests, described by command
|
||||
name, client (registrar) id, and return status code.
|
||||
* `/epp/request_time` -- A [Distribution][distribution] representing
|
||||
the processing time for EPP requests, described by command name, traffic type,
|
||||
and return status code.
|
||||
* `/custom/rdap/requests` -- A count of RDAP requests, described by command
|
||||
name, number of returned results, and return status code.
|
||||
* `/custom/rdap/processing_time` -- A [Distribution][distribution]
|
||||
representing the processing time for RDAP requests, described by command
|
||||
name, number of returned results, and return status code.
|
||||
* `/rdap/requests` -- A count of RDAP requests, described by endpoint
|
||||
type, deleted inclusion, registrar specification, authorization, and
|
||||
HTTP method.
|
||||
* `/rdap/request_time` -- A [Distribution][distribution]
|
||||
representing the processing time for RDAP requests, described by endpoint
|
||||
type, search type, wildcard type, HTTP status code, and
|
||||
incompleteness warning type.
|
||||
* `/lock/acquire_lock_requests` -- A count of lock acquisition attempts,
|
||||
described by TLD, resource name, and the existing lock state.
|
||||
* `/lock/lock_duration` -- A [Distribution][distribution] representing
|
||||
the lock lifetime in milliseconds, described by TLD and resource name.
|
||||
* `/cache/lookups` -- A count of cache lookups, described by cache name
|
||||
(e.g. domain, host) and the hit type (LOCAL, REMOTE, MISS,
|
||||
MISS_NONEXISTENT).
|
||||
* `/domain_label/reserved/checks` -- A count of reserved list checks,
|
||||
described by TLD, number of matching lists, most severe list name, and
|
||||
most severe reservation type.
|
||||
* `/domain_label/reserved/processing_time` -- A [Distribution][distribution]
|
||||
representing the amount of time in milliseconds required to check a label
|
||||
against all reserved lists.
|
||||
* `/domain_label/reserved/hits` -- A count of reserved list hits,
|
||||
described by TLD, reserved list name, and the reservation type found.
|
||||
|
||||
Follow the guide to
|
||||
[set up a Stackdriver account](https://cloud.google.com/monitoring/accounts/guide)
|
||||
@@ -60,11 +75,11 @@ Cursors can be updated as follows:
|
||||
```shell
|
||||
$ nomulus -e {ENVIRONMENT} update_cursors exampletld --type RDE_STAGING \
|
||||
--timestamp 2016-09-01T00:00:00Z
|
||||
Update Cursor@ahFzfmRvbWFpbi1yZWdpc3RyeXIzCxIPRW50aXR5R3JvdXBSb290Igljcm9zcy10bGQMCxIIUmVnaXN0cnkiB3lvdXR1YmUM_RDE_STAGING
|
||||
cursorTime: 2016-09-23T00:00:00.000Z -> 2016-09-01T00:00:00.000Z
|
||||
Change cursorTime of RDE_STAGING for Scope:exampletld to 2016-09-01T00:00:00Z
|
||||
|
||||
Perform this command? (y/N): Y
|
||||
Updated 1 entities.
|
||||
Running ...
|
||||
Updated 1 cursors.
|
||||
```
|
||||
|
||||
## gTLD reporting
|
||||
@@ -89,9 +104,11 @@ Nomulus provides
|
||||
ICANN requires monthly activity and transaction reporting. The details are
|
||||
contained in Specification 3 of the [registry agreement][registry-agreement].
|
||||
|
||||
These reports are mostly generated by querying the Cloud SQL database. There is
|
||||
currently a Google proprietary class to query DNS related activities that is not
|
||||
included in the open source Nomulus release.
|
||||
These reports are generated by querying BigQuery, using database snapshots
|
||||
loaded into BigQuery. The default `DnsCountQueryCoordinator` implementation
|
||||
(`CloudDnsCountQueryCoordinator`) relies on Google-internal DNS tables, so
|
||||
external users will need to provide their own implementation to query their DNS
|
||||
statistics.
|
||||
|
||||
### Zone File Access (ZFA)
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ deposits). Some information related to BRDA can be found at:
|
||||
https://icannwiki.com/Onboarding_Information_Request#BRDA
|
||||
|
||||
BRDA deposits are generated by the
|
||||
[RdeStagingAction](https://github.com/google/nomulus/blob/master/java/google/registry/rde/RdeStagingAction.java)
|
||||
[RdeStagingAction][rde-staging-action]
|
||||
job. This is the same job that generates RDE deposits. Its Javadoc goes into
|
||||
great detail about how it's implemented.
|
||||
|
||||
@@ -44,24 +44,54 @@ The cursor can be checked using the `nomulus pending_escrow` command.
|
||||
command output doesn't contain any TLDs for tests.
|
||||
|
||||
```shell
|
||||
$ nomulus -e production list_tlds --fields=tldStr,tldType | grep REAL | awk '{print $1}' > realtlds.txt`
|
||||
$ nomulus -e production list_tlds --fields=tldStr,tldType \
|
||||
| grep REAL | awk '{print $1}' > realtlds.txt
|
||||
```
|
||||
|
||||
* Generate .ryde and .sig files of TLDs specified for given date(s) in the
|
||||
current directory.
|
||||
* Kick off the server-side generation of thin escrow XML files (GhostRyDE
|
||||
encrypted) under the GCS RDE bucket manual directory:
|
||||
|
||||
```shell
|
||||
$ mkdir /tmp/brda.$$; for date in 2015-02-26 2015-03-05; \
|
||||
$ for date in 2015-02-26 2015-03-05; \
|
||||
do for tld in $(cat realtlds.txt); \
|
||||
do nomulus -e production create_brda_deposit --tld=${tld} --watermark=${date}T00:00:00Z --outdir=/tmp/brda.$$ & sleep 30; \
|
||||
do nomulus -e production generate_escrow_deposit \
|
||||
--tld=${tld} \
|
||||
--watermark=${date}T00:00:00Z \
|
||||
--mode=THIN \
|
||||
--outdir=manual_brda; \
|
||||
done; \
|
||||
done
|
||||
```
|
||||
|
||||
* Store the generated files to the GCS bucket.
|
||||
* Download and decrypt the GhostRyDE files locally, then encrypt them for
|
||||
sending to the escrow provider (note that files are located in
|
||||
subdirectories named after the Dataflow job under `manual_brda`, which
|
||||
we match using a wildcard):
|
||||
|
||||
```shell
|
||||
$ gcloud storage cp /tmp/brda.$$/*.{ryde,sig} gs://{PROJECT-ID}-icann-brda/`
|
||||
$ mkdir /tmp/brda_out; for date in 2015-02-26 2015-03-05; \
|
||||
do for tld in $(cat realtlds.txt); \
|
||||
do \
|
||||
gcloud storage cat \
|
||||
gs://{PROJECT-ID}-rde/manual/manual_brda/*/${tld}_${date}_thin_S1_R0.xml.ghostryde \
|
||||
| nomulus -e production ghostryde --decrypt \
|
||||
> /tmp/${tld}_${date}_thin_S1_R0.xml; \
|
||||
nomulus -e production encrypt_escrow_deposit \
|
||||
--mode=THIN \
|
||||
--tld=${tld} \
|
||||
--input=/tmp/${tld}_${date}_thin_S1_R0.xml \
|
||||
--outdir=/tmp/brda_out; \
|
||||
rm /tmp/${tld}_${date}_thin_S1_R0.xml; \
|
||||
done; \
|
||||
done
|
||||
```
|
||||
|
||||
* Store the generated `.ryde` and `.sig` files to the BRDA GCS bucket.
|
||||
|
||||
```shell
|
||||
$ gcloud storage cp /tmp/brda_out/*.{ryde,sig} gs://{PROJECT-ID}-icann-brda/
|
||||
```
|
||||
|
||||
* Mirror the files in the GCS bucket to the sFTP server.
|
||||
|
||||
[rde-staging-action]: https://github.com/google/nomulus/blob/master/core/src/main/java/google/registry/rde/RdeStagingAction.java
|
||||
|
||||
@@ -40,11 +40,13 @@ Once the file containing the premium prices is ready, run the
|
||||
`create_premium_list` command to load it into the database as follows:
|
||||
|
||||
```shell
|
||||
$ nomulus -e {ENVIRONMENT} create_premium_list -n exampletld -i exampletld.txt
|
||||
$ nomulus -e {ENVIRONMENT} create_premium_list -n exampletld \
|
||||
-i exampletld.txt -c USD
|
||||
|
||||
You are about to save the premium list exampletld with 2 items:
|
||||
Create new premium list for exampletld?
|
||||
Perform this command? (y/N): y
|
||||
Successfully saved premium list exampletld
|
||||
Running ...
|
||||
Saved premium list exampletld with 2 entries.
|
||||
```
|
||||
|
||||
`-n` is the name of the list to be created, and `-i` is the input filename. Note
|
||||
@@ -64,9 +66,12 @@ from a text file, the procedure is exactly the same, except using the
|
||||
```shell
|
||||
$ nomulus -e {ENVIRONMENT} update_premium_list -n exampletld -i exampletld.txt
|
||||
|
||||
You are about to save the premium list exampletld with 2 items:
|
||||
Update premium list for exampletld?
|
||||
Old List: PremiumList{name=exampletld, ...}
|
||||
New List: PremiumList{name=exampletld, ...}
|
||||
Perform this command? (y/N): y
|
||||
Successfully saved premium list exampletld
|
||||
Running ...
|
||||
Saved premium list exampletld with 2 entries.
|
||||
```
|
||||
|
||||
### Note:
|
||||
@@ -140,8 +145,11 @@ $ nomulus -e production check_domain {domain_name}
|
||||
|
||||
**Note that the list can be cached for up to 60 minutes, so the old value may
|
||||
still be returned for a little while**. If it is urgent that the new pricing
|
||||
changes be applied, and it's OK to potentially interrupt client connections,
|
||||
then you can use the GCP web console to kill instances of the `frontend`
|
||||
service, as the cache is per-instance. Once you've killed all the existing
|
||||
instances (don't kill them all at once!), all the newly spun up instances will
|
||||
now be using the new values you've configured.
|
||||
changes be applied, you can perform a rolling restart of the `frontend` service
|
||||
deployment:
|
||||
|
||||
```shell
|
||||
$ kubectl rollout restart deployment frontend
|
||||
```
|
||||
|
||||
This will cycle the pods and clear the per-instance caches without causing downtime.
|
||||
|
||||
@@ -9,11 +9,11 @@ have this requirement, in which case the following would not apply.
|
||||
The RDE process takes care of escrow deposit processing. It happens in three
|
||||
phases:
|
||||
|
||||
1. [Staging](https://github.com/google/nomulus/blob/master/java/google/registry/rde/RdeStagingAction.java):
|
||||
1. [Staging][rde-staging-action]:
|
||||
Generate XML deposit and XML report files on Google Cloud Storage.
|
||||
2. [Upload](https://github.com/google/nomulus/blob/master/java/google/registry/rde/RdeUploadAction.java):
|
||||
2. [Upload][rde-upload-action]:
|
||||
Transmit XML deposit to the escrow provider via sFTP.
|
||||
3. [Report](https://github.com/google/nomulus/blob/master/java/google/registry/rde/RdeReportAction.java):
|
||||
3. [Report][rde-report-action]:
|
||||
Transmit XML *report* file to ICANN via HTTPS.
|
||||
|
||||
Each phase happens with an GCP task queue entry that retries on failure. When
|
||||
@@ -101,7 +101,7 @@ that no cooldown period is necessary.
|
||||
|
||||
You can list the files in Cloud Storage for a given TLD using the gcloud storage
|
||||
tool. All files are stored in the {PROJECT-ID}-rde bucket, where {PROJECT-ID} is
|
||||
the name of the App Engine project for the particular environment you are
|
||||
the name of the GCP project for the particular environment you are
|
||||
checking.
|
||||
|
||||
```shell
|
||||
@@ -114,7 +114,7 @@ gs://{PROJECT-ID}-rde/zip_2015-05-16.xml.length
|
||||
## Normal launch
|
||||
|
||||
Under normal circumstances, RDE is launched by TldFanoutAction, configured in
|
||||
cron.xml. If the App Engine's cron executor isn't working, you can spawn it
|
||||
Cloud Scheduler. If the Cloud Scheduler trigger fails, you can spawn it
|
||||
manually by visiting the following URL:
|
||||
|
||||
```
|
||||
@@ -192,8 +192,7 @@ In general RDE files should be regenerated by updating the cursors to before the
|
||||
desired date and then re-running the mapreduce (either by waiting until the next
|
||||
scheduled cron execution, or by manually invoking the RDE staging action).
|
||||
|
||||
In very rare cases (and only for small TLDs) you can use `nomulus` to generate
|
||||
the RDE files on a per-TLD basis. Here's an example:
|
||||
In very rare cases, you can use `nomulus` to trigger the RDE generation manually on a per-TLD basis. Here's how:
|
||||
|
||||
```shell
|
||||
# Define the tld/date combination.
|
||||
@@ -201,17 +200,34 @@ the RDE files on a per-TLD basis. Here's an example:
|
||||
$ tld=xxx
|
||||
$ date=2015-05-16
|
||||
|
||||
# 1. Generate the deposit. This drops a bunch of files in the current directory.
|
||||
# 1. Trigger the server-side staging. This enqueues a Cloud Task that runs
|
||||
# RdeStagingAction on GKE, writing the generated GhostRyDE files to the GCS RDE
|
||||
# bucket manual directory.
|
||||
|
||||
$ nomulus -e production generate_escrow_deposit --tld=${tld} --watermark=${date}T00:00:00Z
|
||||
$ nomulus -e production generate_escrow_deposit \
|
||||
--tld=${tld} \
|
||||
--watermark=${date}T00:00:00Z \
|
||||
--outdir=manual_rde
|
||||
|
||||
$ ls -l
|
||||
total 22252
|
||||
-rw-r----- 1 tjb eng 2292 May 19 16:49 xxx_2015-05-16_full_S1_R0-report.xml
|
||||
-rw-r----- 1 tjb eng 1321343 May 19 16:49 xxx_2015-05-16_full_S1_R0.ryde
|
||||
-rw-r----- 1 tjb eng 361 May 19 16:49 xxx_2015-05-16_full_S1_R0.sig
|
||||
-rw-r----- 1 tjb eng 21448005 May 19 16:49 xxx_2015-05-16_full_S1_R0.xml
|
||||
-rw-r----- 1 tjb eng 977 May 19 16:49 xxx.pub
|
||||
# 2. Download and decrypt the staged GhostRyDE files locally for inspection or validation.
|
||||
# Note that the files will be stored in a subdirectory named after the Dataflow job
|
||||
# (you can find the job ID in the output of the previous command, or by running
|
||||
# `gcloud storage ls gs://{PROJECT-ID}-rde/manual/manual_rde/`):
|
||||
|
||||
$ JOB_NAME=rde-2015-05-16t000000z-some-suffix
|
||||
$ gcloud storage cp \
|
||||
gs://{PROJECT-ID}-rde/manual/manual_rde/${JOB_NAME}/${tld}_${date}_full_S1_R0.xml.ghostryde .
|
||||
$ gcloud storage cp \
|
||||
gs://{PROJECT-ID}-rde/manual/manual_rde/${JOB_NAME}/${tld}_${date}_full_S1_R0-report.xml.ghostryde .
|
||||
|
||||
$ nomulus -e production ghostryde --decrypt \
|
||||
--input=${tld}_${date}_full_S1_R0.xml.ghostryde \
|
||||
--output=${tld}_${date}_full_S1_R0.xml
|
||||
$ nomulus -e production ghostryde --decrypt \
|
||||
--input=${tld}_${date}_full_S1_R0-report.xml.ghostryde \
|
||||
--output=${tld}_${date}_full_S1_R0-report.xml
|
||||
|
||||
# 3. Validate the decrypted XML file:
|
||||
|
||||
$ nomulus -e production validate_escrow_deposit -i ${tld}_${date}_full_S1_R0.xml
|
||||
ID: AAAACTK2AU2AA
|
||||
@@ -220,30 +236,24 @@ Type: FULL
|
||||
Watermark: 2015-05-16T00:00:00.000Z
|
||||
RDE Version: 1.0
|
||||
RDE Object URIs:
|
||||
- urn:ietf:params:xml:ns:rdeDomain-1.0
|
||||
- urn:ietf:params:xml:ns:rdeHeader-1.0
|
||||
- urn:ietf:params:xml:ns:rdeHost-1.0
|
||||
- urn:ietf:params:xml:ns:rdeRegistrar-1.0
|
||||
- urn:ietf:params:xml:ns:rdeDomain-1.0
|
||||
- urn:ietf:params:xml:ns:rdeHeader-1.0
|
||||
- urn:ietf:params:xml:ns:rdeHost-1.0
|
||||
- urn:ietf:params:xml:ns:rdeRegistrar-1.0
|
||||
Contents:
|
||||
- XjcRdeDomain: 2,667 entries
|
||||
- XjcRdeHeader: 1 entry
|
||||
- XjcRdeHost: 35,932 entries
|
||||
- XjcRdeRegistrar: 146 entries
|
||||
- XjcRdeDomain: 2,667 entries
|
||||
- XjcRdeHeader: 1 entry
|
||||
- XjcRdeHost: 35,932 entries
|
||||
- XjcRdeRegistrar: 146 entries
|
||||
RDE deposit is XML schema valid
|
||||
```
|
||||
|
||||
# 2. GhostRyDE it!
|
||||
If you need the files to be picked up by the regular upload task, copy them
|
||||
back to the root of the RDE bucket:
|
||||
|
||||
$ nomulus -e production ghostryde --encrypt \
|
||||
--input=${tld}_${date}_full_S1_R0-report.xml \
|
||||
--output=${tld}_${date}_full_S1_R0-report.xml.ghostryde
|
||||
|
||||
$ nomulus -e production ghostryde --encrypt \
|
||||
--input=${tld}_${date}_full_S1_R0.xml \
|
||||
--output=${tld}_${date}_full_S1_R0.xml.ghostryde
|
||||
|
||||
# 3. Copy to Cloud Storage so RdeUploadTask can find them.
|
||||
|
||||
$ gcloud storage cp ${tld}_${date}_full_S1_R0{,-report}.xml.ghostryde gs://{PROJECT-ID}-rde/
|
||||
```shell
|
||||
$ gcloud storage cp \
|
||||
${tld}_${date}_full_S1_R0{,-report}.xml.ghostryde gs://{PROJECT-ID}-rde/
|
||||
```
|
||||
|
||||
## Updating an RDE cursor
|
||||
@@ -262,7 +272,7 @@ $ nomulus -e production update_cursors --timestamp=2015-05-21T00:00:00Z --type=R
|
||||
|
||||
These instructions work for Iron Mountain, and should be applicable to other
|
||||
escrow providers as well. We upload the RDE deposits to an sFTP server (see the
|
||||
[ConfigModule](https://github.com/google/nomulus/blob/master/java/google/registry/config/ConfigModule.java)
|
||||
[RegistryConfig][registry-config]
|
||||
for specific URLs). First, you need a generated deposit .xml file (see above for
|
||||
how to generate such a file locally, or how to decrypt a .ghostryde file into
|
||||
the .xml original).
|
||||
@@ -270,10 +280,11 @@ the .xml original).
|
||||
### Encrypting the RDE deposits for sending to the escrow provider
|
||||
|
||||
```shell
|
||||
$ nomulus -e production encrypt_escrow_deposit --tld=$tld --input=${tld}_${date}_full_S1_R0-report.xml
|
||||
$ nomulus -e production encrypt_escrow_deposit --tld=$tld \
|
||||
--input=${tld}_${date}_full_S1_R0.xml
|
||||
$ ls *.ryde *.sig
|
||||
${tld}_${date}_full_S1_R0-report.ryde
|
||||
${tld}_${date}_full_S1_R0-report.sig
|
||||
${tld}_${date}_full_S1_R0.ryde
|
||||
${tld}_${date}_full_S1_R0.sig
|
||||
```
|
||||
|
||||
### Verifying the deposit signature (optional)
|
||||
@@ -283,7 +294,7 @@ To verify the deposit signature, you will need a file containing the public key.
|
||||
```shell
|
||||
$ (umask 0077; mkdir gpgtemp)
|
||||
$ GNUPGHOME=gpgtemp gpg --import ./rde-signing-public
|
||||
$ GNUPGHOME=gpgtemp gpg --verify ${tld}_${date}_full_S1_R0-report.{sig,ryde}
|
||||
$ GNUPGHOME=gpgtemp gpg --verify ${tld}_${date}_full_S1_R0.{sig,ryde}
|
||||
...
|
||||
gpg: Good signature from ...
|
||||
...
|
||||
@@ -328,3 +339,8 @@ notification report to ICANN:
|
||||
# This command assumes the report XML file is in your current working directory
|
||||
$ nomulus -e production send_escrow_report_to_icann xxx_2015-05-16_full_S1_R0-report.xml
|
||||
```
|
||||
|
||||
[rde-staging-action]: https://github.com/google/nomulus/blob/master/core/src/main/java/google/registry/rde/RdeStagingAction.java
|
||||
[rde-upload-action]: https://github.com/google/nomulus/blob/master/core/src/main/java/google/registry/rde/RdeUploadAction.java
|
||||
[rde-report-action]: https://github.com/google/nomulus/blob/master/core/src/main/java/google/registry/rde/RdeReportAction.java
|
||||
[registry-config]: https://github.com/google/nomulus/blob/master/core/src/main/java/google/registry/config/RegistryConfig.java
|
||||
|
||||
@@ -75,9 +75,11 @@ purposes of this example, we are creating a common reserved list named
|
||||
|
||||
```shell
|
||||
$ nomulus -e {ENVIRONMENT} create_reserved_list -i common_bad-words.txt
|
||||
[ ... snip long confirmation prompt ... ]
|
||||
reservedListMap=[(availableinga, ALLOWED_IN_SUNRISE),
|
||||
(reserveddomain, FULLY_BLOCKED), ...]
|
||||
Perform this command? (y/N): y
|
||||
Updated 1 entities.
|
||||
Running ...
|
||||
Saved reserved list common_bad-words with 2 entries.
|
||||
```
|
||||
|
||||
Note that `-i` is the input file containing the list. You can optionally specify
|
||||
@@ -97,9 +99,11 @@ file containing the reserved list entries, then pass it as input to the
|
||||
|
||||
```shell
|
||||
$ nomulus -e {ENVIRONMENT} update_reserved_list -i common_bad-words.txt
|
||||
[ ... snip diff of changes to list entries ... ]
|
||||
Update reserved list for common_bad-words?
|
||||
[diff of changes...]
|
||||
Perform this command? (y/N): y
|
||||
Updated 1 entities.
|
||||
Running ...
|
||||
Saved reserved list common_bad-words with 2 entries.
|
||||
```
|
||||
|
||||
Note that, like the create command, the name of the list is inferred from the
|
||||
@@ -168,9 +172,11 @@ $ nomulus -e production check_domain {domain_name}
|
||||
```
|
||||
|
||||
**Note that the list can be cached for up to 60 minutes, so changes may not take
|
||||
place immediately**. If it is urgent that the new changes be applied, and it's
|
||||
OK to potentially interrupt client connections, then you can use the GCP web
|
||||
console to kill instances of the `frontend` service, as the cache is
|
||||
per-instance. Once you've killed all the existing instances (don't kill them all
|
||||
at once!), all the newly spun up instances will now be using the new values
|
||||
you've configured.
|
||||
place immediately**. If it is urgent that the new changes be applied, you can
|
||||
perform a rolling restart of the `frontend` service deployment:
|
||||
|
||||
```shell
|
||||
$ kubectl rollout restart deployment frontend
|
||||
```
|
||||
|
||||
This will cycle the pods and clear the per-instance caches without causing downtime.
|
||||
|
||||
@@ -31,5 +31,5 @@ allowedFullyQualifiedHostNames:
|
||||
|
||||
When nameserver restrictions are set on a TLD, any domain mutation flow under
|
||||
that TLD will verify that the supplied nameservers are not empty and that they
|
||||
are a strict subset of the allowed nameservers and registrants on the TLD. If no
|
||||
are a strict subset of the allowed nameservers on the TLD. If no
|
||||
restrictions are set, domains can be created or updated without nameservers.
|
||||
|
||||
+1
-1
@@ -452,7 +452,7 @@ immediately after.
|
||||
```bash
|
||||
$ gcloud container clusters create proxy-americas-cluster --enable-autorepair \
|
||||
--enable-autoupgrade --enable-autoscaling --max-nodes=3 --min-nodes=1 \
|
||||
--zone=us-east1-c --cluster-version=1.9.4-gke.1 --tags=proxy-cluster \
|
||||
--zone=us-east1-c --tags=proxy-cluster \
|
||||
--service-account=<service-account-email>
|
||||
```
|
||||
|
||||
|
||||
+2
-2
@@ -43,8 +43,8 @@ containing the requested data.
|
||||
## Nomulus RDAP request endpoints <a id="endpoints"></a>
|
||||
|
||||
The suite of URL endpoint paths is listed below. The paths should be tacked onto
|
||||
the usual App Engine server name. For example, if the App Engine project ID is
|
||||
`project-id`, the full path for a domain lookup of domain iam.soy would be:
|
||||
the pubapi workload host name. For example, if the base domain is
|
||||
`mydomain.com`, the full path for a domain lookup of domain iam.soy would be:
|
||||
|
||||
```
|
||||
https://pubapi.mydomain.com/rdap/domain/iam.soy
|
||||
|
||||
@@ -524,10 +524,10 @@ need to specify two items in <domain:create> requests, one for the normal price,
|
||||
one for the Early Access Fee. These should be specified as in the following
|
||||
example:
|
||||
|
||||
```
|
||||
<fee:create xmlns:fee="urn:ietf:params:xml:ns:fee-0.6">
|
||||
```xml
|
||||
<fee:create xmlns:fee="urn:ietf:params:xml:ns:epp:fee-1.0">
|
||||
<fee:currency>USD</fee:currency>
|
||||
<fee:fee description="create">70</fee:fee>
|
||||
<fee:fee description="create">70.00</fee:fee>
|
||||
<fee:fee description="Early Access Period">80.00</fee:fee>
|
||||
</fee:create>
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user